Obtener un ARCHIVO* de un std::fstream
Pregunta
¿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*.
Solución
La respuesta corta es no.
La razón, es porque el std::fstream
no es necesario utilizar un FILE*
como parte de su implementación.Entonces, incluso si logras extraer el descriptor de archivo del std::fstream
objeto y construye manualmente un objeto ARCHIVO, entonces tendrás otros problemas porque ahora tendrás dos objetos almacenados en el búfer escribiendo en el mismo descriptor de archivo.
La verdadera pregunta es ¿por qué quieres convertir el std::fstream
objeto en un FILE*
?
Aunque no lo recomiendo, puedes intentar buscar funopen()
.
Desafortunadamente, esto es no una API POSIX (es una extensión BSD), por lo que su portabilidad está en duda.Probablemente también sea por eso que no puedo encontrar a nadie que haya envuelto un std::stream
con un objeto como este.
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 *)
);
Esto le permite construir un FILE
objeto y especifique algunas funciones que se utilizarán para realizar el trabajo real.Si escribe funciones apropiadas, puede hacer que lean desde el std::fstream
objeto que realmente tiene el archivo abierto.
Otros consejos
No existe una forma estandarizada.Supongo que esto se debe a que el grupo de estandarización de C++ no quería asumir que un identificador de archivo se puede representar como un fd.
La mayoría de las plataformas parecen ofrecer alguna forma no estándar de hacer esto.
http://www.ginac.de/~kreckel/fileno/ proporciona un buen resumen de la situación y proporciona un código que oculta toda la aspereza específica de la plataforma, al menos para GCC.Dado lo asqueroso que es esto solo en GCC, creo que, si es posible, evitaría hacer esto por completo.
ACTUALIZAR:Ver @Jettatura cuál creo que es la mejor respuesta. https://stackoverflow.com/a/33612982/225186 (¿Solo Linux?).
ORIGINAL:
(Probablemente no sea multiplataforma, pero sí simple)
Simplificando el hack en http://www.ginac.de/~kreckel/fileno/ (respuesta de dvorak), y mirando esta extensión gcc http://gcc.gnu.org/onlinedocs/gcc-4.6.2/libstdc++/api/a00069.html#a59f78806603c619eafcd4537c920f859, Tengo esta solución en la que funciona GCC
(4,8 al menos) y clang
(3.3 al menos)
#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());}
y se puede utilizar 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)
Considero que es importante
fflush
despuésfprintf
imprimiendo astd::ofstream
, De lo contrario, "muestra2" aparece antes de "muestra1" en el ejemplo anterior.No sé si hay una mejor solución para eso que usarfflush
.Notablementeofs << flush
no ayuda.No se puede extraer ARCHIVO* de
std::stringstream
, Ni siquiera sé si es posible.(ver más abajo para una actualización).Todavía no sé cómo extraer las C.
stderr
destd::cerr
etc., por ejemplo para utilizar enfprintf(stderr, "sample")
, en un código hipotético como estefprintf(cfile(std::cerr), "sample")
.
Respecto a la última limitación, la única solución que encontré es agregar estas sobrecargas:
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
}
Intentar manejar iostringstream
Es posible leer con fscanf
de istream
usando fmemopen
, pero eso requiere mucha contabilidad y actualización de la posición de entrada de la secuencia después de cada lectura, si se quiere combinar lecturas de C y lecturas de C++.No pude convertir esto en un cfile
funcionar como arriba.(Tal vez un cfile
clase que se siga actualizando 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<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;
}
Bueno, puedes obtener el descriptor del archivo. No recuerdo si el método es fd() o getfd(). Las implementaciones que he usado proporcionan tales métodos, pero creo que el estándar del lenguaje no los requiere; al estándar no debería importarle si su plataforma usa fd para archivos.
A partir de ahí, puedes 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ás usando fstream y FILE*, el almacenamiento en búfer puede arruinarte.
Además, si se cierra fstream O ARCHIVO, probablemente cerrarán el fd subyacente, por lo que debe asegurarse de vaciar AMBOS antes de cerrar CUALQUIERA.
En una aplicación POSIX de un solo subproceso, 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 interrumpe en una aplicación de subprocesos múltiples si este código compite con otros subprocesos al abrir descriptores de archivos.
Otra forma más de hacer esto en Linux:
#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
Uso, por ejemplo:
#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;
}
Hay una manera de obtener el descriptor de archivo desde fstream
y luego convertirlo a FILE*
(a través de fdopen
).Personalmente no veo ninguna necesidad FILE*
, pero con el descriptor de archivo puedes hacer muchas cosas interesantes como redirigir (dup2
).
Solución:
#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();
La última cadena funciona para libstdc++.Si está utilizando alguna otra biblioteca, necesitará realizarle un poco de ingeniería inversa.
Este truco es sucio y expondrá a todos los miembros públicos y privados de fstream.Si desea utilizarlo en su código de producción, le sugiero que cree por separado .cpp
y .h
con una sola función int getFdFromFstream(std::basic_ios<char>& fstr);
.El archivo de encabezado no debe incluir fstream.