まえがき
筆者は、高校生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 の様々なことについて取り上げていくつもりですので、お楽しみにお待ち下さい!