【问题标题】:Accessing Delphi DLL throwing ocasional exception访问 Delphi DLL 偶尔抛出异常
【发布时间】:2012-01-21 22:34:43
【问题描述】:

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

试图读取或写入受保护的内存。这通常表明其他内存已损坏。

有人知道为什么我有时只得到这个异常吗?为什么有时运行流畅?

【问题讨论】:

  • DLL中函数的调用约定是什么?
  • 请出示Delphi方法的代码
  • Delphi 中函数的大概率声明与 C# 对应的函数不同

标签: c# delphi interop pinvoke delphi-7


【解决方案1】:

您显然在 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 负责释放返回值的内存。它必须使用与本机代码相同的分配器。编组器的假设是共享 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
);

德尔福

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

【讨论】:

  • 您的回答很棒,解决了很多问题。刚才我意识到我将 extern 方法发布为字符串,但它是无效的。但这没问题,因为它让你考虑到这一点,它只是添加到答案中。不过,你介意再给我一个例子吗?我应该如何使用带有 ref (out) 传递的字符串参数的 Delphi 函数?非常感谢您长时间清晰而精彩的回答,您真的帮助了我。
  • 使用BSTR/WideString 进行操作。在 Delphi 端是 var 或 out 参数,在 C# 端是 ref 或 out。真的,BSTR/WideString 是让这一切变得简单的方法。
  • 太棒了。再次非常感谢。 :)
  • 在 Delphi 5 中,当我将返回类型设置为 WideString 时,dll 崩溃。 .Net 应用程序报告“外部组件已引发异常”。这可能是Delphi 5的问题吗?我完全复制了你的例子。
  • @Jamie WideString 的变体不起作用。我在写完这个答案后才知道这一点。在此处查看我的问题:stackoverflow.com/questions/9349530/… 该问题提供了两种解决方法
【解决方案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”单元的使用不会改变任何东西。

【讨论】:

猜你喜欢
  • 2013-02-22
  • 2012-05-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-29
  • 1970-01-01
  • 2017-09-18
相关资源
最近更新 更多