【发布时间】:2010-10-27 13:47:51
【问题描述】:
我正在一个 dll 中编写一些插件代码,该 dll 由我无法控制的主机调用。
主机假定插件被导出为 __stdcall 函数。主机被告知函数的名称和它所期望的参数的详细信息,并通过 LoadLibrary、GetProcAddress 和手动将参数推送到堆栈上动态地构建对它的调用。
通常插件 dll 会公开一个常量接口。我的插件公开了一个在 dll 加载时配置的接口。为了实现这一点,我的插件公开了一组在编译 dll 时定义的标准入口点,并根据需要将它们分配给正在公开的内部功能。
每个内部函数都可以采用不同的参数,但这些参数会与物理入口点名称一起传递给主机。我所有的物理 dll 入口点都被定义为采用单个 void * 指针,我自己通过从第一个参数的偏移量和已与主机通信的已知参数列表来编组后续参数。
主机可以使用正确的参数成功调用我的插件中的函数,并且一切正常......但是,我知道 a) 我的函数没有像他们应该的那样清理堆栈' 被定义为 __stdcall 函数,它采用 4 字节指针,因此即使调用者将更多参数压入堆栈,它们也总是在最后执行 'ret 4'。 b) 我无法处理不带参数的函数,因为 ret 4 在返回时会从堆栈中弹出太多 4 个字节。
从我的插件中追踪到主机的调用代码后,我可以看到实际上 a) 没什么大不了的;主机丢失了一些堆栈空间,直到它从调度调用返回,此时它清理了清理我的垃圾的堆栈帧;不过……
我可以通过切换到 __cdecl 而根本不清理来解决 b)。我假设我可以通过切换到裸函数并编写我自己的通用参数清理代码来解决 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 需要编译时间常数...有什么建议吗?
更新:
感谢 Rob Kennedy 的建议,我得到了这个,这似乎工作......
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
}
}
这看起来对吗?
【问题讨论】:
-
我个人不会打扰帧指针,因为它从未使用过,但除此之外,您的解决方案看起来不错。
-
最初我是从帧指针索引 args 空间和返回值空间,但后来我取消了将返回值从 eax 中移出然后再移回它的做法,好吧,我猜需要帧指针消失了...我想我应该删除它...感谢您的帮助。
标签: visual-studio-2008 plugins calling-convention stdcall