MUI

Windows Vista以降、Windowsのローカライズの方法に多少の修正が加わって、いわゆる、今までの各国語版というのがなくなった。パッケージとしては残ってはいるものの、システムとしては、ニュートラルなWindowsに対して、言語パックを足せば、何語版にでもなるという構造になった。

そのため、例えば、日本語環境だと、calc.exeに対して、ja-JP\calc.exe.muiというファイルが関連付けられてインストールされるようになった。さらに、英語のGUIが欲しければ、en-US\calc.exe.muiを足せば良いという理屈。

今までも、他言語対応のアプリケーションだったら、

application.exe
lang-0409.dll
lang-0411.dll

みたいな構造でリソースのDLLを足せば良いみたいな形のものはいくらでもあったので、0409みたいなLANGIDの部分が、en-USといった言語タグに取って代わられただけで別に特別に新しい仕組みにも見えないのだけれども、一方で、この新しい仕組みは、Vista以降でサポートされた新しいMUI(ムイって読んで良いらしい)という仕組みに基づいている。

要は、今まではこのリソースDLLを読み込むコードを全部、自分で書かないといけなかったけど、Vista以降では、OS側のフレームワークである程度肩代わりしてくれるよということらしい。

具体的にコードを書くとすれば、エラー処理まで省けば、コードは次の量で済む:

#include <windows.h>
#include <tchar.h>

int APIENTRY _tWinMain(
  HINSTANCE hInstance,
  HINSTANCE hPrevInstance,
  LPTSTR lpCmdLine,
  int nCmdShow)
{
  TCHAR buf[512];
  LoadString(hInstance, 101, buf, 512);
  MessageBox(NULL, buf, _T("muitest"), MB_OK | MB_SYSTEMMODAL);
}

WinMainにくるhInstanceを直接使っている様に見えるけど、これで正解。このhInstanceは、ロードされたMUI(リソース)のDLLとも裏で連携できるようになっていて、あたかも、exeに直接リンクされたリソースをロードしているような感覚でリソースを扱える。

また、一応、下位互換用のライブラリも提供されるようで、muiload.libというライブラリを使って、MUILoadLibrary/MUIFreeLibraryを使えば・・・っていう話もあるけど、FreeLibraryじゃなくて、MUIFreeLibraryを使わないといけないってところにかなりの悲しみを感じます。これじゃ、使いにくいんだよ。Microsoftさん・・・。
結局、下位互換用には従来のコードを使い回した方が良さそう。

MUIのメリット

でも、別に、今までも、

hoge.exe
lang-0409.dll
lang-0411.dll

みたいな構造で事は足りてたのに、なんでこの新しいMUIっていう構造を使わないといけないの?って話になるんだけど、確かにその恩恵はちょっと考えたぐらいでは分からない。

なので、ここでは具体的に僕が前から思っていた事しかかけないんだけど、その一つは、ファイル関連付けのローカライズ。これは、レジストリに書き込む物なので、例えば、

HKEY_CLASSES_ROOT\.hoge
@="hoge.document"

HKEY_CLASSES_ROOT\hoge.document
@="ホゲ文書"

みたいにしてしまうと、.hogeの説明が常に日本語になってしまう。ローカライズしたいなぁと思うと、固定値は不味い。リソースDLLから読みたい。

なので、これを回避するために、FriendlyTypeNameというものが導入された。これだと、

HKEY_CLASSES_ROOT\.hoge
@="hoge.document"

HKEY_CLASSES_ROOT\hoge.document
@="ホゲ文書"
"FriendlyTypeName"="@C:\Program Files\....\hoge-0411.dll,-101"

(hoge-0411.dllのID=101のテキストを設定すると言うこと)ってやりたいなぁ・・・って、これだと、やっぱり日本語になってしまう。あれれれ、解決になってない。

そこで、今回のMUIを使うと、そもそも、EXEにしろ、DLLにしろ、

hoge.exe
en-US/
hoge.exe.mui
ja-JP/
hoge.exe.mui

