Come costruire un c ++ fstream da un descrittore di file POSIX?


93

Fondamentalmente sto cercando una versione C ++ di fdopen (). Ho fatto un po 'di ricerca su questo ed è una di quelle cose che sembra dovrebbe essere facile, ma risulta essere molto complicata. Mi manca qualcosa in questa convinzione (cioè è davvero facile)? In caso contrario, c'è una buona libreria là fuori da qualche parte per gestire questo?

EDIT: spostato la mia soluzione di esempio in una risposta separata.


@Kazark - passa a una risposta separata ora, grazie.
BD a Rivenhill

Windows e Linux possono eseguire operazioni mmapsul file ed esporne il contenuto come array di byte.
truthadjustr

Risposte:


72

Dalla risposta data da Éric Malenfant:

Per quanto ne so, non c'è modo di farlo in C ++ standard. A seconda della piattaforma, l'implementazione della libreria standard può offrire (come estensione non standard) un costruttore fstream che accetta un descrittore di file come input. (Questo è il caso di libstdc ++, IIRC) o di un FILE *.

Sulla base delle osservazioni precedenti e della mia ricerca di seguito, c'è un codice funzionante in due varianti; uno per libstdc ++ e un altro per Microsoft Visual C ++.


libstdc ++

C'è un __gnu_cxx::stdio_filebufmodello di classe non standard che eredita std::basic_streambufe ha il seguente costruttore

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

con descrizione Questo costruttore associa un buffer del flusso di file a un descrittore di file POSIX aperto.

Lo creiamo passando l'handle POSIX (riga 1) e poi lo passiamo al costruttore di istream come basic_streambuf (riga 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 ++

C'era una versione non standard del costruttore di ifstream che accettava il descrittore di file POSIX, ma manca sia dalla documentazione corrente che dal codice. Esiste un'altra versione non standard del costruttore di ifstream che accetta FILE *

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

e non è documentato (non sono riuscito nemmeno a trovare alcuna vecchia documentazione dove sarebbe stata presente). Lo chiamiamo (riga 1) con il parametro che è il risultato della chiamata a _fdopen per ottenere il FILE di flusso C * dall'handle del file 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;
}

2
Ora la risposta accettata per completezza. Altri potrebbero essere interessati alla mia soluzione utilizzando boost, che è stata spostata in una risposta separata.
BD a Rivenhill

1
Per Linux: se guardi ios_init.cc in gcc (il sorgente che ho è per la versione 4.1.1) std :: cout viene inizializzato inizializzando uno stdio_sync_filebuf <char> attorno al tuo descrittore di file, quindi inizializzando su ostream attorno al tuo stdio_sync_filebuf < char>. Non posso affermare che questo sarà stabile però.
Sparky

@Sparky Esaminare l' std::coutimplementazione è una buona idea. Mi chiedo qual è la differenza tra stdio_filebufe stdio_sync_filebuf?
Piotr Dobrogost

Gli fds POSIX in MSVC sono un'emulazione. L'API di Windows per le operazioni sui file differisce da quelle POSIX in molti modi: diversi nomi di funzioni e tipi di dati dei parametri. Windows utilizza internamente i cosiddetti "handle" per identificare vari oggetti API di Windows, e il tipo di API di Windows HANDLE è definito come void *, quindi su minimo non si adatterà a "int" (che è a 32 bit) su piattaforme a 64 bit. Quindi per Windows potresti essere interessato a cercare stream che permetta di lavorare su file API di Windows HANDLE.
ivan.ukr

40

Per quanto ne so, non c'è modo di farlo in C ++ standard. A seconda della piattaforma, l'implementazione della libreria standard può offrire (come estensione non standard) un costruttore fstream che accetta un descrittore di file (questo è il caso di libstdc ++, IIRC) o un FILE*come input.

Un'altra alternativa potrebbe essere quella di utilizzare un dispositivo boost :: iostreams :: file_descriptor , che potresti racchiudere in un boost :: iostreams :: stream se desideri avere un'interfaccia std :: stream.


4
Considerando che questa è l'unica soluzione portatile, non capisco perché questa non sia la risposta accettata o apprezzata.
Maarten

8

Ci sono buone probabilità che il tuo compilatore offra un costruttore fstream basato su FILE, anche se non è standard. Per esempio:

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

Ma per quanto ne so, non esiste un modo portatile per farlo.


2
Nota che g ++ (correttamente) non lo consentirà in modalità c ++ 11
Mark K Cowan

8

Parte della motivazione originale (non dichiarata) di questa domanda è di avere la capacità di passare i dati tra programmi o tra due parti di un programma di test utilizzando un file temporaneo creato in modo sicuro, ma tmpnam () lancia un avviso in gcc, quindi volevo utilizzare invece mkstemp (). Ecco un programma di test che ho scritto basandomi sulla risposta data da Éric Malenfant ma usando mkstemp () invece di fdopen (); funziona sul mio sistema Ubuntu con le librerie Boost installate:

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


4

Ho provato la soluzione proposta sopra per libstdc ++ da Piotr Dobrogost e ho scoperto che aveva un doloroso difetto: a causa della mancanza di un costruttore di mosse appropriato per istream, è molto difficile estrarre l'oggetto istream di nuova costruzione dalla funzione di creazione . Un altro problema è che perde un oggetto FILE (anche se non il descrittore di file posix sottostante). Ecco una soluzione alternativa che evita questi problemi:

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

La chiamata a posix_fadvise () dimostra un potenziale utilizzo. Si noti inoltre che l'esempio utilizza static_assert e l' utilizzo di C ++ 11, oltre a questo dovrebbe essere compilato correttamente in modalità C ++ 03.


Cosa intendi per versione corretta del costruttore di mosse ? Quale versione di gcc hai usato? Forse questa versione non aveva ancora implementati i costruttori di mosse - vedi Il costruttore di mosse di ifsteam è implicitamente cancellato? ?
Piotr Dobrogost

1
Questo è un trucco che dipende dai dettagli di implementazione sottostanti. Spero che nessuno lo utilizzi mai nel codice di produzione.
davmac

-4

La mia comprensione è che non esiste alcuna associazione con i puntatori FILE o i descrittori di file nel modello a oggetti iostream C ++ al fine di mantenere il codice portatile.

Detto questo, ho visto diversi punti fare riferimento a mds-utils o boost per aiutare a colmare questa lacuna.


9
FILE * è lo standard C e quindi C ++, quindi non vedo come abilitare i flussi C ++ a funzionare con i flussi C possa danneggiare la portabilità
Piotr Dobrogost
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.