【问题标题】:Getting a FILE* from a std::fstream从 std::fstream 获取 FILE*
【发布时间】:2021-11-24 20:08:09
【问题描述】:

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

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

【问题讨论】:

    标签: c++ c file file-io fstream


    【解决方案1】:

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

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

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

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

    【讨论】:

    【解决方案2】:

    简短的回答是否定的。

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

    【讨论】:

    • 太糟糕了,它只适用于 BSD;这将是一个很好的解决方案,因为它允许将 FILE* 与任何类型的 C++ 流一起使用。
    • 您问“为什么?”:因为可能有一个用 C 编写的 print 的 C 实现,可能要为 C++ ostreams(或 ofstreams)重用什么。
    • 为什么?因为当我使用一个对象时,重载操作符out << someObject << anotherObject 但使用 fprintf(ofp, "%8.1lf %2d\n", doubleVar, intVar) 实现 operator
    • 如果您想/需要使用“printf”格式范例,您可以随时使用 sprintf 创建一个 c 字符串,然后将其发送到您的 ofstream。当然,这意味着您需要了解格式化字符串长度的上限,以便正确调整 char[] 的大小。
    • GNU fopencookie 函数具有相同的功能,但界面略有不同,
    【解决方案3】:

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

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

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

    【讨论】:

    【解决方案4】:

    更新:查看@Jetttatura 我认为这是最佳答案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";
    }
    

    限制:(欢迎 cmets)

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

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

    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

    可以使用istreamfmemopen 读取fscanf,但是如果想要结合C-reads 和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;
    }
    

    【讨论】:

    • 这太棒了!我最近不得不做一些 tcsetattr() 的工作来处理一些作为 ofstream 的东西,你的文章真的帮助了我。
    • 同意。阅读 Boost.Interprocess 的文档,似乎有许多特定于平台的行为会使标准化变得烦人。尽管如此,它的缺失还是留下了一个相当大的漏洞,所以我觉得值得努力让平台人员支持一个共同的行为子集,这些行为可以为未来 C++ 标准的支持奠定基础。
    • 这也许应该是公认的答案。我在我的问题stackoverflow.com/questions/109449/… 中引用了它,这是出于使用std::freadstd::ifstream::read 的性能提升。
    • @OliverSchönrock ,这个问题是 2008 年一位未注册用户 (Bek?) 提出的,我怀疑他是否仍然存在或可以恢复帐户以接受答案。我不知道管理员是否可以接受答案。
    • 我没有。对于我的情况来说代码太多了,而且仍然只适用于 linux,我更喜欢你的。特别是因为我也需要寻找,所以它看起来在那个实现中缺少什么?顺便说一句,我在上面粘贴了错误的链接。这是我的问题:stackoverflow.com/questions/69435310/…
    【解决方案5】:

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

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

    【讨论】:

      【解决方案6】:

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

      【讨论】:

      • 太好了,我会用template&lt;class Stream&gt; FILE* cfile(Stream&amp; s){ return STDIOAdapter&lt;Stream&gt;::yield(&amp;s); } 来补充它。它适用于std::coutstd::cerr 以及混合fprintf 和C++ 代码。我会推荐使用这个。
      • 我再次发表评论以确认这适用于std::ostringstream
      • 拥有seek 函数是否有意义。 int cookieSeek(void* cookie, ssize_t* off, int way){ static_cast&lt;std::iostream*&gt;(cookie)-&gt;seekg(*off, static_cast&lt;Stream*&gt;(cookie)-&gt;end); return way; } ? (我不确定我是否正确使用way)。
      • 我不明白这里发生了什么。 Cookie 有什么用?
      • @Jetttatura:所以,它是非标准的。
      【解决方案7】:

      有一种方法可以从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&lt;char&gt;&amp; fstr);。头文件不能包含 fstream。

      【讨论】:

        猜你喜欢
        相关资源
        最近更新 更多
        热门标签