76

C++ std::fstream から C FILE* ハンドルを取得する (クロスプラットフォーム) 方法はありますか?

私が尋ねる理由は、私の C++ ライブラリが fstreams を受け入れ、ある特定の関数で FILE* を受け入れる C ライブラリを使用したいからです。

4

7 に答える 7

49

短い答えはノーです。

その理由は、 がその実装の一部としてstd::fstreamを使用する必要がないためです。FILE*したがって、std::fstreamオブジェクトからファイル記述子を抽出して手動で FILE オブジェクトを作成できたとしても、2 つのバッファリングされたオブジェクトが同じファイル記述子に書き込まれるため、別の問題が発生します。

本当の問題は、なぜ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、実際にファイルを開いているオブジェクトから読み取ることができます。

于 2008-09-20T21:43:45.680 に答える
18

標準化された方法はありません。これは、C++標準化グループがファイルハンドルをfdとして表すことができると想定したくなかったためだと思います。

ほとんどのプラットフォームは、これを行うためのいくつかの非標準的な方法を提供しているようです。

http://www.ginac.de/~kreckel/fileno/は、状況の適切な記述を提供し、少なくともGCCについては、プラットフォーム固有の粗さをすべて隠すコードを提供します。これがGCCだけでどれほどひどいものであるかを考えると、可能であれば、これをすべて一緒に行うことは避けたいと思います。

于 2008-09-20T21:53:40.397 に答える
12

更新: @Jettatura を参照してください。これが最良の答えだと思いますhttps://stackoverflow.com/a/33612982/225186 (Linux のみ?)。

オリジナル:

(おそらくクロスプラットフォームではありませんが、シンプルです)

http://www.ginac.de/~kreckel/fileno/ (dvorak answer)のハックを単純化し、この gcc 拡張機能http://gcc.gnu.org/onlinedocs/gcc-4.6.2/libstdc++/を調べます。 api/a00069.html#a59f78806603c619eafcd4537c920f859GCC 、私は(少なくとも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に出力した後が重要であることがわかりfprintfましたstd::ofstream。そうしないと、上記の例では「sample2」が「sample1」の前に表示されます。を使用するよりも良い回避策があるかどうかはわかりませんfflush。特にofs << flush役に立ちません。

  2. から FILE* を抽出できませんstd::stringstream。可能かどうかさえわかりません。(更新については以下を参照してください)。

  3. たとえば、このような架空のコードで使用するなど、stderrからCを抽出する方法はまだわかりません。std::cerrfprintf(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

fscanffrom istreamusing を使用して読み取ることは可能ですが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;
}
于 2013-11-03T01:48:42.827 に答える
4

さて、あなたはファイル記述子を得ることができます-私はメソッドがfd()であるかgetfd()であるかを忘れています。 私が使用した実装はそのようなメソッドを提供しますが、言語標準はそれらを必要としないと私は信じています-標準はあなたのプラットフォームがファイルにfdを使用するかどうかを気にしないはずです。

そこから、fdopen(fd、mode)を使用してFILE*を取得できます。

ただし、標準がSTDIN / cin、STDOUT / cout、およびSTDERR/cerrを同期するために必要なメカニズムを表示する必要はないと思います。したがって、fstreamとFILE *の両方を使用している場合、バッファリングによって混乱する可能性があります。

また、fstreamまたはFILEのいずれかが閉じた場合、それらはおそらく基になるfdを閉じるので、どちらかを閉じる前に両方をフラッシュすることを確認する必要があります。

于 2008-09-20T21:26:56.953 に答える
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

このコードがファイル記述子を開く他のスレッドと競合する場合、このメソッドはマルチスレッド アプリケーションで中断します。

于 2014-11-04T23:03:02.797 に答える