PInvoke と文字コード

次のような2つのメソッドを考える。

[DllImport("test.dll", CharSet=CharSet.Auto)]
extern void Test1(string msg);

[DllImport("test.dll", CharSet=CharSet.Auto)]
extern void Test2(IntPtr buffer, int size);

前者には小さな問題はあるが大きな問題はない。
test.dll が Ansi 用のエントリポイントしか提供しなければ、引数 msg は現在スレッドのカルチャと OS の文字セットに従った ANSI コードにマーシャリングされるし、Unicode 用のエントリポイントを持っていれば文字コードの変換なしに、いわゆる C-String としてポインタだけが渡される。
問題は、以前、GDNJ の掲示板でも書いたが、後者の buffer に対して文字列を渡したい場合である。
IntPtr 型の変数に適切にメモリを割り当て、手動でエンコードをあわせてマーシャリングする……それだけのことなのだが、Test2 が test.dll において Ansi 用のエントリポイントに解決されたのか、Unicode 用のエントリポイントに解決されたのか、判断する手段がない。
Marshal クラスには、明示的に Ansi/Unicode を指定しない StringToHGlobalAuto() や StringToCoTaskMemAuto() というメソッドがあるが、こいつらには何も期待できない。
こいつらは、あらかじめ Marshal クラスが初期化されたときに決定*1された Ansi/Unicode の種別を用いて、同Ansi() および 同Unicode() を呼び分けるだけである。

*1:この判断は lstrlen API を用いて行われるため、lstrlenW が提供される環境では Unicode と判定されるようになっているようだ

PInvoke と名前解決順序

DllImportAttribute で ExactSpelling を true に設定しないで CharSet を Ansi と指定すると、Test1 が発見されない場合に Test1A を検索してくれ、CharSet が Unicode の場合、Test1 より先に Test1W を検索し、発見されない場合に Test1 を検索する。
つまり、優先順位として Test1W → Test1 → Test1A ということになる。
CharSet.Auto の場合の検索順序はドキュメント上に明記されていないが、上記の通りになっていることは3つすべてのエントリを持つ DLL を作成することで確認できる

ResouceManagerのメソッドは遅い?

いや、マジで。
リソースの取得を行うコードとして、

private static ResourceManager rm = new ResourceManager(typeof(...));

private void ToAruMethod()
{
  string s = rm.GetString("key");
    :
}

なんてコードを何度も何度も通るようなところに置いたら、すごい遅かった。
毎回すべてのリソースセットから CurrentThraed.CurrentUICulture を検索して、フォールバック処理してリソースセットを決定して、それから文字列取得をやってるんだろうか。

生成できないニュートラルカルチャと生成できるニュートラルカルチャ?

カルチャとフォールバックといえば、CultureInfo.CreateSpecificCulture() の動き。
このメソッド、通常はニュートラルカルチャを指定すると

    ... CultureInfo.CreateSpecificCulture("zh-CHT")...

    呼び出しのターゲットが例外をスローしました。
    zh-CN、zh-HK、zh-TW、zh-MO、zh-SG などの、具体的なカルチャを選択してください。

などと例外が飛んでおわるのだが、いくつかのニュートラルカルチャを指定して生成すると成功しちゃうものがある。たとえば、"ja" とか "en" が生成に成功し、それぞれ "ja-JP" と "en-US" の具体カルチャが生成されるようになっている。
"ja" なんて "ja-JP" と "ja-OSAKA" ぐらいしか存在しないので、"ja-JP" が生成されるのは良いとして、"en" の具体カルチャは多量にあるにもかかわらず、なぜ "en-US" が選択されるのだろう?
なんらかの理由で "en" が "en-US" にマップされるなら、同じ理由で "zh-CHT" を "zh-TW" なんかにマップしたりはしないのだろうか?


アプリケーションを多言語対応するとき、ユーザに OS の動作言語とは別に、アプリケーションの動作言語の選択を行えるようにする場合、ほとんどのアプリケーションのローカライズの度合いでは UI にはニュートラルカルチャをリストアップすることになる*1のだが、前述の通りニュートラルカルチャは直接生成できないものがある。
また .NET の現実問題として、ニュートラルカルチャは CurrentCulture に対して代入できない*2ので、ユーザが選択した結果として、なんらかの具体カルチャが必要になるのだが、ニュートラルカルチャの名前が手に入っても CreateSpecificCulture() にそのまま渡すと失敗する場合があり、具体カルチャを簡単に入手することはできないという事態が発生する。


ま、実際の所ローカライズしたニュートラルカルチャのリストを元に、各ニュートラルカルチャを親にもった具体カルチャをリストアップして保持し、UI にはリスト内のカルチャに対して CultureInfo.Parent.DisplayName を表示しておけば、ほとんどの場合に解決できる問題なんだけども。

*1:翻訳などのローカライズのコストなどの都合もあるし、個別地域の風習まで実装の手がまわらない

*2:当然? だが CurrentUICulture には代入できる