从std :: fstream获取文件*

时间:2008-09-20 21:19:16

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

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

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

8 个答案:

答案 0 :(得分:33)

简短的回答是否定的。

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

答案 1 :(得分:17)

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

大多数平台确实提供了一些非标准的方法来实现这一目标。

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

答案 2 :(得分:9)

  

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

ORIGINAL:

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

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

可以使用,

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

限制:(欢迎提出意见)

  1. 我发现fflush打印到fprintfstd::ofstream非常重要,否则“sample2”会出现在上面示例中的“sample1”之前。我不知道是否有比使用fflush更好的解决方法。值得注意的是ofs << flush没有帮助。

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

  3. 我仍然不知道如何从stderr等中提取C std::cerr,例如在fprintf(stderr, "sample")中使用,在这样的假设代码中{{1 }}。

  4. 关于最后一个限制,我发现的唯一解决方法是添加这些重载:

    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使用istream进行阅读,但是如果需要的话,这需要大量的簿记并在每次阅读后更新流的输入位置结合C读取和C ++ - 读取。我无法将其转换为上面的fmemopen函数。 (也许是cfile ,每次阅读后都会不断更新。)

    cfile

答案 3 :(得分:4)

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

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

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

另外,如果fstream或FILE关闭,它们可能会关闭底层fd,所以你需要确保在关闭之前清空BOTH。

答案 4 :(得分:4)

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

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

答案 5 :(得分:2)

另一种在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;
}

答案 6 :(得分:1)

请看这个图书馆

MDS utils

它解决了这个问题,因为它允许将C FILE *视为C ++流。它使用Boost C ++库。您必须使用Doxygen查看文档。

答案 7 :(得分:1)

有一种方法可以从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创建单独的.hint getFdFromFstream(std::basic_ios<char>& fstr);。头文件不得包含fstream。