有没有一种(跨平台的)方法可以从C++ std :: fstream获取C FILE*句柄?
我问这个问题是因为我的C ++库接受fstream,而在一个特定的函数中,我想使用一个接受FILE *的C库。
简短的回答是不行。
原因是,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,我认为这是最好的答案https://dev59.com/oHVD5IYBdhLWcg3wDG_m#33612982(仅适用于Linux?)。
原始:
(可能不跨平台,但简单)
简化http://www.ginac.de/~kreckel/fileno/(dvorak答案)中的hack,并查看此gcc扩展http://gcc.gnu.org/onlinedocs/gcc-4.6.2/libstdc++/api/a00069.html#a59f78806603c619eafcd4537c920f859,
我有一个解决方案,在C++11之前可以在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";
}
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++ 语言的读取,则需要进行大量的记录和更新流的输入位置。我无法将其转换为像上面的 cfile
函数那样的函数。(也许一个每次读取后都会更新的 cfile
类是正确的方法)。请保留 HTML 标签。
// 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;
}
std::fread
和 std::ifstream::read
带来的性能提升所激发的。 - Oliver Schönrock好的,你可以获取文件描述符 - 我忘记方法是fd()还是getfd()。我使用的实现提供了这样的方法,但是我相信语言标准不要求它们 - 标准不应该关心你的平台是否使用fd来处理文件。
从那里,您可以使用fdopen(fd, mode)获取FILE*。
但是,我认为标准所需的用于同步STDIN / cin,STDOUT / cout和STDERR / cerr的机制不必对您可见。因此,如果您同时使用fstream和FILE*,缓冲可能会使您混乱。
另外,如果fstream或FILE中任何一个关闭,它们可能会关闭底层fd,因此在关闭任何一个之前,您需要确保将两者都刷新。
#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;
}
template<class Stream> FILE* cfile(Stream& s){ return STDIOAdapter<Stream>::yield(&s); }
来补充它。它可以与std::cout
、std::cerr
以及混合使用fprintf
和C++代码。我建议使用这个。 - alfCstd::ostringstream
。 - alfCseek
函数呢?int cookieSeek(void* cookie, ssize_t* off, int way){ static_cast<std::iostream*>(cookie)->seekg(*off, static_cast<Stream*>(cookie)->end); return way; }
?
(我不确定我是否正确使用了way
参数。) - alfCint 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
如果此代码与其他线程打开文件描述符的操作同时进行,该方法在多线程应用程序中会出现问题。
有一种方法可以从 fstream
中获取文件描述符,然后通过 fdopen
将其转换为 FILE*
。个人认为没有必要使用 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();
int getFdFromFstream(std::basic_ios<char>& fstr);
的函数。头文件不得包含fstream。#define
技巧。C++始终创建完全相同的结构,无论一切是否公开。 - Alexis Wilke_M_filebuf
已经是一个 libstdc++ 特定的内部实现。由于它没有在 C++ 标准中定义,因此他们可以自由地在版本之间进行修改或删除。您没有任何保证,并且链接到另一个库版本可能会破坏您的代码,或者更糟糕的是,运行不同的二进制文件可能会导致糟糕的结果发生。 - JHBonariusstdio_filebuf
(C++98)和stdio_sync_filebuf
(C++11及更高版本)已经有两个实现。所以我们已经有了这样的东西。旧的实现方式可以让你访问file()
。上面的代码在C++98版本中无法编译。关于不兼容的二进制文件,我偶尔也会遇到这个问题。我经常从头开始重新编译整个项目(到目前为止有902个C++文件),因为它会崩溃...在大多数情况下,这是因为我改变了一个类,因此字段的布局在版本之间发生了变化,而我错过了编译/安装修改后的库。 - Alexis Wilke当我面临只能在文件描述符上运行的isatty()
时,我遇到了这个问题。
在新版本的C++标准库中(至少从C++11开始),alfC提出的解决方案不再适用,因为那个类已经改变成了一个新类。
旧方法仍然可以在使用非常旧的编译器版本时使用。在更新的版本中,您需要使用std::basic_filebuf<>()
。但是,这对于标准I/O(如std::cout
)无效。对于这些内容,您需要使用__gnu_cxx::stdio_sync_filebuf<>()
。
我在我的isatty()
C++流实现中有一个功能示例。您应该能够将那个文件拿走并在自己的项目中重复使用它。不过,在您的情况下,您想要FILE*
指针,所以只需返回它而不是::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
是什么???_M_file
指针是受保护的,并且在std::basic_filebuf
中没有file()
(或fd()
)。因此,我创建了一个外壳类,它可以访问受保护的字段,这样我就可以返回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
字段。
Alexis Wilke的想法修订 它在我的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;
}
out << someObject << anotherObject
,但使用fprintf(ofp, "%8.1lf %2d\n", doubleVar, intVar)
来实现operator<<操作符。 - riderBillfopencookie
函数具有相同的功能,但接口略有不同。 - Chris Dodd