2021年08月27日

LuaAppMaker: Windows の日本語パス名で大苦戦

 LuaAppMaker で、Windows の日本語パス名で大苦戦した。以下、ざっくりと報告しておきます。

 まず、日本語パス上にある dll ファイルを require でロードできない。前に fopen() で引っかかったやつと同じだな。調べてみると、lib_package.c の中に、こういうコードがある。

static void *ll_load(lua_State *L, const char *path, int gl)
{
  HINSTANCE lib = LoadLibraryExA(path, NULL, 0);
  if (lib == NULL) pusherror(L);
  UNUSED(gl);
  return lib;
}

 はい、LoadLibraryExA() を呼んでますね。Lua の中では日本語文字はすべて UTF-8 だから、このシステムコールも UTF-8 エンコーディングのパス名で呼んでしまう。Windows のシステムコールは UTF-8 を受け付けないので(少なくとも古い Windows はそう)、当然失敗する。

 これの修正はかなり手こずった。fopen() の時は、「UTF-8 パスを wchar_t に変換して、_wfopen() を呼ぶ」関数を、同じ「fopen()」という名前で定義して、静的にリンクすることで切り抜けた。今回は、同じやり方では修正できない。システムコールは WINBASEAPI というキーワードをつけて宣言されている。これは __declspec(dllexport) に読み替えられるので、dll からしか読み込めない。このため、差し替えるとしたら、LoadLibraryExA() を含む dll を作成して、製品に同梱しないといけない。配布するファイルが増えるのは嬉しくない。そこで、#include <Windows.h> の「後」に、#define LoadLibraryExA LoadLibraryExA_Patch のようにマクロ定義を挿入する。こうすると、ヘッダ中の LoadLibraryExA は元のままだが、ソースコード中の LoadLibraryExALoadLibraryExA_Patch に置き換わる。そこで、LoadLibraryExA_Patch() 関数を自前で書いてリンクすればよい。

 実際には、インクルードパス中に、下の内容で Windows.h というファイルを作成した。LoadLibraryEx の他にも、「A」サフィックスのついたシステムコールをいくつか使っていたので、すべて差し替えるようにした。#include_next は、インクルードサーチパス中の「次のディレクトリ」からヘッダファイルを探索する。この機能を使えば、ソースコードには一切の変更なしで、ヘッダだけを差し替えることができる。

#include_next "Windows.h"

#define LoadLibraryExA LoadLibraryExA_Patch
#define FormatMessageA FormatMessageA_Patch
#define GetModuleFileNameA GetModuleFileNameA_Patch
#define GetModuleHandleA GetModuleHandleA_Patch
#define GetModuleHandleExA GetModuleHandleExA_Patch

#ifdef __cplusplus
extern "C" {
#endif

extern HMODULE LoadLibraryExA_Patch(
  LPCSTR lpLibFileName,
  HANDLE hFile,
  DWORD  dwFlags
  );

extern HMODULE GetModuleHandleA_Patch(
  LPCSTR lpModuleName
  );

extern DWORD GetModuleFileNameA_Patch(
  HMODULE hModule,
  LPSTR   lpFilename,
  DWORD   nSize
  );

extern DWORD FormatMessageA_Patch(
  DWORD   dwFlags,
  LPCVOID lpSource,
  DWORD   dwMessageId,
  DWORD   dwLanguageId,
  LPSTR   lpBuffer,
  DWORD   nSize,
  va_list *Arguments
  );

extern BOOL GetModuleHandleExA_Patch(
  DWORD   dwFlags,
  LPCSTR  lpModuleName,
  HMODULE *phModule
  );

#ifdef __cplusplus
}
#endif

 LoadLibraryExA_Patch() は、次のように実装した。uf8towchar() は、名前の通り UTF-8 から wchar_t に変換する関数。

HMODULE LoadLibraryExA_Patch(
  LPCSTR lpLibFileName,
  HANDLE hFile,
  DWORD  dwFlags
)
{
  wchar_t *wpath, *wmode;
  HMODULE retval;
  if (utf8towchar(lpLibFileName, &wpath) != 0) {
    return NULL;
  }
  retval = LoadLibraryExW(wpath, hFile, dwFlags);
  free(wpath);
  return retval;
}

 なんとか切り抜けました。そしてもう一つ難所があったんだけど、長くなったので後日書きます。

タグ:Lua Mac wxWidgets
Posted at 2021年08月27日 21:17:21
email.png