6

所以我有一个我正在使用的本地 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;
    }

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

helloworld.exe 中 0x04d1004c 处的未处理异常: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 对象,从而使应用程序崩溃。

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

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

4

4 回答 4

4

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

 [UnmanagedFunctionPointer(CallingConvention.Cdecl)]

在回调委托声明上。

于 2009-09-11T20:43:18.430 回答
0

这并不能直接回答您的问题,但它可能会引导您朝着正确的方向前进,即调试模式好与发布模式不好:

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

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

于 2009-09-11T19:34:35.873 回答
0

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

几点:

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 回调或调用函数?

于 2009-09-16T17:08:10.733 回答
0

我和@jdehaan 在一起,除了 CallingConvetion.StdCall 可能是答案,尤其是当 3rd 方库是用 BC++ 编写的时候。

于 2009-09-16T19:15:20.040 回答