Obtener un ARCHIVO * de una std:: fstream


¿Hay una forma (multiplataforma) de obtener un manejador de ARCHIVO C* de un C++ std::fstream ?

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

Author: Doug T., 2008-09-21

8 answers

La respuesta corta es no.

La razón, es porque el std::fstream no es necesario utilizar un FILE* como parte de su aplicación. Por lo tanto, incluso si logra extraer el descriptor de archivo del objeto std::fstream y construir manualmente un objeto de ARCHIVO, entonces tendrá otros problemas porque ahora tendrá dos objetos en búfer escribiendo en el mismo descriptor de archivo.

La verdadera pregunta es ¿por qué desea convertir el objeto std::fstream en un FILE*?

Aunque no lo recomiendo, podrías intentar mirar hacia arriba funopen().
Desafortunadamente, esto es no una API POSIX (es una extensión BSD) por lo que su portabilidad está en cuestión. Que es también probablemente 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 utilizarán para hacer el trabajo real. Si escribe las funciones apropiadas, puede hacer que se lean desde el objeto std::fstream que realmente tiene el archivo abierto.

 31
Author: Martin York,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-27 18:29:27

No Hay una forma estandarizada. Asumo que esto se debe a que el grupo de estandarización de C++ no quería asumir que un controlador de archivo se puede representar como un fd.

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

Http://www.ginac.de/~kreckel/fileno / proporciona un buen resumen de la situación y proporciona código que oculta toda la grosería específica de la plataforma, al menos para GCC. Dado lo asqueroso que es esto solo en GCC, creo que evitaría hacer todo esto juntos si es posible.

 16
Author: dvorak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-20 21:53:40

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

ORIGINAL:

(Probablemente no multiplataforma, sino simple)

Simplificando el hackeo en http://www.ginac.de / ~kreckel / fileno / (respuesta dvorak), y mirando esta extensión gcchttp://gcc.gnu.org/onlinedocs/gcc-4.6.2/libstdc++/api/a00069.html#a59f78806603c619eafcd4537c920f859, Tengo esto 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 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)

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

  2. No se puede extraer EL ARCHIVO * de std::stringstream, ni siquiera sé si es posible. (ver más abajo para una actualización).

  3. Todavía no sé cómo extraer C stderr 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").

Con 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
}

Intento de manejar iostringstream

Es posible leer con fscanf desde istream usando fmemopen, pero eso requiere mucho de mantenimiento de libros y actualización de la posición de entrada de la secuencia después de cada lectura, si se quiere combinar C-reads y C++-reads. No pude convertir esto en una función cfile como la anterior. (Tal vez un cfile la clase que sigue actualizándose 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;
}
 8
Author: alfC,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-28 23:30:31

Bueno, puedes obtener el descriptor de fichero - 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 le debería importar si su plataforma usa fd para archivos.

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

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

Además, si se cierra el fstream O el ARCHIVO, probablemente cerrarán el fd subyacente, por lo que debe asegurarse de limpiar AMBOS antes de cerrar CUALQUIERA.

 4
Author: Mike G.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-20 21:40:50

En una aplicación POSIX de un solo subproceso 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

Este método se rompe en una aplicación multihilo si este código corre con otros hilos abriendo descriptores de archivo.

 3
Author: Maxim Egorushkin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-11-04 23:03:02

Otra forma 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
Author: Jettatura,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-07 18:54:47

Por favor, mira esta biblioteca

MDS utils

Resuelve el problema, porque permite tratar un ARCHIVO C* como una secuencia de C++. Utiliza bibliotecas Boost C++. Tienes que usar Doxygen para ver la documentación.

 1
Author: alfC,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-11-15 08:24:41

Hay una manera de obtener descriptor de archivo de fstream y luego convertirlo a FILE* (a través de fdopen). Personalmente no veo ninguna necesidad en FILE*, pero con 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, necesitará realizar ingeniería inversa un poco.

Este truco es sucio y expondrá a todos los miembros públicos y privados de fstream. Si quieres para usarlo 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.

 1
Author: yanpas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-15 21:54:31