Pergunta

Existe uma maneira (cross-platform) para obter um arquivo C * alça de um C ++ std :: fstream?

A razão que eu peço é porque minha biblioteca C ++ aceita fstreams e em uma função particular, eu gostaria de usar uma biblioteca C que aceita um arquivo *.

Foi útil?

Solução

A resposta curta não é.

A razão, é porque o std::fstream não é obrigado a usar um FILE* como parte de sua implementação. Assim, mesmo se você conseguir extrair descritor de arquivo do objeto std::fstream e manualmente criar um objeto de arquivo, então você vai ter outros problemas, porque agora você vai ter dois objetos buffer de escrita para o mesmo descritor de arquivo.

A verdadeira questão é por que você deseja converter o objeto std::fstream em um FILE*?

Embora eu não recomendo isso, você pode tentar olhar para cima funopen().
Infelizmente, esta é não a API POSIX (é uma extensão BSD), de modo a sua portabilidade está em questão. Que também é provavelmente por isso que eu não consigo encontrar ninguém que envolveu um std::stream com um objeto como este.

FILE *funopen(
              const void *cookie,
              int    (*readfn )(void *, char *, int),
              int    (*writefn)(void *, const char *, int),
              fpos_t (*seekfn) (void *, fpos_t, int),
              int    (*closefn)(void *)
             );

Isso permite que você construa um objeto FILE e especificar algumas funções que serão usadas para fazer o trabalho real. Se você escrever funções apropriadas você pode levá-los a ler a partir do objeto std::fstream que realmente tem a abrir o arquivo.

Outras dicas

Não há uma maneira padronizada. Suponho que isso é porque o grupo padronização C ++ não queria assumir que um identificador de arquivo pode ser representado como um fd.

A maioria das plataformas parecem fornecer alguma forma não-padrão para fazer isso.

http://www.ginac.de/~kreckel/fileno/ fornece um bom writeup da situação e fornece código que esconde todas a grosseria específico da plataforma, pelo menos por GCC. Dado como bruto este é apenas no GCC, eu acho que eu evite fazer isso todos juntos, se possível.

UPDATE: Veja @Jettatura o que eu acho que é a melhor resposta https://stackoverflow.com/a/33612982/225186 (somente Linux?).

ORIGINAL:

(Provavelmente não cruz plataforma, mas simples)

simplificando o hack em http://www.ginac.de/~kreckel/fileno/ (resposta Dvorak), e olhando para esta extensão gcc http://gcc.gnu.org/onlinedocs/gcc-4.6.2/libstdc++/api/a00069.html#a59f78806603c619eafcd4537c920f859 , Eu tenho essa solução que funciona em GCC (4,8 pelo menos) e clang (3,3 pelo menos)

#include<fstream>
#include<ext/stdio_filebuf.h>

typedef std::basic_ofstream<char>::__filebuf_type buffer_t;
typedef __gnu_cxx::stdio_filebuf<char>            io_buffer_t; 
FILE* cfile_impl(buffer_t* const fb){
    return (static_cast<io_buffer_t* const>(fb))->file(); //type std::__c_file
}

FILE* cfile(std::ofstream const& ofs){return cfile_impl(ofs.rdbuf());}
FILE* cfile(std::ifstream const& ifs){return cfile_impl(ifs.rdbuf());}

e pode ser usado isso,

int main(){
    std::ofstream ofs("file.txt");
    fprintf(cfile(ofs), "sample1");
    fflush(cfile(ofs)); // ofs << std::flush; doesn't help 
    ofs << "sample2\n";
}

Limitações: (comentários são bem-vindas)

  1. Eu acho que é importante fflush após a impressão fprintf para std::ofstream, caso contrário, o aparece "sample2" antes "sample1" no exemplo acima. Eu não sei se há uma melhor solução para isso do que usando fflush. Notavelmente ofs << flush não ajuda.

  2. Não é possível extrair o arquivo * from std::stringstream, eu nem sei se é possível. (Veja abaixo para uma atualização).

  3. Eu ainda não sei como extrair stderr de C a partir std::cerr etc., por exemplo, para uso em fprintf(stderr, "sample"), em um código hipotética assim fprintf(cfile(std::cerr), "sample").

Em relação ao último limitação, a única solução que eu encontrei é adicionar estas sobrecargas:

FILE* cfile(std::ostream const& os){
    if(std::ofstream const* ofsP = dynamic_cast<std::ofstream const*>(&os)) return cfile(*ofsP);
    if(&os == &std::cerr) return stderr;
    if(&os == &std::cout) return stdout;
    if(&os == &std::clog) return stderr;
    if(dynamic_cast<std::ostringstream const*>(&os) != 0){
       throw std::runtime_error("don't know cannot extract FILE pointer from std::ostringstream");
    }
    return 0; // stream not recognized
}
FILE* cfile(std::istream const& is){
    if(std::ifstream const* ifsP = dynamic_cast<std::ifstream const*>(&is)) return cfile(*ifsP);
    if(&is == &std::cin) return stdin;
    if(dynamic_cast<std::ostringstream const*>(&is) != 0){
        throw std::runtime_error("don't know how to extract FILE pointer from std::istringstream");
    }
    return 0; // stream not recognized
}

