Получение FILE * из std:: fstream
Есть ли (кросс-платформенный) способ получить C FILE * дескриптор из С++ std:: fstream?
Причина, по которой я спрашиваю, заключается в том, что моя библиотека С++ принимает Fstreams и в одной конкретной функции, я хотел бы использовать библиотеку C, которая принимает файл FILE *.
Ответы
Ответ 1
Короткий ответ - нет.
Причина в том, что std::fstream
не требуется использовать FILE*
как часть его реализации. Поэтому даже если вам удастся извлечь файловый дескриптор из объекта std::fstream
и вручную создать объект FILE, тогда у вас появятся другие проблемы, потому что теперь у вас будет два буферизованных объекта, записывающих один и тот же дескриптор файла.
Реальный вопрос, почему вы хотите преобразовать объект std::fstream
в FILE*
?
Хотя я не рекомендую его, вы можете попробовать посмотреть funopen()
.
К сожалению, это not 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
, на котором фактически открыт файл.
Ответ 2
Нет стандартизированного способа. Я предполагаю, что это связано с тем, что группа стандартизации С++ не хотела предполагать, что дескриптор файла может быть представлен как fd.
Большинство платформ, похоже, предоставляют нестандартный способ сделать это.
http://www.ginac.de/~kreckel/fileno/ обеспечивает хорошую запись ситуации и предоставляет код, который скрывает всю грубость платформы, по крайней мере для GCC. Учитывая, насколько валовой это только на GCC, я думаю, что избегаю делать это вместе, если это возможно.
Ответ 3
UPDATE: см. @Jettatura, что я считаю лучшим ответом fooobar.com/questions/113124/... (только Linux).
ОРИГИНАЛ:
(Возможно, не перекрестная платформа, но простая)
Упрощение взлома http://www.ginac.de/~kreckel/fileno/ (ответ dvorak) и просмотр этого расширения 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";
}
Ограничения: (комментарии приветствуются)
-
Я считаю, что после fprintf
печатать до std::ofstream
важно fflush
, иначе "sample2" появится перед "sample1" в приведенном выше примере. Я не знаю, есть ли лучшее решение для этого, чем использование fflush
. В частности ofs << flush
не помогает.
-
Невозможно извлечь файл * из 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
С помощью fmemopen
можно читать с помощью fscanf
из istream
, но для этого требуется много учета и обновления позиции ввода потока после каждого чтения, если вы хотите объединить С-чтения и С++ - читает. Я не смог преобразовать это в функцию 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
Ну, вы можете получить дескриптор файла - я забываю, является ли метод fd() или getfd(). Реализации, которые я использовал, предоставляют такие методы, но стандарт языка не требует их, я считаю - стандарту не должно волновать, использует ли ваша платформа fd для файлов.
Из этого вы можете использовать fdopen (fd, mode), чтобы получить FILE *.
Однако я считаю, что механизмы, требуемые стандартом для синхронизации STDIN/cin, STDOUT/cout и STDERR/cerr, не обязательно должны быть вам видны. Поэтому, если вы используете как fstream, так и FILE *, буферизация может помешать вам.
Кроме того, если либо Fstream, либо FILE закрывается, они, вероятно, закрывают базовый fd, поэтому вам нужно убедиться, что вы очистите BOTH перед закрытием EITHER.
Ответ 5
В однопоточном приложении 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
Этот метод ломается в многопоточном приложении, если этот код запускается с другими потоками, открывая дескрипторы файлов.
Ответ 6
еще один способ сделать это в 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;
}
Ответ 7
Пожалуйста, посмотрите на эту библиотеку
Утилиты MDS
Он решает проблему, потому что позволяет обрабатывать C FILE * как поток С++. Он использует библиотеки Boost С++. Вы должны использовать Doxygen для просмотра документации.
Ответ 8
Существует способ получить дескриптор файла из 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();
Последняя строка работает для libstdС++. Если вы используете какую-то другую библиотеку, вам нужно немного ее перестроить.
Этот трюк грязный и выведет всех частных и публичных членов fstream. Если вы хотите использовать его в своем производственном коде, я предлагаю вам создать отдельные .cpp
и .h
с помощью одной функции int getFdFromFstream(std::basic_ios<char>& fstr);
. Файл заголовка не должен включать файл fstream.