私が制御できないホストによって呼び出される dll に、いくつかのプラグイン コードを書いています。
ホストは、プラグインが __stdcall 関数としてエクスポートされると想定します。ホストは、関数の名前と、それが期待する引数の詳細を通知され、LoadLibrary、GetProcAddress を介して動的に呼び出しを作成し、手動で引数をスタックにプッシュします。
通常、プラグイン dll は一定のインターフェイスを公開します。私のプラグインは、dll の読み込み時に設定されるインターフェイスを公開します。これを実現するために、私のプラグインは、dll のコンパイル時に定義された一連の標準エントリ ポイントを公開し、公開されている内部機能に必要に応じてそれらを割り当てます。
内部関数はそれぞれ異なる引数を取る場合がありますが、これは物理エントリポイント名とともにホストに伝えられます。私の物理的な dll エントリポイントはすべて単一の void * ポインターを取るように定義されており、最初の引数からのオフセットと、ホストに伝達された既知の引数リストを使用して、スタックから後続のパラメーターを自分でマーシャリングします。
ホストは正しい引数を使用してプラグインの関数を正常に呼び出すことができ、すべて正常に動作します...ただし、a) 私の関数は、定義されているはずのようにスタックをクリーンアップしていません。 __stdcall 関数は 4 バイトのポインターを受け取るため、呼び出し元がさらに多くの引数をスタックにプッシュした場合でも、常に最後に 'ret 4' を実行します。b) 戻り時に ret 4 が 4 バイトをスタックからあまりにも多くポップするため、引数を取らない関数を処理できません。
私のプラグインからホストの呼び出しコードまでたどってみると、実際には a) はそれほど大したことではないことがわかります。ホストは、ディスパッチ呼び出しから戻るまでスタックスペースをいくらか失います。その時点で、スタックフレームがクリーンアップされ、ゴミがクリーンアップされます。でも...
b) は __cdecl に切り替えて、まったくクリーンアップしないことで解決できます。a) ネイキッド関数に切り替えて、独自のジェネリック引数クリーンアップ コードを記述することで、a) を解決できると思います。
呼び出されたばかりの関数が使用する引数スペースの量を知っているので、次のように単純になることを望んでいました。
extern "C" __declspec(naked) __declspec(dllexport) void * __stdcall EntryPoint(void *pArg1)
{
size_t argumentSpaceUsed;
{
void *pX = RealEntryPoint(
reinterpret_cast<ULONG_PTR>(&pArg1),
argumentSpaceUsed);
__asm
{
mov eax, dword ptr pX
}
}
__asm
{
ret argumentSpaceUsed
}
}
しかし、 ret にはコンパイル時定数が必要なため、それは機能しません...何か提案はありますか?
更新しました:
ロブ・ケネディの提案のおかげで、うまくいくようです...
extern "C" __declspec(naked) __declspec(dllexport) void * __stdcall EntryPoint(void *pArg1)
{
__asm {
push ebp // Set up our stack frame
mov ebp, esp
mov eax, 0x0 // Space for called func to return arg space used, init to 0
push eax // Set up stack for call to real Entry point
push esp
lea eax, pArg1
push eax
call RealEntryPoint // result is left in eax, we leave it there for our caller....
pop ecx
mov esp,ebp // remove our stack frame
pop ebp
pop edx // return address off
add esp, ecx // remove 'x' bytes of caller args
push edx // return address back on
ret
}
}
これは正しく見えますか?