美文网首页
一个.net调试引发的错误A call to PInvoke f

一个.net调试引发的错误A call to PInvoke f

作者: CodingCode | 来源:发表于2023-11-18 09:52 被阅读0次
  1. 问题描述

这个问题触发比较特殊。

在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.'
  1. 贴出完整的重新代码

先定义一个.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");
}

非常简单就两个函数,编译成动态库即可。

  1. 原因

参考: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环境下除非了此异常。

  1. 解决方法

按照上述链接给出的一个建议是在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清理。

事实上我还是没有弄清楚异常的原因。

  1. 不管是.net还是动态库dll都是使用Visual Studio同一个版本编译的,他们的calling convention应该自动就是一样的啊。
  2. 只有在IDE里面debug运行的时候才会抛出异常,如果直接运行编译好的exe程序也是没有问题的。并且x64的IDE里面debug也没有问题,当然编译成Release运行也是没有问题。

也就是只有Debug+x86+IDE Debuging才会抛出这个异常。

最后贴一个错误现场截图:


Screen Shot 2023-11-18 at 17.10.58.png
  1. 结论
  1. 为什么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.
  1. 为什么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.

相关文章

  • 部署k8s时候一些设置

    参数 如果报错/proc/sys/net/bridge/bridge-nf-call-iptables not f...

  • JSdeep(this&arguments&bi

    call 浏览器在执行点击事件时,默认点击事件函数的call参数为,引发点击事件的元素(button,div) f...

  • windows API 函数查找方法

    (1)可以在MSDN中参照相应的 函数相关 (2)可以直接在检索 http://pinvoke.net/defau...

  • gdb调试命令

    推荐一个比较好使的gdb调试时的命令。 在遇到段错误[https://so.csdn.net/so/search?...

  • Unity+SLua的一些小零碎(2)

    增加调试控制台 Unity版本:2019.3.4f1SLua版本:1.7.0.NET : 4.7.1 前文已经把U...

  • [JavaScript] Function.prototype.

    试分析以下代码的执行过程: (1)重新理解f.call(a,b,c);的执行过程首先我们看到f.call也是一个函...

  • 函数

    函数的 5 种声明 函数调用 f(1,3)f.call(undefined,1,2)1、call的第一个参数可以用...

  • 微信小程序入门到实战(四)

    调试方法 单步调试是F10 跳到下一个断点是F8小程序的调试和一般的网页调试差不多,但是注意几点: 调试文件的选择...

  • debugger调试

    在需要调试的函数中添加debugger 按 F8 F10 F11进行调试代码 F8为跳到下一个断点,F10为执行...

  • Idea的编译和调试快捷键

    shift+F10:编译 shift+F9:调试按下调试后,在Intellij下方出现如下的界面 F9:调试窗口的...

网友评论

      本文标题:一个.net调试引发的错误A call to PInvoke f

      本文链接:https://www.haomeiwen.com/subject/nawuwdtx.html