Visual studio 2008 在VisualStudio中使用自定义prolog和epilog代码编写裸函数

Visual studio 2008 在VisualStudio中使用自定义prolog和epilog代码编写裸函数,visual-studio-2008,plugins,calling-convention,stdcall,Visual Studio 2008,Plugins,Calling Convention,Stdcall,我正在一个dll中编写一些插件代码,这个dll由一个我无法控制的主机调用 主机假定插件导出为uu stdcall函数。主机被告知函数的名称和它所期望的参数的详细信息,并通过LoadLibrary、GetProcAddress和手动将参数推送到堆栈上动态地对其进行调用 通常插件DLL公开一个常量接口。我的插件公开了一个在dll加载时配置的接口。为了实现这一点,我的插件公开了一组在编译dll时定义的标准入口点,并根据需要将它们分配给正在公开的内部功能 每个内部函数可能采用不同的参数,但这将与物理入口

我正在一个dll中编写一些插件代码,这个dll由一个我无法控制的主机调用

主机假定插件导出为uu stdcall函数。主机被告知函数的名称和它所期望的参数的详细信息,并通过LoadLibrary、GetProcAddress和手动将参数推送到堆栈上动态地对其进行调用

通常插件DLL公开一个常量接口。我的插件公开了一个在dll加载时配置的接口。为了实现这一点,我的插件公开了一组在编译dll时定义的标准入口点,并根据需要将它们分配给正在公开的内部功能

每个内部函数可能采用不同的参数,但这将与物理入口点名称一起传递给主机。我的所有物理dll入口点都被定义为使用一个void*指针,我自己通过处理第一个参数和已与主机通信的已知参数列表的偏移量,从堆栈封送后续参数

主机可以用正确的参数成功地调用我插件中的函数,并且一切都很好。。。但是,我知道a)我的函数没有像它们被定义为u stdcall函数那样清理堆栈,它们使用4字节指针,因此即使调用方将更多参数推到堆栈上,它们也总是在最后执行“ret 4”。b)我不能处理不带参数的函数,因为ret4在返回时会从堆栈中弹出太多的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
需要一个常量参数,所以您需要安排函数具有常量参数,但这种情况仅在您准备从函数返回时才需要。因此,在函数结束之前,请执行以下操作:

  • 从堆栈顶部弹出返回地址,并将其存储在临时文件夹中<代码>ECX是个好地方
  • 从堆栈中删除可变数量的参数,方法是分别弹出每个参数,或直接调整
    ESP
  • 将返回地址推回到堆栈上
  • 使用带有常量参数的
    ret

  • 顺便说一句,在一般情况下,您称为(a)的问题实际上是一个问题。幸运的是,调用方似乎总是使用帧指针而不是堆栈指针引用自己的局部变量。不过,函数不需要这样做,也不能保证宿主程序的未来版本会继续以这种方式工作。编译器还可能只在调用期间在堆栈上保存一些寄存器值,然后期望能够在调用后再次弹出它们。你的代码会破坏这一点。

    谢谢Rob,我会尝试一下。我个人不会为帧指针操心,因为它从未使用过,但除此之外,你的解决方案看起来不错。最初我是从帧指针索引args空间和返回值空间,但后来我放弃了将返回值移出eax,然后再回到eax中,我想对帧指针的需求消失了。。。我想我应该把它去掉。。。谢谢你的帮助。
    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                        
       }
    }