2018年11月8日木曜日

C# : 「64bitだとWindowFromPointが動かないよー」という過去の課題が見つかったのでちょいと調べる

自分用のツールを作ろうかと色々調べていたら過去の自分の投稿にたどり着きました。

「64bit環境でC#からWin32APIのWindowFromPointを呼び出すと失敗する」と書いてあります。 「じゃあ今やろうとしている事できないの?」とさらに検索しようとしたら......WindowFromPointとテキストボックスに入れた時点でサジェッションに64bitとかx64とか出てくるんですよね。 「もしかして未だに解決してない?」と思ったら最初の検索結果であっさり解決しました。

6年前のQ&Aじゃないか。 曰く、「WindowFromPointの引数の型間違ってるよ。正しい型なら動きます」⇒ 確かに動きました。


最初はマーシャリングのためにこんな風に書いてたんですよね。 引数をint型2つにしてました。

正しい引数は構造体でした。

どちらも32bitの環境だと正常に動きます。 でも64bitだとダメ。 どうしてでしょう? 昔勉強した記憶を引っ張り出して考えてみました。

C++で関数を呼ぶとき、組み込み変数や構造体はスタック渡しなんですよね? 32bitで変数の型を間違えても動いていたのは、どちらも32bitの値x、yを同じ順番、同じ位置にスタックに積んでからコールしてるからなのでしょう。 呼び出された側からしたら同じ受け取り方で引数を受け取れた。 だから型を間違えても問題は無かった。 でも64bitだと何かが変わったので動かなくなってしまった、と。

WindowFromPointの「問題」が出来てしまったのは昔の32bit時代の人が「動けばよかろう」とint2つで書いたコードが出回ったせいかな? 64bitのコンピュータが普及し始めたときに、過去の自分もそれを踏んで間違ったコードを書いてしまったのでしょう。

64bitでダメなのは変数のメモリ上のアライメントが原因でしょうね。 確認せずにそう決め打って文章を書いて投稿しようとしたけど、思いとどまりました。 簡単に確かめてみました。


用意したのはこのサンプルコード。 WindowFromPointの代わりに同じ引数を持ったfuncA、funcBを用意しました。

これをVisual C++で/FAオプションを付けて、x86をターゲットにしてコンパイル。 .asmのソースコードを吐かせます。 もちろん最適化オプションは無しです。 C++なんて何年ぶりでしょう? プロトタイプ宣言とか忘れてました。

問題の関数呼び出し部分を抜粋すると......。

C++の20行目に当たる部分が「; Line 20」です。 pushしているのが引数x、yにあたる数字ですね。 y、xの順に32bit値をpushしてからfuncAをcallしてます。

23行目、24行目に当たるコードで構造体に値を設定。 25行目で構造体のyに当たるメンバーをレジスタのeaxを経由してpush、xに当たるメンバーをecxを経由してpush。 そしてfuncBをcall。 呼び出された側から見れば、引数x、yを積んだ場合とPOINT構造体を積んだ場合に違いが無い事が分かります。 ここまでは思った通り。

ではx64でも同様に。 アセンブラの勉強をかじってた時に64bit環境とかありませんでした。 どうなってるんでしょうね? とは言ってもアライメント違うだけでしょ……あれ? callの前にpushが無いぞ!?

どうやらx64だと引数がレジスタ渡しになってしまうようです。(←浦島) int型の引数2つだと32bitのレジスタ2つで値を渡しています。 それに対してPOINT構造体はrcxという64bitのレジスタ1つで値を渡しています。 え? 最適化オプション無しでもこんな事してくれるの?

なんだ、全然違うじゃないか。 こりゃ動かんわ。 そしてアライメントは全然関係ありませんでした。 確認してよかった。


過去に解決できずにそのままになっていた事を解決できてスッキリしましたね。 そして思い込みでものを書かない事の重要性を再認識。 今回は勘違い未遂で済みました。

これで安心してWindowFromPointを使え……フリーソフトで済ませようかな?