Obteniendo un ARCHIVO * desde un std :: fstream

¿Existe una forma (multiplataforma) de obtener un identificador C FILE * desde C ++ std :: fstream?

La razón por la que pregunto es porque mi biblioteca C ++ acepta fstreams y en una función particular me gustaría usar una biblioteca C que acepte un ARCHIVO *.

No hay una forma estandarizada. Supongo que esto se debe a que el grupo de estandarización de C ++ no quería suponer que un identificador de archivo se puede representar como fd.

La mayoría de las plataformas parecen proporcionar alguna forma no estándar para hacer esto.

http://www.ginac.de/~kreckel/fileno/ proporciona una buena descripción de la situación y proporciona un código que oculta toda la grosería específica de la plataforma, al menos para GCC. Teniendo en cuenta lo bruto que esto es solo en GCC, creo que evitaría hacer todo esto juntos si es posible.

ACTUALIZACIÓN: vea @Jettatura lo que creo que es la mejor respuesta https://stackoverflow.com/a/33612982/225186 (¿solo Linux?).

ORIGINAL:

(Probablemente no de plataforma cruzada, pero simple)

Simplificando el truco en http://www.ginac.de/~kreckel/fileno/ (respuesta de dvorak), y mirando esta extensión de gcc http://gcc.gnu.org/onlinedocs/gcc-4.6.2/libstdc++/ api / a00069.html # a59f78806603c619eafcd4537c920f859 , tengo esta solución que funciona en GCC (4.8 al menos) y clang (3.3 al menos)

 #include #include typedef std::basic_ofstream::__filebuf_type buffer_t; typedef __gnu_cxx::stdio_filebuf io_buffer_t; FILE* cfile_impl(buffer_t* const fb){ return (static_cast(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());} 

y se puede usar esto,

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

Limitaciones: (los comentarios son bienvenidos)

  1. Considero que es importante fflush después de la impresión de fprintf a std::ofstream , de lo contrario, la "muestra2" aparece antes de "sample1" en el ejemplo anterior. No sé si hay una mejor solución para eso que usar fflush . Notablemente ofs << flush no ayuda.

  2. No se puede extraer FILE * de std::stringstream , ni siquiera sé si es posible. (ver a continuación para una actualización).

  3. Todavía no sé cómo extraer la stderr de C de std::cerr , etc., por ejemplo para usar en fprintf(stderr, "sample") , en un código hipotético como este fprintf(cfile(std::cerr), "sample") .

En cuanto a la última limitación, la única solución alternativa que encontré es agregar estas sobrecargas:

 FILE* cfile(std::ostream const& os){ if(std::ofstream const* ofsP = dynamic_cast(&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(&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(&is)) return cfile(*ifsP); if(&is == &std::cin) return stdin; if(dynamic_cast(&is) != 0){ throw std::runtime_error("don't know how to extract FILE pointer from std::istringstream"); } return 0; // stream not recognized } 

Intento de manejar iostringstream

Es posible leer con fscanf desde istream usando fmemopen , pero eso requiere mucho mantenimiento y actualización de la posición de entrada de la secuencia después de cada lectura, si se quiere combinar lecturas C y lecturas C ++. No pude convertir esto en una función cfile como la de arriba. (Tal vez una clase de cfile que se mantiene actualizada después de cada lectura es el camino a seguir).

 // hack to access the protected member of istreambuf that know the current position char* access_gptr(std::basic_streambuf>& bs){ struct access_class : std::basic_streambuf>{ 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; } 

Bueno, puedes obtener el descriptor de archivo – Olvido si el método es fd () o getfd (). Las implementaciones que he utilizado proporcionan tales métodos, pero el estándar de lenguaje no los requiere, creo: al estándar no debería importarle si su plataforma usa archivos fd para archivos.

A partir de eso, puede usar fdopen (fd, mode) para obtener un ARCHIVO *.

Sin embargo, creo que los mecanismos que requiere el estándar para sincronizar STDIN / cin, STDOUT / cout y STDERR / cerr no tienen que ser visibles para usted. Entonces, si está usando tanto fstream como FILE *, el almacenamiento en búfer puede arruinarlo.

Además, si se cierra el Fstream O el ARCHIVO, probablemente cierren el fd subyacente, por lo que debes asegurarte de tirar AMBOS antes de cerrar CUALQUIERA.

En una aplicación POSIX de subproceso único, puede obtener fácilmente el número fd de forma portátil:

 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 

Este método se rompe en una aplicación multiproceso si este código compite con otros hilos que abren descriptores de archivos.

otra forma de hacerlo en Linux:

 #include  #include  template 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 (cookie); writer->write(buf, size); return size; } int static cookieClose(void* cookie) { return EOF; } }; // STDIOAdapter 

Uso, por ejemplo:

 #include  #include  #include  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::yield(&out); assert(fp > 0); fputs("Was up, Man", fp); fflush (fp); fclose(fp); return 1; } 

Por favor, mira esta biblioteca

MDS utils

Resuelve el problema, porque permite tratar un C FILE * como un flujo de C ++. Utiliza las bibliotecas Boost C ++. Debes usar Doxygen para ver la documentación.

Hay una manera de obtener el descriptor de archivo de fstream y luego convertirlo a FILE* (a través de fdopen ). Personalmente no veo ninguna necesidad en FILE* , pero con el descriptor de archivo puede hacer muchas cosas interesantes como redirigir ( dup2 ).

Solución:

 #define private public #define protected public #include  #undef private #undef protected std::ifstream file("some file"); auto fno = file._M_filebuf._M_file.fd(); 

La última cadena funciona para libstdc ++. Si está utilizando alguna otra biblioteca, tendrá que realizar una ingeniería inversa un poco.

Este truco es sucio y expondrá a todos los miembros privados y públicos de fstream. Si desea utilizarlo en su código de producción, le sugiero que cree .cpp y .h separados con una sola función int getFdFromFstream(std::basic_ios& fstr); . El archivo de encabezado no debe incluir fstream.