在VS2010中,托管调试助手会给你一个pInvokeStackImbalance异常(pInvokeStackImbalance MDA) 如果您使用错误的调用约定调用函数,通常是因为您在调用 C 库时没有指定 CallingConvention = Cdecl。例如。你写了
[DllImport("some_c_lib.dll")]
static extern void my_c_function(int arg1, int arg2);
代替
[DllImport("some_c_lib.dll", CallingConvention=CallingConvention.Cdecl)]
static extern void my_c_function(int arg1, int arg2);
因此得到了 StdCall 调用约定而不是 Cdelc。
如果您回答这个问题,您已经知道其中的区别,但对于该线程的其他访问者来说:StdCall 意味着被调用者从堆栈中清除参数,而 Cdecl 意味着调用者清除堆栈。
因此,如果您的 C 代码中的调用约定错误,您的堆栈不会被清理,并且您的程序会崩溃。
然而,.NET 程序似乎不会崩溃,即使它们使用 StdCall 来执行 Cdecl 函数。VS2008 默认情况下未启用堆栈不平衡检查,因此某些 VS2008 项目使用了作者不知道的错误调用约定。我刚刚尝试过GnuMpDotNet即使缺少 Cdelc 声明,示例也运行得很好。这同样适用于X-MPIR.
它们都在调试模式下抛出 pInvokeStackImbalance MDA 异常,但在发布模式下不会崩溃。为什么是这样? .NET VM 是否包装对本机代码的所有调用并随后恢复堆栈本身?如果是这样,为什么还要费心 CallingConvention 属性呢?
这是因为方法退出时堆栈指针的恢复方式。方法的标准序言,针对 x86 抖动显示;
00000000 push ebp ; save old base pointer
00000001 mov ebp,esp ; setup base pointer to point to activation frame
00000003 sub esp,34h ; reserve space for local variables
以及它的结束方式:
0000014a mov esp,ebp ; restore stack pointer
0000014c pop ebp ; restore base pointer
0000014d ret
使 esp 值不平衡在这里不是问题,它可以从 ebp 寄存器值恢复。然而,当抖动优化器可以将局部变量存储在 CPU 寄存器中时,它经常会优化这一点。当 RET 指令从堆栈中检索到错误的返回地址时,您将崩溃并烧毁。无论如何,希望当它偶然落在一大块机器代码上时真的很糟糕。
当您在没有调试器的情况下运行发布版本时,很可能会发生这种情况,如果没有 MDA 来帮助您,则很难排除故障。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)