Wie erstelle ich einen c ++ fstream aus einem POSIX-Dateideskriptor?

93

Ich suche im Grunde nach einer C ++ - Version von fdopen (). Ich habe ein bisschen darüber recherchiert und es ist eines dieser Dinge, die einfach zu sein scheinen, sich aber als sehr kompliziert herausstellen. Vermisse ich etwas in diesem Glauben (dh es ist wirklich einfach)? Wenn nicht, gibt es irgendwo eine gute Bibliothek, um das zu erledigen?

BEARBEITEN: Meine Beispiellösung wurde in eine separate Antwort verschoben.

BD in Rivenhill
quelle
@Kazark - jetzt zu einer separaten Antwort verschoben, danke.
BD in Rivenhill
Windows und Linux können mmapmit der Datei umgehen und ihren Inhalt als Byte-Array verfügbar machen.
Truthadjustr

Antworten:

71

Aus der Antwort von Éric Malenfant:

AFAIK, dies ist in Standard-C ++ nicht möglich. Abhängig von Ihrer Plattform bietet Ihre Implementierung der Standardbibliothek möglicherweise (als nicht standardmäßige Erweiterung) einen fstream-Konstruktor, der einen Dateideskriptor als Eingabe verwendet. (Dies ist der Fall für libstdc ++, IIRC) oder eine DATEI *.

Basierend auf den obigen Beobachtungen und meinen Recherchen unten gibt es Arbeitscode in zwei Varianten; eine für libstdc ++ und eine für Microsoft Visual C ++.


libstdc ++

Es gibt eine nicht standardmäßige __gnu_cxx::stdio_filebufKlassenvorlage, die std::basic_streambufden folgenden Konstruktor erbt und hat

stdio_filebuf (int __fd, std::ios_base::openmode __mode, size_t __size=static_cast< size_t >(BUFSIZ)) 

mit Beschreibung Dieser Konstruktor ordnet einen Dateistream-Puffer einem geöffneten POSIX-Dateideskriptor zu.

Wir erstellen es über das POSIX-Handle (Zeile 1) und übergeben es dann als basic_streambuf (Zeile 2) an den Konstruktor von istream:

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

using namespace std;

int main()
{
    ofstream ofs("test.txt");
    ofs << "Writing to a basic_ofstream object..." << endl;
    ofs.close();

    int posix_handle = fileno(::fopen("test.txt", "r"));

    __gnu_cxx::stdio_filebuf<char> filebuf(posix_handle, std::ios::in); // 1
    istream is(&filebuf); // 2

    string line;
    getline(is, line);
    cout << "line: " << line << std::endl;
    return 0;
}

Microsoft Visual C ++

Früher gab es eine nicht standardmäßige Version des Konstruktors von ifstream, die den POSIX-Dateideskriptor verwendet, aber er fehlt sowohl in aktuellen Dokumenten als auch im Code. Es gibt eine andere nicht standardmäßige Version des Konstruktors von ifstream, die FILE * verwendet

explicit basic_ifstream(_Filet *_File)
    : _Mybase(&_Filebuffer),
        _Filebuffer(_File)
    {   // construct with specified C stream
    }

und es ist nicht dokumentiert (ich konnte nicht einmal eine alte Dokumentation finden, in der es vorhanden wäre). Wir nennen es (Zeile 1), wobei der Parameter das Ergebnis des Aufrufs von _fdopen ist , um den C-Stream FILE * vom POSIX- Dateihandle abzurufen .

#include <cstdio>
#include <iostream>
#include <fstream>
#include <string>

using namespace std;

int main()
{
    ofstream ofs("test.txt");
    ofs << "Writing to a basic_ofstream object..." << endl;
    ofs.close();

    int posix_handle = ::_fileno(::fopen("test.txt", "r"));

    ifstream ifs(::_fdopen(posix_handle, "r")); // 1

    string line;
    getline(ifs, line);
    ifs.close();
    cout << "line: " << line << endl;
    return 0;
}
Piotr Dobrogost
quelle
2
Nun die akzeptierte Antwort der Vollständigkeit halber. Andere interessieren sich möglicherweise für meine Lösung mit Boost, die in eine separate Antwort verschoben wurde.
BD in Rivenhill
1
Für Linux: Wenn Sie sich ios_init.cc in gcc ansehen (die Quelle, die ich habe, ist für Version 4.1.1), wird std :: cout initialisiert, indem ein stdio_sync_filebuf <char> um Ihren Dateideskriptor herum initialisiert und dann in ostream um Ihren stdio_sync_filebuf <initialisiert wird char>. Ich kann nicht behaupten, dass dies stabil sein wird.
Sparky
@Sparky Ein Blick in die std::coutImplementierung ist eine gute Idee. Ich frage mich, was ist der Unterschied zwischen stdio_filebufund stdio_sync_filebuf?
Piotr Dobrogost
POSIX-FDS in MSVC sind Emulationen. Die Windows-API für Dateivorgänge unterscheidet sich in vielerlei Hinsicht von POSIX-APIs - verschiedene Funktionsnamen und Datentypen von Parametern. Windows verwendet intern sogenannte "Handles", um verschiedene Windows-API-Objekte zu identifizieren, und der Windows-API-Typ HANDLE ist als void * definiert Zumindest passt es nicht in "int" (32-Bit) auf 64-Bit-Plattformen. Für Windows könnten Sie also daran interessiert sein, nach einem Stream zu suchen, mit dem Sie die Windows-API-Datei HANDLE bearbeiten können.
ivan.ukr
40

AFAIK, dies ist in Standard-C ++ nicht möglich. Abhängig von Ihrer Plattform bietet Ihre Implementierung der Standardbibliothek möglicherweise (als nicht standardmäßige Erweiterung) einen fstream-Konstruktor, der einen Dateideskriptor (dies ist bei libstdc ++, IIRC der Fall) oder FILE*eine Eingabe verwendet.

