Question

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

La raison pour laquelle je pose cette question est parce que ma bibliothèque C ++ accepte les fstream et que, dans une fonction particulière, j'aimerais utiliser une bibliothèque C qui accepte un fichier *.

Était-ce utile?

La solution

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 sa mise en œuvre. Ainsi, même si vous parvenez à extraire le descripteur de fichier de l'objet funopen() et à créer manuellement un objet FILE, vous aurez d'autres problèmes, car vous aurez maintenant deux objets en mémoire tampon écrivant dans le même descripteur de fichier.

La vraie question est pourquoi voulez-vous convertir l'objet std::stream en un FILE?

Bien que je ne le recommande pas, vous pouvez essayer de rechercher <=>.
Malheureusement, il ne s'agit pas d'une API POSIX (c'est une extension BSD), de sorte que sa portabilité est en cause. C’est aussi probablement la raison pour laquelle je ne trouve personne qui ait enveloppé un <=> 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 *)
             );

Ceci vous permet de construire un objet <=> 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 sur lequel le fichier est ouvert.

Autres conseils

Il n’existe 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é sous la forme d’un fd.

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

http://www.ginac.de/~kreckel/fileno/ fournit une bonne écriture de la situation et fournit un code qui cache toute la grossièreté spécifique à la plate-forme, au moins pour GCC. Étant donné à quel point cela n’est que flagrant pour GCC, je pense que j’éviterais de le faire tous ensemble si possible.

  

UPDATE: Voir @Jettatura ce que j'estime être la meilleure réponse https://stackoverflow.com/a/33612982/225186 (Linux uniquement?).

ORIGINAL:

(Probablement pas multiplateforme, mais simple)

Simplifier le piratage dans http://www.ginac.de/~kreckel/fileno/ (réponse dvorak) et en regardant 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 (au moins 4,8) et clang (au moins 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());}

et peut être utilisé,

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 l'impression sur std::ofstream, sinon le & "; exemple2 &"; apparaît avant " sample1 " dans l'exemple ci-dessus. Je ne sais pas s'il existe une meilleure solution de contournement que d'utiliser ofs << flush. Notamment, std::stringstream n’aide pas.

  2. Impossible d'extraire FILE * de stderr, 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 les C std::cerr de fprintf(stderr, "sample") etc., par exemple à utiliser dans fprintf(cfile(std::cerr), "sample"), dans un code hypothétique tel que celui-ci iostringstream.

En ce qui concerne la dernière limitation, la seule solution que j'ai trouvée consiste à ajouter les surcharges suivantes:

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
}

Tentative de gestion fscanf

Il est possible de lire avec istream de fmemopen avec cfile, mais cela nécessite beaucoup de comptabilité et de mise à jour de la position d'entrée du flux après chaque lecture, si l'on veut combiner C-reads et C ++ - lit. Je n’ai pas pu convertir cela en une <=> fonction comme ci-dessus. (Peut-être qu'une <=> classe qui se met à jour après chaque lecture est la voie à suivre).

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

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 le standard de langage ne les exige pas, je crois - le standard ne devrait pas se soucier de savoir si votre plateforme utilise les fichiers fd pour les fichiers.

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

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

De plus, si le fstream OU le fichier se ferme, ils fermeront probablement le fichier fd sous-jacent. Vous devez donc vous assurer de vider LES DEUX avant de fermer SOIN.

Dans une application POSIX à un seul thread, 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 s'interrompt dans une application multi-thread si ce code est utilisé avec d'autres descripteurs de fichier d'ouverture par thread.

encore une autre façon de faire cela 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;
}

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 dup2, mais avec le descripteur de fichier, vous pouvez faire beaucoup de choses intéressantes, telles que la redirection (.cpp).

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 aurez besoin de la désosser 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 séparément .h et int getFdFromFstream(std::basic_ios<char>& fstr); avec une seule fonction <=>. Le fichier d’en-tête ne doit pas inclure fstream.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top