Bagaimana cara membuat c ++ fstream dari deskriptor file POSIX?

93

Saya pada dasarnya mencari versi C ++ dari fdopen (). Saya melakukan sedikit penelitian tentang ini dan ini adalah salah satu hal yang tampaknya mudah, tetapi ternyata sangat rumit. Apakah saya melewatkan sesuatu dalam keyakinan ini (yaitu sangat mudah)? Jika tidak, apakah ada perpustakaan yang bagus di luar sana untuk menangani ini?

EDIT: Memindahkan solusi contoh saya ke jawaban terpisah.

BD di Rivenhill
sumber
@Kazark - pindah ke jawaban terpisah sekarang, terima kasih.
BD di Rivenhill
Windows dan Linux dapat melakukan mmapfile tersebut dan mengekspos isinya sebagai byte array.
eigenfield

Jawaban:

72

Dari jawaban yang diberikan oleh Éric Malenfant:

AFAIK, tidak ada cara untuk melakukan ini dalam C ++ standar. Bergantung pada platform Anda, implementasi library standar Anda mungkin menawarkan (sebagai ekstensi nonstandar) konstruktor fstream yang menggunakan deskriptor file sebagai input. (Ini adalah kasus untuk libstdc ++, IIRC) atau FILE *.

Berdasarkan pengamatan di atas dan penelitian saya di bawah ini ada kode yang berfungsi dalam dua varian; satu untuk libstdc ++ dan satu lagi untuk Microsoft Visual C ++.


libstdc ++

Ada __gnu_cxx::stdio_filebuftemplate kelas non-standar yang mewarisi std::basic_streambufdan memiliki konstruktor berikut

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

dengan deskripsi Konstruktor ini mengaitkan buffer aliran file dengan deskriptor file POSIX yang terbuka.

Kami membuatnya melalui pegangan POSIX (baris 1) dan kemudian kami meneruskannya ke konstruktor istream sebagai basic_streambuf (baris 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 ++

Dulu ada versi non-standar dari konstruktor ifstream yang mengambil deskriptor file POSIX tetapi tidak ada baik dari dokumen saat ini maupun dari kode. Ada versi non-standar lain dari konstruktor ifstream yang menggunakan FILE *

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

dan tidak didokumentasikan (saya bahkan tidak dapat menemukan dokumentasi lama yang akan menampilkannya). Kami menyebutnya (baris 1) dengan parameter yang merupakan hasil dari pemanggilan _fdopen untuk mendapatkan C stream FILE * dari pegangan 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;
}
Piotr Dobrogost
sumber
2
Sekarang jawaban diterima karena kelengkapan. Orang lain mungkin tertarik dengan solusi saya menggunakan dorongan, yang telah dipindahkan ke jawaban terpisah.
BD di Rivenhill
1
Untuk linux: Jika Anda melihat ios_init.cc di gcc (sumber yang saya miliki adalah untuk versi 4.1.1) std :: cout dijalankan dengan menginisialisasi stdio_sync_filebuf <char> di sekitar deskriptor file Anda, kemudian inisialisasi pada ostream di sekitar stdio_sync_filebuf < char>. Saya tidak dapat mengklaim bahwa ini akan stabil.
Sparky
@Sparky Melihat std::coutimplementasi adalah ide yang bagus. Saya bertanya-tanya apa perbedaan antara stdio_filebufdan stdio_sync_filebuf?
Piotr Dobrogost
FDS POSIX di MSVC adalah emulasi. API Windows untuk operasi file berbeda dari POSIX dalam banyak hal - nama fungsi yang berbeda dan tipe data parameter. Windows secara internal menggunakan apa yang disebut "pegangan" untuk mengidentifikasi berbagai objek API Windows, dan jenis API Windows HANDLE didefinisikan sebagai kosong *, jadi di minimum itu tidak akan cocok dengan "int" (yaitu 32-bit) pada platform 64-bit. Jadi untuk Windows Anda mungkin tertarik untuk mencari aliran yang memungkinkan untuk bekerja melalui file API Windows HANDLE.
ivan.ukr
40

