Heliodor 2019/12/15 18:04

WideCharToMultiByteの罠

Visual Studio でワイド文字 → UTF8 な変換をしたい時にはこれまでWideCharToMultiByte を使っていたわけですが、これは当然 Windows 専用な訳です。

そして、C++標準の変換方法がやっと用意された (std::wstring_convert) かと思ったら、C++17で非推奨になってしまいました。

なので結局 WideCharToMultiByte を使ってワイド文字から UTF8 にすることになったのですが、ここで変なハマり方をしました。

自分のPCでは普通に動くのに、他のPCでは落ちるんですよ。


……なぜ??
(でもこれはワリと良くあること)


良く調べてみると、Windows10 のPCでは正常動作しますが、Windows7 と Windows8 なマシンでは落ちます。


なんでやねん!
(これはさすがに珍しい)


ログを取ってみたところ、ディレクトリを再帰検索する時に無限再帰になり、スタックオーバーフローで停止してるんですね。
それはまずいので、万が一のために再帰深さが一定以上になったら強○停止するようにしたんですが、そもそもなぜ無限再帰になったのかって話ですよ。

具体的にどのディレクトリを調べているときに落ちるかログを取って見てみたのですが、おかしいことに検索対象のディレクトリ名が常に空文字列 "" になっているのです。
空文字列を指定するということは、カレントディレクトリを指定するのと同じです。
どんなに再帰しても常に出発点がカレントディレクトリになるので、決して終わりまでたどり着きません。


で、調べてみたら文字コードの変換に失敗していた事が原因でした。
具体的には WideCharToMultiByte で wide → UTF8 をやっているところで、

int length = WideCharToMultiByte(CP_UTF8, 0, wide_string, -1, NULL, 0, "?", &used_default_char);

のようにしてたんです。ところがマイクロソフトの説明をよく見るとですね、

https://docs.microsoft.com/en-us/windows/desktop/api/stringapiset/nf-stringapiset-widechartomultibyte
> CP_UTF8
> UTF-8. With this value set, lpDefaultChar and lpUsedDefaultChar must be set to NULL.

て書いてあったんです。CP_UTF8 を使う時は lpDefaultChar と lpUsedDefaultChar を NULL にしておけよ、と。
つまり、CP_UTF8 を使っているのに、余計な引数を指定したことで関数が失敗し、文字変換が行われず、ディレクトリ名が全て空文字列 "" になってしまっていたわけです。


ちなみに同様のことが MultiByteToWideChar にも当てはまります。この関数で CP_UTF8 を指定した場合、フラグは 0 または MB_ERR_INVALID_CHARS にしないといけません。それ以外だとエラーになります。

https://docs.microsoft.com/en-us/windows/desktop/api/stringapiset/nf-stringapiset-multibytetowidechar
> For UTF-8 or code page 54936 (GB18030, starting with Windows Vista),
> dwFlags must be set to either 0 or MB_ERR_INVALID_CHARS. Otherwise,
> the function fails with ERROR_INVALID_FLAGS.

※余談ですが、APIのヘルプを見る時は日本語ではなく英語版を読むことをお勧めします。
翻訳の段階でかなり情報が落ちていることがあるからです。特に文字列関係の処理で size と書いてあった場合、これがバイト数を意味するのか、文字数を意味するのか、日本語の説明では不明瞭な場合があります。


これ、「変換不能な文字を見つけたときの挙動」を指定しているだけなので、変換不能でない限りはエラーにならず無視してくれればいいのに……とぶつくさ言いながらも、直しておきました。


結局最後まで不可解だったのは OS によって挙動が異なる点です。
Windows7 と Windows8 では確かにエラーになって戻り値が 0 になるんですが、Windows10で実行すると普通に成功するんですよね。
それがハマった主な原因なのですが。

なんかOSによって挙動が違うという話もありますし
http://blog.livedoor.jp/blackwingcat/archives/976097.html



釈然としませんが、まあWindows7 はもうじきサポート終了するし、無償アップデートあるし、いずれは解決するのかなぁ……とも思うのですが、未だにXPでヴィータ大脱出動きますか?なんて問い合わせがあったりします。

昔のゲームを動かすためのスタンドアロン環境でも構築しているんですかね?
同人ゲーマー侮り難し……。

ちなみにヴィータ大脱出はDirectX9入ってればXPでもだいたい動きます

この記事が良かったらチップを贈って支援しましょう!

チップを贈るにはユーザー登録が必要です。チップについてはこちら

記事のタグから探す

月別アーカイブ

限定特典から探す

記事を検索