70

Is there a (cross-platform) way to get a C FILE* handle from a C++ std::fstream ?

The reason I ask is because my C++ library accepts fstreams and in one particular function I'd like to use a C library that accepts a FILE*.

Doug T.
  • 59,839
  • 22
  • 131
  • 193

7 Answers7

42

The short answer is no.

The reason, is because the std::fstream is not required to use a FILE* as part of its implementation. So even if you manage to extract file descriptor from the std::fstream object and manually build a FILE object, then you will have other problems because you will now have two buffered objects writing to the same file descriptor.

The real question is why do you want to convert the std::fstream object into a FILE*?

Though I don't recommend it, you could try looking up funopen().
Unfortunately, this is not a POSIX API (it's a BSD extension) so its portability is in question. Which is also probably why I can't find anybody that has wrapped a std::stream with an object like this.

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 *)
             );

This allows you to build a FILE object and specify some functions that will be used to do the actual work. If you write appropriate functions you can get them to read from the std::fstream object that actually has the file open.

Kimbluey
  • 961
  • 1
  • 9
  • 22
Martin York
  • 234,851
  • 74
  • 306
  • 532
  • 6
    Too bad it's only for BSD; it would have been a great solution since it would allow to use a FILE* with any kind of C++ stream. –  Sep 21 '08 at 19:19
  • 3
    You asked "why?": because one may have a C implementation of print written in C that one may what to reuse for C++ ostreams (or ofstreams). – alfC Nov 03 '13 at 04:20
  • Why? Because overloading the operator<< is very convenient when I'm *using* an object, but formatting stream output can be messy and painful. Formatting with fprintf() is compact and easy. In other words, I would like to be able to write `out << someObject << anotherObject` but implement operator<< using `fprintf(ofp, "%8.1lf %2d\n", doubleVar, intVar)` – riderBill Dec 05 '17 at 17:52
  • If you want/need to use the "printf" formatting paradigm, you could always use sprintf to create a c-string and then send that to your ofstream. Of course, this means you need to have a sense of the upper bound on your formatted string length so that you can size your char[] correctly. – MikeMayer67 Sep 05 '18 at 12:57
  • @MikeMayer67 There are better ways to get sprinf formatting in streams. Boost had a stream format class a while back. Even I wrote a stream formatter object for fun a while back [C-String Formatter Code Review](https://codereview.stackexchange.com/questions/188812/c-string-formatter-again-part-4) See [Part 1 of the review for usage examples](https://codereview.stackexchange.com/q/188809/507) – Martin York Sep 05 '18 at 18:32
  • @MartinYork. Agreed, but not environments have Boost (not on the system I develop on... and it will not be added). Writing your own formatter takes a little more time than simply using sprintf. It may not be the best solution (no argument here), but it is a very quick implementation where development speed is a driving design factor. – MikeMayer67 Sep 13 '18 at 12:07
  • The GNU `fopencookie` function has the same functionality with a slightly different interface, – Chris Dodd Jan 19 '21 at 20:40
17

There isn't a standardized way. I assume this is because the C++ standardization group didn't want to assume that a file handle can be represented as a fd.

Most platforms do seem to provide some non-standard way to do this.

http://www.ginac.de/~kreckel/fileno/ provides a good writeup of the situation and provides code that hides all the platform specific grossness, at least for GCC. Given how gross this is just on GCC, I think I'd avoid doing this all together if possible.

dvorak
  • 28,591
  • 4
  • 25
  • 29
  • 4
    A `FILE*` and file descriptor are *different* objects, and they are used by different components. One is used by the C runtime library, the other is used by the OS. See [What's the difference between a file descriptor and file pointer?](http://stackoverflow.com/q/2423628) – jww Apr 21 '15 at 22:39
10

UPDATE: See @Jettatura what I think it is the best answer https://stackoverflow.com/a/33612982/225186 (Linux only?).

ORIGINAL:

(Probably not cross platform, but simple)

Simplifiying the hack in http://www.ginac.de/~kreckel/fileno/ (dvorak answer), and looking at this gcc extension http://gcc.gnu.org/onlinedocs/gcc-4.6.2/libstdc++/api/a00069.html#a59f78806603c619eafcd4537c920f859, I have this solution that works on GCC (4.8 at least) and clang (3.3 at least)

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

and can be used this,

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

Limitations: (comments are welcomed)

  1. I find that it is important to fflush after fprintf printing to std::ofstream, otherwise the "sample2" appears before "sample1" in the example above. I don't know if there is a better workaround for that than using fflush. Notably ofs << flush doesn't help.

  2. Cannot extract FILE* from std::stringstream, I don't even know if it is possible. (see below for an update).

  3. I still don't know how to extract C's stderr from std::cerr etc., for example to use in fprintf(stderr, "sample"), in an hypothetical code like this fprintf(cfile(std::cerr), "sample").

Regarding the last limitation, the only workaround I found is to add these overloads:

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
}

Attempt to handle iostringstream

It is possible to read with fscanf from istream using fmemopen, but that requires a lot of book keeping and updating the input position of the stream after each read, if one wants to combine C-reads and C++-reads. I wasn't able to convert this into a cfile function like above. (Maybe a cfile class that keeps updating after each read is the way to go).

// 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;
}
alfC
  • 10,293
  • 4
  • 42
  • 88
  • This is great! I recently had to do some tcsetattr() work on something that came in as an ofstream and your writeup really helped me out. – Paul Mikesell Dec 11 '13 at 22:53
  • Thank you. I used this approach for file locking, as summarized here: https://stackoverflow.com/a/53689083/5899976 – Droid Coder Dec 09 '18 at 03:28
  • @DroidCoder, interesting that there is no mechanism to replace flock in fstreams. I wonder if that is because it is not a very portable (across filesystem) feature in the first place. – alfC Dec 09 '18 at 03:49
  • 3
    Agreed. Reading the documentation of Boost.Interprocess, it seems there are a number of platform-specific behaviors that would make it annoying to standardize. Still, its absence leaves a pretty large hole, so I feel it's worth the effort to try and get the platform folks to support a common subset of behaviors that can lay the groundwork for support in a future C++ standard. – Droid Coder Dec 09 '18 at 22:59
