Een BESTAND* ophalen van een std::fstream

Is er een (platformonafhankelijke) manier om een ​​C FILE*-handle te krijgen van een C++ std::fstream ?

De reden dat ik het vraag is omdat mijn C++-bibliotheek fstreams accepteert en in een bepaalde functie zou ik een C-bibliotheek willen gebruiken die een FILE* accepteert.


Antwoord 1, autoriteit 100%

Het korte antwoord is nee.

De reden is dat de std::fstreamgeen FILE*hoeft te gebruiken als onderdeel van de implementatie. Dus zelfs als het je lukt om de bestandsdescriptor uit het std::fstream-object te extraheren en handmatig een FILE-object te bouwen, dan heb je andere problemen omdat je nu twee gebufferde objecten hebt die naar dezelfde bestandsdescriptor schrijven .

De echte vraag is waarom je het std::fstreamobject wilt converteren naar een FILE*?

Hoewel ik het niet aanraad, kun je proberen funopen()op te zoeken.
Helaas is dit geeneen POSIX API (het is een BSD-extensie), dus de draagbaarheid staat ter discussie. Dat is waarschijnlijk ook de reden waarom ik niemand kan vinden die een std::streamheeft omwikkeld met een object als dit.

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

Hiermee kunt u een FILE-object bouwen en enkele functies specificeren die zullen worden gebruikt om het eigenlijke werk te doen. Als je de juiste functies schrijft, kun je ze laten lezen van het std::fstream-object dat het bestand daadwerkelijk heeft geopend.


Antwoord 2, autoriteit 38%

Er is geen gestandaardiseerde manier. Ik neem aan dat dit komt omdat de C++-standaardisatiegroep niet wilde aannemen dat een bestandshandle kan worden weergegeven als een fd.

De meeste platforms lijken hiervoor een niet-standaard manier te bieden.

http://www.ginac.de/~kreckel/fileno/biedt een goede beschrijving van de situatie en biedt code die alle platformspecifieke grofheid verbergt, althans voor GCC. Gezien hoe smerig dit alleen op GCC is, denk ik dat ik dit indien mogelijk zou vermijden.


Antwoord 3, autoriteit 25%

UPDATE: zie @Jettatura wat volgens mij het beste antwoord is https://stackoverflow.com/a/33612982/225186(alleen Linux?).

ORIGINEEL:

(Waarschijnlijk niet cross-platform, maar eenvoudig)

De hack vereenvoudigen in http://www.ginac.de/~kreckel/fileno/(antwoord van dvorak), en kijkend naar deze gcc-extensie http://gcc.gnu.org/onlinedocs/gcc-4.6.2/libstdc++/api/a00069.html#a59f78806603c619eafcd4537c920f859,
Ik heb deze oplossing die werkt op GCC(minstens 4.8) en clang(minstens 3.3)

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

en kan dit worden gebruikt,

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

Beperkingen:(opmerkingen zijn welkom)

  1. Ik vind dat het belangrijk is om fflushna fprintfte drukken naar std::ofstream, anders wordt de “sample2” verschijnt voor “sample1” in het bovenstaande voorbeeld. Ik weet niet of er een betere oplossing voor die dan met fflush. Met name ofs << flushhelpt niet.

  2. Kan niet worden uitgepakt FILE * van std::stringstream, ik weet niet eens of het mogelijk is. (Zie hieronder voor een update).

  3. Ik weet nog steeds niet hoe je C’s stderrvan std::cerrenz., Bijvoorbeeld om het gebruik in fprintf(stderr, "sample"), in een hypothetische code als volgt fprintf(cfile(std::cerr), "sample").

Met betrekking tot de laatste beperking, is de enige oplossing die ik gevonden is om deze overbelasting toe te voegen:

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
}

Poging om handle iostringstream

Het is mogelijk om te lezen met fscanfvan istreammet fmemopen, maar dat veel van de financiële administratie en het bijwerken van de ingang positie van vereist de stroom na elke lezen, als men wil combineren C-leest en C++ – leest. Ik was niet in staat om dit om te zetten in een cfilefunctie als hierboven. (Misschien een cfileclass , dat houdt het bijwerken na elke lezen is de manier om te gaan).

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

Antwoord 4, Autoriteit 8%

Nou ja, kunt u het bestand descriptor – ik weet niet meer of de methode is fd () of getfd (). De implementaties die ik heb gebruikt te bieden dergelijke methoden, maar de taal standaard hen niet nodig, ik geloof – de norm moet niet uit of uw platform gebruik fd’s naar bestanden

.

Vanaf dat, kunt u gebruik maken fdopen (fd, mode) om een ​​bestand te krijgen *.

Ik denk echter dat de mechanismen van de standaard vereist voor synchronisatie STDIN / CIN, STDOUT / cout en STDERR / cerr hoeven niet zichtbaar zijn voor jou te zijn. Dus als je gebruik maakt van zowel de fstream en FILE *, bufferen mei u omhoog knoeien.

Als ofwel de fstream OF het BESTAND sluit, zullen ze waarschijnlijk de onderliggende fd sluiten, dus u moet ervoor zorgen dat u BEIDE doorspoelt voordat u BEIDE sluit.


Antwoord 5, autoriteit 8%

In een POSIX-toepassing met één thread kunt u het fd-nummer gemakkelijk op een draagbare manier verkrijgen:

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

Deze methode breekt in een toepassing met meerdere threads als deze code racet met andere threads die bestandsdescriptors openen.


Antwoord 6, autoriteit 6%

nog een andere manier om dit in Linux te doen:

#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

Gebruik, bijvoorbeeld:

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

Antwoord 7, autoriteit 4%

Er is een manier om een ​​bestandsdescriptor uit fstreamte halen en deze vervolgens te converteren naar FILE*(via fdopen). Persoonlijk zie ik geen noodzaak in FILE*, maar met file descriptor kun je veel interessante dingen doen, zoals omleiden (dup2).

Oplossing:

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

De laatste string werkt voor libstdc++. Als u een andere bibliotheek gebruikt, moet u deze een beetje reverse-engineeren.

Deze truc is vies en zal alle private en publieke leden van fstream blootstellen. Als je het in je productiecode wilt gebruiken, raad ik je aan om aparte .cppen .hte maken met een enkele functie int getFdFromFstream(std::basic_ios<char>& fstr);. Headerbestand mag geen fstream bevatten.

Other episodes