2011-07-15 111 views
3

我想與一個Dll實現幾個函數接口,其中一個接受一個空終止的字符串和一個int,並返回一個空終止的字符串。我試圖用這樣的方法接口:DllImport返回null終止字符串AccessViolationException

[DllImport(dll_loc)] 
[return : MarshalAs(UnmanagedType.LPStr)] 
public static extern StringBuilder GetErrorMessage([MarshalAs(UnmanagedType.LPStr)] 
                 StringBuilder message, 
                 int error_code); 

我再嘗試調用這樣的方法:

StringBuilder message = new StringBuilder(1000); 
StringBuilder out2 = new StringBuilder(1000); 
out2 = GetErrorMessage(message, res0); 

然而,當我嘗試這一點,AccessViolationException拋出告訴我,我是試圖訪問受保護的內存。

我是成功的在聲明不同的方法,如:

[DllImport(dll_loc)] 
public static extern int GetVersion([MarshalAs(UnmanagedType.LPStr)] 
             StringBuilder version); 

,把它以同樣的方式,但這種方法不適用於當前函數調用運行。

我也試過返回一個IntPtr,因爲文檔技術上說該方法返回一個指向字符串緩衝區的第一個字符的指針,但無濟於事。

有沒有人對這裏可能出現的問題有所瞭解?這兩種方法可能會導致dll嘗試訪問它不應該訪問的內存。或者,你會如何建議去調試這個問題?

+0

在禁止某些Windows DLL時遇到類似問題。我發現參數中的一些小調整導致了巨大的改進。即使是一些錯誤的編組/數據類型/等等。造成巨大的分歧。你必須仔細看看你的非託管DLL接口的文檔。 –

回答

10

返回字符串的C函數是內存管理問題。 C字符串需要一個數組,並且該字符串消耗後需要釋放該數組的內存。這使得這些函數很難在C程序中使用,幾乎不可能與pinvoke一起使用。它也是一個經典的C錯誤,它返回一個指向棧上字符串的指針。

pinvoke編組將試圖釋放返回的字符串,以避免內存泄漏。它將使用CoTaskMemFree()。這通常不會很好,但C代碼實際上使用CoTaskMemAlloc爲數組分配內存很少。在XP上,這會導致無聲的內存泄漏。 Vista和Win7具有更嚴格的堆管理器,如果連接了非託管調試器,它們將調用調試中斷。然後用一個AccessViolation炸掉程序。

通過將返回類型聲明爲IntPtr並封送字符串,可以避免自動封送處理行爲。通常使用Marshal.PtrToStringAnsi()。但是,你仍然面臨釋放內存的任務。你不能,你沒有CRT堆的句柄,你不能調用free()。

返回字符串的C函數應該使用傳遞字符串緩衝區的參數聲明。還有一個說法是說緩衝區有多大。像這樣:

int GetErrorMessage(int errorCode, char* buffer, size_t bufferSize); 

現在很簡單,調用者可以爲緩衝區分配內存並釋放它。 C函數只是將字符串複製到緩衝區中。返回值可用於指示覆制了多少個字符,或指示需要更大的緩衝區。不要吝嗇bufferSize參數,緩衝區溢出是致命的,並且調用可怕的FatalExecutionEngineError異常,這是一種與AV無法混合的異常,因爲GC堆損壞直到很晚才被檢測到。您在C#端使用StringBuilder,並使用非零容量進行適當初始化。 bufferSize的值。

+0

不幸的是,我不能訪問通過dll定義函數調用I接口的C代碼。如果我從託管代碼中調用函數時忽略返回值,而只是傳遞一個字符串緩衝區,那麼是否仍然存在內存泄漏問題?或者這會解決問題。如果有內存泄漏,我可以在返回的IntPtr上調用'Marshal.FreeHGlobal'來釋放內存嗎? – Patrick

+0

再看看聲明,這個函數可能會簡單地返回它被調用的指針。在這種情況下,你沒有問題,聲明它在[DllImport]聲明中返回void,所以pinvoke編組不再嘗試做正確的事情。使用message.ToString()來恢復返回的字符串。如果它實際上返回了一個不同的字符串指針,那麼你卡住了,你不能捏住它。 –

1

System.IntPtr替換所有StringBuilder的,分配只有message VAR與System.Runtime.InteropServices.Marshal.AllocHGlobal

然後轉換messageout2串與PtrToStringAuto

然後調用System.Runtime.InteropServices.Marshal.FreeHGlobal

對於我這樣總是工作當處理char*wchar_t*

+0

我不確定如何做到這一點。我嘗試定義爲公共靜態外部IntPtr GetErrorMessage(IntPtr消息,詮釋error_code);然後調用:'IntPtr消息=新的IntPtr(); Marshal.AllocHGlobal(1000); IntPtr out2 = GetErrorMessage(message,res0);',但是拋出了相同的異常。難道我做錯了什麼? – Patrick

+0

IntPtr message = Marshal.AllocHGlobal(1000); – Djole