Skip to content

Instantly share code, notes, and snippets.

@Alexhuszagh
Created October 10, 2016 18:11
Show Gist options
  • Save Alexhuszagh/3cd3aefac3e4a43724723edf2a01bc53 to your computer and use it in GitHub Desktop.
Save Alexhuszagh/3cd3aefac3e4a43724723edf2a01bc53 to your computer and use it in GitHub Desktop.
Filestream with support for Windows wide API file names.
/** \brief FStream-like handle for Wide API support.
*
* Although the C standard makes no guarantees a C FILE descriptor will
* be used internally, both GCC and Clang do so, with GCC providing
* an extension to create a basic_filebuf from an existing C FILE or
* POSIX file descriptor[1]. As basic_fstream is merely a wrapper around
* basic_iostream with a basic_filebuf, this provides a full API
* that should have native performance.
*
* By using a GNU extension to solve a GNU-specific problem, or std::fstream
* elsewhere, this code should work with MSVC and MinGW on Windows, and
* with any compiler elsewhere.
*
* 1. http://cs.brown.edu/~jwicks/libstdc++/html_user/stdio__filebuf_8h-source.html
* 2. https://github.com/llvm-mirror/libcxx/blob/master/include/fstream#L132
*
* \license MIT or BDS-like, same as the LLVM source code from which
* it is derived.
*/
#include <fstream>
#include <sstream>
#include <string>
#include <iostream>
#ifdef __MINGW32__
# include <ext/stdio_filebuf.h>
#endif
#ifdef _WIN32
# include <windows.h>
#endif
#ifdef __MINGW32__
namespace detail
{
/** \brief Convert C++ mode to C mode.
*/
const char *cMode(const std::ios_base::openmode mode)
{
using namespace std;
switch (mode & ~ios_base::ate) {
case ios_base::out:
case ios_base::out | ios_base::trunc:
return "w";
case ios_base::out | ios_base::app:
case ios_base::app:
return "a";
case ios_base::in:
return "r";
case ios_base::in | ios_base::out:
return "r+";
case ios_base::in | ios_base::out | ios_base::trunc:
return "w+";
case ios_base::in | ios_base::out | ios_base::app:
case ios_base::in | ios_base::app:
return "a+";
case ios_base::out | ios_base::binary:
case ios_base::out | ios_base::trunc | ios_base::binary:
return "wb";
case ios_base::out | ios_base::app | ios_base::binary:
case ios_base::app | ios_base::binary:
return "ab";
case ios_base::in | ios_base::binary:
return "rb";
case ios_base::in | ios_base::out | ios_base::binary:
return "r+b";
case ios_base::in | ios_base::out | ios_base::trunc | ios_base::binary:
return "w+b";
case ios_base::in | ios_base::out | ios_base::app | ios_base::binary:
case ios_base::in | ios_base::app | ios_base::binary:
return "a+b";
default:
return nullptr;
}
}
/** \brief Convert narrow, UTF-8 code-page to wide UTF-16.
*/
inline wchar_t * toWide(const char *narrow)
{
int size = MultiByteToWideChar(CP_UTF8, 0, narrow, -1, nullptr, 0 );
wchar_t *wide = new wchar_t[size];
MultiByteToWideChar(CP_UTF8, 0, narrow, -1, wide, size);
return wide;
}
/** \brief Get narrow, C FILE pointer.
*/
FILE *getFile(const std::string &narrow,
const std::ios_base::openmode mode)
{
const char *modeString = cMode(mode);
if (modeString != nullptr) {
return fopen(narrow.data(), modeString);
}
return nullptr;
}
/** \brief Get wide, C FILE pointer.
*/
FILE *getFile(const std::wstring &wide,
const std::ios_base::openmode mode)
{
const char *cModeString = cMode(mode);
if (cModeString != nullptr) {
wchar_t *modeString = toWide(cModeString);
FILE *file = _wfopen(wide.data(), modeString);
delete[] modeString;
return file;
}
}
} /* detail */
/** \brief Stream supporting narrow and wide APIs on Windows.
*
* Only used if MinGW (which does not overload wchar_t for std::fstream)
* and Windows is used.
*
* \note ifstream and ofstream are not shown, for brevity, but are
* merely replacing the base class with istream and ostream, respectively,
* and the default openmode for the constructor.
*/
class FStream: public std::iostream
{
FILE *file = nullptr;
__gnu_cxx::stdio_filebuf<char> buffer;
typedef char char_type;
typedef std::char_traits<char> traits_type;
typedef typename traits_type::int_type int_type;
typedef typename traits_type::pos_type pos_type;
typedef typename traits_type::off_type off_type;
public:
FStream();
FStream(const std::string &narrow,
const std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out);
FStream(const std::wstring &wide,
const std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out);
virtual ~FStream();
__gnu_cxx::stdio_filebuf<char>* rdbuf() const;
bool is_open() const;
void close();
};
/** \brief Null constructor.
*/
FStream::FStream()
{}
/** \brief Narrow constructor.
*/
FStream::FStream(const std::string &narrow,
const std::ios_base::openmode mode):
file(detail::getFile(narrow, mode)),
buffer(file, mode),
std::iostream(&buffer)
{}
/** \brief Wide constructor.
*/
FStream::FStream(const std::wstring &wide,
const std::ios_base::openmode mode):
file(detail::getFile(wide, mode)),
buffer(file, mode),
std::iostream(&buffer)
{}
/** \Brief Virtual dtor.
*/
FStream::~FStream()
{
close();
}
/** \brief Access underlying buffer.
*/
__gnu_cxx::stdio_filebuf<char> * FStream::rdbuf() const
{
return const_cast<__gnu_cxx::stdio_filebuf<char>*>(&buffer);
}
/** \brief Check if stream is open.
*/
bool FStream::is_open() const
{
return buffer.is_open();
}
/** \Brief Ensure file handle and buffer closed.
*/
void FStream::close()
{
if (file != nullptr) {
buffer.close();
fclose(file);
}
file = nullptr;
}
#else
typedef std::fstream FStream;
#endif
int main(int argc, char *argv[])
{
// Hangeul, UTF-16LE or UTF-8, "한국어"
#ifdef _WIN32
std::wstring name = {0xd55c, 0xad6d, 0xc5b4};
std::wcout << name << std::endl;
#else
std::string name = {-19, -107, -100, -22, -75, -83, -20, -106, -76};
std::cout << name << std::endl;
#endif
// write bytes
FStream output(name, std::ios_base::out);
output << "This\n\nis a\n\n\n\nsample\n\nfile.\n";
output.close();
// read bytes
std::string line;
FStream input(name, std::ios_base::in);
while (std::getline(input, line)) {
std::cout << line << std::endl;
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment