C++ 从 std::fstream 获取 FILE*

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/109449/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 12:54:37  来源:igfitidea点击:

Getting a FILE* from a std::fstream

c++cfilefile-iofstream

提问by

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

是否有(跨平台)方法可以从 C++ std::fstream 获取 C FILE* 句柄?

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*.

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

采纳答案by Martin York

The short answer is no.

最简洁的答案是不。

The reason, is because the std::fstreamis not required to use a FILE*as part of its implementation. So even if you manage to extract file descriptor from the std::fstreamobject 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.

原因是因为std::fstream不需要使用 aFILE*作为其实现的一部分。因此,即使您设法从std::fstream对象中提取文件描述符并手动构建 FILE 对象,您也会遇到其他问题,因为您现在将有两个缓冲对象写入同一个文件描述符。

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

真正的问题是为什么要将std::fstream对象转换为FILE*?

Though I don't recommend it, you could try looking up funopen().
Unfortunately, this is nota 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::streamwith an object like this.

虽然我不推荐它,但你可以尝试查找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 *)
             );

This allows you to build a FILEobject 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::fstreamobject that actually has the file open.

这允许您构建一个FILE对象并指定一些将用于执行实际工作的函数。如果您编写了适当的函数,您可以让它们从std::fstream实际打开文件的对象中读取。

回答by dvorak

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.

没有标准的方法。我认为这是因为 C++ 标准化组不想假设文件句柄可以表示为 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.

http://www.ginac.de/~kreckel/fileno/提供了对情况的很好的描述,并提供了隐藏所有平台特定粗体的代码,至少对于 GCC。考虑到这在 GCC 上有多严重,我想如果可能的话我会避免一起做这件事。

回答by alfC

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

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

ORIGINAL:

原来的:

(Probably not cross platform, but simple)

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

Simplifiying 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)

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

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

Limitations:(comments are welcomed)

限制:(欢迎评论)

  1. I find that it is important to fflushafter fprintfprinting 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 << flushdoesn'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 stderrfrom std::cerretc., for example to use in fprintf(stderr, "sample"), in an hypothetical code like this fprintf(cfile(std::cerr), "sample").

  1. 我发现fflushfprintf打印到之后很重要std::ofstream,否则在上面的例子中“sample2”出现在“sample1”之前。我不知道是否有比使用fflush. 显然ofs << flush没有帮助。

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

  3. 我仍然不知道如何stderrstd::cerr等中提取 C ,例如fprintf(stderr, "sample")在这样的假设代码中使用 in 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

尝试处理 iostringstream

It is possible to read with fscanffrom istreamusing 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 cfilefunction like above. (Maybe a cfileclassthat keeps updating after each read is the way to go).

这是可能的读取fscanf距离istream使用fmemopen,但需要大量的记账和每次读取之后更新流的输入位置的,如果一个人想结合C-读取和C ++ -读取。我无法将其转换为上述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;
}

回答by Maxim Egorushkin

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

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

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

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

回答by Mike G.

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.

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

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

由此,您可以使用 fdopen(fd, mode) 来获取 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.

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

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.

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

回答by Jettatura

yet another way to do this in Linux:

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

回答by yanpas

There is a way to get file descriptor from fstreamand 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).

有一种方法可以从中获取文件描述符fstream,然后将其转换为FILE*(via fdopen)。个人而言,我没有看到任何的需要FILE*,但随着文件描述符你可以做很多有趣的事情,如重定向(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.

最后一个字符串适用于 libstdc++。如果您正在使用其他一些库,则需要对其进行一些逆向工程。

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 .cppand .hwith single function int getFdFromFstream(std::basic_ios<char>& fstr);. Header file must not include fstream.

这个技巧很脏,会暴露 fstream 的所有私有和公共成员。如果你想在你的生产代码中使用它,我建议你创建单独的.cpp.h单一的功能int getFdFromFstream(std::basic_ios<char>& fstr);。头文件不得包含 fstream。