web-dev-qa-db-fra.com

Comment construire un flux c ++ à partir d'un descripteur de fichier POSIX?

Je recherche essentiellement une version C++ de fdopen (). J'ai fait un peu de recherche à ce sujet et c'est une de ces choses qui semble être facile, mais qui s'avère très compliquée. Suis-je en train de manquer quelque chose dans cette croyance (c'est-à-dire que c'est vraiment facile)? Sinon, existe-t-il une bonne bibliothèque quelque part pour gérer cela?

EDIT: Déplacé mon exemple de solution vers une réponse distincte.

86
BD at Rivenhill

D'après la réponse d'Éric Malenfant:

AFAIK, il n'y a aucun moyen de le faire en C++ standard. Selon votre plate-forme, votre implémentation de la bibliothèque standard peut offrir (en tant qu'extension non standard) un constructeur fstream prenant un descripteur de fichier en entrée. (C'est le cas pour libstdc ++, IIRC) ou un FILE *.

Sur la base des observations ci-dessus et de mes recherches ci-dessous, il existe un code de travail en deux variantes; un pour libstdc ++ et un autre pour Microsoft Visual C++.


libstdc ++

Il y a non standard __gnu_cxx::stdio_filebuf modèle de classe qui hérite de std::basic_streambuf et a le constructeur suivant

stdio_filebuf (int __fd, std::ios_base::openmode __mode, size_t __size=static_cast< size_t >(BUFSIZ)) 

avec description Ce constructeur associe un tampon de flux de fichiers à un descripteur de fichier POSIX ouvert.

Nous le créons en passant le handle POSIX (ligne 1) puis nous le transmettons au constructeur d'istream en tant que basic_streambuf (ligne 2):

#include <ext/stdio_filebuf.h>
#include <iostream>
#include <fstream>
#include <string>

using namespace std;

int main()
{
    ofstream ofs("test.txt");
    ofs << "Writing to a basic_ofstream object..." << endl;
    ofs.close();

    int posix_handle = fileno(::fopen("test.txt", "r"));

    __gnu_cxx::stdio_filebuf<char> filebuf(posix_handle, std::ios::in); // 1
    istream is(&filebuf); // 2

    string line;
    getline(is, line);
    cout << "line: " << line << std::endl;
    return 0;
}

Microsoft Visual C++

Il y avait autrefois version non standard du constructeur ifstream prenant le descripteur de fichier POSIX mais il manque à la fois dans les documents current et dans le code. Il existe une autre version non standard du constructeur ifstream prenant FILE *

explicit basic_ifstream(_Filet *_File)
    : _Mybase(&_Filebuffer),
        _Filebuffer(_File)
    {   // construct with specified C stream
    }

et ce n'est pas documenté (je n'ai même pas pu trouver d'anciens documents où ils seraient présents). Nous l'appelons (ligne 1) avec le paramètre résultant de l'appel de _ fdopen pour obtenir le flux C FILE * à partir du descripteur de fichier POSIX.

#include <cstdio>
#include <iostream>
#include <fstream>
#include <string>

using namespace std;

int main()
{
    ofstream ofs("test.txt");
    ofs << "Writing to a basic_ofstream object..." << endl;
    ofs.close();

    int posix_handle = ::_fileno(::fopen("test.txt", "r"));

    ifstream ifs(::_fdopen(posix_handle, "r")); // 1

    string line;
    getline(ifs, line);
    ifs.close();
    cout << "line: " << line << endl;
    return 0;
}
66
Piotr Dobrogost

AFAIK, il n'y a aucun moyen de le faire en C++ standard. Selon votre plateforme, votre implémentation de la bibliothèque standard peut offrir (comme une extension non standard) un constructeur fstream prenant un descripteur de fichier (c'est le cas pour libstdc ++, IIRC) ou un FILE* en entrée.

