2009-09-11 193 views
6

因此,我有一个本机第三方C++代码库我正在使用(.lib和.hpp文件),我用它在C++/CLI中构建包装以供最终使用在C#中。访问冲突异常/从C++回调崩溃到C#函数

从调试模式切换到发布模式时遇到特定问题,因为回调代码返回时出现访问冲突异常。

从回调函数的格式原来的HPP文件的代码:

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

从C++/CLI包装代码的回调函数格式: (我会解释为什么我在某一时刻宣布二)

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

--Quickly,原因我声明一个第二“UnManagedCallbackFunction”是我试图创建在包装的“中介”回调,所以链条从本机C++> C#改变为版本本机C++>的C++/CLI包装器> C#...完全公开这个问题仍然存在,它现在被推到了C++/CLI包装器的同一行(返回)。

最后,从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 

现在,我知道,调试之间并释放出一些东西都在微软的世界完全不同。当然,我担心字节填充和变量的初始化,所以如果有些东西我不在这里提供,只需告诉我,我将添加到(已经很长)的文章中。我也认为托管代码可能不会释放所有权,然后本机C++的东西(我没有代码)可能试图删除或关闭pData对象,从而导致应用程序崩溃。

更全面的披露,这一切工作正常(看似)在调试模式!

一个真正的头刮伤问题,将不胜感激任何帮助!

回答

3

我认为堆栈被压碎,因为不匹配调用约定: 尝试把属性

[UnmanagedFunctionPointer(CallingConvention.Cdecl)] 

在回调委托声明。

+0

对于支持,这是最正确的。联系第三方供应商后,我们发现他们使用cdecl规范进行编译,而不是托管代码合规性所需的stdcall:http://msdn.microsoft.com/en-us/library/367eeye0%28VS.80%29.aspx 。我在StackOverflow上添加了一个问题,询问为什么需要这样做?希望有人会比引用的MSDN文章给出更好的解释。 – TomO 2009-09-21 15:14:30

+0

如果未使用__declspec()指定,则在项目设置中会使用调用约定(C/C++)的默认值。该调用约定在代码中不可见。不匹配约定会发生什么情况很明显:如果堆栈清理的责任不匹配,则会压碎堆栈(由于双重清理或太少而未重置为调用之前的状态)。这取决于栈上传递的参数的数量。 http://en.wikipedia.org/wiki/Calling_convention – jdehaan 2009-09-21 16:20:22

0

This doesn't directly answer your question,但它可能就导致你在正确的方向调试模式好与释放模式不行:

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

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

0

我不确定你在努力达到什么目的。

的几点:

1)垃圾收集器是在释放模式更具侵略性所以不好拥有你所描述的现象并不少见。

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

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

您使用GCHandle.Alloc在内存中锁定DataLoggerWrap的实例,但你永远不会把它传递出非托管 - 那么你为什么锁定? 你也永远免费吗?

第二行然后抓回参考 - 为什么圆形路径?为什么参考 - 你永远不会使用它?

3)您将IntPtrs设置为空 - 为什么? - 这在功能范围之外没有任何效果。

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

0

我与@jdehaan,除了CallingConvetion.StdCall可能是答案,尤其是当第三方的lib是写在BC++,例如。