Получение ФАЙЛА * из std :: fstream - PullRequest
53 голосов
/ 21 сентября 2008

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

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

Ответы [ 8 ]

33 голосов
/ 21 сентября 2008

Короткий ответ - нет.

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

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

Хотя я не рекомендую этого, вы можете попробовать поискать funopen().
К сожалению, это , а не 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, у которого фактически открыт файл.

17 голосов
/ 21 сентября 2008

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

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

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

9 голосов
/ 03 ноября 2013

ОБНОВЛЕНИЕ: смотрите @Jettatura, что я думаю, что это лучший ответ https://stackoverflow.com/a/33612982/225186 (только для Linux?).

ОРИГИНАЛ:

(возможно, не кроссплатформенный, а простой)

Упрощение взлома в http://www.ginac.de/~kreckel/fileno/ (ответ дворака) и просмотр этого расширения gcc http://gcc.gnu.org/onlinedocs/gcc-4.6.2/libstdc++/api/a00069.html#a59f78806603c619eafcd4537c920f859, У меня есть это решение, которое работает на 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";
}

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

  1. Я считаю, что важно fflush после fprintf печати до std::ofstream, в противном случае «sample2» появляется перед «sample1» в примере выше. Я не знаю, есть ли лучший обходной путь для этого, чем использование fflush. В частности ofs << flush не помогает.

  2. Невозможно извлечь FILE * из std::stringstream, я даже не знаю, возможно ли это. (см. ниже обновление).

  3. Я до сих пор не знаю, как извлечь C stderr из std::cerr и т. Д., Например, использовать в fprintf(stderr, "sample"), в гипотетическом коде, подобном этому 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
}

Попытка обработать iostringstream

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

// 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;
}
4 голосов
/ 05 ноября 2014

В однопоточном приложении 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

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

4 голосов
/ 21 сентября 2008

Ну, вы можете получить дескриптор файла - я забыл, является ли метод fd () или getfd (). Реализации, которые я использовал, предоставляют такие методы, но языковой стандарт не требует их, я полагаю - стандарт не должен заботиться о том, использует ли ваша платформа fd для файлов.

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

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

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

2 голосов
/ 09 ноября 2015

еще один способ сделать это в 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;
}
1 голос
/ 16 июня 2017

Есть способ получить дескриптор файла из 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.

1 голос
/ 03 июня 2009

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

Утилиты MDS

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...