Une autre alternative serait d'utiliser un appareil boost :: iostreams :: file_descriptor , que vous pourriez encapsuler dans un boost :: iostreams :: stream si vous voulez avoir un std :: interface de flux vers elle.

38
Éric Malenfant

Une partie de la motivation originale (non déclarée) de cette question est d'avoir la possibilité de passer des données entre des programmes ou entre deux parties d'un programme de test en utilisant un fichier temporaire créé en toute sécurité, mais tmpnam () lance un avertissement dans gcc, donc je voulais d'utiliser à la place mkstemp (). Voici un programme de test que j'ai écrit sur la base de la réponse donnée par Éric Malenfant mais en utilisant mkstemp () au lieu de fdopen (); cela fonctionne sur mon système Ubuntu avec les bibliothèques Boost installées:

#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <string>
#include <iostream>
#include <boost/filesystem.hpp>
#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/stream.hpp>

using boost::iostreams::stream;
using boost::iostreams::file_descriptor_sink;
using boost::filesystem::path;
using boost::filesystem::exists;
using boost::filesystem::status;
using boost::filesystem::remove;

int main(int argc, const char *argv[]) {
  char tmpTemplate[13];
  strncpy(tmpTemplate, "/tmp/XXXXXX", 13);
  stream<file_descriptor_sink> tmp(mkstemp(tmpTemplate));
  assert(tmp.is_open());
  tmp << "Hello mkstemp!" << std::endl;
  tmp.close();
  path tmpPath(tmpTemplate);
  if (exists(status(tmpPath))) {
    std::cout << "Output is in " << tmpPath.file_string() << std::endl;
    std::string cmd("cat ");
    cmd += tmpPath.file_string();
    system(cmd.c_str());
    std::cout << "Removing " << tmpPath.file_string() << std::endl;
    remove(tmpPath);
  }
}
8
BD at Rivenhill

Il y a de fortes chances que votre compilateur propose un constructeur fstream basé sur FILE, même s'il n'est pas standard. Par exemple:

FILE* f = fdopen(my_fd, "a");
std::fstream fstr(f);
fstr << "Greetings\n";

Mais pour autant que je sache, il n'y a aucun moyen portable de le faire.

8
Darryl

J'ai essayé la solution proposée ci-dessus pour libstdc ++ par Piotr Dobrogost, et j'ai trouvé qu'elle avait un défaut douloureux: en raison de l'absence d'un constructeur de déplacement approprié pour istream, il est très difficile de retirer l'objet istream nouvellement construit de la fonction de création . Un autre problème avec cela est qu'il fuit un objet FILE (même si ce n'est pas le descripteur de fichier posix sous-jacent). Voici une solution alternative qui évite ces problèmes:

#include <fstream>
#include <string>
#include <ext/stdio_filebuf.h>
#include <type_traits>

bool OpenFileForSequentialInput(ifstream& ifs, const string& fname)
{
    ifs.open(fname.c_str(), ios::in);
    if (! ifs.is_open()) {
        return false;
    }

    using FilebufType = __gnu_cxx::stdio_filebuf<std::ifstream::char_type>;
    static_assert(  std::is_base_of<ifstream::__filebuf_type, FilebufType>::value &&
                    (sizeof(FilebufType) == sizeof(ifstream::__filebuf_type)),
            "The filebuf type appears to have extra data members, the cast might be unsafe");

    const int fd = static_cast<FilebufType*>(ifs.rdbuf())->fd();
    assert(fd >= 0);
    if (0 != posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL)) {
        ifs.close();
        return false;
    }

    return true;
}

L'appel à posix_fadvise () montre une utilisation potentielle. Notez également que l'exemple utilise static_assert et sing qui sont C++ 11, à part cela, il devrait se construire très bien en mode C++ 03.

4
YitzikC

C'est en fait assez simple. Nicolai M. Josuttis a publié fdstream conjointement avec son livre La bibliothèque standard C++ - Un tutoriel et une référence . Vous pouvez trouver l'implémentation de 184 lignes ici .

4
Mark