从std :: fstream获取文件*

54 c c++ file-io fstream file

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

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

Mar*_*ork 36

最简洁的答案是不.

原因是因为std::fstream不需要使用a 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 *)
             );
Run Code Online (Sandbox Code Playgroud)

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

  • 太糟糕了,它只适用于BSD; 它本来是一个很好的解决方案,因为它允许使用任何类型的C++流的FILE*. (6认同)
  • 你问"为什么?":因为你可能有一个用C语言编写的C实现,可能会重用C++ ostreams(或ofstreams). (3认同)
  • GNU `fopencookie` 函数具有相同的功能,但界面略有不同, (2认同)

dvo*_*rak 17

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

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

http://www.ginac.de/~kreckel/fileno/提供了一个很好的情况描述,并提供了隐藏所有平台特定粗糙度的代码,至少对于GCC而言.鉴于这对GCC来说有多严重,我想如果可能的话,我会避免这样做.

  • "FILE*"和文件描述符是*不同的*对象,它们由不同的组件使用.一个由C运行时库使用,另一个由OS使用.请参阅[文件描述符和文件指针之间有什么区别?](http://stackoverflow.com/q/2423628) (3认同)

alf*_*lfC 9

更新:请参阅@Jettatura我认为这是最好的答案/sf/answers/2352908771/(仅限Linux?).

原版的:

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

http://www.ginac.de/~kreckel/fileno/(dvorak回答)中简化黑客攻击,并查看此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());}
Run Code Online (Sandbox Code Playgroud)

并且可以使用这个,

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

限制:(欢迎评论)

  1. 我发现打印fflush后很重要,否则"sample2"出现在上面例子中的"sample1"之前.我不知道是否有比使用更好的解决方法.值得注意的是没有用.fprintfstd::ofstreamfflushofs << flush

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

  3. 我仍然不知道如何stderr从像这样的假设代码中提取C std::cerr等,例如使用.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
}
Run Code Online (Sandbox Code Playgroud)

尝试处理 iostringstream

可以fscanfistream使用中读取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;
}
Run Code Online (Sandbox Code Playgroud)

  • 同意 在阅读Boost.Interprocess的文档时,似乎有许多特定于平台的行为会使标准化变得烦人。尽管如此,它的缺失仍然留下了很大的漏洞,因此我认为值得尝试让平台人员支持行为的公共子集,这可以为将来的C ++标准的支持打下基础。 (2认同)

Mik*_* G. 5

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

由此,您可以使用 fdopen(fd, mode) 来获取 FILE*。

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

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