Получение ФАЙЛА * из std::fstream
Есть ли (кросс-платформенный) способ получить дескриптор C FILE* из C++ std::fstream?
Причина, по которой я спрашиваю, состоит в том, что моя библиотека C++ принимает fstreams и в одной конкретной функции я хотел бы использовать библиотеку C, которая принимает FILE*.
10 ответов
Краткий ответ: нет.
Причина в том, что std::fstream
не требуется использовать FILE*
как часть его реализации. Так что даже если вам удастся извлечь дескриптор файла из std::fstream
объект и вручную построить объект FILE, тогда у вас будут другие проблемы, потому что теперь у вас будет два буферизованных объекта, записывающих в один и тот же дескриптор файла.
Реальный вопрос заключается в том, почему вы хотите преобразовать std::fstream
возражать в FILE*
?
Хотя я не рекомендую это, вы можете попробовать поискать funopen()
,
К сожалению, это не POSIX API (это расширение 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
объект, который на самом деле имеет открытый файл.
Там нет стандартизированного способа. Я предполагаю, что это потому, что группа стандартизации C++ не хотела предполагать, что дескриптор файла может быть представлен как fd.
Большинство платформ, кажется, предоставляют какой-то нестандартный способ сделать это.
http://www.ginac.de/~kreckel/fileno/ обеспечивает хорошее описание ситуации и предоставляет код, который скрывает всю специфичность платформы, по крайней мере для GCC. Учитывая, насколько это просто на GCC, я думаю, что я бы избегал делать все это вместе, если это возможно.
ОБНОВЛЕНИЕ: смотрите @Jettatura, что я думаю, что это лучший ответ /questions/40333088/poluchenie-fajla-iz-stdfstream/40333100#40333100 (только для Linux?).
ОРИГИНАЛ:
(Вероятно, не кроссплатформенный, но простой)
Упрощение взлома в http://www.ginac.de/~kreckel/fileno/ (dvorak answer) и просмотр этого расширения gcc http://gcc.gnu.org/onlinedocs/gcc-4.6.2/libstdc++/api/a00069.html, у меня есть это решение, которое работает на 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";
}
Ограничения: (комментарии приветствуются)
Я считаю, что важно
fflush
послеfprintf
печать вstd::ofstream
в противном случае "sample2" появляется перед "sample1" в примере выше. Я не знаю, есть ли лучший обходной путь для этого, чем использованиеfflush
, особенноofs << flush
не помогаетНевозможно извлечь FILE* из
std::stringstream
Я даже не знаю, возможно ли это. (см. ниже для обновления).Я до сих пор не знаю, как извлечь 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;
}
Ну, вы можете получить дескриптор файла - я забыл, является ли метод fd() или getfd(). Реализации, которые я использовал, предоставляют такие методы, но языковой стандарт не требует их, я считаю - стандарт не должен заботиться о том, использует ли ваша платформа fd для файлов.
Исходя из этого, вы можете использовать fdopen(fd, mode), чтобы получить ФАЙЛ *.
Тем не менее, я думаю, что механизмы, требуемые стандартом для синхронизации STDIN/cin, STDOUT/cout и STDERR/cerr, не обязательно должны быть видны вам. Так что, если вы используете как fstream, так и FILE*, буферизация может вас испортить.
Кроме того, если fstream ИЛИ ФАЙЛ закрывается, они, вероятно, закроют нижележащий fd, так что вам нужно убедиться, что вы сбросили ОБА перед закрытием EITHER.
В однопоточном приложении 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
Этот метод ломается в многопоточном приложении, если этот код конкурирует с другими потоками, открывающими файловые дескрипторы.
Еще один способ сделать это в 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;
}
Есть способ получить дескриптор файла из 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.
Пожалуйста, посмотрите на эту библиотеку
Это решает проблему, потому что позволяет обрабатывать C FILE* как поток C++. Он использует библиотеки Boost C++. Вы должны использовать Doxygen для просмотра документации.
Я столкнулся с этой проблемой, когда столкнулся с работой только с файловым дескриптором.
В более новых версиях стандартной библиотеки C++ (по крайней мере, начиная с C++11) решение, предложенное alfC, больше не работает, потому что один класс был изменен на новый класс.
Старый метод по-прежнему будет работать, если вы используете очень старые версии компилятора. В более новой версии вам нужно использоватьstd::basic_filebuf<>()
. Но это не работает со стандартным вводом-выводом, таким какstd::cout
. Для них вам нужно использовать__gnu_cxx::stdio_sync_filebuf<>()
.
У меня есть функциональный пример в моей реализацииisatty()
для потоков С++ здесь. Вы должны иметь возможность снять этот файл и повторно использовать его в своем собственном проекте. Однако в вашем случае вам нужен указатель, поэтому просто верните его вместо результата::isatty(fileno(<of FILE*>))
.
Вот копия функции шаблона:
template<typename _CharT
, typename _Traits = std::char_traits<_CharT>>
bool isatty(std::basic_ios<_CharT, _Traits> const & s)
{
{ // cin, cout, cerr, and clog
typedef __gnu_cxx::stdio_sync_filebuf<_CharT, _Traits> io_sync_buffer_t;
io_sync_buffer_t * buffer(dynamic_cast<io_sync_buffer_t *>(s.rdbuf()));
if(buffer != nullptr)
{
return ::isatty(fileno(buffer->file()));
}
}
{ // modern versions
typedef std::basic_filebuf<_CharT, _Traits> file_buffer_t;
file_buffer_t * file_buffer(dynamic_cast<file_buffer_t *>(s.rdbuf()));
if(file_buffer != nullptr)
{
typedef detail::our_basic_filebuf<_CharT, _Traits> hack_buffer_t;
hack_buffer_t * buffer(static_cast<hack_buffer_t *>(file_buffer));
if(buffer != nullptr)
{
return ::isatty(fileno(buffer->file()));
}
}
}
{ // older versions
typedef __gnu_cxx::stdio_filebuf<_CharT, _Traits> io_buffer_t;
io_buffer_t * buffer(dynamic_cast<io_buffer_t *>(s.rdbuf()));
if(buffer != nullptr)
{
return ::isatty(fileno(buffer->file()));
}
}
return false;
}
Теперь вы должны спросить: а что это за класс деталей?
our_basic_filebuf
?!?
И это хороший вопрос. Дело в том, что указатель защищен и нетfile()
(илиfd()
) вstd::basic_filebuf
. По этой причине я создал класс оболочки , который имеет доступ к защищенным полям, и таким образом я могу вернутьFILE*
указатель.
template<typename _CharT
, typename _Traits = std::char_traits<_CharT>>
class our_basic_filebuf
: public std::basic_filebuf<_CharT, _Traits>
{
public:
std::__c_file * file() throw()
{
return this->_M_file.file();
}
};
Это несколько уродливо, но самое чистое, что я мог придумать, чтобы получить доступ к_M_file
поле.
Доработка идеи Алексиса Вилке У меня работает под MSVC 2022 и MSYS2
namespace detail {
#ifdef _WIN32
using handle_type = HANDLE;
#else
#define INVALID_HANDLE_VALUE (-1)
using handle_type = int;
#endif
template <
typename _Elem,
typename _Traits = std::char_traits<_Elem>
>
class basic_filebuf_hack :
#ifdef _MSC_VER
public std::basic_streambuf<_Elem, _Traits> {
public:
using _Cvt = std::codecvt<_Elem, char, typename _Traits::state_type>;
const _Cvt * _Pcvt;
_Elem _Mychar;
bool _Wrotesome;
typename _Traits::state_type _State;
bool _Closef;
FILE * _Myfile;
_Elem * _Set_eback;
_Elem * _Set_egptr;
__forceinline
auto file() throw() {
return this->_Myfile;
}
#else
public std::basic_filebuf<_Elem, _Traits> {
auto file() throw() {
return this->_M_file.file();
}
#endif
};
static_assert(sizeof(std::basic_filebuf<char>) == sizeof(basic_filebuf_hack<char>), "sizes not same");
} // namespace detail
template <
typename _CharT,
typename _Traits = std::char_traits<_CharT>
>
detail::handle_type ios_fileno(const std::basic_ios<_CharT, _Traits> & s) {
using file_buffer_t = std::basic_filebuf<_CharT, _Traits>;
auto file_buffer = dynamic_cast<file_buffer_t *>(s.rdbuf());
if (file_buffer != nullptr) {
using hack_buffer_t = detail::basic_filebuf_hack<_CharT, _Traits>;
auto buffer = reinterpret_cast<hack_buffer_t *>(file_buffer);
if (buffer != nullptr) {
auto file = buffer->file();
if (file != nullptr) {
#ifdef _WIN32
return detail::handle_type(_get_osfhandle(_fileno(file)));
#else
return fileno(file);
#endif
}
}
}
return INVALID_HANDLE_VALUE;
}