访问Delphi DLL时偶尔会抛出异常

本文关键字:抛出异常 偶尔 Delphi DLL 访问 | 更新日期: 2023-09-27 18:18:08

当我调用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.

有谁知道为什么我只得到这个异常有时?为什么它有时运行得很平稳?

访问Delphi DLL时偶尔会抛出异常

显然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,在Delphi端封送为WideString。这些匹配类型也由COM分配器分配。双方都确切地知道如何处理这些类型,这将使你的生活更轻松。

不幸的是,你不能从Delphi函数跨互操作边界返回WideString,因为Delphi对函数返回值使用不同的ABI。这个问题的更多细节可以在我的问题中找到为什么WideString不能用作互操作的函数返回值?

因此,为了解决这个问题,我们可以在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;

对于我来说,使用UnmanagedType将Delphi WideString编组为。net字符串。BStr在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 "External component has thrown an exception",如果这两个单元都被排除,应用程序会显示一个"Runtime error 203 at 009C43B4"错误对话框并终止其执行。顺便说一句,使用"ShareMem"单元不会改变任何东西。