2009-09-11 199 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++,例如。