Tentar lidar com iostringstream

É possível ler com fscanf de istream usando fmemopen, mas que requer um monte de contabilidade e atualizar a posição de entrada da corrente depois de cada leitura, se alguém quiser combinar C-lê e C ++ - lê. Eu não era capaz de converter isso em uma função cfile como acima. (Talvez um cfile class que mantém atualizando após cada leitura é o caminho a percorrer).

// hack to access the protected member of istreambuf that know the current position
char* access_gptr(std::basic_streambuf<char, std::char_traits<char>>& bs){
    struct access_class : std::basic_streambuf<char, std::char_traits<char>>{
        char* access_gptr() const{return this->gptr();}
    };
    return ((access_class*)(&bs))->access_gptr();
}

int main(){
    std::istringstream iss("11 22 33");
    // read the C++ way
    int j1; iss >> j1;
    std::cout << j1 << std::endl;

    // read the C way
    float j2;

    char* buf = access_gptr(*iss.rdbuf()); // get current position
    size_t buf_size = iss.rdbuf()->in_avail(); // get remaining characters
    FILE* file = fmemopen(buf, buf_size, "r"); // open buffer memory as FILE*
    fscanf(file, "%f", &j2); // finally!
    iss.rdbuf()->pubseekoff(ftell(file), iss.cur, iss.in); // update input stream position from current FILE position.

    std::cout << "j2 = " << j2 << std::endl;

    // read again the C++ way
    int j3; iss >> j3;
    std::cout << "j3 = " << j3 << std::endl;
}

Bem, você pode obter o descritor de arquivo - eu esqueço se o método é fd () ou getfd (). As implementações que eu usei fornecer tais métodos, mas o padrão de linguagem não necessita deles, eu acredito - o padrão não deve se preocupar se seus usos plataforma fd do para arquivos

.

A partir daí, você pode usar fdopen (fd, modo) para obter um arquivo *.

No entanto, penso que os mecanismos A norma exige para sincronizar STDIN / cin, STDOUT / cout e STDERR / cerr não tem que ser visível para você. Então, se você estiver usando tanto o fstream e FILE *, buffer de Maio de bagunçar tudo.

Além disso, se tanto o fstream OU o fechamento de arquivos, eles vão provavelmente perto do fd subjacente, então você precisa se certificar de que você lave antes de fechar SEJA.

Em um aplicativo POSIX single-threaded você pode facilmente obter o número fd de uma maneira portátil:

int fd = dup(0);
close(fd);
// POSIX requires the next opened file descriptor to be fd.
std::fstream file(...);
// now fd has been opened again and is owned by file

Este método breaks em uma aplicação multi-threaded se corridas este código com outros tópicos abertura descritores de arquivos.

ainda uma outra maneira de fazer isso no Linux:

#include <stdio.h>
#include <cassert>

template<class STREAM>
struct STDIOAdapter
{
    static FILE* yield(STREAM* stream)
    {
        assert(stream != NULL);

        static cookie_io_functions_t Cookies =
        {
            .read  = NULL,
            .write = cookieWrite,
            .seek  = NULL,
            .close = cookieClose
        };

        return fopencookie(stream, "w", Cookies);
    }

    ssize_t static cookieWrite(void* cookie,
        const char* buf,
        size_t size)
    {
        if(cookie == NULL)
            return -1;

        STREAM* writer = static_cast <STREAM*>(cookie);

        writer->write(buf, size);

        return size;
    }

    int static cookieClose(void* cookie)
    {
         return EOF;
    }
}; // STDIOAdapter

Uso, por exemplo:

#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/filter/bzip2.hpp>
#include <boost/iostreams/device/file.hpp>

using namespace boost::iostreams;

int main()
{   
    filtering_ostream out;
    out.push(boost::iostreams::bzip2_compressor());
    out.push(file_sink("my_file.txt"));

    FILE* fp = STDIOAdapter<filtering_ostream>::yield(&out);
    assert(fp > 0);

    fputs("Was up, Man", fp);

    fflush (fp);

    fclose(fp);

    return 1;
}

Existe uma maneira de obter descritor de arquivo de fstream e depois convertê-lo para FILE* (via fdopen). Pessoalmente, eu não vejo nenhuma necessidade em FILE*, mas com descritor de arquivo que você pode fazer muitas coisas interessantes, como o redirecionamento (dup2).

Solução:

#define private public
#define protected public
#include <fstream>
#undef private
#undef protected

std::ifstream file("some file");
auto fno = file._M_filebuf._M_file.fd();

A última seqüência funciona para libstdc ++. Se você estiver usando alguma outra biblioteca terá de engenharia reversa-lo um pouco.

Este truque é sujo e irá expor todos os membros públicos e privados de fstream. Se você gostaria de usá-lo em seu código de produção, sugiro que você crie .cpp separada e .h com int getFdFromFstream(std::basic_ios<char>& fstr); função única. arquivo de cabeçalho não deve incluir fstream.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top