【问题标题】:Access Violation Exception/Crash from C++ callback to C# function从 C++ 回调到 C# 函数的访问冲突异常/崩溃
【发布时间】:2010-11-27 12:31:09
【问题描述】:

所以我有一个我正在使用的本地 3rd 方 C++ 代码库(.lib 和 .hpp 文件),我曾经在 C++/CLI 中构建一个包装器,以便最终在 C# 中使用。

从调试模式切换到发布模式时,我遇到了一个特殊问题,即当回调的代码返回时,我得到了访问冲突异常。

回调函数格式的原始hpp文件中的代码:

typedef int (*CallbackFunction) (void *inst, const void *data);

来自 C++/CLI Wrapper 的回调函数格式代码: (稍后我会解释为什么我声明了两个)

public delegate int ManagedCallbackFunction (IntPtr oInst, const IntPtr oData);
public delegate int UnManagedCallbackFunction (void* inst, const void* data);

--很快,我声明第二个“UnManagedCallbackFunction”的原因是我试图在包装器中创建一个“中间”回调,因此链从 Native C++ > C# 更改为 Native C++ > C++/CLI 的版本Wrapper > C#...完全披露,问题仍然存在,它刚刚被推送到 C++/CLI Wrapper 现在在同一行(返回)。

最后,来自 C# 的崩溃代码:

public static int hReceiveLogEvent(IntPtr pInstance, IntPtr pData)
    {
        Console.WriteLine("in hReceiveLogEvent...");
        Console.WriteLine("pInstance: {0}", pInstance);
        Console.WriteLine("pData: {0}", pData);

        // provide object context for static member function
        helloworld hw = (helloworld)GCHandle.FromIntPtr(pInstance).Target;
        if (hw == null || pData == null)
        {
            Console.WriteLine("hReceiveLogEvent: received null instance pointer or null data\n");
            return 0;
        }

        // typecast data to DataLogger object ptr
        IntPtr ip2 = GCHandle.ToIntPtr(GCHandle.Alloc(new DataLoggerWrap(pData)));
        DataLoggerWrap dlw = (DataLoggerWrap)GCHandle.FromIntPtr(ip2).Target;

        //Do Logging Stuff

        Console.WriteLine("exiting hReceiveLogEvent...");
        Console.WriteLine("pInstance: {0}", pInstance);
        Console.WriteLine("pData: {0}", pData);
        Console.WriteLine("Setting pData to zero...");
        pData = IntPtr.Zero;
        pInstance = IntPtr.Zero;
        Console.WriteLine("pData: {0}", pData);
        Console.WriteLine("pInstance: {0}", pInstance);

        return 1;
    }

对控制台的所有写入都已完成,然后我们在返回时看到可怕的崩溃:

在 0x04d1004c 中未处理的异常 helloworld.exe:0xC0000005:访问 违规读取位置0x04d1004c。

如果我从这里进入调试器,我所看到的只是调用堆栈上的最后一个条目是: > "04d1004c()" 其计算结果为十进制值:80805964

只有当您查看显示以下内容的控制台时才会感兴趣:

entering registerDataLogger
pointer to callback handle: 790848
fp for callback: 2631370
pointer to inst: 790844
in hReceiveLogEvent...
pInstance: 790844
pData: 80805964
exiting hReceiveLogEvent...
pInstance: 790844
pData: 80805964
Setting pData to zero...
pData: 0
pInstance: 0

现在,我知道在调试和发布之间,在 Microsoft 世界中有些事情是完全不同的。我当然担心变量的字节填充和初始化,所以如果有什么我没有在这里提供,请告诉我,我会添加到(已经很长的)帖子中。我还认为托管代码可能不会释放所有所有权,然后本机 C++ 内容(我没有代码)可能会尝试删除或终止 pData 对象,从而使应用程序崩溃。

更全面的披露,在调试模式下一切正常(似乎)!

一个真正的头疼问题,希望得到任何帮助!

【问题讨论】:

    标签: c# c++ c++-cli callback access-violation


    【解决方案1】:

    This doesn't directly answer your question,但它可能会引导您朝着正确的方向前进,调试模式可以,而发布模式不行:

    由于调试器向堆栈中添加了大量记录信息,通常会在内存中填充程序的大小和布局,因此我在调试模式下“走运”了,在 912 字节的内存中乱涂乱画。非常重要。但是,如果没有调试器,我会在相当重要的事情上乱涂乱画,最终走出自己的内存空间,导致 Interop 删除不属于它的内存。

    DataLoggerWrap 的定义是什么?对于您接收的数据,char 字段可能太小。

    【讨论】:

      【解决方案2】:

      我认为堆栈由于调用约定不匹配而被压碎: 试试把属性放上去

       [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
      

      关于回调委托声明。

      【讨论】:

      • 对于支持,这基本上是正确的。在联系了第 3 方供应商后,我们发现他们使用 cdecl 规范而不是托管代码合规性所需的 stdcall 进行编译:msdn.microsoft.com/en-us/library/367eeye0%28VS.80%29.aspx。我在 StackOverflow 上添加了一个问题,问为什么需要这样?希望有人能给出比参考的 MSDN 文章更好的解释。
      • 在项目设置中,如果未使用 __declspec() 指定,则使用的调用约定 (C/C++) 有一个默认值。此调用约定在代码中不可见。不匹配的约定会发生什么很清楚:如果堆栈清理的责任不匹配,它会压碎堆栈(由于双重清理或太少而不会重置到调用之前的状态)。这取决于堆栈上传递的参数数量。 en.wikipedia.org/wiki/Calling_convention
      【解决方案3】:

      我不确定你想要达到什么目的。

      几点:

      1) 垃圾收集器在释放模式下更具攻击性,因此在所有权不正确的情况下,您描述的行为并不少见。

      2) 我不明白下面的代码试图做什么?

      IntPtr ip2 = GCHandle.ToIntPtr(GCHandle.Alloc(new DataLoggerWrap(pData)));
      DataLoggerWrap dlw = (DataLoggerWrap)GCHandle.FromIntPtr(ip2).Target;
      

      您使用 GCHandle.Alloc 将 DataLoggerWrap 的实例锁定在内存中,但您从未将其传递给非托管 - 那么为什么要锁定它呢? 你也从来没有释放它?

      然后第二行获取一个引用 - 为什么是循环路径?为什么引用 - 你从不使用它?

      3) 您将 IntPtrs 设置为 null - 为什么? - 这在函数范围之外没有任何影响。

      4) 你需要知道回调的合约是什么。谁拥有 pData 回调或调用函数?

      【讨论】:

        【解决方案4】:

        我支持@jdehaan,除了 CallingConvetion.StdCall 可能是答案,尤其是当 3rd 方库是用 BC++ 编写时。

        【讨论】:

          猜你喜欢
          • 2012-11-14
          • 1970-01-01
          • 2016-03-27
          • 1970-01-01
          • 1970-01-01
          • 2022-01-07
          • 1970-01-01
          • 2021-05-11
          • 2022-01-08
          相关资源
          最近更新 更多