Win32とWin64の関数呼び出し

差分パッチを作るときに、x64版を作ろうとしていろいろと四苦八苦しましたが・・・。

あまり気がつかれにくい話題ですが、はまった人がいるかな~と思って話題です。

Win32には関数呼び出しの形式がVC++で実装すると、コンパイラの内部的に4つ存在します。

  • cdecl => 引数を逆順でスタックに。スタック解放は呼び出し元コード (printfなどの可変引数はこの呼び出し形式)
  • stdcall => 引数を逆順にスタックに。スタック解放は呼び出し先関数 (WINAPI、CALLBACKがこの呼び出し形式)
  • thiscall => 引数を逆順にスタックに。thisポインタをecxレジスタに格納。スタック解放は呼び出し先関数 (クラスの呼び出し形式)
  • fastcall => 引数を先頭二つをのぞいて逆順にスタックに。一番目をecxレジスタ、二番目をedxレジスタに。スタック解放は呼び出し先関数

通常のコード実装ではcdeclかstdcallかどちらかを選択することになるので、関数ポインタを汎用的に管理するクラス(delegateなど)を

自前で実装すると、両方とも(クラスも入れると3つとも)受け入れられるように実装することになります。

ちなみに、クラスのメンバ関数でcdeclやstdcallを記述すると、スタックの先頭をthisポインタにして呼び出します。

ところが、Win64ではCPUのレジスタ数が増えた(8個=>16個)ため、コンパイラが内部的にcdecl=stdcall(=fastcall)としてしまいます。

ある程度の引数の個数ならスタックにPushしなくても、レジスタ渡しで十分だからですね。

そのため、Win32用に、両方でコードを組んでいると、Win64でコンパイルしようとすると、オーバーロードが効かない、というよりは、同じ関数定義と勘違いされます。

おかげでこんな実装が必要になっちゃいます。以下は私がライブラリで使用しているhandle_holderというテンプレートクラスです。

不必要になると解放関数を呼び出して自動的にハンドルを解放するクラスです。コードの途中だけ。

public:
	typedef int (*LPRELEASEFUNC)(T handle);
	typedef int (__cdecl *LPRELEASEFUNC_CDECL)(T handle);
	typedef int (__stdcall *LPRELEASEFUNC_STDCALL)(T handle);
	void set(T handle) { dec_ref(); if(sethandle(handle,NULL,NULL)){ inc_ref(); } }
	void set(T handle,T errorcode) { dec_ref(); if(sethandle(handle,NULL,errorcode)){ inc_ref(); } }
#if defined(_WIN64) || !defined(_WIN32)
	void set(T handle,LPRELEASEFUNC fn) { dec_ref(); if(sethandle(handle,fn,NULL)){ inc_ref(); } }
	void set(T handle,LPRELEASEFUNC fn,T errorcode) { dec_ref(); if(sethandle(handle,fn,errorcode)){ inc_ref(); } }
#else
	void set(T handle,LPRELEASEFUNC_CDECL fn) { dec_ref(); if(sethandle(handle,fn,NULL)) inc_ref(); }
	void set(T handle,LPRELEASEFUNC_CDECL fn,T errorcode) { dec_ref(); if(sethandle(handle,fn,errorcode)) inc_ref(); }
	void set(T handle,LPRELEASEFUNC_STDCALL fn) { dec_ref(); if(sethandle(handle,fn,NULL)) inc_ref(); }
	void set(T handle,LPRELEASEFUNC_STDCALL fn,T errorcode) { dec_ref(); if(sethandle(handle,fn,errorcode)) inc_ref(); }
#endif //defined(_WIN64) || !defined(_WIN32)

#ifをつかって、コンパイル時に共通となる部分がオーバーロードしないように分けているわけですね・・・。

こういうのが必要になるのがかなり面倒なのですが・・・。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

この記事のトラックバック用URL