というレイアウトになって、一応、主体としては、hoge.exeを考える事が出来るようになる。なので、こういうレジストリに対しては、

HKEY_CLASSES_ROOT\.hoge
@="hoge.document"

HKEY_CLASSES_ROOT\hoge.document
@="Hoge Document"
"FriendlyTypeName"="@C:\Program Files\....\hoge.exe,-101"

って書けば良くなる。後は、各言語用にmuiファイル群を作っていけば良い。

hoge.exeに対して、hoge.exe.muiを作る

そして、ここからが本題なんだけど、これが実は著しく面倒な上に難解。
今まで通りにリソースDLLを作って、名前だけ変えて配置という分けにはいかない。
hoge.exeとhoge.exe.muiはディレクトリ構造と名前さえ一致してくれればロードしてくれる・・・っていうわけじゃなくて、チェックサムで結ばれていて、ちゃんと設定しないと、勝手にロードしてくれるようにはなりません。

一応、概要というか、全部、

Resource Utilities - MSDN

Using Multilingual User Interface - MSDN

Getting started with Win32 MUI development

に書いてある。けど、これは分からない。強烈に何がしたいのか分からない。

というか、さっきのMUIのメリットさえ無視できるんだったら、今までのリソースDLLのやり方に執着した方が数百倍楽だし。その辺の読み手の欲求を完全に無視しているとしか思えない文書構成。

なので、ここでは、既に、

hoge.exe
lang-0409.dll
lang-0411.dll

という構造を作れる大本が手元にあることを前提で話をする。この構造の成果物があるということは、普通に考えれば、

lang-0409.rc
lang-0411.rc
resource.h

があるということだ。ここでは、話を簡単にするために、普通は、hoge.exeにリンクされるであろう、アイコンやらバージョンリソースの話は無視する。resource.hは、lang-XXXX.hの付属品だと思ってください。

全体のフローは、

Vista Win32 MUI Application Developmentによると、

f:id:espresso3389:20120309183407p:image

といった感じ。リソース側から"Compiled LN resources"という物をアプリ側にリンクさせてあげるということ。

rcconfigファイル

で、最初にやることは、

lang-0409.rc
lang-0411.rc

から、不純物を取り除くこと。これがわかりにくい。
簡単に言えば、lang-XXXX.rcは、本来、ローカライズされるべき部分と、何となく付随してしまっている物が混ざっている。

例えば、文字列は間違いなく、言語毎にローカライズされなければならないけども、普通は、アイコンとかカーソルぐらいは言語間で共有されても良いかもしれない(宗教上の理由などで国別に変えないといけない場合もあるだろうけど)。

そのために、どの種類のリソースは、言語DLL側に残して、どこの部分は全言語で共有するか(つまり、hoge.exe側に持たせる)という判断をすることになる。

これをやるのがrcconfigファイル。説明は、一応、

Preparing a Resource Configuration File - MSDN

に書かれているが、やはりピントがつかめない文書。天下り的に答えを書くと、下みたいなファイルになる。

<?xml version="1.0" encoding="utf-8"?>
<localization>
  <resources>
    <win32Resources fileType="Application">
      <localizedResources>
        <resourceType typeNameId="#1"/> <!-- CURSOR -->
        <resourceType typeNameId="#2"/> <!-- BITMAP -->
        <resourceType typeNameId="#3"/> <!-- ICON -->
        <resourceType typeNameId="#4"/> <!-- MENU -->
        <resourceType typeNameId="#5"/> <!-- DIALOG -->
        <resourceType typeNameId="#6"/> <!-- STRING -->
        <resourceType typeNameId="#9"/> <!-- ACCELERATORS -->
        <resourceType typeNameId="#10"/> <!-- RCDATA -->
        <resourceType typeNameId="#241"/> <!-- TOOLBAR -->
      </localizedResources>
    </win32Resources>
  </resources>
</localization>

typeNameIdの後ろに書いてあるのは、リソースタイプに割り振られたID。これは、上にあるページよりも、

