Jak skonstruować c ++ fstream z deskryptora pliku POSIX?

94

Zasadniczo szukam wersji C ++ fdopen (). Zrobiłem trochę badań na ten temat i jest to jedna z tych rzeczy, które wydają się być łatwe, ale okazują się bardzo skomplikowane. Czy czegoś mi brakuje w tym przekonaniu (tj. To naprawdę jest łatwe)? Jeśli nie, czy jest gdzieś dobra biblioteka, która może się tym zająć?

EDYCJA: Przeniosłem moje przykładowe rozwiązanie do osobnej odpowiedzi.

BD w Rivenhill
źródło
@Kazark - teraz przejdź do osobnej odpowiedzi, dzięki.
BD w Rivenhill,
Windows i Linux mogą zrobić mmapz plikiem i ujawnić jego zawartość jako tablicę bajtów.
eigenfield

Odpowiedzi:

73

Z odpowiedzi udzielonej przez Érica Malenfanta:

AFAIK, nie ma takiej możliwości w standardowym C ++. W zależności od platformy Twoja implementacja biblioteki standardowej może oferować (jako niestandardowe rozszerzenie) konstruktor fstream pobierający deskryptor pliku jako dane wejściowe. (Tak jest w przypadku libstdc ++, IIRC) lub PLIK *.

Bazując na powyższych obserwacjach i moich badaniach poniżej, kod działa w dwóch wariantach; jeden dla libstdc ++, a drugi dla Microsoft Visual C ++.


libstdc ++

Istnieje niestandardowy __gnu_cxx::stdio_filebufszablon klasy, który dziedziczy std::basic_streambufi ma następujący konstruktor

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

z opisem Ten konstruktor wiąże bufor strumienia plików z otwartym deskryptorem pliku POSIX.

Tworzymy go, przekazując uchwyt POSIX (linia 1), a następnie przekazujemy go do konstruktora istream jako basic_streambuf (linia 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 ++

Kiedyś istniała niestandardowa wersja konstruktora ifstream pobierającego deskryptor pliku POSIX, ale brakuje go zarówno w bieżącej dokumentacji, jak iw kodzie. Istnieje inna niestandardowa wersja konstruktora ifstream pobierająca PLIK *

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

i nie jest udokumentowane (nie mogłem nawet znaleźć żadnej starej dokumentacji, w której byłaby obecna). Nazywamy to (wiersz 1), a parametr jest wynikiem wywołania _fdopen w celu pobrania strumienia C PLIK * z uchwytu pliku 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
źródło
2
Teraz zaakceptowana odpowiedź ze względu na kompletność. Inni mogą być zainteresowani moim rozwiązaniem wykorzystującym doładowanie, które zostało przeniesione do osobnej odpowiedzi.
BD w Rivenhill,
1
Linux: Jeśli spojrzysz na ios_init.cc w gcc (źródło, które mam, jest dla wersji 4.1.1) std :: cout jest inicjowane przez zainicjowanie stdio_sync_filebuf <char> wokół deskryptora pliku, a następnie inicjalizację na ostream wokół twojego stdio_sync_filebuf < char>. Nie mogę jednak twierdzić, że to będzie stabilne.
Sparky
@Sparky Spojrzenie na std::coutimplementację to dobry pomysł. Zastanawiam się, jaka jest różnica między stdio_filebufi stdio_sync_filebuf?
Piotr Dobrogost
Pliki POSIX w MSVC są emulacją. Windows API dla operacji na plikach różni się od POSIX-owych pod wieloma względami - różne nazwy funkcji i typy danych parametrów. Windows wewnętrznie używa tak zwanych "uchwytów" do identyfikacji różnych obiektów Windows API, a Windows API typu HANDLE jest zdefiniowany jako void *, więc na przynajmniej nie będzie pasować do „int” (czyli 32-bitowego) na platformach 64-bitowych. Więc dla Windows możesz być zainteresowany szukaniem strumienia, który pozwala na pracę nad plikiem Windows API HANDLE.
ivan.ukr
40

AFAIK, nie ma takiej możliwości w standardowym C ++. W zależności od platformy Twoja implementacja biblioteki standardowej może oferować (jako niestandardowe rozszerzenie) konstruktor fstream pobierający deskryptor pliku (tak jest w przypadku libstdc ++, IIRC) lub FILE*jako dane wejściowe.

Inną alternatywą byłoby użycie urządzenia boost :: iostreams :: file_descriptor , które można opakować w boost :: iostreams :: stream, jeśli chcesz mieć do niego interfejs std :: stream.

Éric Malenfant
źródło
4
Biorąc pod uwagę, że jest to jedyne przenośne rozwiązanie, nie rozumiem, dlaczego nie jest to akceptowana lub najwyżej oceniana odpowiedź.
Maarten
8

Istnieje duża szansa, że ​​Twój kompilator oferuje konstruktor fstream oparty na PLIKU, nawet jeśli jest niestandardowy. Na przykład:

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

Ale o ile wiem, nie ma na to przenośnego sposobu.

Darryl
źródło
2
Zauważ, że g ++ (poprawnie) nie pozwoli na to w trybie c ++ 11
Mark K Cowan
8

Częścią pierwotnej (nieokreślonej) motywacji tego pytania jest możliwość przekazywania danych między programami lub między dwoma częściami programu testowego przy użyciu bezpiecznie utworzonego pliku tymczasowego, ale tmpnam () wyświetla ostrzeżenie w gcc, więc chciałem zamiast tego użyć mkstemp (). Oto program testowy, który napisałem na podstawie odpowiedzi udzielonej przez Érica Malenfanta, ale używając mkstemp () zamiast fdopen (); to działa na moim systemie Ubuntu z zainstalowanymi bibliotekami 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);
  }
}
BD w Rivenhill
źródło
4

Wypróbowałem rozwiązanie zaproponowane powyżej dla libstdc ++ autorstwa Piotra Dobrogosta i stwierdziłem, że ma ono bolesną wadę: ze względu na brak odpowiedniego konstruktora ruchu dla istream bardzo trudno jest usunąć nowo skonstruowany obiekt istream z funkcji tworzącej . Innym problemem jest to, że przecieka obiekt FILE (nawet jeśli nie jest to podstawowy deskryptor pliku POSIX). Oto alternatywne rozwiązanie, które pozwala uniknąć tych problemów:

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

Wywołanie posix_fadvise () demonstruje potencjalne zastosowanie. Zauważ również, że przykład używa static_assert i używa C ++ 11, poza tym, że powinien dobrze budować w trybie C ++ 03.

YitzikC
źródło
Co masz na myśli mówiąc o odpowiedniej wersji konstruktora przenoszenia ? Z jakiej wersji gcc korzystałeś? Może ta wersja nie ma jeszcze zaimplementowanych konstruktorów przenoszenia - zobacz Czy konstruktor przenoszenia ifsteam został niejawnie usunięty? ?
Piotr Dobrogost
1
To hack zależny od podstawowych szczegółów implementacji. Mam nadzieję, że nikt nigdy nie użyje tego w kodzie produkcyjnym.
davmac
-4

Rozumiem, że nie ma powiązania ze wskaźnikami FILE lub deskryptorami plików w modelu obiektowym C ++ iostream w celu zachowania przenośności kodu.

To powiedziawszy, widziałem kilka miejsc odnoszących się do mds-utils lub boost, aby pomóc wypełnić tę lukę.

cokół
źródło
9
FILE * to standardowe C, a tym samym C ++, więc nie widzę, jak włączenie strumieni C ++ do pracy ze strumieniami C może zaszkodzić przenośności
Piotr Dobrogost