AFAIK, tidak ada cara untuk melakukan ini dalam C ++ standar. Bergantung pada platform Anda, implementasi pustaka standar Anda mungkin menawarkan (sebagai ekstensi nonstandar) konstruktor fstream yang mengambil deskriptor file (Ini adalah kasus untuk libstdc ++, IIRC) atau a FILE*sebagai masukan.

Alternatif lain adalah dengan menggunakan perangkat boost :: iostreams :: file_descriptor , yang dapat Anda bungkus dalam boost :: iostreams :: stream jika Anda ingin memiliki antarmuka std :: stream.

Éric Malenfant
sumber
4
Mengingat ini adalah satu-satunya solusi portabel, saya tidak mengerti mengapa ini bukan jawaban yang diterima atau berperingkat teratas.
Maarten
8

Ada kemungkinan besar kompiler Anda menawarkan konstruktor fstream berbasis FILE, meskipun itu non-standar. Sebagai contoh:

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

Tapi sejauh yang saya tahu, tidak ada cara portabel untuk melakukan ini.

Darryl
sumber
2
Perhatikan bahwa g ++ (dengan benar) tidak akan mengizinkan ini dalam mode c ++ 11
Mark K Cowan
8

Bagian dari motivasi asli (tidak disebutkan) dari pertanyaan ini adalah memiliki kemampuan untuk melewatkan data baik di antara program atau di antara dua bagian program pengujian menggunakan file sementara yang dibuat dengan aman, tetapi tmpnam () memberikan peringatan di gcc, jadi saya ingin untuk menggunakan mkstemp () sebagai gantinya. Berikut adalah program tes yang saya tulis berdasarkan jawaban yang diberikan oleh Éric Malenfant tetapi menggunakan mkstemp () daripada fdopen (); ini berfungsi pada sistem Ubuntu saya dengan pustaka Boost diinstal:

#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 di Rivenhill
sumber
4

Saya telah mencoba solusi yang diusulkan di atas untuk libstdc ++ oleh Piotr Dobrogost, dan menemukan bahwa itu memiliki cacat yang menyakitkan: Karena kurangnya konstruktor pemindahan yang tepat untuk istream, sangat sulit untuk mengeluarkan objek istream yang baru dibangun dari fungsi pembuatan . Masalah lain dengan itu adalah kebocoran objek FILE (bahkan dianggap bukan deskriptor file posix yang mendasarinya). Berikut solusi alternatif yang menghindari masalah ini:

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

Panggilan ke posix_fadvise () mendemonstrasikan penggunaan potensial. Perhatikan juga bahwa contoh tersebut menggunakan static_assert dan yang menggunakan C ++ 11, selain itu seharusnya dibuat dengan baik dalam mode C ++ 03.

YitzikC
sumber
Apa yang Anda maksud dengan versi konstruktor pemindahan yang tepat ? Versi gcc apa yang Anda gunakan? Mungkin versi ini belum menerapkan konstruktor pemindahan - lihat Apakah konstruktor pemindahan ifsteam dihapus secara implisit? ?
Piotr Dobrogost
1
Ini adalah peretasan yang bergantung pada detail implementasi yang mendasarinya. Saya berharap tidak ada yang pernah menggunakan ini dalam kode produksi.
davmac
-4

Pemahaman saya adalah bahwa tidak ada hubungan dengan penunjuk FILE atau deskriptor file dalam model objek C ++ iostream untuk menjaga agar kode tetap portabel.

Konon, saya melihat beberapa tempat merujuk ke mds-utils atau boost untuk membantu menjembatani celah itu.

alas tiang
sumber
9
FILE * adalah standar C dan dengan demikian C ++ jadi saya tidak melihat bagaimana mengaktifkan aliran C ++ untuk bekerja dengan aliran C dapat merusak portabilitas
Piotr Dobrogost