Вопрос по fstream, c++, file-io, c, file – Получение ФАЙЛА * из std :: fstream

53

Есть ли (кросс-платформенный) способ получить дескриптор C FILE * из C ++ std :: fstream?

Причина, по которой я спрашиваю, состоит в том, что моя библиотека C ++ принимает fstreams и в одной конкретной функции я хотел бы использовать библиотеку C, которая принимает FILE *.

Ваш Ответ

8   ответов
9

UPDATE: See @Jettatura what I think it is the best answer https://stackoverflow.com/a/33612982/225186 (Linux only?).

ОРИГИНАЛ:

(Вероятно, не кроссплатформенный, но простой)

Упрощение взлома вhttp://www.ginac.de/~kreckel/fileno/ (ответ dvorak), и глядя на это расширение GCChttp://gcc.gnu.org/onlinedocs/gcc-4.6.2/libstdc++/api/a00069.html#a59f78806603c619eafcd4537c920f859, I have this solution that works on GCC (Минимум 4,8) иclang (Не менее 3,3)

#include<fstream>
#include<ext/stdio_filebuf.h>

typedef std::basic_ofstream<char>::__filebuf_type buffer_t;
typedef __gnu_cxx::stdio_filebuf<char>            io_buffer_t; 
FILE* cfile_impl(buffer_t* const fb){
    return (static_cast<io_buffer_t* const>(fb))->file(); //type std::__c_file
}

FILE* cfile(std::ofstream const& ofs){return cfile_impl(ofs.rdbuf());}
FILE* cfile(std::ifstream const& ifs){return cfile_impl(ifs.rdbuf());}

и может быть использовано это,

int main(){
    std::ofstream ofs("file.txt");
    fprintf(cfile(ofs), "sample1");
    fflush(cfile(ofs)); // ofs << std::flush; doesn't help 
    ofs << "sample2\n";
}

Limitations: (комментарии приветствуются)

  1. I find that it is important to fflush after fprintf printing to std::ofstream, otherwise the "sample2" appears before "sample1" in the example above. I don't know if there is a better workaround for that than using fflush. Notably ofs << flush doesn't help.

  2. Cannot extract FILE* from std::stringstream, I don't even know if it is possible. (see below for an update).

  3. I still don't know how to extract C's stderr from std::cerr etc., for example to use in fprintf(stderr, "sample"), in an hypothetical code like this fprintf(cfile(std::cerr), "sample").

Что касается последнего ограничения, единственный найденный мной обходной путь - добавить следующие перегрузки:

FILE* cfile(std::ostream const& os){
    if(std::ofstream const* ofsP = dynamic_cast<std::ofstream const*>(&os)) return cfile(*ofsP);
    if(&os == &std::cerr) return stderr;
    if(&os == &std::cout) return stdout;
    if(&os == &std::clog) return stderr;
    if(dynamic_cast<std::ostringstream const*>(&os) != 0){
       throw std::runtime_error("don't know cannot extract FILE pointer from std::ostringstream");
    }
    return 0; // stream not recognized
}
FILE* cfile(std::istream const& is){
    if(std::ifstream const* ifsP = dynamic_cast<std::ifstream const*>(&is)) return cfile(*ifsP);
    if(&is == &std::cin) return stdin;
    if(dynamic_cast<std::ostringstream const*>(&is) != 0){
        throw std::runtime_error("don't know how to extract FILE pointer from std::istringstream");
    }
    return 0; // stream not recognized
}

Attempt to handle iostringstream

Можно читать сfscanf отistream с помощьюfmemopen, но это требует большого бухгалтерского учета и обновления позиции ввода потока после каждого чтения, если кто-то хочет объединить C-чтения и C ++ - чтения. Я не смог преобразовать это вcfile функционировать как выше. (Может бытьcfile class который продолжает обновляться после каждого чтения, это путь).

// hack to access the protected member of istreambuf that know the current position
char* access_gptr(std::basic_streambuf<char, std::char_traits<char>>& bs){
    struct access_class : std::basic_streambuf<char, std::char_traits<char>>{
        char* access_gptr() const{return this->gptr();}
    };
    return ((access_class*)(&bs))->access_gptr();
}

