Как создать c ++ fstream из файлового дескриптора POSIX?

Я в основном ищу версию fdopen () на C ++. Я немного исследовал это, и это одна из тех вещей, которые кажутся простыми, но на деле оказываются очень сложными. Я что-то упускаю в этом убеждении (то есть это действительно просто)? Если нет, есть ли где-нибудь хорошая библиотека, чтобы справиться с этим?

РЕДАКТИРОВАТЬ: мой пример решения перенесен в отдельный ответ.


person BD at Rivenhill    schedule 30.04.2010    source источник
comment
@Kazark - теперь перешел в отдельный ответ, спасибо.   -  person BD at Rivenhill    schedule 25.02.2013
comment
Windows и Linux могут делать mmap с файлом и отображать его содержимое как массив байтов.   -  person eigenfield    schedule 22.04.2020


Ответы (7)


Из ответа Эрика Маленфана:

AFAIK, в стандартном С ++ нет возможности сделать это. В зависимости от вашей платформы ваша реализация стандартной библиотеки может предлагать (как нестандартное расширение) конструктор fstream, принимающий дескриптор файла в качестве входных данных. (Это относится к libstdc ++, IIRC) или FILE *.

На основании вышеизложенных наблюдений и моих исследований ниже есть рабочий код в двух вариантах; один для libstdc ++ и другой для Microsoft Visual C ++.


libstdc ++

Существует нестандартный шаблон класса __gnu_cxx::stdio_filebuf, который наследует std::basic_streambuf и имеет следующий конструктор

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

с описанием Этот конструктор связывает буфер файлового потока с открытым файловым дескриптором POSIX.

Мы создаем его, передав дескриптор POSIX (строка 1), а затем передаем его конструктору istream как basic_streambuf (строка 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 ++

Раньше была нестандартная версия конструктора ifstream, принимающего дескриптор файла POSIX, но он отсутствует в текущих документов и из кода. Есть еще одна нестандартная версия конструктора ifstream, принимающая FILE *

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

и это не задокументировано (я даже не смог найти старую документацию, где она могла бы присутствовать). Мы вызываем его (строка 1) с параметром, являющимся результатом вызова _ fdopen, чтобы получить ФАЙЛ потока C * из дескриптора файла 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;
}
person Piotr Dobrogost    schedule 09.03.2011
comment
Теперь принятый ответ в силу полноты. Других может заинтересовать мое решение с использованием boost, которое вынесено в отдельный ответ. - person BD at Rivenhill; 25.02.2013
comment
Для linux: если вы посмотрите на ios_init.cc в gcc (у ​​меня есть исходный код для версии 4.1.1) std :: cout инициализируется путем инициализации stdio_sync_filebuf ‹char› вокруг вашего файлового дескриптора, а затем инициализации в ostream вокруг вашего stdio_sync_filebuf ‹ символ ›. Я не могу утверждать, что это будет стабильно. - person Sparky; 26.06.2014
comment
@Sparky Хорошая идея - изучить std::cout реализацию. Мне интересно, в чем разница между stdio_filebuf и stdio_sync_filebuf? - person Piotr Dobrogost; 27.04.2016
comment
Файлы POSIX в MSVC - это эмуляция. Windows API для файловых операций во многом отличается от POSIX - разными именами функций и типами данных параметров. Windows внутренне использует так называемые дескрипторы для идентификации различных объектов Windows API, а тип HANDLE Windows API определяется как void *, поэтому как минимум не вписывается в int (который является 32-битным) на 64-битных платформах. Так что для Windows вас может заинтересовать поток, который позволяет работать с файлом HANDLE Windows API. - person ivan.ukr; 08.01.2019

AFAIK, в стандартном С ++ нет возможности сделать это. В зависимости от вашей платформы ваша реализация стандартной библиотеки может предлагать (как нестандартное расширение) конструктор fstream, принимающий дескриптор файла (это случай для libstdc ++, IIRC) или FILE* в качестве входных данных.

Другой альтернативой было бы использование boost :: iostreams :: file_descriptor, которое можно обернуть в boost :: iostreams :: stream, если вы хотите иметь для него интерфейс std :: stream.

person Éric Malenfant    schedule 30.04.2010
comment
Учитывая, что это единственное портативное решение, я не понимаю, почему это не принятый и не самый популярный ответ. - person Maarten; 12.03.2016

Есть большая вероятность, что ваш компилятор предлагает конструктор fstream на основе ФАЙЛОВ, даже если он нестандартный. Например:

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

Но насколько мне известно, портативного способа сделать это нет.

person Darryl    schedule 30.04.2010
comment
Обратите внимание, что g ++ (правильно) не допускает этого в режиме c ++ 11 - person Mark K Cowan; 18.08.2014

Частично исходная (неустановленная) мотивация этого вопроса заключается в том, чтобы иметь возможность передавать данные либо между программами, либо между двумя частями тестовой программы с использованием безопасно созданного временного файла, но tmpnam () выдает предупреждение в gcc, поэтому я хотел вместо этого использовать mkstemp (). Вот тестовая программа, которую я написал на основе ответа Эрика Маленфанта, но с использованием mkstemp () вместо fdopen (); это работает в моей системе Ubuntu с установленными библиотеками Boost:

#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);
  }
}
person BD at Rivenhill    schedule 25.02.2013

На самом деле это довольно просто. Николай М. Йосуттис выпустил fdstream вместе со своей книгой Стандартная библиотека C ++ - Учебное пособие и справочник. Реализацию 184 строк можно найти здесь.

person Mark    schedule 16.11.2017
comment
Это интересное применение фразы довольно легко. - person Mark Adler; 28.03.2021

Я попробовал решение, предложенное выше для libstdc ++ Петром Доброгостом, и обнаружил, что у него есть болезненный недостаток: из-за отсутствия подходящего конструктора перемещения для istream очень сложно получить вновь созданный объект istream из функции создания. . Другая проблема заключается в том, что он пропускает объект FILE (даже если не думать о базовом дескрипторе файла posix). Вот альтернативное решение, позволяющее избежать этих проблем:

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

Вызов posix_fadvise () демонстрирует потенциальное использование. Также обратите внимание, что в этом примере используются static_assert и using, которые относятся к C ++ 11, за исключением того, что он должен нормально собираться в режиме C ++ 03.

person YitzikC    schedule 19.04.2015
comment
Что вы имеете в виду под правильной версией конструктора перемещения? Какую версию gcc вы использовали? Возможно, в этой версии еще не реализованы конструкторы перемещения - см. Неявно ли удален конструктор перемещения ifsteam?? - person Piotr Dobrogost; 16.01.2016
comment
Это хакерство, зависящее от деталей базовой реализации. Я надеюсь, что никто никогда не использует это в производственном коде. - person davmac; 16.01.2016

Насколько я понимаю, в объектной модели iostream C ++ нет никакой связи с указателями FILE или файловыми дескрипторами, чтобы код оставался переносимым.

Тем не менее, я видел несколько мест, в которых упоминались mds-utils или boost, чтобы помочь преодолеть этот разрыв.

person plinth    schedule 30.04.2010
comment
FILE * - это стандартный C и, следовательно, C ++, поэтому я не понимаю, как включение потоков C ++ для работы с потоками C может повредить переносимости. - person Piotr Dobrogost; 11.03.2011