Wie Sie einen FILE * von einem std :: fstream
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 *.
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)
#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)
-
Ich finde, dass es wichtig ist, nach
fflush
Druckfprintf
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 alsfflush
verwenden. Bemerkenswerterofs << flush
hilft nicht. -
Die Datei kann nicht * von
std::stringstream
extrahieren, ich weiß nicht einmal, ob es möglich ist. (Siehe unten für ein Update). -
Ich weiß noch nicht, wie C des
stderr
vonstd::cerr
usw. zu extrahieren, zum Beispiel infprintf(stderr, "sample")
zu verwenden, in einem hypothetischen Code wie dieserfprintf(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.