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*.

¿Fue útil?

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)

  1. Considero que es importante fflush después fprintf imprimiendo a std::ofstream, De lo contrario, "muestra2" aparece antes de "muestra1" 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 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 las C. stderr de std::cerr etc., por ejemplo para utilizar en fprintf(stderr, "sample"), en un código hipotético como este fprintf(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.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top