1

多くの人は、ウィンドウ作成などの ATL サンクに精通しています。これを機能させるクラス CStdCallThunk は、WindowProc呼び出しを対象としています。本質的には、グローバル コールバックを C++ オブジェクトのメンバー関数に変換します。

このタイプのサンクは、最初のパラメーターをそのまま必要とするSetWindowsHookEx コールバックでは機能しません。32 ビット Windows の場合、 ATL/AUXライブラリの一部である CAuxThunk で適切な解決策を見つけました。残念ながら、これはネイティブの 64 ビット実行可能ファイルでは機能しません。

この CAuxThunk にパッチを当てて 64 ビット ウィンドウで動作するようにしたり、この __stdcall コールバックをメンバー関数に変換する同等のサンクを作成したりできる x64 アセンブリの第一人者がいるのだろうか?

LRESULT CALLBACK CallWndProc(int nCode, WPARAM wParam, LPARAM lParam)

ありがとう、
ニコス

4

3 に答える 3

4

この回答では、必要サンク コードを生成する一般的な方法について説明しました。演習として、あなたの場合に合わせてやり直しましょう。

クラスが次のように定義されているとします。

struct YourClass {
    LRESULT YourMemberFunc(int nCode, WPARAM wParam, LPARAM lParam);
};

実際のアドレスのプレースホルダーを使用して、サンクを C++ で記述します。

LRESULT CALLBACK CallWndProc(int nCode, WPARAM wParam, LPARAM lParam) {
    YourClass *x = reinterpret_cast<YourClass*>(0x1122112211221122);
    __int64 im = 0x3344334433443344;
    LRESULT (YourClass::*m)(int,WPARAM,LPARAM) = *reinterpret_cast<LRESULT (YourClass::**)(int,WPARAM,LPARAM)>(&im);
    return (x->*m)(nCode, wParam, lParam);
}

そして、コンパイラが呼び出しをインライン化しないように呼び出します。

int main() {
    LRESULT (CALLBACK *volatile fp)(int, WPARAM, LPARAM) = CallWndProc;
    fp(0, 0, 0);
}

リリースでコンパイルし、生成されたアセンブリを確認します (Visual Studio では、デバッグ中にアセンブリ ウィンドウを確認し、[コード バイトの表示] をオンにします)。

4D 8B C8                       mov         r9,r8  
4C 8B C2                       mov         r8,rdx  
8B D1                          mov         edx,ecx  
48 B9 22 11 22 11 22 11 22 11  mov         rcx,1122112211221122h  
48 B8 44 33 44 33 44 33 44 33  mov         rax,3344334433443344h  
48 FF E0                       jmp         rax  

これはサンクになり、実行時44 33 44 33 44 33 44 33にメンバーへのポインター ( &YourClass::YourMemberFunc) に22 11 22 11 22 11 22 11置き換えられ、実際のオブジェクト インスタンスへのポインターに置き換えられます。

サンクで何が起こっているかの説明

x64 呼び出し規則 (Windows には 1 つしかありません) では、最初の 4 つのパラメーターはrcx, rdx, r8, r9、左から右の順序でレジスターに渡されます。したがって、サンクが呼び出されると、

rcx = nCode, rdx = wParam, r8 = lParam

メンバー関数の場合this、ポインターを保持する暗黙の最初のパラメーターがあるため、入力するときにYourMemberFunc持っている必要があります

rcx = this, rdx = nCode, r8 = wParam, r9 = lParam

コンパイラによって生成されたコードは、まさにこの調整を行います。つまり、 をシフトし、プレースホルダーを にr8 -> r9, rdx -> r8, ecx -> edx割り当てます。これでパラメーターが設定され、関数自体への間接的なジャンプを続行できます。戻り値を保持するために使用されるため、関数呼び出し間で保持する必要はありません。これが、宛先アドレスを一時的に保持するためにここで使用されている理由です。これにより、テール コールの最適化 (コールとリターンのペアが単一のジャンプに置き換えられます) の機会が与えられます。this = 1122112211221122rcxrax

なぜ間接呼び出しを行う必要があるのでしょうか。そうしないと、相対的なジャンプが発生するためです。しかし、サンクがメモリ内の異なるアドレスにコピーされるため、ハードコーディングされた相対ジャンプを使用することはできません! したがって、実行時に絶対アドレスを設定し、代わりに間接ジャンプを行うことに頼っています。

HTH

于 2014-04-20T20:46:29.280 に答える
0

更新された以下を参照

SetWindowLongPtr http://msdn.microsoft.com/en-us/library/windows/desktop/ms644898(v=vs.85).aspxをご覧ください。

これにより、ポインターをウィンドウに関連付けることができます。WindowProc が次のようになっているとします。

LRESULT CALLBACK WindowProc( _In_ HWND hwnd, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam );

GetWindowLongPtr で hwnd パラメーターを使用して、コールバックで this ポインターを取得できます。

更新しました

x86 および x64 で関数をサンクする方法をご覧ください(C++ の std::bind に似ていますが、動的です)

これはあなたが探しているもののようです

于 2013-10-28T13:50:57.887 に答える
0

64 ビット モードでは、低レベルの規則はすべての型 (stdcall、cdecl、および thiscall) で同じであるため、これを実現するためにアセンブリ コードは実際には必要ありません。適切なメンバー関数を呼び出す静的関数を作成するだけです。をオブジェクトにthis関連付けるなどして、使用する適切なポインターを把握する必要があります。コールバックが 1 つしかない場合は、もちろん問題ありません。何かのようなもの:hwndlparam

LRESULT CALLBACK static CallWndProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    Window w = GetWindowByHwnd(((CWPSTRUCT*)lParam)->hwnd);
    return w->CallWndProc(nCode, wParam, lParam);
}
于 2013-10-28T10:22:00.770 に答える