web-dev-qa-db-fra.com

Obtenir un FICHIER * à partir d'un std :: fstream

Existe-t-il un moyen (multiplateforme) d'obtenir un descripteur C FILE * à partir d'un std :: fstream C++?

La raison pour laquelle je demande, c'est parce que ma bibliothèque C++ accepte les fstreams et dans une fonction particulière, j'aimerais utiliser une bibliothèque C qui accepte un FILE *.

53
Bek

La réponse courte est non.

La raison en est que std::fstream N'est pas obligé d'utiliser un FILE* Dans le cadre de son implémentation. Ainsi, même si vous parvenez à extraire le descripteur de fichier de l'objet std::fstream Et à créer manuellement un objet FILE, vous rencontrerez d'autres problèmes car vous aurez désormais deux objets tamponnés écrivant dans le même descripteur de fichier.

La vraie question est pourquoi voulez-vous convertir l'objet std::fstream En un FILE*?

Bien que je ne le recommande pas, vous pouvez essayer de rechercher funopen().
Malheureusement, c'est ne pas une API POSIX (c'est une extension BSD) donc sa portabilité est en cause. C'est probablement aussi pourquoi je ne trouve personne qui a enveloppé un std::stream Avec un objet comme celui-ci.

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 *)
             );

Cela vous permet de créer un objet FILE et de spécifier certaines fonctions qui seront utilisées pour effectuer le travail réel. Si vous écrivez des fonctions appropriées, vous pouvez les faire lire à partir de l'objet std::fstream Qui a réellement ouvert le fichier.

35
Martin York

Il n'y a pas de méthode standardisée. Je suppose que c'est parce que le groupe de normalisation C++ ne voulait pas supposer qu'un descripteur de fichier peut être représenté comme un fd.

La plupart des plates-formes semblent fournir un moyen non standard de le faire.

http://www.ginac.de/~kreckel/fileno/ fournit un bon résumé de la situation et fournit un code qui masque toute la grossièreté spécifique à la plate-forme, au moins pour GCC. Étant donné la gravité de ce problème sur GCC, je pense que j'éviterais de faire tout cela ensemble si possible.

17
dvorak

MISE À JOUR: Voir @Jettatura ce que je pense que c'est la meilleure réponse https://stackoverflow.com/a/33612982/225186 (Linux uniquement?).

ORIGINAL:

(Probablement pas multiplateforme, mais simple)

Simplifier le hack dans http://www.ginac.de/~kreckel/fileno/ (réponse dvorak), et regarder cette extension gcc http://gcc.gnu.org /onlinedocs/gcc-4.6.2/libstdc++/api/a00069.html#a59f78806603c619eafcd4537c920f859 , j'ai cette solution qui fonctionne sur GCC (4,8 au moins) et clang (3,3 à moins)

#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());}

et peut être utilisé cela,

int main(){
    std::ofstream ofs("file.txt");
    fprintf(cfile(ofs), "sample1");
    fflush(cfile(ofs)); // ofs << std::flush; doesn't help 
    ofs << "sample2\n";
}

Limitations: (les commentaires sont les bienvenus)

  1. Je trouve qu'il est important de fflush après fprintf d'imprimer sur std::ofstream, Sinon "sample2" apparaît avant "sample1" dans l'exemple ci-dessus. Je ne sais pas s'il existe une meilleure solution de contournement pour cela que d'utiliser fflush. Notamment ofs << flush N'aide pas.

  2. Impossible d'extraire le FICHIER * de std::stringstream, Je ne sais même pas si c'est possible. (voir ci-dessous pour une mise à jour).

  3. Je ne sais toujours pas comment extraire le stderr de C à partir de std::cerr Etc., par exemple pour l'utiliser dans fprintf(stderr, "sample"), dans un code hypothétique comme celui-ci fprintf(cfile(std::cerr), "sample").

Concernant la dernière limitation, la seule solution de contournement que j'ai trouvée est d'ajouter ces surcharges:

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
}

Essayez de gérer iostringstream

Il est possible de lire avec fscanf depuis istream en utilisant fmemopen, mais cela nécessite beaucoup de tenue de livres et de mise à jour de la position d'entrée du flux après chaque lecture, si l'on veut pour combiner les lectures C et les lectures C++. Je n'ai pas pu convertir ceci en une fonction cfile comme ci-dessus. (Peut-être une cfile classe qui continue de se mettre à jour après chaque lecture est le chemin à parcourir).

// 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;
}
9
alfC

Dans une application POSIX monothread, vous pouvez facilement obtenir le numéro fd de manière portable:

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

Cette méthode se casse dans une application multithread si ce code court avec d'autres threads ouvrant des descripteurs de fichier.

4
Maxim Egorushkin

Eh bien, vous pouvez obtenir le descripteur de fichier - j'oublie si la méthode est fd () ou getfd (). Les implémentations que j'ai utilisées fournissent de telles méthodes, mais la norme de langage ne les requiert pas, je crois - la norme ne devrait pas se soucier si votre plate-forme utilise des fd pour les fichiers.

À partir de là, vous pouvez utiliser fdopen (fd, mode) pour obtenir un FICHIER *.

Cependant, je pense que les mécanismes requis par la norme pour synchroniser STDIN/cin, STDOUT/cout et STDERR/cerr ne doivent pas être visibles pour vous. Donc, si vous utilisez à la fois fstream et FILE *, la mise en mémoire tampon peut vous gâcher.

De plus, si soit le fstream OR le FICHIER se ferme, ils fermeront probablement le fd sous-jacent, vous devez donc vous assurer de vider les DEUX avant de fermer l'un ou l'autre.

4
Mike G.

encore une autre façon de le faire sous 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

Utilisation, par exemple:

#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
Jettatura

Il existe un moyen d'obtenir le descripteur de fichier à partir de fstream puis de le convertir en FILE* (Via fdopen). Personnellement, je ne vois aucun besoin dans FILE*, Mais avec le descripteur de fichier, vous pouvez faire beaucoup de choses intéressantes telles que la redirection (dup2).

Solution:

#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 dernière chaîne fonctionne pour libstdc ++. Si vous utilisez une autre bibliothèque, vous devrez la rétroconcevoir un peu.

Cette astuce est sale et exposera tous les membres privés et publics de fstream. Si vous souhaitez l'utiliser dans votre code de production, je vous suggère de créer des .cpp Et .h Séparés avec une seule fonction int getFdFromFstream(std::basic_ios<char>& fstr);. Le fichier d'en-tête ne doit pas inclure fstream.

1
yanpas

S'il vous plaît, regardez cette bibliothèque

tilitaires MDS

Il résout le problème, car permet de traiter un C FILE * comme un flux C++. Il utilise des bibliothèques Boost C++. Vous devez utiliser Doxygen pour afficher la documentation.

1