programing

std :: fstream에서 FILE * 가져 오기

javaba 2021. 1. 15. 19:08
반응형

std :: fstream에서 FILE * 가져 오기


C ++ std :: fstream에서 C FILE * 핸들을 얻는 (크로스 플랫폼) 방법이 있습니까?

내가 묻는 이유는 내 C ++ 라이브러리가 fstream을 허용하고 특정 함수에서 FILE *을 허용하는 C 라이브러리를 사용하고 싶기 때문입니다.


짧은 대답은 아니오입니다.

그 이유는를 구현의 일부로 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실제로 파일이 열려 있는 객체 에서 읽을 수 있습니다 .


표준화 된 방법이 없습니다. 나는 이것이 C ++ 표준화 그룹이 파일 핸들이 fd로 표현 될 수 있다고 가정하고 싶지 않았기 때문이라고 가정합니다.

대부분의 플랫폼은이를 수행하는 비표준 방법을 제공하는 것 같습니다.

http://www.ginac.de/~kreckel/fileno/ 는 상황에 대한 좋은 글을 제공하고 최소한 GCC에 대해 모든 플랫폼 특정 총체 성을 숨기는 코드를 제공합니다. 이것이 GCC에서 얼마나 심한 지 감안할 때 가능하면 모두 함께하지 않는 것이 좋습니다.


업데이트 : @Jettatura를 참조하십시오 https://stackoverflow.com/a/33612982/225186 (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());}

이것을 사용할 수 있습니다.

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한 후에 는 중요합니다 . 그렇지 않으면 위의 예에서 "sample1"앞에 "sample2"가 나타납니다. 를 사용하는 것보다 더 나은 해결 방법이 있는지 모르겠습니다 . 특히 도움이되지 않습니다.fprintfstd::ofstreamfflushofs << flush

  2. 에서 FILE *을 추출 할 수 없습니다 std::stringstream. 가능한지 모르겠습니다. (업데이트는 아래 참조).

  3. 나는 아직도 C의 추출 방법을 모르는 stderrstd::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

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

글쎄, 당신은 파일 기술자를 얻을 수 있습니다-메소드가 fd ()인지 getfd ()인지 잊어 버렸습니다. 내가 사용한 구현은 그러한 방법을 제공하지만 언어 표준은 그것들을 요구하지 않는다고 믿습니다. 표준은 당신의 플랫폼이 파일에 fd를 사용하는지 여부를 신경 쓰지 않아야합니다.

그로부터 fdopen (fd, mode)을 사용하여 FILE *을 얻을 수 있습니다.

그러나 표준에서 STDIN / cin, STDOUT / cout 및 STDERR / cerr을 동기화하는 데 필요한 메커니즘은 눈에 보이지 않아도된다고 생각합니다. 따라서 fstream과 FILE *을 모두 사용하는 경우 버퍼링으로 인해 엉망이 될 수 있습니다.

또한 fstream 또는 FILE이 닫히면 기본 fd를 닫을 수 있으므로 둘 중 하나를 닫기 전에 둘 다 플러시했는지 확인해야합니다.


단일 스레드 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

이 코드가 파일 설명자를 여는 다른 스레드와 경쟁하는 경우이 메서드는 다중 스레드 응용 프로그램에서 중단됩니다.


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

There is a way to get file descriptor from fstream and then convert it to FILE* (via fdopen). Personally I don't see any need in FILE*, but with file descriptor you may do many interesting things such as redirecting (dup2).

Solution:

#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();

The last string works for libstdc++. If you are using some other library you will need to reverse-engineer it a bit.

This trick is dirty and will expose all private and public members of fstream. If you would like to use it in your production code I suggest you to create separate .cpp and .h with single function int getFdFromFstream(std::basic_ios<char>& fstr);. Header file must not include fstream.

ReferenceURL : https://stackoverflow.com/questions/109449/getting-a-file-from-a-stdfstream

반응형