hack のためのネタ帳, etc,,,

IsWindowUnicode の説明にあるように、RegisterClassEx が W と A のどちらで呼ばれたかにより、ウインドウの文字セットが決まる。この際、ウインドウメッセージは Unicode と ANSI の間で双方向に自動変換されるため、DefWindowProc 等、途中経由する関数が W か A で統一されていない場合意図しない文字コード変換が生じてハマる模様。
C/C++ でプログラミングする際は、UNICODE マクロの有無で自動的に統一されるが、Ruby で dl とか fiddle でバインドして呼び出したりする際は注意が必要。

具体例としては、UNICODE てプログラムしたかったので RegisterClassExW → CreateWindowExW と呼んでみたところ、なぜか Unicode 版の CreateWindowExW 呼んでるのに第 2,3 引数に ANSI 与えないと正常に動作しなくてハマるとか。
原因は何のことはない、RegisterClassExW で指定した WindowProc から呼んでる DefWindowProc に suffix 付け忘れたせいで、DefWndProcA が呼ばれて、Unicode への自動変換(実際には Unicode を ANSI とみなして Unicode に変換)が生じていたというオチ。

どうも、Ruby が ANSI 版の API をデフォルトにした状態でコンパイルされてるため、ANSI 版の LoadLibrary を使って dlopen が実装されるんじゃないかと。GetProcAddress には Unicode/ANSI 版の区別がないので LoadLibraryW が使われたか LoadLibraryA が使われたかにより GetProcAddress で検索される関数の suffix の W か A かが決まってるのかな?(要検証 *1)

W, A 付かない関数もあるので、いちいち suffix 気にしながらバインドするのは結構面倒臭いし、W, A の suffix 付けずに DLL 関数探せるのは楽なんだけど、ふとした拍子に W を付け忘れることで、上記ように文字セットの自動変換の罠にハマるみたいな。

2013-03-07: 上記、「要検証 *1」の検証

まず、GetProcAddress が ANSI 版しかないのは、コンパイラやリンカの扱っているシンボルが ANSI のみで UNICODE を使えないという話らしい(参考: Win32API(C言語)編 第58章 LoadLibrary()によるDLLロード)。言われてみれば、最もそうな理由にも思えるのだが、そこはコード変換すれば良いだけの話なので、なんか釈然としない。
とは言え、LoadLibrary が W か A かで、GetProcAddress で検索するシンボルの suffix を補完してくれるとか、そういう気の利いた話ではなかった。
とりあえず user32.dll の中を覗いてみると、例えば MessageBox は MessageBoxA, MessageBoxW しか export されておらず、素の Win32 API を使う限り GetProcAddress では明示的に MessageBoxA, MessageBoxW のいずれかを指定する必要があり MessageBox で探しても何も見つけてくれない。

では Ruby の DL::handle#sym だと MessageBox で検索して MessageBoxA を見つけてくれるのはどういうことかというと、Ruby 側でよきに計らうような実装になっていた。具体的には、Ruby1.9.3-p362/ext/dl/handle.c/dlhandle_sym() 辺りが該当箇所なのだが、シンボルが見つからなかった場合、末尾に 'A' を付けてリトライするようになってるのが確認できる。
と言うことで、Unicode 版の関数を自動的に探すコードは入ってないし、ANSI 版ではなく Unicode 版をデフォルトで探させるようにするスイッチの類も存在しておらず、Unicode 版の API 叩くためには、いちいち suffix に W 付けてまわる必要があるってのが結論。結構面倒臭い状況。
2.1 から 2.2 への大きな変更点として、バックエンドが dl から fiddle/import に変更になっている模様。

ここで、
DL.dlopen() では dllname を "user32" のように ".dll" 抜きで指定すれば良かったのが、
fiddle.dlopen() では dllname を "user32.dll" のように ".dll" も含めて指定しなくてはいけなくなっているようだ。

これに伴い、Win32API.new() する際に ".dll" を付加してないコードは
".dll" を付加するように変更しなければ、動作しない状況に陥っている模様。

関連

コメントをかく


「http://」を含む投稿は禁止されています。

利用規約をご確認のうえご記入下さい

Wiki内検索

フリーエリア

管理人/副管理人のみ編集できます