从 std::fstream 获取 FILE*

发布于 2025-01-14 23:53:06 字数 117 浏览 2 评论 0原文

有没有一种(跨平台)方法从 C++ std::fstream 获取 C FILE* 句柄?

我问的原因是因为我的 C++ 库接受 fstream,并且在一个特定函数中我想使用接受 FILE* 的 C 库。

Is there a (cross-platform) way to get a C FILE* handle from a C++ std::fstream ?

The reason I ask is because my C++ library accepts fstreams and in one particular function I'd like to use a C library that accepts a FILE*.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

滥情稳全场 2025-01-21 23:53:06

简短的回答是否定的。

原因是,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 对象中读取。

The short answer is no.

The reason, is because the std::fstream is not required to use a FILE* as part of its implementation. So even if you manage to extract file descriptor from the std::fstream object and manually build a FILE object, then you will have other problems because you will now have two buffered objects writing to the same file descriptor.

The real question is why do you want to convert the std::fstream object into a FILE*?

Though I don't recommend it, you could try looking up funopen().
Unfortunately, this is not a POSIX API (it's a BSD extension) so its portability is in question. Which is also probably why I can't find anybody that has wrapped a std::stream with an object like this.

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 *)
             );

This allows you to build a FILE object and specify some functions that will be used to do the actual work. If you write appropriate functions you can get them to read from the std::fstream object that actually has the file open.

沩ん囻菔务 2025-01-21 23:53:06

没有一个标准化的方法。我认为这是因为 C++ 标准化小组不想假设文件句柄可以表示为 fd。

大多数平台似乎确实提供了一些非标准的方法来做到这一点。

http://www.ginac.de/~kreckel/fileno/ 提供了很好的文章情况并提供隐藏所有平台特定严重性的代码,至少对于 GCC 来说是这样。考虑到这在海湾合作委员会上是多么恶心,我想如果可能的话我会避免一起做这一切。

There isn't a standardized way. I assume this is because the C++ standardization group didn't want to assume that a file handle can be represented as a fd.

Most platforms do seem to provide some non-standard way to do this.

http://www.ginac.de/~kreckel/fileno/ provides a good writeup of the situation and provides code that hides all the platform specific grossness, at least for GCC. Given how gross this is just on GCC, I think I'd avoid doing this all together if possible.

安静 2025-01-21 23:53:06

更新:请参阅@Jettatura,我认为这是最好的答案https://stackoverflow.com/a/33612982/225186< /a>(仅限 Linux?)。


原文:(

可能不是跨平台,但很简单)

简化 http://www.ginac 中的黑客攻击.de/~kreckel/fileno/ (dvorak 答案),并查看这个 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";
}

注意: stdio_filebuf 未在较新版本的库中使用。 static_cast<>() 也有些危险。如果您得到的 nullptr 不是正确的类,请使用 dynamic_cast<>() 而不是。您可以尝试使用 stdio_sync_filebuf 来代替。该类的问题是 file() 根本不再可用。

限制:(欢迎评论)

  1. 我发现在 fprintf 打印到 std::ofstream 后进行 fflush 很重要,否则上例中“sample2”将出现在“sample1”之前。我不知道是否有比使用 fflush 更好的解决方法。值得注意的是 ofs <<冲洗没有帮助。

  2. 无法从std::stringstream中提取FILE*,我什至不知道是否可能。 (请参阅下面的更新)。

  3. 我仍然不知道如何从 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

可以使用 fscanf 从 < code>istream 使用 fmemopen,但是如果想将 C 读取和 C++ 读取结合起来,则需要在每次读取后进行大量簿记和更新流的输入位置。我无法将其转换为上面那样的 cfile 函数。 (也许在每次读取后不断更新的 cfile class 是可行的方法)。

// 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;
}

UPDATE: See @Jettatura what I think it is the best answer https://stackoverflow.com/a/33612982/225186 (Linux only?).

ORIGINAL:

(Probably not cross platform, but simple)

Simplifying the hack in http://www.ginac.de/~kreckel/fileno/ (dvorak answer), and looking at this gcc extension http://gcc.gnu.org/onlinedocs/gcc-4.6.2/libstdc++/api/a00069.html#a59f78806603c619eafcd4537c920f859,
I have this solution that works on GCC (4.8 at least) and clang (3.3 at least) before C++11:

#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());}

and can be used this,

int main(){
    std::ofstream ofs("file.txt");
    fprintf(cfile(ofs), "sample1");
    fflush(cfile(ofs)); // ofs << std::flush; doesn't help 
    ofs << "sample2\n";
}

Note: The stdio_filebuf is not used in newer versions of the library. The static_cast<>() is somewhat dangerous too. Use a dynamic_cast<>() instead of if you get a nullptr you need that's not the right class. You can try with stdio_sync_filebuf instead. Problem with that class is that the file() is not available at all anymore.

Limitations: (comments are welcome)

  1. I find that it is important to fflush after fprintf printing to std::ofstream, otherwise the "sample2" appears before "sample1" in the example above. I don't know if there is a better workaround for that than using fflush. Notably ofs << flush doesn't help.

  2. Cannot extract FILE* from std::stringstream, I don't even know if it is possible. (see below for an update).

  3. I still don't know how to extract C's stderr from std::cerr etc., for example to use in fprintf(stderr, "sample"), in an hypothetical code like this fprintf(cfile(std::cerr), "sample").

