- 问题描述
这个问题触发比较特殊。
在visual studio Debug|x86调式模式下才会引发错误。
错误信息提示为:
Exception Thrown:
Managed Debugging Assistant 'PInvokeStackImbalance' :
'A call to PInvoke function '<function>' has unbalanced the stack.
This is likely because the managed PInvoke signature does not match the unmanaged target signature.
Check that the calling convention and parameters of the PInvoke signature match the target unmanaged signature.'
- 贴出完整的重新代码
先定义一个.net工程,代码如下:
using System.Threading;
using System.Runtime.InteropServices;
namespace ConsoleApp35
{
internal class Program
{
[DllImport("MyDLL.dll")]
public static extern void Hello0();
[DllImport("MyDLL.dll")]
public static extern void Hello1(int i);
static void Main(string[] args)
{
System.Console.WriteLine("In main()");
Program.Hello0();
Program.Hello1(1);
System.Console.WriteLine("Sleep 2s");Thread.Sleep(2*1000);
}
}
}
这里调用两个外部动态链接库DLL函数,Hello0()和Hello1(int);问题出在调用Hello1(int)的时候,函数已经调进去了,在Hello1(int)的函数内可以跟踪到trace,然后Hello1(int)正常运行结束,但是不能返回给caller了。按照异常的描述就是stack stack乱了。
因为前面Hello0()已经成功调用并返回了,也就是说如果外部动态库函数是没有传入参数的,那么调用会成功,只要有参数就会触发这个异常。
下面贴出MyDLL的源代码:
#include <stdio.h>
__declspec(dllexport)
void Hello0()
{
printf("In Hello0\n");
}
__declspec(dllexport)
void Hello1(int i)
{
printf("In Hello1\n");
}
非常简单就两个函数,编译成动态库即可。
- 原因
参考:A call to PInvoke function has unbalanced the stack. This is likely because the managed PInvoke .. (.NET 4),这是.net framework升级到4之后才出现的问题,framework 4增强了函数检测。
上述代码使用.net framework 3.5的确没有引发此异常,在framework 4.7.2环境下除非了此异常。
- 解决方法
按照上述链接给出的一个建议是在DllImport是附带上属性声明:CallingConvention = CallingConvention.Cdecl
,就是:
[DllImport("MyDLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void Hello0();
[DllImport("MyDLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void Hello1(int i);
关于CallingConvention可以查看.net文档说明。按照文档的描述CallingConvention Enum :
Cdecl 2
The caller cleans the stack. This enables calling functions with varargs, which makes it appropriate to use for methods that accept a variable number of parameters, such as Printf.
这是定义谁来清理函数调用栈的问题,是caller清理还是callee清理。
事实上我还是没有弄清楚异常的原因。
- 不管是.net还是动态库dll都是使用Visual Studio同一个版本编译的,他们的calling convention应该自动就是一样的啊。
- 只有在IDE里面debug运行的时候才会抛出异常,如果直接运行编译好的exe程序也是没有问题的。并且x64的IDE里面debug也没有问题,当然编译成Release运行也是没有问题。
也就是只有Debug+x86+IDE Debuging才会抛出这个异常。
最后贴一个错误现场截图:
Screen Shot 2023-11-18 at 17.10.58.png
- 结论
- 为什么4.x有这个问题,而3.x没有?
<NetFx40_PInvokeStackResilience> Element
因为4.x增强的这块功能;在3.x自动隐藏了这个错误,在4.x里面打开了这个错误。
Starting with the .NET Framework 4, a streamlined interop marshalling architecture
provides a significant performance improvement for transitions from
managed code to unmanaged code.
In earlier versions of the .NET Framework, the marshalling layer detected
incorrect platform invoke declarations on 32-bit platforms and
automatically fixed the stack.
The new marshalling architecture eliminates this step.
As a result, transitions are very fast,
but an incorrect platform invoke declaration can cause a program failure.
- 为什么64bit没有这个问题,只有32bit才有
Unmanaged calling conventions
因为32bit有不同的calling convention,对于c/c++缺省是Cdecl,而对于c#缺省是Stdcall,他们不匹配。而对于64bit只有一种calling convention所以就不存在这个问题,所以对于任意Stdcall或者Cdecl都是统一处理的:
For non-x86 architectures, both Stdcall and Cdecl calling conventions
are treated as the canonical platform default calling convention.
网友评论