从std :: fstream获取FILE *
有一种(跨平台)的方式从C ++ std :: fstream获取C FILE *句柄吗?
我问的原因是因为我的C ++库接受fstreams和在一个特定的函数,我想使用一个C库,接受FILE *。
简短的答案是否定的。
原因是因为std::fstream
不需要使用FILE*
作为其实现的一部分。 所以,即使你设法从std::fstream
对象中提取文件描述符并手动build立一个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 *) );
这允许你build立一个FILE
对象并指定一些将用来做实际工作的函数。 如果你编写适当的函数,你可以让它们从实际上已经打开文件的std::fstream
对象读取。
没有一个标准化的方法。 我认为这是因为C ++标准化组织不希望假定文件句柄可以表示为fd。
大多数平台似乎提供了一些非标准的方式来做到这一点。
http://www.ginac.de/~kreckel/fileno/提供了一个很好的写法,并提供了隐藏所有平台特定的粗糙性的代码,至less对于GCC来说。; 鉴于GCC上的这个问题有多严重,我想尽可能避免这样做。
更新:请参阅@Jettatura我认为这是最好的答案https://stackoverflow.com/a/33612982/225186 (仅Linux?)。
原版的:
(可能不是跨平台,但很简单)
简化http://www.ginac.de/~kreckel/fileno/(dvorak answer)中的破解,并查看这个gcc扩展http://gcc.gnu.org/onlinedocs/gcc-4.6.2/libstdc++/ api / a00069.html#a59f78806603c619eafcd4537c920f859 ,我有这个解决scheme在GCC
(至less4.8)和clang
(至less3.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
提取FILE *,我甚至不知道它是否可能。 (请参阅下面的更新)。 -
我仍然不知道如何从
std::cerr
等提取C的stderr
,例如在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
从istream
读取fscanf
是可能的,但是如果想要结合C读取和C ++读取,那么每次读取后需要大量的logging和更新stream的input位置。 我无法将其转换为上面的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)来获得一个FILE *。
不过,我认为标准所要求的用于同步STDIN / cin,STDOUT / cout和STDERR / cerr的机制并不一定是可见的。 所以如果你同时使用fstream和FILE *,缓冲可能会让你感到困惑。
另外,如果fstream或者FILEclosures,他们可能会closures底层的fd,所以你需要确保在closures之前刷新BOTH。
在单线程的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
如果此代码与其他线程打开文件描述符竞争,则此方法在multithreading应用程序中中断。
另一种在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; }
请看这个图书馆
MDS使用情况
它解决了这个问题,因为允许将C FILE *视为C ++stream。 它使用Boost C ++库。 您必须使用Doxygen查看文档。
有一种方法可以从fstream
获取文件描述符,然后将其转换为FILE*
(通过fdopen
)。 就我个人而言,在FILE*
中看不到任何需要,但是使用文件描述符可能会执行许多有趣的事情,例如redirect( 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();
最后一个string适用于libstdc ++。 如果您正在使用其他库,则需要对其进行反向工程。
这个伎俩是肮脏的,将暴露fstream的所有私人和公共成员。 如果你想在你的生产代码中使用它,我build议你创build单独的.cpp
和.h
单个函数int getFdFromFstream(std::basic_ios<char>& fstr);
。 头文件不能包含fstream。