Resource Types - MSDN

の方が網羅性が高い。しかし、やはり、TOOLBARに該当するIDが書いてなかったりするが、Community Additionsに、親切な人が、TOOLBARのIDを書いてくれている。241らしい。

で、さっき書いたように、ここには、言語DLL側に残したいもののIDを書いていく。つまり、アイコンとか、ビットマップは言語間で共有してもいいやと思えば、#2とか、#3は削除する。

さて、次に、これによってrcファイルを処理する。これには、Windows SDK 6以降のrc.exeが必要らしい。平たく言うと、

C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin
C:\Program Files\Microsoft SDKs\Windows\v7.0A\Bin

あたりにあるrc.exeを使う。VS2010の奴ならいけるんじゃないだろうか(自分のマシンはいろいろ入っていて良く分からない)。一応、rc /?って実行して、オプション一覧に、

C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin>rc /?

Microsoft (R) Windows (R) Resource Compiler Version 6.1.7600.16385
Copyright (C) Microsoft Corporation. All rights reserved.

Usage: rc [options] .RC input file
Switches:
/r Emit .RES file (optional)
/v Verbose (print progress messages)
/d Define a symbol
/u Undefine a symbol
/fo Rename .RES file
/l Default language ID in hex
/i Add a path for INCLUDE searches
/x Ignore INCLUDE environment variable
/c Define a code page used by NLS conversion
/w Warn on Invalid codepage in .rc (default is an error)
/y Don't warn if there are duplicate control ID's
/n Append null's to all strings in the string tables
/fm Localizable resource only dll file name
/q RC Configuration file for the resource only DLL
/g Specify the ultimate fallback language ID in hex
/g1 Specify if version only MUI file can be created
/g2 Specify the custom file version for checksum in MUI creation
/nologo Suppress startup logo
Flags may be either upper or lower case

みたいに、/fmやら、/qというオプションが見つかればOK。

で、さっきのrcconfigがhoge.rcconfigという名前だとすると、このrcを使って、

rc /focommon.res /fmlang-0409.res /q hoge.rcconfig lang-0409.rc

とする。ヘッダ関連のディレクトリとかは適当に設定してくださいな。

そうすると、中間成果物として、

common.res (言語間で共有する奴)
lang-0409.res (英語のリソースDLLに入る奴)

というのができる。これを開いてみると、上のrcconfigで指定したとおりにリソースが選り分けられているだけじゃなくて、"MUI"という良く分からないものが出来ているのがわかる。ここにチェックサムとやらが入っているらしい。

f:id:espresso3389:20120309183408p:image
f:id:espresso3389:20120309183409p:image

で、あとは、

  • common.resはhoge.exe側にリンクしてしまう
  • lang-0409.resはDLLとしてen-US\hoge.exe.muiにする

でやること完了。一応、lang-0409.resはDLLとしてen-US\hoge.exe.muiの手順を書いておくと、こんな感じで良い:

link /out:en-US\hoge.exe.mui /nodefaultlib /noentry /dynamicbase /nxcompat /dll lang-0409.res

/nodefaultlibとか、/noentryは無駄なゴミが入らないように付けている。


他の言語に対しても同じ事をやると、当然、全部に対してcommon.resができちゃうんだけど、いらないので、こっちに関しては捨ててOK。
もちろん、common.resはいらないので、本当は、日本語(0411/ja-JP)だと、

rc /fmlang-0411.res /q hoge.rcconfig lang-0411.rc

で良い。これだと、無駄なファイルが出来ない。

で、重要なステップを忘れちゃいけないんだけど、

lang-0411から、ja-JP\hoge.exe.muiを作ったら、その後に、MUIRCTを使って、

muirct -c hoge.exe -e ja-JP\hoge.exe.mui

として、hoge.exeから、ja-JP\hoge.exe.muiに対して、チェックサム情報を反映してあげる。これをしないと、ja-JP\hoge.exe.muiが正しいmuiのファイルとして認識されない。