Eine andere Alternative wäre die Verwendung eines Boost :: iostreams :: file_descriptor- Geräts, das Sie in einen boost :: iostreams :: stream einbinden könnten, wenn Sie eine std :: stream-Schnittstelle dazu haben möchten.

Éric Malenfant
quelle
4
Angesichts der Tatsache, dass dies die einzige tragbare Lösung ist, verstehe ich nicht, warum dies nicht die akzeptierte oder am besten bewertete Antwort ist.
Maarten
8

Es besteht eine gute Chance, dass Ihr Compiler einen FILE-basierten fstream-Konstruktor anbietet, obwohl dieser nicht dem Standard entspricht. Beispielsweise:

FILE* f = fdopen(my_fd, "a");
std::fstream fstr(f);
fstr << "Greetings\n";

Aber soweit ich weiß, gibt es keine tragbare Möglichkeit, dies zu tun.

Darryl
quelle
2
Beachten Sie, dass g ++ (korrekt) dies im c ++ 11-Modus nicht zulässt
Mark K Cowan
8

Ein Teil der ursprünglichen (nicht angegebenen) Motivation dieser Frage besteht darin, Daten entweder zwischen Programmen oder zwischen zwei Teilen eines Testprogramms mithilfe einer sicher erstellten temporären Datei übertragen zu können, aber tmpnam () gibt eine Warnung in gcc aus, also wollte ich stattdessen mkstemp () verwenden. Hier ist ein Testprogramm, das ich basierend auf der Antwort von Éric Malenfant geschrieben habe, aber mkstemp () anstelle von fdopen () verwendet habe. Dies funktioniert auf meinem Ubuntu-System mit installierten Boost-Bibliotheken:

#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <string>
#include <iostream>
#include <boost/filesystem.hpp>
#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/stream.hpp>

using boost::iostreams::stream;
using boost::iostreams::file_descriptor_sink;
using boost::filesystem::path;
using boost::filesystem::exists;
using boost::filesystem::status;
using boost::filesystem::remove;

int main(int argc, const char *argv[]) {
  char tmpTemplate[13];
  strncpy(tmpTemplate, "/tmp/XXXXXX", 13);
  stream<file_descriptor_sink> tmp(mkstemp(tmpTemplate));
  assert(tmp.is_open());
  tmp << "Hello mkstemp!" << std::endl;
  tmp.close();
  path tmpPath(tmpTemplate);
  if (exists(status(tmpPath))) {
    std::cout << "Output is in " << tmpPath.file_string() << std::endl;
    std::string cmd("cat ");
    cmd += tmpPath.file_string();
    system(cmd.c_str());
    std::cout << "Removing " << tmpPath.file_string() << std::endl;
    remove(tmpPath);
  }
}
BD in Rivenhill
quelle
4

Ich habe die oben für libstdc ++ von Piotr Dobrogost vorgeschlagene Lösung ausprobiert und festgestellt, dass sie einen schmerzhaften Fehler aufweist: Aufgrund des Fehlens eines geeigneten Verschiebungskonstruktors für istream ist es sehr schwierig, das neu erstellte istream-Objekt aus der Erstellungsfunktion herauszuholen . Ein weiteres Problem dabei ist, dass ein FILE-Objekt verloren geht (auch wenn dies nicht der zugrunde liegende Posix-Dateideskriptor ist). Hier ist eine alternative Lösung, die diese Probleme vermeidet:

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

bool OpenFileForSequentialInput(ifstream& ifs, const string& fname)
{
    ifs.open(fname.c_str(), ios::in);
    if (! ifs.is_open()) {
        return false;
    }

    using FilebufType = __gnu_cxx::stdio_filebuf<std::ifstream::char_type>;
    static_assert(  std::is_base_of<ifstream::__filebuf_type, FilebufType>::value &&
                    (sizeof(FilebufType) == sizeof(ifstream::__filebuf_type)),
            "The filebuf type appears to have extra data members, the cast might be unsafe");

    const int fd = static_cast<FilebufType*>(ifs.rdbuf())->fd();
    assert(fd >= 0);
    if (0 != posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL)) {
        ifs.close();
        return false;
    }

    return true;
}

Der Aufruf von posix_fadvise () zeigt eine mögliche Verwendung. Beachten Sie auch, dass das Beispiel static_assert verwendet und C ++ 11 verwendet, außer dass es im C ++ 03-Modus einwandfrei erstellt werden sollte.

YitzikC
quelle
Was meinst du mit der richtigen Version des Verschiebungskonstruktors ? Welche Version von gcc haben Sie verwendet? Möglicherweise waren in dieser Version noch keine Verschiebungskonstruktoren implementiert - siehe Wird der Verschiebungskonstruktor von ifsteam implizit gelöscht? ?
Piotr Dobrogost
1
Dies ist ein Hack, der von den zugrunde liegenden Implementierungsdetails abhängt. Ich würde hoffen, dass niemand dies jemals im Produktionscode verwendet.
Davmac
-4

Nach meinem Verständnis gibt es im C ++ iostream-Objektmodell keine Zuordnung zu FILE-Zeigern oder Dateideskriptoren, um den Code portabel zu halten.

Trotzdem habe ich mehrere Stellen gesehen, die sich auf die mds-utils oder den Boost beziehen , um diese Lücke zu schließen.

Sockel
quelle
9
FILE * ist Standard C und daher C ++, daher sehe ich nicht, wie die Aktivierung von C ++ - Streams mit C-Streams die Portabilität beeinträchtigen kann
Piotr Dobrogost