まえがき
筆者は、高校生2年生のときに WinUI/C# オープンソースプロジェクトである Files App の開発に参加し始め、学部 1 年生の現在は Native AoT デプロイの有効化に向けてソースコードアーキテクチャの再開発を主導しています。
この記事は、高校 1 年生の時に作ったツール(後述)をもとに執筆した Gist の完全版です。内容に不備がありましたらご容赦ください。
導入
Windows を触っていると、C:\Windows\System32\ja-JP\
のようなフォルダの中に、exe ファイルの名前を冠した .mui
ファイルを見かけたことはありませんか?実はあのファイル、単なる謎の付属物ではなく、多言語対応のために用意された .exe
と同じ形式(PE 形式)のバイナリファイルなのです。
.mui
ファイルは「多言語ユーザーインターフェイス (Multilingual User Interface)」用のリソースファイルで、対応する .exe
本体のメッセージや文字列といったローカライズリソースを提供します。ユーザーが使用する Windows のほとんどの .exe
や .dll
ファイルで使われています。
ところが、古いソースコードや資料で .mui
を使った多言語対応に出会うと、具体的にどう作って、どう読み込ませるのかが分からず困ることが多いはずです。Microsoft Learn は *いつも通り* まったく意味が分かりませんし。
ここでは特に文字列リソースの多言語化に重きをおいて、.mui
ファイルの作成からリンク、読み込みまでの一連の流れを体系的に整理して説明します。
1. .mc
ファイルを作る
.mc
ファイルは .mui
に埋め込む文字列リソースの原型となります。
;// ヘッダー セクション
MessageIdTypedef=DWORD
SeverityNames=(
Success=0x0:STATUS_SEVERITY_SUCCESS
Informational=0x1:STATUS_SEVERITY_INFORMATIONAL
Warning=0x2:STATUS_SEVERITY_WARNING
Error=0x3:STATUS_SEVERITY_ERROR
)
FacilityNames=(
System=0x0:FACILITY_SYSTEM
Runtime=0x2:FACILITY_RUNTIME
Stubs=0x3:FACILITY_STUBS
Io=0x4:FACILITY_IO_ERROR_CODE
)
LanguageNames=(English=0x409:msg0409)
;// 文字列リソース定義
MessageId=0x1
Severity=Error
Facility=Runtime
SymbolicName=MSG_BAD_COMMAND
Language=English
You have chosen an incorrect command.
.
MessageId=7006
SymbolicName=MSG_ATTRIB_PARAMETER_NOT_CORRECT
Language=English
Parameter format not correct -
.
MessageId=7007
SymbolicName=MSG_ATTRIB_NOT_RESETTING_SYS_FILE
Language=English
Not resetting system file - %1
.
一つの .mc
ファイルに言語はいくつか追加できますが、普通は 1 ファイルにつき 1 言語ですのでこのまま進みます。注意すべき点はメッセージの最後にドットを挿入することです。サンプルファイルも参照してください。
2. mc.exe
でコンパイル
.mc
ファイルが出来上がれば、mc.exe
でコンパイルしていきます。
mc.exe ./en-US/myapp.mc
すると
myapp.h
myapp.rc
msg0409.bin
が生成されます。.bin
ファイルの名前は .mc
ファイルの LanguageNames
の値に依存します。
3. rc.exe
で中間リソースコンパイル
次に作るものは .mui
にリンクさせるものリスト(.rcconfig
)です。今回は文字列リソースのみを .mui
にリンクさせるので、それを記述していきます。
<?xml version="1.0" encoding="utf-8"?>
<localization>
<resources>
<win32Resources fileType="Application">
<localizedResources>
<resourceType typeNameId="#11"/> <!-- MESSAGETABLE -->
</localizedResources>
</win32Resources>
</resources>
</localization>
そして、myapp.rc
と myapp.rcconfig
で rc.exe
を使って
rc.exe /fo common.res /fm msg0409.res /q myapp.rcconfig myapp.rc
すると
common.res
msg0409.res
ができます。
4. バージョン情報等のリソースもコンパイルする(任意)
バージョン情報等のリソースも .mui
にリンクさせたい場合は、Visual Studio で version.rc
と resource.h
を自動生成させて、version.rc
にバージョンを書き終えたら、
rc.exe version.rc
すると
version.res
ができます。resource.h
は version.rc
内ですでにインクルードされているので指定する必要はありません。
5. common.res
を .exe
にリンクする
.mui
が存在することを .exe
に知らせる必要があるにで、リンクします。
link.exe myapp.exe common.res
Resource Hacker で .exe
の中身を見るとリソースツリーにMUI
ができているのがわかります。
6. msg0409.res
を .mui
に変換する
ここで .mui
を作成します。
link.exe /out:myapp.exe.mui /nodefaultlib /noentry /dynamicbase /nxcompat /dll msg0409.res version.res
ファイルフォーマットは .exe
と同じ PE
ですが、エントリーポイントなしを指定しているので、実行できないタイプです。version.res
は作ったのならこれもリンクします。
7. ほかの言語のリソースを作成(これは省略可)
同じように同じ手順で作成しますが、途中で common.res
が再生成されているので、
rc.exe /fm msg0411.res /q myapp.rcconfig msg0411.rc
で OK です。
8. muirct.exe
を使ってチェックサムを結ぶ
これで最後大詰めです。一番大事な作業、チェックサムを結ぶことです。.mui
は物理ファイルパスではなく、チェックサムでメッセージテーブルを呼び出します。
muirct.exe -c myapp.exe -e en-US\myapp.exe.mui
これで外部メッセージテーブル呼び出しが可能になりました。今回は外部に置きましたが、もちろん内部でも可能です。途中リンクコマンドで msg0409.res
から .mui
ファイルを生成するようにしていますが、common.res
のように .exe
にもリンクできます。
結果のテスト
メッセージテーブルを呼び出します。
BOOL DisplayFormattedMessage(DWORD dwMsgId, DWORD nArgs, ...)
{
va_list args = NULL;
va_start(args, nArgs);
LPWSTR pwszMessage = NULL;
if (!FormatMessageW(FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwMsgId, 0, &pwszMessage, 10, &args))
return FALSE;
va_end(args);
return TRUE;
}
重要なのは FORMAT_MESSAGE_FROM_HMODULE
をフラグに指定しているところです。これでチェックサムが結ばれたメッセージテーブルを探しに行きます。もしなければエラーで終了します。
まとめ
信じられないくらいすごくめんどくさいです。cmake
で自動化させるしかないですね。
ちなみに C を勉強したての頃、コマンドプロンプトを自分で一から作ってみたいと思い、既存の .exe
や .dll
(例えば attrib.exe
)からメッセージテーブルを抽出し .mc
ファイルを作成するツールを作りました。参考までにご覧ください。
今回は時代遅れな .mui
の作り方と .exe
からの呼び出し方について取り上げました。Windows の様々なことについて取り上げていくつもりですので、お楽しみにお待ち下さい!