wxWidgets アプリを Lua で記述する wxLuaApp。wxWidgets 3.0 系列でビルドできるようにするパッチをPaul Kulchenkoさんに送ったら、採用してもらった。これを機会に、こちらのプロジェクトの構成も少し整理して、開発を続けている。
Mac でだいたい動くようになったので、Windows でもビルドしてみた。動くには動くんだけど、日本語のファイル名が通らない。調べてみると、Windows の fopen
関数は、マルチバイト文字のファイル名に対応していない。文字列を wchar_t
の配列に変換して、_wfopen
関数を使うように指定されている。
Fopen
関数は、filename
によって指定されたファイルを開きます。 既定では、ナローファイル名の文字列は、ANSI コードページ (CP_ACP
) を使用して解釈されます。 ..._wfopen
は、fopen
のワイド文字バージョンです。_wfopen
の引数はワイド文字列です。 それ以外の場合、_wfopen
とfopen
は同じように動作します。fopen, _wfopen: Visual Studio 2019 の関数リファレンス
対策は、原理的にはそれほど難しくない。自前の fopen
関数を書いて、UTF-8 で書かれたファイル名をマルチバイト文字列に置き換えて、_wfopen
を呼べばいい。コーディングも難しくない。
/* win_fopen.c */
/* 2019.8.31. Toshi Nagata */
/* Public Domain */
#if _WIN32 || _WIN64
#include <Windows.h>
#include <stdio.h>
static int utf8towchar(const char *utf8, wchar_t **outbuf)
{
size_t buflen = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, (void *)0, 0);
wchar_t *buf = calloc(buflen, sizeof(wchar_t));
if (MultiByteToWideChar(CP_UTF8, 0, utf8, -1, buf, buflen) == 0) {
free(buf);
return -1;
}
*outbuf = buf;
return 0;
}
FILE *
fopen(const char *path, const char *mode)
{
wchar_t *wpath, *wmode;
FILE *f;
if (utf8towchar(path, &wpath) != 0 || utf8towchar(mode, &wmode) != 0)
return NULL;
f = _wfopen(wpath, wmode);
free(wpath);
free(wmode);
return f;
}
#endif
大苦戦したのは、これをどうやって組み込むか。wxLuaApp は、LuaJIT 2.0.5 の動的ライブラリとリンクしている。LuaJIT の中から fopen
を呼び出しているところがいくつもあるので、これをどうやって自前の関数で置き換えるかが、なかなかわからなかった。試行錯誤の結果、次のやり方でうまくいくことがわかった。
win_fopen.c
をコンパイルしてwin_fopen.o
を作っておく。- LuaJIT の動的ライブラリ
lua51.dll
のリンクの時にwin_fopen.o
をオブジェクトファイルとして追加する。LuaJIT のmake
を走らせる時に、TARGET_SHLDFLAGS="path/to/winfopen.o"
を引数として指定すればよい。 - wxLuaApp をビルドする時にも、
win_fopen.o
をリンクする。
DLL とアプリ本体の両方を win_fopen.o
とリンクするのがポイントだった。win_fopen.o
のコードがダブってしまうが、これは止むを得ない。(wxLuaApp 本体が lua51.dll
の中の fopen
を使ってくれればいいのだが、どうやっても msvcrt.dll
の fopen
が優先されてしまう。リンクの順序を変えてもうまくいかなかった。)
なお、DLL やアプリ本体の .exe
ファイルなどが、どの動的ライブラリのどの関数を呼び出しているかは、objdump
を使うとわかる。-p
オプションを使うと、下のように動的ライブラリごとに使用する関数が出力される。
$ x86_64-w64-mingw32-objdump -p lua51.dll | less
...
DLL Name: msvcrt.dll
vma: Hint/Ord Member-Name Bound-To
679f8 59 __DestructExceptionObject
67a14 80 __doserrno
67a22 84 __iob_func
67a30 95 __pioinfo
67a3c 126 _amsg_exit
...
67ce0 1235 tmpfile
67cea 1237 tmpnam
67cf4 1243 ungetc
67cfe 1245 vfprintf
msvcrt.dll
由来の関数の中に fopen
があると、日本語ファイル名が正しく開けない。正しく対策すると、msvcrt.dll
の関数リストから fopen
が消える。