Domanda

C'è un (cross-platform) per ottenere un FILE C* maniglia da un C++ std::fstream ?

Il motivo che mi chiedo è perché la mia libreria C++ accetta fstreams e in una funzione particolare vorrei utilizzare una libreria C che accetta un FILE*.

È stato utile?

Soluzione

La risposta breve è no.

Il motivo è che std::fstream non è tenuto a utilizzare un FILE* come parte della sua implementazione. Quindi, anche se riesci a estrarre il descrittore di file dall'oggetto funopen() e costruisci manualmente un oggetto FILE, allora avrai altri problemi perché ora avrai due oggetti bufferizzati che scrivono nello stesso descrittore di file.

La vera domanda è: perché vuoi convertire l'oggetto std::stream in FILE?

Anche se non lo consiglio, potresti provare a cercare <=>.
Sfortunatamente, questa non è un'API POSIX (è un'estensione BSD) quindi la sua portabilità è in questione. Questo è probabilmente anche il motivo per cui non riesco a trovare nessuno che abbia racchiuso un <=> con un oggetto come questo.

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 *)
             );

Ciò consente di creare un oggetto <=> e specificare alcune funzioni che verranno utilizzate per eseguire il lavoro effettivo. Se scrivi le funzioni appropriate puoi farle leggere dall'oggetto <=> che ha effettivamente il file aperto.

Altri suggerimenti

Non esiste un modo standardizzato. Presumo che ciò sia dovuto al fatto che il gruppo di standardizzazione C ++ non voleva assumere che un handle di file potesse essere rappresentato come fd.

La maggior parte delle piattaforme sembra fornire un modo non standard per farlo.

http://www.ginac.de/~kreckel/fileno/ fornisce un buon resoconto della situazione e fornisce un codice che nasconde tutta la grossolanità specifica della piattaforma, almeno per GCC. Dato quanto questo sia grossolano su GCC, penso che se possibile eviterei di farlo tutti insieme.

  

AGGIORNAMENTO: Vedi @Jettatura quale penso sia la risposta migliore https://stackoverflow.com/a/33612982/225186 (solo Linux?).

ORIGINALE:

(Probabilmente non multipiattaforma, ma semplice)

Semplificazione dell'hacking in http://www.ginac.de/~kreckel/fileno/ (risposta dvorak) e guardando questa estensione gcc http://gcc.gnu.org/onlinedocs/gcc-4.6.2/libstdc++/api/a00069.html#a59f78806603c619eafcd4537c920f859 , Ho questa soluzione che funziona su GCC (4.8 almeno) e clang (3.3 almeno)

#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 può essere usato questo

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

Limitazioni: (i commenti sono benvenuti)

  1. Trovo che sia importante fflush dopo fprintf stampare su std::ofstream, altrimenti " sample2 " appare prima di " sample1 " nell'esempio sopra. Non so se esiste una soluzione migliore per quella rispetto all'utilizzo di ofs << flush. In particolare std::stringstream non aiuta.

  2. Impossibile estrarre FILE * da stderr, non so nemmeno se sia possibile. (vedi sotto per un aggiornamento).

  3. Non so ancora come estrarre C <<> da std::cerr ecc., ad esempio da usare in fprintf(stderr, "sample"), in un ipotetico codice come questo fprintf(cfile(std::cerr), "sample").

Per quanto riguarda l'ultima limitazione, l'unica soluzione che ho trovato è quella di aggiungere questi sovraccarichi:

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
}

Tentativo di gestire iostringstream

È possibile leggere con fscanf da istream utilizzando fmemopen, ma ciò richiede molta conservazione dei libri e l'aggiornamento della posizione di input dello stream dopo ogni lettura, se si desidera combinare letture a C e C ++ - si legge. Non sono riuscito a convertirlo in una funzione cfile come sopra. (Forse una <=> classe che continua ad aggiornarsi dopo ogni lettura è la strada da percorrere).

// 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;
}

Bene, puoi ottenere il descrittore di file - Ho dimenticato se il metodo è fd () o getfd (). Le implementazioni che ho usato forniscono tali metodi, ma credo che lo standard linguistico non li richieda: allo standard non dovrebbe importare se la tua piattaforma utilizza fd per i file.

Da questo, puoi usare fdopen (fd, mode) per ottenere un FILE *.

Tuttavia, penso che i meccanismi richiesti dallo standard per la sincronizzazione di STDIN / cin, STDOUT / cout e STDERR / cerr non debbano essere visibili a voi. Quindi, se stai usando sia il flusso che il FILE *, il buffering potrebbe rovinarti.

Inoltre, se il flusso o il FILE si chiudono, probabilmente chiuderanno il file fd sottostante, quindi è necessario assicurarsi di SCARICARE ENTRAMBI prima di chiudere TUTTO.

In un'applicazione POSIX a thread singolo è possibile ottenere facilmente il numero fd in modo portatile:

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

Questo metodo si interrompe in un'applicazione multi-thread se questo codice corre con altri thread che aprono i descrittori di file.

ancora un altro modo per farlo in 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

Utilizzo, ad esempio:

#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;
}

C'è un modo per ottenere il descrittore di file da fstream e poi convertire FILE* (via fdopen).Personalmente non vedo la necessità FILE*, ma con il descrittore di file si possono fare molte cose interessanti come il rinvio (dup2).

Soluzione:

#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();

L'ultima stringa funziona per libstdc++.Se si utilizza altra biblioteca è necessario per decodificare un po'.

Questo trucco è sporco e si espongono tutti privati e pubblici membri del fstream.Se si desidera utilizzare nel vostro codice di produzione, ti consiglio di creare separato .cpp e .h con la sola funzione di int getFdFromFstream(std::basic_ios<char>& fstr);.File di intestazione non deve includere fstream.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top