c++ - open - Obteniendo un ARCHIVO*desde un std:: fstream
read file c++ line by line (8)
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<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 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)
Considero que es importante
fflush
después de la impresión defprintf
astd::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 usarfflush
. Notablementeofs << flush
no ayuda.No se puede extraer FILE * de
std::stringstream
, ni siquiera sé si es posible. (ver a continuación para una actualización).Todavía no sé cómo extraer la
stderr
de C destd::cerr
, etc., por ejemplo para usar enfprintf(stderr, "sample")
, en un código hipotético como estefprintf(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<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
}
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<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;
}
¿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 *.
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.
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 <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, 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<char>& fstr);
. El archivo de encabezado no debe incluir fstream.
La respuesta corta es no.
El motivo es que std::fstream
no necesita utilizar un FILE*
como parte de su implementación. Por lo tanto, incluso si logra extraer el descriptor de archivo del objeto std::fstream
y crear manualmente un objeto FILE, entonces tendrá otros problemas porque ahora tendrá dos objetos almacenados en el mismo descriptor de archivo.
La verdadera pregunta es por qué quieres convertir el objeto std::fstream
en un FILE*
?
Aunque no lo recomiendo, podrías intentar buscar funopen()
.
Desafortunadamente, esto no es una API POSIX (es una extensión BSD) por lo que su portabilidad está en duda. Probablemente también sea la razón por la 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 objeto FILE
y especificar algunas funciones que se usarán para hacer el trabajo real. Si escribe funciones apropiadas, puede hacer que lean desde el objeto std::fstream
que realmente tiene el archivo abierto.
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.
Por favor, mira esta biblioteca
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.
otra forma de hacerlo 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;
}