web-dev-qa-db-ja.com

std :: fstreamからFILE *を取得する

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

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

53
Bek

短い答えはノーです。

その理由は、実装の一部として_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_オブジェクトからそれらを読み取ることができます。

35
Martin York

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

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

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

17
dvorak

更新:@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が_std::ofstream_に出力された後、fprintfが重要であることがわかります。それ以外の場合、上記の例では「sample1」の前に「sample2」が表示されます。 fflushを使用するよりも良い回避策があるかどうかはわかりません。特に_ofs << flush_は役に立ちません。

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

  3. Cのstderrを_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
}
_

iostringstreamを処理しようとしました

fscanfを使用してistreamからfmemopenで読み取ることは可能ですが、C読み取りとC++読み取りを組み合わせたい場合は、読み取りごとに大量のブックキーピングとストリームの入力位置の更新が必要です。これを上記のようなcfile関数に変換できませんでした。 (多分、cfileclassは、読み取りのたびに更新を続けていく方法です)。

_// 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;
}
_
9
alfC

シングルスレッドの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

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

4

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

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

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

また、fstream OR FILEが閉じた場合、おそらく基礎となるfdを閉じます。そのため、両方を閉じる前に両方をフラッシュする必要があります。

4
Mike G.

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;
}
2
Jettatura

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のすべてのプライベートメンバーとパブリックメンバーを公開します。実稼働コードで使用する場合は、単一の関数int getFdFromFstream(std::basic_ios<char>& fstr);を使用して_.cpp_と_.h_を別々に作成することをお勧めします。ヘッダーファイルにfstreamを含めることはできません。

1
yanpas

このライブラリを見てください

MDS utils

C FILE *をC++ストリームとして扱うことができるため、問題を解決します。 Boost C++ライブラリを使用します。ドキュメントを表示するには、Doxygenを使用する必要があります。

1