Regarding the last limitation, the only workaround I found is to add these overloads:

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
}

Attempt to handle iostringstream

It is possible to read with fscanf from istream using fmemopen, but that requires a lot of book keeping and updating the input position of the stream after each read, if one wants to combine C-reads and C++-reads. I wasn't able to convert this into a cfile function like above. (Maybe a cfile class that keeps updating after each read is the way to go).

// 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;
}
独夜无伴 2025-01-21 23:53:06

好吧,你可以获取文件描述符 - 我忘记了该方法是 fd() 还是 getfd()。 我使用的实现提供了这样的方法,但我相信语言标准不需要它们 - 标准不应该关心您的平台是否使用文件描述符。

从中,您可以使用fdopen(fd, mode) 获取 FILE*。

但是,我认为标准所需的同步 STDIN/cin、STDOUT/cout 和 STDERR/cerr 的机制不必对您可见。因此,如果您同时使用 fstream 和 FILE*,缓冲可能会让您陷入困境。

另外,如果 fstream 或 FILE 关闭,它们可能会关闭底层 fd,因此您需要确保在关闭之前刷新两者。

Well, you can get the file descriptor - I forget whether the method is fd() or getfd(). The implementations I've used provide such methods, but the language standard doesn't require them, I believe - the standard shouldn't care whether your platform uses fd's for files.

From that, you can use fdopen(fd, mode) to get a FILE*.

However, I think that the mechanisms the standard requires for synching STDIN/cin, STDOUT/cout and STDERR/cerr don't have to be visible to you. So if you're using both the fstream and FILE*, buffering may mess you up.

Also, if either the fstream OR the FILE closes, they'll probably close the underlying fd, so you need to make sure you flush BOTH before closing EITHER.

安稳善良 2025-01-21 23:53:06

在 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;
}

yet another way to do this in 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

Usage, for example:

#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;
}
甲如呢乙后呢 2025-01-21 23:53:06

在单线程 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

如果此代码与打开文件描述符的其他线程竞争,则此方法会在多线程应用程序中中断。

In a single-threaded POSIX application you can easily get the fd number in a portable way:

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

This method breaks in a multi-threaded application if this code races with other threads opening file descriptors.

风月客 2025-01-21 23:53:06

有一种方法可以从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 的所有私有和公共成员。如果您想在生产代码中使用它,我建议您使用单个函数 int getFdFromFstream(std::basic_ios<字符>&fstr);。头文件不得包含 fstream。

There is a way to get file descriptor from fstream and then convert it to FILE* (via fdopen). Personally I don't see any need in FILE*, but with file descriptor you may do many interesting things such as redirecting (dup2).

Solution:

#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();

The last string works for libstdc++. If you are using some other library you will need to reverse-engineer it a bit.

This trick is dirty and will expose all private and public members of fstream. If you would like to use it in your production code I suggest you to create separate .cpp and .h with single function int getFdFromFstream(std::basic_ios<char>& fstr);. Header file must not include fstream.

过期以后 2025-01-21 23:53:06

当我遇到仅处理文件描述符的 istty() 时,我遇到了这个问题。

在较新版本的 C++ 标准库中(至少自 C++11 以来),alfC 提出的解决方案不再有效,因为一个类已更改为新类。

如果您使用非常旧版本的编译器,旧方法仍然有效。在较新的版本中,您需要使用std::basic_filebuf<>()。但这不适用于标准 I/O,例如 std::cout。对于这些,您需要使用 __gnu_cxx::stdio_sync_filebuf<>()。

我在 isatty()< 的实现中有一个功能示例/code>用于此处的 C++ 流。您应该能够删除该文件并在您自己的项目中重复使用它。不过,就您而言,您需要 FILE* 指针,因此只需返回该指针,而不是返回 ::isatty(fileno())

这是模板函数的副本:

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 中没有 file()(或 fd()): :basic_filebuf。因此,我创建了一个 shell 类,它可以访问受保护的字段,这样我就可以返回 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 字段。

I ran in that problem when I was faced with isatty() only working on a file descriptor.

In newer versions of the C++ standard library (at least since C++11), the solution proposed by alfC does not work anymore because that one class was changed to a new class.

The old method will still work if you use very old versions of the compiler. In newer version, you need to use std::basic_filebuf<>(). But that does not work with the standard I/O such as std::cout. For those, you need to use __gnu_cxx::stdio_sync_filebuf<>().

I have a functional example in my implementation of isatty() for C++ streams here. You should be able to lift off that one file and reuse it in your own project. In your case, though, you wanted the FILE* pointer, so just return that instead of the result of ::isatty(fileno(<of FILE*>)).

Here is a copy of the template function:

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;
}

Now, you should be asking: But what is that detail class our_basic_filebuf?!?

And that's a good question. The fact is that the _M_file pointer is protected and there is no file() (or fd()) in the std::basic_filebuf. For that reason, I created a shell class which has access to the protected fields and that way I can return the FILE* pointer.

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();
    }
};

This is somewhat ugly, but cleanest I could think off to gain access to the _M_file field.

街角卖回忆 2025-01-21 23:53:06

对 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;
}

Revision of the idea of Alexis Wilke
It works for me under MSVC 2022 and 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;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文