If compiled with mingw, use glibc++ extension stdio_filebuf
to open the file by FILE*
instead of filename.
In other condition, we can use boost::fstream.
If compiled with mingw, use glibc++ extension stdio_filebuf
to open the file by FILE*
instead of filename.
In other condition, we can use boost::fstream.
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.
but there’s override:
0#include <fstream>
1int main(int argc, char* argv[])
2{
3 std::fstream tr(L"hk");
4 return 0;
5}
compiles fine.
( msys2 )
0c++ json_c.cpp -lws2_32 -static-libgcc -static-libstdc++ -static-libgcc -static-libstdc++ -Wl,-Bstatic -lstdc++ -lpthread
.h
files are nothing more then msvc copy-paste. it may not work but linux edition has same headers .
i’ve used it in msvc with np. this applies to constructor only. this stuff isn’t avaible for
.open()
and others. last time i’ve seen there smth own-written was windows edition of watcom c .
I don’t think I understand how this PR works (does it depend on #13866?), or why reimplementing fstream classes is a good solution compared to alternatives. If this is a bug in boost, wouldn’t it be better to fix the bug upstream and maybe patch it locally? Or could we drop the use of boost here and instead use std::ifstream
and std::ofstream
?
It would be helpful to have a clear description of the bug and possible workarounds.
41@@ -38,6 +42,41 @@ namespace fsbridge {
42 void* hFile = (void*)-1; // INVALID_HANDLE_VALUE
43 #endif
44 };
45+
46+
47+#if defined WIN32 && defined __GNUC__
Should this be checking for __GLIBCXX__
instead of __GNUC__
? It looks like __gnu_cxx::stdio_filebuf
might be present in libstdc++ but not libc++ (https://stackoverflow.com/questions/22624503/how-to-get-a-file-descriptor-from-a-stdbasic-ios-for-clang-on-os-x).
Will different workarounds be needed for MSVC and clang?
boost::filesystem::i/ofstream
calls std::filebuf
internally. So if std::filebuf(wchar_t*)
exist, boost will call that function (MSVC). Otherwise it will call std::filebuf(char*)
which can lead to encoding issue. After c++17, we can use std::i/ofstream(std::filesystem::path)
. But before then, we can’t.
If building with libstdc++, we can use its extension stdio_filebuf by passing FILE *
.
Will different workarounds be needed for MSVC and clang?
I don’t know about clang for Windows. But for MSVC, we can use boost::filesystem::i/ofstream
.
does it depend on #13866?
Without #13866, it would work. But to solve the encoding issue, it requires #13866.
203+ if (file != nullptr) {
204+ filebuf.close();
205+ fclose(file);
206+ }
207+ file = nullptr;
208+}
I think it would be good to add a static assert here to detect if this problem happens with other libraries or versions of libraries:
0#if defined WIN32 && defined __GLIBCXX__
1...
2#elif WIN32
3static_assert(sizeof(*fs::path().BOOST_FILESYSTEM_C_STR) == sizeof(wchar_t),
4 "Warning: This build is using boost::filesystem ofstream and ifstream "
5 "implementations which will fail to open paths containing multibyte "
6 "characters. You should delete this static_assert to ignore this warning, "
7 "or switch to a different C++ standard library like the Microsoft C++ "
8 "Standard Library (where boost uses non-standard extensions to construct "
9 "stream objects with wide filenames), or the GNU libstdc++ library (where "
10 "a more complicated workaround has been implemented above).");
11#endif
41@@ -38,6 +42,41 @@ namespace fsbridge {
42 void* hFile = (void*)-1; // INVALID_HANDLE_VALUE
43 #endif
44 };
45+
46+
47+#if defined WIN32 && defined __GLIBCXX__
Can we add a comment to describe the bug and workaround? Suggestion:
0// GNU libstdc++ specific workaround for opening UTF-8 paths on Windows.
1//
2// On Windows, it is only possible to reliably access multibyte file paths through
3// `wchar_t` APIs, not `char` APIs. But because the C++ standard doesn't require
4// ifstream/ofstream `wchar_t` constructors, and the GNU library doesn't
5// provide them (in contrast to the Microsoft C++ library, see
6// https://stackoverflow.com/questions/821873/how-to-open-an-stdfstream-ofstream-or-ifstream-with-a-unicode-filename/822032#822032),
7// Boost is forced to fall back to `char` APIs which may not work properly.
8//
9// Work around this issue by creating stream objects with `_wfopen` in
10// combination with `__gnu_cxx::stdio_filebuf`. This workaround can be removed
11// with an upgrade to C++17, where streams can be constructed directly from
12// `std::filesystem::path` objects.
96+#if defined WIN32 && defined __GLIBCXX__
97+
98+/**
99+* reference: https://github.com/gcc-mirror/gcc/blob/571ee70a6d4226145f77b885937a8eda678e0fb2/libstdc%2B%2B-v3/include/std/fstream#L283
100+*
101+* +---------------------------------------------------------+
I think I would drop this table but keep the URL link, since the table just duplicates information provided by the code below, and seems actually less readable than the code.
For reference, https://en.cppreference.com/w/cpp/io/basic_filebuf/open also has a similar table.
162+}
163+
164+void ifstream::open(const fs::path& p, std::ios_base::openmode mode)
165+{
166+ close();
167+ file = fopen(p, openmodeToStr(mode).c_str());
I don’t think it’s obvious that this is intended to call the fsbridge::fopen
function above rather than ::fopen
. Would suggesting adding a comment here or writing the namespace.
Could also do the same in ofstream::open
below.
166+ close();
167+ file = fopen(p, openmodeToStr(mode).c_str());
168+ if (file == nullptr) {
169+ return;
170+ }
171+ filebuf = std::move(__gnu_cxx::stdio_filebuf<char>(file, mode));
std::move
can be dropped here. Argument is already an rvalue so it has no effect.
Same applies in ofstream::open
below.
68+ void open(const fs::path& p, std::ios_base::openmode mode = std::ios_base::out);
69+ bool is_open() { return filebuf.is_open(); }
70+ void close();
71+
72+ private:
73+ __gnu_cxx::stdio_filebuf<char> filebuf;
m_
to match recommended style?
169+ return;
170+ }
171+ filebuf = std::move(__gnu_cxx::stdio_filebuf<char>(file, mode));
172+ rdbuf(&filebuf);
173+ if (mode & std::ios_base::ate) {
174+ seekg(0, std::ios_base::end);
49+// On Windows, it is only possible to reliably access multibyte file paths through
50+// `wchar_t` APIs, not `char` APIs. But because the C++ standard doesn't require
51+// ifstream/ofstream `wchar_t` constructors, and the GNU library doesn't
52+// provide them (in contrast to the Microsoft C++ library, see
53+// https://stackoverflow.com/questions/821873/how-to-open-an-stdfstream-ofstream-or-ifstream-with-a-unicode-filename/822032#822032),
54+// Boost is forced to fall back to `char` APIs which may not work properly.
In commit “utils: Add fsbridge fstream function wrapper” (0548f48f19d33cb57b8279d7a647e91d375a916e)
I know this is my own comment, maybe replace “APIs” with “constructors” on this to make clearer this is referring to the same constructors previously mentioned above.
7+
8+#include <boost/test/unit_test.hpp>
9+
10+BOOST_FIXTURE_TEST_SUITE(fs_tests, BasicTestingSetup)
11+
12+BOOST_AUTO_TEST_CASE(fsbridge_fstream)
static_assert
, m_
prefixes, comments, and new tests. Thanks especially for the new tests.
41@@ -39,6 +42,54 @@ namespace fsbridge {
42 };
43
44 std::string get_filesystem_error_message(const fs::filesystem_error& e);
45+
46+ // GNU libstdc++ specific workaround for opening UTF-8 paths on Windows.
47+ //
48+ // On Windows, it is only possible to reliably access multibyte file paths through
49+ // `wchar_t` constructors, not `char` APIs. But because the C++ standard doesn't
60+#if defined WIN32 && defined __GLIBCXX__
61+ class ifstream : public std::istream
62+ {
63+ public:
64+ ifstream() = default;
65+ ifstream(const fs::path& p, std::ios_base::openmode mode = std::ios_base::in) { open(p, mode); }
02018-09-22 21:43:16 cpplint(pr=13878): src/fs.h:65: Constructors callable with one argument should be marked explicit. [runtime/explicit] [5]
74+ };
75+ class ofstream : public std::ostream
76+ {
77+ public:
78+ ofstream() = default;
79+ ofstream(const fs::path& p, std::ios_base::openmode mode = std::ios_base::out) { open(p, mode); }
02018-09-22 21:43:16 cpplint(pr=13878): src/fs.h:79: Constructors callable with one argument should be marked explicit. [runtime/explicit] [5]
11@@ -12,4 +12,9 @@
12 Outputs="$(MSBuildThisFileDirectory)..\src\config\bitcoin-config.h">
13 <Copy SourceFiles="$(MSBuildThisFileDirectory)bitcoin_config.h" DestinationFiles="$(MSBuildThisFileDirectory)..\src\config\bitcoin-config.h" />
14 </Target>
15+ <ItemDefinitionGroup>
16+ <ClCompile>
17+ <AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>
18+ </ClCompile>
19+ </ItemDefinitionGroup>
20 </Project>