4

Well, you can get the file descriptor - I forget whether the method is fd() or getfd(). The implementations I've used provide such methods, but the language standard doesn't require them, I believe - the standard shouldn't care whether your platform uses fd's for files.

From that, you can use fdopen(fd, mode) to get a FILE*.

However, I think that the mechanisms the standard requires for synching STDIN/cin, STDOUT/cout and STDERR/cerr don't have to be visible to you. So if you're using both the fstream and FILE*, buffering may mess you up.

Also, if either the fstream OR the FILE closes, they'll probably close the underlying fd, so you need to make sure you flush BOTH before closing EITHER.

Mike G.
  • 1,650
  • 11
  • 17
  • Sorry, it's going to depend on which compiler/library you're using. For libstdc++, it's fd(), it looks like: https://gcc.gnu.org/onlinedocs/libstdc++/manual/ext_io.html – Mike G. May 21 '14 at 23:31
4

In a single-threaded POSIX application you can easily get the fd number in a portable way:

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

This method breaks in a multi-threaded application if this code races with other threads opening file descriptors.

Maxim Egorushkin
  • 119,842
  • 14
  • 147
  • 239
3

yet another way to do this in 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

Usage, for example:

#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;
}
Alexis Wilke
  • 15,168
  • 8
  • 60
  • 116
Jettatura
  • 484
  • 2
  • 10
  • 2
    Excellent, I would complement it with `template FILE* cfile(Stream& s){ return STDIOAdapter::yield(&s); }`. It works with `std::cout`, `std::cerr` and mixing `fprintf` and C++ code. I would recommend using this. – alfC May 07 '16 at 22:03
  • 1
    I am commenting again to confirm that this works with `std::ostringstream`. – alfC Apr 13 '17 at 10:03
  • does it make sense to have `seek` function as well. `int cookieSeek(void* cookie, ssize_t* off, int way){ static_cast(cookie)->seekg(*off, static_cast(cookie)->end); return way; }` ? (I am not sure if I am using `way` correctly). – alfC Apr 13 '17 at 22:43
  • I don't understand what's going on here. What's this business with cookies? – einpoklum Jun 27 '19 at 08:28
  • @Jettatura: So, it's non-standard. – einpoklum Jun 27 '19 at 20:31
2

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.

yanpas
  • 1,931
  • 1
  • 15
  • 23