int main(){
    std::istringstream iss("11 22 33");
    // read the C++ way
    int j1; iss >> j1;
    std::cout << j1 << std::endl;

    // read the C way
    float j2;

    char* buf = access_gptr(*iss.rdbuf()); // get current position
    size_t buf_size = iss.rdbuf()->in_avail(); // get remaining characters
    FILE* file = fmemopen(buf, buf_size, "r"); // open buffer memory as FILE*
    fscanf(file, "%f", &j2); // finally!
    iss.rdbuf()->pubseekoff(ftell(file), iss.cur, iss.in); // update input stream position from current FILE position.

    std::cout << "j2 = " << j2 << std::endl;

    // read again the C++ way
    int j3; iss >> j3;
    std::cout << "j3 = " << j3 << std::endl;
}
Это замечательно! Недавно мне пришлось поработать с tcsetattr () над чем-то, что пришло как офстрим, и твоя рецензия действительно помогла мне.
Спасибо. Я использовал этот подход для блокировки файлов, как показано здесь:stackoverflow.com/a/53689083/5899976
@DroidCoder, интересно, что в fstreams нет механизма замены стада. Интересно, если это потому, что это не очень переносимая (через файловую систему) функция в первую очередь.
Согласовано. Читая документацию Boost.Interprocess, кажется, что существует ряд платформо-зависимых поведений, которые могут раздражать от стандартизации. Тем не менее, его отсутствие оставляет довольно большую дыру, поэтому я считаю, что стоит попытаться заставить людей платформы поддерживать общее подмножество поведений, которые могут заложить основу для поддержки в будущем стандарте C ++.
2

еще один способ сделать это в Linux:

#include <stdio.h>
#include <cassert>

template<class STREAM>
struct STDIOAdapter
{
    static FILE* yield(STREAM* stream)
    {
        assert(stream != NULL);

        static cookie_io_functions_t Cookies =
        {
            .read  = NULL,
            .write = cookieWrite,
            .seek  = NULL,
            .close = cookieClose
        };

        return fopencookie(stream, "w", Cookies);
    }

    ssize_t static cookieWrite(void* cookie,
        const char* buf,
        size_t size)
    {
        if(cookie == NULL)
            return -1;

        STREAM* writer = static_cast <STREAM*>(cookie);

        writer->write(buf, size);

        return size;
    }

    int static cookieClose(void* cookie)
    {
         return EOF;
    }
}; // STDIOAdapter

Использование, например:

#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/filter/bzip2.hpp>
#include <boost/iostreams/device/file.hpp>

using namespace boost::iostreams;

int main()
{   
    filtering_ostream out;
    out.push(boost::iostreams::bzip2_compressor());
    out.push(file_sink("my_file.txt"));

    FILE* fp = STDIOAdapter<filtering_ostream>::yield(&out);
    assert(fp > 0);

    fputs("Was up, Man", fp);

    fflush (fp);

    fclose(fp);

    return 1;
}
Я комментирую снова, чтобы подтвердить, что это работает сstd::ostringstream.
имеет ли смысл иметьseek функция также.int cookieSeek(void* cookie, ssize_t* off, int way){ static_cast<std::iostream*>(cookie)->seekg(*off, static_cast<Stream*>(cookie)->end); return way; } ? (Я не уверен, что используюway правильно).
Отлично, я бы дополнил этоtemplate<class Stream> FILE* cfile(Stream& s){ return STDIOAdapter<Stream>::yield(&s); }, Работает сstd::cout, std::cerr и смешиваниеfprintf и код C ++. Я бы порекомендовал использовать это.
17

Не существует стандартизированного способа. Я предполагаю, что это потому, что группа стандартизации C ++ не хотела предполагать, что дескриптор файла может быть представлен как fd.

Большинство платформ предоставляют нестандартный способ сделать это.

http://www.ginac.de/~kreckel/fileno/ обеспечивает хорошую запись ситуации и предоставляет код, который скрывает всю грубость, характерную для платформы, по крайней мере для GCC. Учитывая, насколько это велико только в GCC, я думаю, что я бы избегал делать все это вместе, если это возможно.

FILE* и дескриптор файлаdifferent объекты, и они используются различными компонентами. Один используется библиотекой времени выполнения C, другой - ОС. УвидетьWhat's the difference between a file descriptor and file pointer?
Отличная рецензия в этой ссылке, спасибо!
1

Есть способ получить дескриптор файла изfstream а затем преобразовать его вFILE* (с помощьюfdopen). Лично я не вижу никакой необходимости вFILE*, но с файловым дескриптором вы можете сделать много интересных вещей, таких как перенаправление (dup2).

Решение:

#define private public
#define protected public
#include <fstream>
#undef private
#undef protected

std::ifstream file("some file");
auto fno = file._M_filebuf._M_file.fd();

Последняя строка работает для libstdc ++. Если вы используете какую-то другую библиотеку, вам придется немного перепроектировать ее.

