UnmanagedFunctionPointer 在使用 .NET 4.0 时会导致 ***,3.5 有效

Posted

技术标签:

【中文标题】UnmanagedFunctionPointer 在使用 .NET 4.0 时会导致 ***,3.5 有效【英文标题】:UnmanagedFunctionPointer causes *** when using .NET 4.0, 3.5 works 【发布时间】:2016-10-17 01:41:25 【问题描述】:

我在点击处理程序中有一个简单的函数,它有一个 try catch 块。如果我在这个 try catch 块中抛出异常,它会成功捕获异常。

如果我在抛出异常之前调用了非托管 DLL,则异常未处理且未捕获。

什么是未管理的 DLL 调用可能会破坏我的程序异常处理?

如果我在调试模式下运行程序,即使所有异常都未勾选“异常中断”,它也会捕获异常。应用程序不会崩溃并按预期运行。

如果我以“启动而不调试”的方式运行程序并在它崩溃时点击调试,我会收到以下错误“堆栈 cookie 检测代码检测到基于堆栈的缓冲区溢出”

编辑: 看来堆栈溢出破坏了异常处理

我附上了一个导致崩溃的简化程序。

ISOConnection _comm;  //This is instantiated at another time in the same thread

//C# test function that crashes when run without a debugger attached
bool DoMagic()

    try
    
        //if I uncomment this line the exception becomes unhandled and cannot be caught
        //_comm.ConnectISO15765();

        throw new Exception();
    
    catch (Exception ex)
    
        MessageBox.Show("Caught exception")
    

//Within ISOConnection class
public void ConnectISO15765()
    ...
    lock(syncLock)
        uint returnCode = J2534Interface.PassThruConnect((uint)DeviceId, (uint)ProtocolID.ISO15765, (uint)ConnectFlag.NONE, (uint)BaudRate.ISO15765, ref ChannelId);


//C# UnmanagedFunctionPointer allocation code
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate uint PassThruConnect(uint deviceId, uint protocolId, uint flags, uint baudRate, ref uint channelId);
public PassThruConnect Connect;

[DllImport("kernel32.dll")]
public static extern IntPtr LoadLibrary(string dllToLoad);

m_pDll = NativeMethods.LoadLibrary(path);
...
pAddressOfFunctionToCall = NativeMethods.GetProcAddress(m_pDll, "PassThruConnect");
if (pAddressOfFunctionToCall != IntPtr.Zero)
    Connect = (PassThruConnect)Marshal.GetDelegateForFunctionPointer(
        pAddressOfFunctionToCall,
        typeof(PassThruConnect));

//C++ function declaration
long PassThruConnect(unsigned long DeviceID, unsigned long ProtocolID, unsigned long Flags, unsigned long Baudrate, unsigned long *pChannelID);

更新

如果我将调用 UnmanagedFunctionPointer PassThurConnect 替换为以下内容,则不会发生崩溃

[DllImport("op20pt32.dll", EntryPoint = "PassThruConnect", CallingConvention = CallingConvention.Cdecl)]
public static extern uint PassThruConnect2(uint deviceId, uint protocolId, uint flags, uint baudRate, ref uint channelId);

在分配 UnmanagedFunctionPointer 时是否存在我没​​有执行或执行不正确的事情,这会导致缺少调试器来创建 *** 崩溃?

更奇怪的是,这段代码在几周前似乎就可以工作了。主要变化是 try catch 在另一个线程中,而我没有使用 lock(syncLock)。现在一切都在一个线程中,但是在 BackgroundWorker 中运行时也会发生同样的崩溃。

更新 #2 问题半解决

好的,所以我一一回滚了我的提交,直到它起作用。改变的是我从 .NET 3.5 转到 .NET 4.0

.NET 3.5 无论是否附加调试器都不会崩溃。如果未附加调试器,.NET 4.0 会崩溃。为了排除我的代码中的错误,我只是删除了我的日志的 ConcurrentQueue(我正在使用的唯一 4.0 功能)并将我当前的代码库转换回 3.5,我没有收到此错误。

为了 100% 确定这是 4.0 的问题,然后我将代码库从 3.5 转换回 4.0,并将 ConcurrentQueue 排除在外(实际上只是更改了构建选项并进行了重建),*** 崩溃又回来了。

我更喜欢使用 4.0,有什么想法可以调试这个问题吗?

编辑:.NET 4.6.1 也崩溃

更新 #3 http://codenition.blogspot.com.au/2010/05/pinvokestackimbalance-in-net-40i-beg.html

显然pinvokestackimbalance在.NET 3.5中基本上被忽略了,所以问题仍然存在,它只是不会让我的应用程序崩溃。

将以下代码添加到 App.Config 会导致 .NET 在转换回托管代码时修复堆栈。对性能的影响很小,但可以解决问题。

虽然这确实解决了问题,但我想知道我的 UnmanagedFunctionPointer 有什么问题首先导致了问题。

<configuration> 
  <runtime> 
    <NetFx40_PInvokeStackResilience enabled="1"/>

编辑:这个帖子不是重复的,另一个被删除了......

【问题讨论】:

.Net 代码无法捕捉到几个致命的异常——我相信其中之一是堆栈损坏。很可能您的互操作包装器错误并且堆栈未对齐......或者本机代码很糟糕。 好的,所以我在托管调用之前添加了 Debugger.Launch() 并引发 *** 异常,这无疑会破坏异常处理。为什么从一开始就附加调试器时不会发生这种情况? 好的,如果我使用具有相同函数原型的 dllimport,我不会得到 ***。使用非托管函数指针时我还需要做些什么吗? 您应该edit您的帖子,其中包含您找到的信息,以便更多人看到(编辑将帖子移回主页)......所以在互操作方面有更多经验的人可以回答。我还建议在 DLL 中添加函数的 C 签名,因为它可以帮助某人回复。 (如果我需要做类似的事情,我只需在 pinvoke.net 上找到类似的功能并复制它的声明方式......) 谢谢。更新了更多信息。 【参考方案1】:

好的,问题是调用约定应该是 StdCall 而不是 Cdecl

这是有道理的,因为通用 J2534 API 文档指定了以下标头。尽管提供给我的头文件并未制定此规范。

extern "C" long WINAPI PassThruConnect
(
unsigned long ProtocolID;
unsigned long Flags
unsigned long *pChannelID
)

WINAPI 也称为 StdCall,而不是像大多数 C/C++ 库通常使用的 Cdecl。

.NET 3.5 允许错误的调用约定并将“修复”堆栈。从 4.0 开始,情况不再如此,并且引发了 PinvokeStackImbalance 异常。

您可以强制 4.0 也通过将以下代码添加到您的 App.Config 来修复堆栈

<configuration> 
  <runtime> 
    <NetFx40_PInvokeStackResilience enabled="1"/>

或者您可以通过将 Cdecl 更改为 StdCall 来简单地修复您的调用约定:

[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate uint PassThruConnect(uint deviceId, uint protocolId, uint flags, uint baudRate, ref uint channelID);

【讨论】:

以上是关于UnmanagedFunctionPointer 在使用 .NET 4.0 时会导致 ***,3.5 有效的主要内容,如果未能解决你的问题,请参考以下文章

反射__获取delegate的信息

C# P/Invoke:可变参数委托回调

从 C++ 到 C# 的结构中的 Marshal 回调

C# 尝试读取或写入受保护的内存。这通常指示其他内存已损坏

尝试读取或写入受保护的内存。这通常表明 c# 中的其他内存已损坏(访问冲突)