Ale*_*Che 4 .net pinvoke cdecl calling-convention stdcall
我的解决方案有一个非托管C++ DLL,它导出一个函数,一个托管应用程序PInvokes这个函数.
我刚刚将解决方案从.NET 3.5转换为.NET 4.0并得到了这个PInvokeStackImbalance "对PInvoke函数的调用[...]已经使堆栈失衡"异常.事实证明,我正在调用__cdecl'ed函数,因为它是__stdcall:
C++部分(被调用者):
__declspec(dllexport) double TestFunction(int param1, int param2); // by default is __cdecl
Run Code Online (Sandbox Code Playgroud)
C#部分(来电者):
[DllImport("TestLib.dll")] // by default is CallingConvention.StdCall
private static extern double TestFunction(int param1, int param2);
Run Code Online (Sandbox Code Playgroud)
所以,我已经修复了这个bug,但现在我对.NET 3.5中的工作原理感兴趣吗?当没有人(既不是被叫者也不是调用者)清理堆栈时,为什么(多次重复)情况没有引起堆栈溢出或其他一些不当行为,但只是工作正常?Pnvoke中是否有某种检查,就像Raymond Chen在他的文章中提到的那样?这也很有趣,为什么相反类型的破坏约定(让__stdcall被调用者像被__cdecl一样被PInvoked)根本不起作用,导致只有EntryPointNotFoundException.
PInvokeStackImbalance也不例外.它是一个MDA警告,由托管调试助手实现.让MDA处于活动状态是可选的,您可以从Debug + Exceptions对话框配置它.在没有调试器的情况下运行它永远不会处于活动状态.
使堆栈不平衡可能会导致非常令人讨厌的问题,从奇怪的数据损坏到获取SOE或AVE.很难诊断.但它也可以完全没有问题,当方法返回时,堆栈指针会恢复.
编译为64位的代码往往具有弹性,更多的函数参数通过寄存器而不是堆栈传递.当被迫在x86(VS2010的新默认值)上运行时,它将失败.
经过一番调查:
保存情况不会崩溃的助手是另一个寄存器 - EBP,指向堆栈帧开头的基指针.所有对函数局部变量的访问都是通过该指针完成的(优化代码除外,请参见下面的编辑).在函数返回之前,堆栈指针被重置为基指针的值.
在函数(比如PInvoke)调用另一个函数(导入的DLL的函数)之前,堆栈指针指向调用函数的局部变量的末尾.然后调用者将参数推送到堆栈并调用其他函数.
在所描述的情况下,当一个函数将另一个函数调用为__stdcall时,它实际上是__cdecl,没有人从这些参数中清除堆栈.因此,从被调用者返回后,堆栈指针指向推送参数块的末尾.它就像调用函数(PInvoke)只获得了几个局部变量.
由于通过基指针访问调用者的局部变量,因此不会破坏任何内容.唯一可能发生的坏事是,如果一次调用被调用函数很多次.在这种情况下,堆栈将增长并可能溢出.但是由于PInvoke只调用DLL的函数一次,然后返回,所以堆栈指针只是重置为基指针,一切都很好. 编辑:如前所述这里,代码还可以优化存储本地变量在CPU只注册.在这种情况下,不使用EBP,因此无效的ESP可能导致返回无效地址.
归档时间: |
|
查看次数: |
1435 次 |
最近记录: |