Comment construire un fstream C ++ à partir d'un descripteur de fichier POSIX?

93

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 devoir être facile, mais qui s'avère très compliquée. Est-ce que je manque quelque chose dans cette croyance (c'est-à-dire que c'est vraiment facile)? Sinon, y a-t-il une bonne bibliothèque quelque part pour gérer cela?

EDIT: J'ai déplacé mon exemple de solution vers une réponse distincte.

BD chez Rivenhill
la source
@Kazark - déplacé vers une réponse distincte maintenant, merci.
BD à Rivenhill le
Windows et Linux peuvent faire mmaple fichier et exposé son contenu sous forme de tableau d'octets.
eigenfield le

Réponses:

72

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 de libstdc ++, IIRC) ou d'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 existe un __gnu_cxx::stdio_filebufmodèle de classe non standard qui hérite std::basic_streambufet 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 passons 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 une version non standard du constructeur d'ifstream prenant le descripteur de fichier POSIX, mais il manque à la fois dans la documentation actuelle et dans le code. Il existe une autre version non standard du constructeur d'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 de documentation ancienne où il serait présent). 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;
}
Piotr Dobrogost
la source
2
Maintenant, la réponse acceptée en raison de l'exhaustivité. D'autres pourraient être intéressés par ma solution utilisant boost, qui a été déplacée vers une réponse distincte.
BD à Rivenhill le
1
Pour linux: Si vous regardez ios_init.cc dans gcc (la source que j'ai est pour la version 4.1.1) std :: cout est initialisé en initialisant un stdio_sync_filebuf <char> autour de votre descripteur de fichier, puis en initialisant sur ostream autour de votre stdio_sync_filebuf < char>. Je ne peux pas prétendre que cela va être stable.
Sparky
@Sparky Examiner la std::coutmise en œuvre est une bonne idée. Je me demande quelle est la différence entre stdio_filebufet stdio_sync_filebuf?
Piotr Dobrogost
Les fds POSIX dans MSVC sont une émulation. L'API Windows pour les opérations sur les fichiers diffère de celles de POSIX à bien des égards - différents noms de fonctions et types de paramètres de données.Windows utilise en interne des «poignées» pour identifier divers objets API Windows, et le type d'API Windows HANDLE est défini comme void *, donc à au minimum, il ne rentrera pas dans "int" (qui est 32 bits) sur les plates-formes 64 bits. Donc, pour Windows, vous pouvez être intéressé par la recherche d'un flux qui permet de travailler sur le fichier API Windows HANDLE.
ivan.ukr
40

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

Une autre alternative serait d'utiliser un périphérique boost :: iostreams :: file_descriptor , que vous pouvez envelopper dans un boost :: iostreams :: stream si vous voulez avoir une interface std :: stream.

Éric Malenfant
la source
4
Étant donné que c'est la seule solution portable, je ne comprends pas pourquoi ce n'est pas la réponse acceptée ou la mieux notée.
Maarten
8

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 pas de moyen portable de le faire.

Darryl
la source
2
Notez que g ++ (correctement) ne permettra pas cela en mode c ++ 11
Mark K Cowan
8

Une partie de la motivation originale (non déclarée) de cette question est d'avoir la possibilité de transmettre des données soit entre programmes, soit entre deux parties d'un programme de test en utilisant un fichier temporaire créé en toute sécurité, mais tmpnam () jette un avertissement dans gcc, donc je voulais pour utiliser mkstemp () à la place. 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);
  }
}
BD chez Rivenhill
la source
4

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 mouvement approprié pour istream, il est très difficile d'extraire l'objet istream nouvellement construit de la fonction de création . Un autre problème avec celui-ci 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 () démontre une utilisation potentielle. Notez également que l'exemple utilise static_assert et utilise qui sont C ++ 11, à part cela, il devrait très bien se construire en mode C ++ 03.

YitzikC
la source
Qu'entendez-vous par version appropriée du constructeur de mouvement ? Quelle version de gcc avez-vous utilisée? Peut-être que cette version n'avait pas encore implémenté de constructeurs de déplacement - voir Le constructeur de déplacement d'ifsteam est-il implicitement supprimé? ?
Piotr Dobrogost
1
Il s'agit d'un hack qui dépend des détails d'implémentation sous-jacents. J'espère que personne ne l'utilisera jamais dans le code de production.
davmac
-4

Je crois comprendre qu'il n'y a aucune association avec des pointeurs FILE ou des descripteurs de fichier dans le modèle d'objet C ++ iostream afin de garder le code portable.

Cela dit, j'ai vu plusieurs endroits faire référence aux mds-utils ou boost pour aider à combler cet écart.

socle
la source
9
FILE * est C standard et donc C ++, donc je ne vois pas comment l'activation des flux C ++ pour fonctionner avec les flux C pourrait nuire à la portabilité
Piotr Dobrogost