Wie konstruiere ich einen C++-Fstream aus einem POSIX-Dateideskriptor?
Lesezeit: 8 Minuten
BD in Rivenhill
Ich suche im Grunde nach einer C++-Version von fdopen(). Ich habe ein wenig darüber recherchiert und es ist eines dieser Dinge, die so aussehen, als sollten sie einfach sein, sich aber als sehr kompliziert herausstellen. Übersehe ich etwas in diesem Glauben (dh es ist wirklich einfach)? Wenn nicht, gibt es irgendwo eine gute Bibliothek, um damit umzugehen?
BEARBEITEN: Meine Beispiellösung wurde in eine separate Antwort verschoben.
Windows und Linux können das mmap in die Datei und stellte ihren Inhalt als Byte-Array bereit.
– Eigenfeld
21. April 2020 um 21:57 Uhr
Piotr Dobrogost
Aus der Antwort von Éric Malenfant:
AFAIK, es gibt keine Möglichkeit, dies in Standard-C++ zu tun. Abhängig von Ihrer Plattform kann Ihre Implementierung der Standardbibliothek (als nicht standardmäßige Erweiterung) einen fstream-Konstruktor anbieten, der einen Dateideskriptor als Eingabe verwendet. (Dies ist der Fall bei libstdc++, IIRC) oder einer FILE*.
Basierend auf den obigen Beobachtungen und meinen Recherchen unten gibt es funktionierenden Code in zwei Varianten; eine für libstdc++ und eine für Microsoft Visual C++.
libstdc++
Es gibt Nicht-Standard __gnu_cxx::stdio_filebuf Klassenvorlage, die erbt std::basic_streambuf und hat den folgenden Konstruktor
mit Beschreibung Dieser Konstruktor ordnet einen Dateistrompuffer einem offenen POSIX-Dateideskriptor zu.
Wir erstellen es, indem wir das POSIX-Handle (Zeile 1) übergeben, 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 Nicht-Standard Ausführung des Konstruktors von ifstream, der den POSIX-Dateideskriptor verwendet, aber es fehlen beide Strom docs und aus dem 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 rufen es auf (Zeile 1), wobei der Parameter das Ergebnis des Aufrufs ist _fdöffnen um C-Stream FILE* vom POSIX-Dateihandle zu erhalten.
#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;
}
Nun die akzeptierte Antwort der Vollständigkeit halber. Andere könnten an meiner Lösung mit Boost interessiert sein, die in eine separate Antwort verschoben wurde.
– BD in Rivenhill
25. Februar 2013 um 19:16 Uhr
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 um Ihren Dateideskriptor initialisiert wird, und dann auf ostream um Ihren stdio_sync_filebuf< initialisiert wird Zeichen>. Ich kann jedoch nicht behaupten, dass dies stabil sein wird.
– Funkelnd
26. Juni 2014 um 12:17 Uhr
@Sparky Nachsehen std::cout Umsetzung ist eine gute Idee. Ich frage mich, was der Unterschied zwischen ist stdio_filebuf und stdio_sync_filebuf?
– Piotr Dobrogost
27. April 2016 um 7:40 Uhr
POSIX fds in MSVC sind Emulation. Die Windows-API für Dateioperationen unterscheidet sich in vielerlei Hinsicht von der POSIX-API – 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, so weiter Minimum passt es nicht in “int” (das 32-Bit ist) auf 64-Bit-Plattformen. Für Windows könnten Sie also daran interessiert sein, nach einem Stream zu suchen, der es ermöglicht, über die Windows-API-Datei HANDLE zu arbeiten.
– ivan.ukr
8. Januar 2019 um 7:58 Uhr
Eric Malenfant
AFAIK, es gibt keine Möglichkeit, dies in Standard-C++ zu tun. Abhängig von Ihrer Plattform kann Ihre Implementierung der Standardbibliothek (als nicht standardmäßige Erweiterung) einen fstream-Konstruktor anbieten, der einen Dateideskriptor verwendet (dies ist der Fall für libstdc++, IIRC) oder a FILE* als Eingang.
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.
– Martin
12. März 2016 um 10:40 Uhr
Ich denke, diese Antwort zeigt ein Beispiel: stackoverflow.com/questions/1957761/…
– Mark Lakata
9. Februar um 0:30 Uhr
Es besteht eine gute Chance, dass Ihr Compiler einen FILE-basierten fstream-Konstruktor anbietet, obwohl er nicht dem Standard entspricht. Zum Beispiel:
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.
Beachten Sie, dass g++ (korrekterweise) dies im c++11-Modus nicht zulässt
– Mark K. Cowan
18. August 2014 um 9:57 Uhr
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 um stattdessen mkstemp() zu verwenden. Hier ist ein Testprogramm, das ich basierend auf der Antwort von Éric Malenfant geschrieben habe, aber mkstemp() anstelle von fdopen() verwende; 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);
}
}
Dies ist eine interessante Anwendung des Ausdrucks „ziemlich einfach“.
– Markus Adler
28. März 2021 um 3:25 Uhr
Es wird einfacher, wenn Sie die Boost-Klasse verwenden, die diesen Code enthält, wie in einer anderen Antwort vorgeschlagen.
– Toby Speight
27. Oktober 2021 um 15:27 Uhr
Isac Casapu
Ich habe die oben vorgeschlagene Lösung für libstdc++ von Piotr Dobrogost ausprobiert und festgestellt, dass sie einen schmerzhaften Fehler hatte: Aufgrund des Fehlens eines geeigneten Bewegungskonstruktors für istream ist es sehr schwierig, das neu konstruierte istream-Objekt aus der Erstellungsfunktion herauszuholen . Ein weiteres Problem dabei ist, dass ein FILE-Objekt verloren geht (obwohl nicht der zugrunde liegende Posix-Dateideskriptor). 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() demonstriert eine mögliche Verwendung. Beachten Sie auch, dass das Beispiel verwendet static_assert und verwenden die C++ 11 sind, ansonsten sollte es im C++ 03-Modus gut gebaut werden.
Dies ist eine interessante Anwendung des Ausdrucks „ziemlich einfach“.
– Markus Adler
28. März 2021 um 3:25 Uhr
Es wird einfacher, wenn Sie die Boost-Klasse verwenden, die diesen Code enthält, wie in einer anderen Antwort vorgeschlagen.
– Toby Speight
27. Oktober 2021 um 15:27 Uhr
Sockel
Mein Verständnis ist, dass es keine Zuordnung zu FILE-Zeigern oder Dateideskriptoren im C++-Iostream-Objektmodell gibt, um Code portabel zu halten.
Das heißt, ich habe mehrere Orte gesehen, auf die verwiesen wird mds-utils oder steigern, um diese Lücke zu schließen.
FILE* ist Standard-C und damit C++, daher sehe ich nicht, wie die Aktivierung von C++-Streams für die Arbeit mit C-Streams die Portabilität beeinträchtigen könnte
– Piotr Dobrogost
11. März 2011 um 20:48 Uhr
9902300cookie-checkWie konstruiere ich einen C++-Fstream aus einem POSIX-Dateideskriptor?yes
Windows und Linux können das
mmap
in die Datei und stellte ihren Inhalt als Byte-Array bereit.– Eigenfeld
21. April 2020 um 21:57 Uhr