是否有(跨平台)方法从 C++ std::fstream 获取 C FILE* 句柄?
我问的原因是因为我的 C++ 库接受 fstreams 并且在一个特定的函数中我想使用一个接受 FILE* 的 C 库。
最简洁的答案是不。
原因,是因为std::fstream
不需要使用 aFILE*
作为其实现的一部分。因此,即使您设法从对象中提取文件描述符std::fstream
并手动构建 FILE 对象,您也会遇到其他问题,因为您现在将有两个缓冲对象写入同一个文件描述符。
真正的问题是为什么要将std::fstream
对象转换为FILE*
?
虽然我不推荐,但你可以试试看funopen()
。
不幸的是,这不是POSIX API(它是 BSD 扩展),因此它的可移植性存在问题。这也可能是为什么我找不到任何人std::stream
用这样的对象包裹 a 的原因。
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,我认为这是最好的答案https://stackoverflow.com/a/33612982/225186(仅限 Linux?)。
原来的:
(可能不是跨平台,但很简单)
在http://www.ginac.de/~kreckel/fileno/ (dvorak 答案)中简化 hack ,并查看这个 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";
}
限制:(欢迎评论)
我发现打印到fflush
之后很重要,否则在上面的示例中,“sample2”出现在“sample1”之前。我不知道是否有比使用. 显然没有帮助。fprintf
std::ofstream
fflush
ofs << flush
无法从中提取 FILE* std::stringstream
,我什至不知道是否可能。(请参阅下面的更新)。
我仍然不知道如何stderr
从std::cerr
等中提取 C,例如fprintf(stderr, "sample")
在这样的假设代码中使用 in 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
from istream
using进行读取fmemopen
,但如果想要结合 C-reads 和 C++-reads,则需要在每次读取后大量保存和更新流的输入位置。我无法将其转换为上述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 或 FILE 关闭,它们可能会关闭底层 fd,因此您需要确保在关闭任何一个之前都刷新两者。
在单线程 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。