访问Delphi DLL抛出ocasional异常

时间:2011-12-16 11:25:47

标签: c# delphi interop pinvoke delphi-7

当我调用Dll方法时,它有时会引发异常,有时则不会。

我这样称呼它:

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.

有没有人知道我为什么只得到这个例外有时候?为什么它有时会顺利运行?

2 个答案:

答案 0 :(得分:14)

你明显地在p / invoke代码和Delphi代码之间存在不匹配。您没有显示Delphi代码,但C#代码足以知道Delphi代码应该是什么样的。

您的DllImport属性使用默认值来调用约定和字符集。这意味着调用约定为stdcall,字符集为ANSI。您尚未指定任何编组属性,因此必须使用默认编组。

因此,您的Delphi代码必须如下所示:

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

现在问题就在于此。 p / invoke marshaller以非常特殊的方式处理string返回值。它假定p / invoke marshaller负责释放返回值的内存。它必须使用与本机代码相同的分配器。 marshaller的假设是将使用共享的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和Delphi侧的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
);

<强>的Delphi

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

答案 1 :(得分:2)

对我来说,使用UnmanagedType.BStr将Delphi WideString编组为.Net字符串在In和Out参数的情况下非常有效。但是在函数返回字符串的情况下它会失败。我有一个Delphi函数 -

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”单元不会改变任何东西。