2008-09-20 10 views
47

¿Existe una forma (multiplataforma) de obtener un identificador C FILE * desde un C++ std :: fstream?Obtención de un ARCHIVO * desde un archivo estándar

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

Respuesta

31

La respuesta corta es no.

La razón es porque el std::fstream no es necesario para usar 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, puede intentar buscar funopen().
Desafortunadamente, esto es no una API POSIX (es una extensión BSD) por lo que su portabilidad está en cuestión. Probablemente también sea por eso que no puedo encontrar a nadie que haya envuelto 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 de las funciones que se utilizan para hacer el trabajo real. Si escribe funciones apropiadas, puede hacer que lean desde el objeto std::fstream que realmente tiene el archivo abierto.

+5

Lástima que es solo para BSD; Hubiera sido una gran solución, ya que permitiría usar un ARCHIVO * con cualquier tipo de flujo de C++. –

+3

Usted preguntó "¿por qué?": Porque uno puede tener una implementación en C de la impresión escrita en C que uno puede reutilizar para C++ ostreams (o ofstreams). – alfC

+0

¿Por qué? Porque sobrecargar al operador << es muy conveniente cuando estoy * usando * un objeto, pero formatear la salida de la secuencia puede ser complicado y doloroso. Formatear con fprintf() es compacto y fácil. En otras palabras, me gustaría poder escribir 'out << someObject << anotherObject' pero implementar operator << usando' fprintf (ofp, "% 8.1lf% 2d \ n", doubleVar, intVar) ' – riderBill

4

Bueno, puede obtener el descriptor de archivo - Olvidé si el método es fd() o getfd(). Las implementaciones que he usado proporcionan tales métodos, pero el estándar de lenguaje no las requiere, creo, al estándar no debería importar si su plataforma usa archivos fd para archivos.

De eso, puede usar fdopen (fd, modo) 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 el archivo fstream O el ARCHIVO se cierra, es probable que cierren el fd subyacente, por lo que debe asegurarse de enjuagar AMBOS antes de cerrar CUALQUIERA.

+1

¿Qué método 'fd()' o 'getfd()' me puede señalar a ellos? – towi

+0

Disculpe, dependerá del compilador/biblioteca que esté utilizando. Para libstdC++, es fd(), parece que: https://gcc.gnu.org/onlinedocs/libstdc++/manual/ext_io.html –

+0

no portátil, ¿eh? lástima. – towi

15

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 de hacerlo.

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.

+0

Great writeup en ese enlace, gracias! –

+3

Un 'FILE *' y un descriptor de archivo son * objetos * diferentes, y los utilizan diferentes componentes. Uno es utilizado por la biblioteca C runtime, el otro es utilizado por el sistema operativo. Consulte [¿Cuál es la diferencia entre un descriptor de archivo y un puntero de archivo?] (Http://stackoverflow.com/q/2423628) – jww

1

Por favor, mira esta biblioteca

MDS utils

Se resuelve el problema, porque permite tratar un archivo C * como corriente de C++. Utiliza las bibliotecas Boost C++.Debes usar Doxygen para ver la documentación.

7

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

ORIGINAL:

(probablemente no es multiplataforma, pero simple)

Simplifiying el hack en http://www.ginac.de/~kreckel/fileno/ (respuesta Dvorak), y mirando a esta extensión 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 por lo menos) y clang (al menos 3.3)

#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 ca n ser utilizado 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: (comentarios son bienvenidos)

  1. Me parece que es importante fflush después fprintf impresión a std::ofstream, de lo contrario el "sample2" 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 de stderr de std::cerr etc., por ejemplo, para su uso en fprintf(stderr, "sample"), en un código hipotético como éste fprintf(cfile(std::cerr), "sample") C.

En cuanto a la última limitación, la única solución que encontré es añadir 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 de istream usando fmemopen, pero eso requiere una gran cantidad de libros y la actualización de la posición de entrada de la secuencia después de cada lectura, si uno quiere combinar C-re anuncios y C++ - lee. No pude convertir esto en una función cfile como la anterior. (Tal vez una cfileclase 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; 
} 
+0

¡Esto es genial! Recientemente tuve que hacer algunos tcsetattr() trabajo en algo que vino como un flujo y su escritura realmente me ayudó. . –

3

En una aplicación de un solo subproceso POSIX puede obtener fácilmente el número fd de una manera 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 

rompe este método en una aplicación multi-hilo si este código carreras con otros hilos de abrir descriptores de archivos.

2

otra manera 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; 
} 
+2

Excelente, me complementarlo con 'plantilla ARCHIVO * cfile (la secuencia y de s) { \t retorno STDIOAdapter :: rendimiento (&s); }' 'Trabaja con std :: cout',' std: : cerr' y mezclando 'fprintf' y código de C++. Yo recomendaría usar esto. – alfC

+0

Estoy comentando nuevamente para confirmar que esto funciona con' std :: ostringstream'. – alfC

+0

tiene sentido tener también la función 'seek'. \t 'int cookieSeek (void * galleta, ssize_t * apagado, int modo) { \t \t static_cast (cookie) -> seekg (* apagado, static_cast (cookie) -> final); \t \t manera de retorno; \t} '? (no estoy seguro si estoy usando 'way' correctamente). – alfC

0

Hay una manera de conseguir descriptor desde el fstream y luego convertirlo a FILE* (a través 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 trabaja para libstdC++. Si está utilizando alguna otra biblioteca, tendrá que realizar una ingeniería inversa un poco.

Este truco está 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 con una sola función int getFdFromFstream(std::basic_ios<char>& fstr);. El archivo de encabezado no debe incluir fstream.

Cuestiones relacionadas