Frage

Gibt es eine (Cross-Plattform) Art und Weise eine C FILE * Handle von einer C zu erhalten ++ std :: fstream?

Der Grund, warum ich frage ist, weil mein C ++ Bibliothek fstreams akzeptiert und in einer bestimmten Funktion würde Ich mag eine C-Bibliothek verwenden, das eine Datei akzeptiert *.

War es hilfreich?

Lösung

Die kurze Antwort ist nein.

Der Grund ist, weil die std::fstream kein FILE* verwenden im Rahmen ihrer Umsetzung erforderlich ist. Also selbst wenn Sie verwalten Dateideskriptors aus dem std::fstream Objekt zu extrahieren und manuell ein File-Objekt bauen, dann werden Sie andere Probleme haben, weil Sie jetzt zwei auf den gleichen Dateideskriptor Schreiben gepuffert Objekte haben.

Die eigentliche Frage ist, warum wollen Sie das std::fstream Objekt in ein FILE* konvertieren?

Auch wenn ich es nicht empfehlen, Sie könnten versuchen, aufzublicken funopen().
Leider ist dies nicht ein POSIX-API (es ist eine BSD-Erweiterung) so seine Portabilität in Frage. Welches auch ist wahrscheinlich, warum ich niemanden finden können, die eine std::stream mit einem Objekt wie folgt gewickelt hat.

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

Damit können Sie ein FILE Objekt bauen und einige Funktionen angeben, die verwendet werden, um die eigentliche Arbeit zu tun. Wenn Sie über die entsprechenden Funktionen schreiben können Sie erhalten sie von dem std::fstream Objekt zu lesen, die tatsächlich die Datei geöffnet hat.

Andere Tipps

Es gibt keine standardisierte Art und Weise. Ich nehme an, das liegt daran, dass die C ++ Standardisierungsgruppe wollte nicht davon ausgehen, dass ein Datei-Handle kann als fd dargestellt werden.

Die meisten Plattformen scheinen einige Nicht-Standard-Weg, um dies zu tun.

http://www.ginac.de/~kreckel/fileno/ bietet eine gute Zuschreibung der Situation und bietet Code, die alle Plattform, zumindest für GCC spezifische Rohheit versteckt. Bedenkt man, wie gross diese nur auf GCC, ich denke, ich würde all dies zusammen, wenn möglich vermeiden zu tun.

  

UPDATE: Siehe @Jettatura, was ich denke, es ist die beste Antwort https://stackoverflow.com/a/33612982/225186 (nur Linux?).

ORIGINAL:

(Wahrscheinlich nicht Plattform überqueren, aber einfach)

Simplifiying den Hack in http://www.ginac.de/~kreckel/fileno/ (Dvorak Antwort), und bei dieser gcc Erweiterung suchen http://gcc.gnu.org/onlinedocs/gcc-4.6.2/libstdc++/api/a00069.html#a59f78806603c619eafcd4537c920f859 , Ich habe diese Lösung, die auf GCC (4.8 zumindest) und clang (3.3 mindestens)

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

und kann diese verwendet werden,

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

Einschränkungen: (Kommentare sind willkommen)

  1. Ich finde, dass es wichtig ist, nach fflush Druck fprintf std::ofstream, da sonst die „sample2“ erscheint vor „sample1“ in dem obigen Beispiel. Ich weiß nicht, ob es eine bessere Abhilfe für das heißt als fflush verwenden. Bemerkenswerter ofs << flush hilft nicht.

  2. Die Datei kann nicht * von std::stringstream extrahieren, ich weiß nicht einmal, ob es möglich ist. (Siehe unten für ein Update).

  3. Ich weiß noch nicht, wie C des stderr von std::cerr usw. zu extrahieren, zum Beispiel in fprintf(stderr, "sample") zu verwenden, in einem hypothetischen Code wie dieser fprintf(cfile(std::cerr), "sample").

Im Hinblick auf die letzte Einschränkung, die einzige Abhilfe, die ich gefunden ist, diese Überlastungen hinzuzufügen:

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
}

Der Versuch zu behandeln iostringstream

Es ist möglich, mit fscanf von istream zu lesen fmemopen verwenden, aber das erfordert eine Menge der Buchhaltung und die Eingabeposition des Stroms nach jeder Lese aktualisieren, wenn ein C-liest und C ++ kombinieren will - liest. Ich war nicht in der Lage, dies in eine cfile Funktion zu konvertieren wie oben. (Vielleicht ein cfile Klasse Das hält die Aktualisierung nach jeder Lese ist der Weg zu gehen).

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

Nun können Sie den Dateideskriptor bekommen - ich habe vergessen, ob die Methode fd () oder getfd (). Die Implementierungen ich verwendet habe, solche Verfahren zur Verfügung stellen, aber der Sprachstandard nicht benötigt werden, glaube ich -. Die Norm sollte es egal, ob Ihre Plattform fd die für Dateien verwendet

Von diesem können Sie verwenden fdopen (fd, mode) *, eine Datei zu erhalten.

Aber ich denke, dass die Mechanismen der Standard für synching STDIN / cin erfordert, STDOUT / cout und STDERR / cerr müssen nicht für Sie sichtbar. Also, wenn Sie sowohl die fstream und FILE * verwenden, Pufferung kann Schlamassel Ihnen.

Auch, wenn entweder die fstream oder die Datei geschlossen wird, werden sie wahrscheinlich die zugrunde liegende fd schließen, so müssen Sie Sie beide stellen Sie sicher, spülen Sie vor dem Schließen nicht.

In einer Single-Threaded-POSIX-Anwendung können Sie bequem die fd-Nummer in einer tragbaren Weise erhalten:

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

Diese Methode bricht in einer Multi-Threaded-Anwendung, wenn diese Code-Rennen mit anderen Threads Filedeskriptoren öffnen.

noch eine andere Möglichkeit, dies in Linux zu tun:

#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

Verwendung, zum Beispiel:

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

Es gibt einen Weg Dateideskriptors von fstream zu bekommen und es dann (über FILE*) fdopen zu konvertieren. Persönlich sehe ich keine Notwendigkeit, in FILE*, aber mit Dateideskriptor können Sie viele interessante Dinge wie Umleiten (dup2) tun.

Lösung:

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

Die letzte Zeichenfolge arbeitet für libstdc ++. Wenn Sie eine andere Bibliothek verwenden, müssen Sie ein Reverse-Engineering es ein bisschen.

Dieser Trick ist schmutzig und alle privaten und öffentlichen Mitglieder fstream aussetzen. Wenn Sie es in Ihrem Produktionscode verwenden mögen, empfehle ich Ihnen, getrennten .cpp und .h mit einzelner Funktion int getFdFromFstream(std::basic_ios<char>& fstr); zu erstellen. Header-Datei darf nicht fstream enthalten.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top