【问题标题】:C# Interop Delphi DLLC# 互操作 Delphi DLL
【发布时间】:2013-08-25 03:02:12
【问题描述】:

我有一个用 Delphi "a.dll" 编写的第三方 DLL(无源代码)。

这个 DLL 有一个带有这个签名的方法。

function GetAny(pFileName: String): String;

我无法从 c# 进行互操作调用,因为“字符串类型”在 delphi 中具有私有访问权限。

因此,在 delphi 中构建另一个 DLL 来包装该调用。

德尔福。

function GetAny(pFileName: String): String; external 'a.dll'

function GetWrapper(url : PChar) : PChar; stdcall;
begin
    Result := PChar(GetAny(url)); // I need avoid this String allocation, is throwing a exception.
end;

C#。

[DllImport("wrapper.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
public static extern IntPtr GetWrapper(String url);

在“GetWrapper”内部,我调用了外部“GetAny”,结果正常(在 delphi 中我可以调试),但在我将这个结果返回到 c# 端之前,它抛出了一个异常。

IntPtr test = GetWrapper("a");
String result = Marshal.PtrToStringAnsi(test);

【问题讨论】:

  • 将您的 Delphi 包装器更改为接受 char 目标缓冲区的过程。

标签: c# delphi pinvoke


【解决方案1】:

您的包装 DLL 也无法调用 GetAny,因为字符串是托管的 Delphi 类型,不能跨模块边界传递。

问题是 GetAny 的返回值在一个模块中分配并在另一个模块中释放。它在实现 GetAny 的 DLL 中分配,并在调用 GetAny 的 DLL 中释放。由于这两个 DLL 使用不同的内存管理器,因此您最终会尝试释放分配在不同堆上的内存。

如果可以说服实现 GetAny 的 DLL 共享内存管理器,那么您可以轻松解决该问题。

不过,我确实质疑您提出的事实。就目前而言,除非 DLL 设计为与 ShareMem 一起使用,否则永远无法安全地调用该函数。

如果你准备泄漏内存,你可以试试这个:

德尔福

function GetAny(pFileName: string): PChar; external 'a.dll'

procedure GetWrapper(url: PChar; out value: WideString); stdcall;
var
  P: PChar;
begin
    P := GetAny(url);
    if Assigned(P) then
      Value := P
    else
      Value := '';
end;

C#

[DllImport("wrapper.dll"]
public static extern void GetWrapper(
    string url,
    [MarshalAs(UnmanagedType.BStr)]
    out string value
);

【讨论】:

  • 我开始认为问题不在于“String cast”,我做了一些测试,我相信 GetAny ("a.dll") 会引发异常。
  • 我所说的是准确的。除非 DLL 用于 ShareMem,否则无法安全地调用该函数。根据 Q 中的事实,我的回答是准确的。
  • 有人可以看看吗?两个简单的项目,还是谢谢。 dropbox.com/s/ci2l431tsku52n7/C%23_Delphi%20Interop.rar
【解决方案2】:

我已经下载了你的代码...

解决方案可能是这样的:

  • 在 Delphi 中使用“cdecl”声明创建一个包装过程,其中包含 2 个 PChar 类型的参数

    • 第一个是IN参数
    • 第二个是OUT参数

原德尔福函数:

function GetAny(pFileName: String): String; external 'a.dll';

Delphi – 带有包装函数的 DLL:

 procedure GetWrapper (url: PChar; var urlNew: PChar) cdecl;
 var str: string;
 begin
      urlStr = string(url);
      urNewStr := GetAny(urlStr);
      urlNew := PChar(urNewStr);
 end;

 exports
    GetWrapper;
 begin
 end.
  • 在 Visual Studio 中:

将项目设为 x32 位(不是示例中的 x64)

  • 将 DLL 导入为 Cdecl

[DllImport("wrapper.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]

  • 编组

public static extern void GetWrapper ([MarshalAs(UnmanagedType.LPStr)]string url, [MarshalAs(UnmanagedType.LPStr)] out string urlNew);

  • 在 C# 中调用:

    字符串文件名;// = @"wertwertwertwertwer";

    GetWrapper("2.jpg", out fileName);

    Console.WriteLine(fileName);

在我的环境中它有效。 (德尔福 5 和 VS2012)。

希望它也对你有用。

【讨论】:

  • 这不能回答问题。它也完全被破坏了,因为您返回了一个指向局部变量的指针。而且您还要求编组器对未使用 CoTaskMemAlloc 分配的内容调用 CoTaskMemFree。
  • 很多原因。首先,fnNew 指向的内存在函数返回后立即被释放。其次,marshaller 的东西要释放,并在返回的指针上调用CoTaskMemFree。最后,您从未将函数称为 DLL,GetAny,这正是问题的重点。正如我所解释的,这甚至是不可能的!
  • 大卫,你能解释一下为什么它不起作用吗? Rafael 创建了一个包装函数,我建议对其进行一些更改。用 2 个参数创建一个过程,而不是函数。
  • @David,请原谅我的问题重复(我只是想编辑评论)。谢谢你的解释。我不知道 Marshaling 是如何分配和释放内存的。我已经为可能感兴趣并会选择合适解决方案的开发人员编辑了上面的函数名称。 (在上一篇文章中,我只是使用了 Rafael 在他的 DropBox 文件夹中提供的函数名称)
  • 1.在 Win32 DLL 中使用 Delphi String 作为参数是不对的。因此,函数函数 GetAny(pFileName: String): String;不能在 Delphi 之外访问。 2. PChar 类型的包装器对我来说听起来不错。 3. 在这种情况下,建议的解决方案是创建一个具有两个 PChar 参数的过程:一个是 IN 参数,另一个是 OUT 参数。 4. 从 C# 代码调用此函数。 (可能是我尝试的 Marshaling 不是一个正确的解决方案 - David 是该领域的专家,也许他可以提供帮助)。
猜你喜欢
  • 1970-01-01
  • 2012-04-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-15
  • 2012-07-19
相关资源
最近更新 更多