2011-12-16 62 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”单元的使用不会改变任何东西。