2011-12-16 60 views
1

當我調用Dll方法時,它有時會引發異常,有時不會引發異常。訪問Delphi DLL引發ocasional異常

我打電話這樣說:

public class DllTest 
{ 

    [DllImport(@"MyDll.dll")] 
    public extern static string MyMethod(string someStringParam); 
} 


class Program 
{  

    static void Main(string[] args) 
    { 
     DllTest.MyMethod("SomeString"); 
    } 
} 

而例外,我得到有時是這樣的:

AccessViolationException

Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

沒有人有任何想法,爲什麼我只得到此異常有時候?爲什麼它有時會順利運行?

+0

DLL中函數的調用約定是什麼? – ain 2011-12-16 11:31:50

+2

請顯示Delphi方法的代碼 – 2011-12-16 11:32:08

+0

Delphi中函數的大機會聲明不同於C#的對應方 – 2011-12-16 11:39:22

回答

14

你很明顯在p/invoke代碼和Delphi代碼之間不匹配。您沒有顯示Delphi代碼,但是C#代碼足以知道Delphi代碼應該是什麼樣子。

DllImport屬性使用默認值調用約定和字符集。這意味着調用約定是stdcall,字符集是ANSI。您尚未指定任何編組屬性,因此必須使用默認編組。

因此你的Delphi代碼必須是這樣的:

function MyMethod(someStringParam: PChar): PChar; stdcall; 
begin 
    Result := ??; 
end; 

而現在這裏的問題。 p/invoke編組人員以一種非常特殊的方式處理一個string返回值。它假定p/invoke編組人員負責解除分配返回值的內存。它必須使用與本地代碼相同的分配器。編組人員所做的假設是共享COM分配器將被使用。

所以規則是,本地代碼必須通過調用CoTaskMemAlloc來分配帶有COM分配器的內存。我敢打賭,你的代碼沒有這樣做,那肯定會導致錯誤。

下面是一個如何創建與代碼中的C#簽名配合使用的原生Delphi函數的示例。

function MyMethod(someStringParam: PChar): PChar; stdcall; 
var 
    Size: Integer; 
begin 
    Size := SizeOf(Char)*(StrLen(someStringParam)+1);//+1 for zero-terminator 
    Result := CoTaskMemAlloc(Size); 
    Move(someStringParam^, Result^, Size); 
end; 

雖然你可以採用這種方式我推薦的替代方案。將所有的字符串統一爲C#端的BSTR和德爾福端的WideString。這些是由COM分配器分配的匹配類型。雙方都知道如何處理這些類型,並會讓你的生活更輕鬆。

不幸的是,您不能從Delphi函數跨越互操作邊界返回WideString,因爲Delphi使用不同的ABI函數返回值。這個問題的更多細節可以在我的問題中找到Why can a WideString not be used as a function return value for interop?

所以要解決這個問題,我們可以聲明Delphi代碼的返回類型爲TBStr。然後,您的代碼應該是這樣的:

C#

[DllImport(@"MyDll.dll")] 
[return: MarshalAs(UnmanagedType.BStr)] 
private static extern string MyMethod(
    [MarshalAs(UnmanagedType.BStr)] 
    string someStringParam 
); 

德爾福

function MyMethod(someStringParam: WideString): TBStr; stdcall; 
begin 
    Result := SysAllocString(POleStr(someStringParam)); 
end; 
2

對於我來說,德爾福WideString的編組的使用UnmanagedType.BStr淨字符串工作得很好在輸入和輸出參數的情況下。但在函數返回字符串的情況下失敗。我有一個德爾福功能 -

function WS(val: WideString): WideString; stdcall; 
begin 
    result := val; 
end; 

procedure WS1(out result: widestring); stdcall; 
begin 
    result := 'ABCDE'; 
end; 

和記者的.Net聲明 -

[DllImport(@"my.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)] 
[return: MarshalAs(UnmanagedType.BStr)] 
static extern string WS(
    [MarshalAs(UnmanagedType.BStr)] 
    string val 
); 

[DllImport("my.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)] 
static extern void WS1(
    [MarshalAs(UnmanagedType.BStr)] 
    out string res); 

打電話給WS1()工作得很好,而WS()拋出一個例外。異常取決於Delphi項目中包含的單元。如果包含「SysUtils」或「Classes」,則.Net應用程序會引發SEHException「外部組件已拋出異常」,如果兩個單元都被排除,應用程序將顯示「009C43B4運行時錯誤203」錯誤對話框並終止其執行。順便說一下,「ShareMem」單元的使用不會改變任何東西。