Этот трюк грязный и разоблачит всех частных и публичных членов fstream. Если вы хотите использовать его в своем рабочем коде, я предлагаю вам создать отдельный.cpp а также.h с одной функциейint getFdFromFstream(std::basic_ios<char>& fstr);, Заголовочный файл не должен содержать fstream.

33

Краткий ответ: нет.

Причина в том, чтоstd::fstream не требуется использоватьFILE* как часть его реализации. Так что даже если вам удастся извлечь дескриптор файла изstd::fstream объект и вручную построить объект FILE, тогда у вас будут другие проблемы, потому что теперь у вас будет два буферизованных объекта, записывающих в один и тот же дескриптор файла.

Реальный вопрос в том, почему вы хотите преобразоватьstd::fstream возражать вFILE*?

Хотя я не рекомендую его, вы можете попробовать поискатьfunopen().
К сожалению этоnot API POSIX (это расширение BSD), поэтому его переносимость находится под вопросом. Возможно, именно поэтому я не могу найти никого, кто завернулstd::stream с таким объектом

FILE *funopen(
              const void *cookie,
              int    (*readfn )(void *, char *, int),
              int    (*writefn)(void *, const char *, int),
              fpos_t (*seekfn) (void *, fpos_t, int),
              int    (*closefn)(void *)
             );

Это позволяет вам построитьFILE Объект и укажите некоторые функции, которые будут использоваться для выполнения реальной работы. Если вы пишете соответствующие функции, вы можете получить их для чтения изstd::fstream объект, который на самом деле имеет открытый файл.

Вы спросили «почему?»: Потому что у кого-то может быть реализация print, написанная на C, написанная на C, которую можно использовать для Ostream-ов C ++ (или ofstreams).
Зачем? Потому что перегрузка оператора & lt; & lt; очень удобно, когда яusing объект, но форматирование потока вывода может быть грязным и болезненным. Форматирование с помощью fprintf () компактно и просто. Другими словами, я хотел бы иметь возможность написатьout << someObject << anotherObject но используйте оператор & lt; & lt; с помощьюfprintf(ofp, "%8.1lf %2d\n", doubleVar, intVar)
@ MikeMayer67 Есть лучшие способы получить форматирование sprinf в потоках. У Boost недавно был класс потокового формата. Даже я написал объект потокового форматера для развлечения некоторое время назадC-String Formatter Code Review УвидетьPart 1 of the review for usage examples
Если вы хотите / должны использовать & quot; printf & quot; парадигма форматирования, вы всегда можете использовать sprintf для создания c-строки, а затем отправить ее в свой ofstream. Конечно, это означает, что вам нужно иметь представление о верхней границе длины отформатированной строки, чтобы вы могли правильно определить размер вашего char [].
Жаль, что это только для BSD; это было бы отличным решением, так как позволяло бы использовать FILE * с любым видом потока C ++.
4

Ну, вы можете получить дескриптор файла - я забыл, является ли метод fd () или getfd ().The implementations I've used provide such methods, but the language standard doesn't require them, I believe - the standard shouldn't care whether your platform uses fd's for files.

Исходя из этого, вы можете использовать fdopen (fd, mode), чтобы получить ФАЙЛ *.

Однако я думаю, что механизмы, которые требуются в стандарте для синхронизации STDIN / cin, STDOUT / cout и STDERR / cerr, не должны быть вам видны. Поэтому, если вы используете как fstream, так и FILE *, буферизация может вас испортить.

Кроме того, если либо fstream, либо FILE закрывается, они, вероятно, закроют базовый fd, поэтому вам необходимо убедиться, что вы сбросили ОБА перед закрытием EITHER.

Извините, это будет зависеть от того, какой компилятор / библиотека вы используете. Для libstdc ++ это fd (), это выглядит так:gcc.gnu.org/onlinedocs/libstdc++/manual/ext_io.html
не портативный, а? Жаль.
Какой методfd() или жеgetfd() можете ли вы указать мне на них?
1

Пожалуйста, посмотрите на эту библиотеку

MDS утилит

Это решает проблему, потому что позволяет обрабатывать C FILE * как поток C ++. Он использует библиотеки Boost C ++. Вы должны использовать Doxygen для просмотра документации.

4

В однопоточном приложении POSIX вы можете легко получить номер fd переносимым способом:

int fd = dup(0);
close(fd);
// POSIX requires the next opened file descriptor to be fd.
std::fstream file(...);
// now fd has been opened again and is owned by file

Этот метод ломается в многопоточном приложении, если этот код конкурирует с другими потоками, открывающими файловые дескрипторы.

Похожие вопросы