【问题标题】:Using Delphi DLL with dynamic array from C#将 Delphi DLL 与来自 C# 的动态数组一起使用
【发布时间】:2011-11-13 01:53:26
【问题描述】:

我有一个包含以下类型的 Delphi DLL:

type
  TStepModeType = (smSingle, smMultiStep);

  TParameter = record
    Number: Integer;
  end;

  TStruct = record
    ModType: PAnsiChar;
    ModTypeRev: Integer;
    ModTypeID: Integer;
    RecipeName: PAnsiChar;
    RecipeID: Double;
    RootParamCount: Integer;
    StepMode: TStepModeType;
    ParamCount: Integer;
    Parameters: array of TParameter;
  end;

我需要从 C# 调用这个 DLL,传递一个与 DLL 将填充和返回的 Delphi 类型相对应的 ref 对象。我在我的 C# 代码中定义了这样的结构:

enum stepModeType
{
    Single,
    MultiStep
}

[StructLayout(LayoutKind.Sequential)]
struct parameter
{
    public int Number;
}

[StructLayout(LayoutKind.Sequential)]
struct recipe
{
    public string modType;
    public int modTypeRev;
    public int modTypeId;
    public string recipeName;
    public double recipeId;
    public int rootParamCount;
    public stepModeType stepMode;
    public int paramCount;
    public IntPtr parameters;
}

在遇到 Delphi 代码中的动态数组(参数:TParameter 数组)之前,我做得很好。我知道动态数组是 Delphi 唯一的构造,所以我选择在我的 C# 代码中使用 IntPtr 以希望获得指向数组的指针并提取内容。不幸的是,我对这个互操作的东西相当陌生,我不知道如何处理 IntPtr。

假设 Delphi DLL 使用 2 个参数项填充动态数组。一旦从 Delphi DLL 传递回我的 C# 调用应用程序,有人可以向我展示 C# 代码,该代码会将这两个参数项从数组中取出吗?

更新:好吧,碰巧我得到的 Delphi 代码是一个简化版本。我们的一位 Delphi 开发人员认为简化版本比实际版本更容易上手,实际版本包含动态数组的动态数组的动态数组要复杂得多。无论如何,我现在完全超出了我的头脑。我只知道德尔福是危险的。下面是 Delphi 代码中真实结构的代码。任何有关如何从我的 C# 调用应用程序处理这些结构的进一步指导将不胜感激。动态数组的嵌套甚至是不可能的。

type
  TStepModeType = (smSingle, smMultiStep);

  TParamValue = record
    strVal: String;
    fVal: Double;
    Changed: Boolean;
  end;

  TSteps = array of TParamValue;

  TRule = record
    Value: String;
    TargetEnabled: Boolean;
  end;

  TParamInfo = record
    Caption: String;
    Units: String;
    RuleCount: Integer;
    Rules: array of TRule;
  end;

  TParameter = record
    Info: TParamInfo;
    Steps: TSteps;
  end;

  TStruct = record
    ModType: PAnsiChar;
    ModTypeRev: Integer;
    ModTypeID: Integer;
    RecipeName: PAnsiChar;
    RecipeID: Double;
    RootParamCount: Integer;
    StepMode: TStepModeType;
    ParamCount: Integer;
    Parameters: array of TParameter;
  end;

【问题讨论】:

  • Delphi DLL 是否包含释放结构中动态内存的函数?
  • 确实如此,但正如您在回答中指出的那样,我仍在错误地使用字符串字段,这会导致内存问题。

标签: c# delphi pinvoke


【解决方案1】:

我假设 DLL 有一个释放 recipe 结构的函数。这是您不可能希望从 C# 中做的事情。稍后会详细介绍这一点。

Delphi 动态数组不是有效的互操作类型。它真的应该只在使用单一版本的编译器编译的 Delphi 代码内部使用。公开它类似于从 DLL 中导出 C++ 类。

在理想情况下,您将重新编写 Delphi 代码,以便它使用正确的互操作类型导出数组。但是,在这种情况下,您无需调整 Delphi 代码就可以相对容易地进行编组。

Delphi 动态数组早在 Delphi 4 中就被引入了,从那时起它们的实现一直保持不变。 array of T 动态数组变量实际上是指向第一个元素的指针。元素在内存中按顺序排列。动态数组变量还维护(在负偏移处)引用计数和数组的大小。您可以放心地忽略这些,因为您既不需要修改动态数组,也不需要确定它的大小。

IntPtr 用于Parameters 字段是完美的。因为 TParameter 只包含一个 32 位整数,您可以使用 Marshal.Copy 将其直接复制到 int[] 数组中。

因此,当 Delphi DLL 返回时,您可以使用 Marshal.Copy 执行最后的编组步骤。

if (theRecipe.paramCount>0)
{
    int[] parameters = new int[theRecipe.paramCount];
    Marshal.Copy(theRecipe.parameters, parameters, 0, theRecipe.paramCount);
    ... do something with parameters
}

这涉及动态数组,但碰巧你的代码存在另一个问题。您在 C# 结构中将这两个字符串声明为 string。这意味着编组器将负责释放 Delphi DLL 在两个 PAnsiChar 字段中返回的内存。它将通过调用CoTaskMemFree 来实现。我相当确定这与 Delphi 代码中的 PAnsiChar 字段的分配不匹配。

如上所述,我希望此接口的约定是您调用另一个 DLL 函数来释放 recipe 结构引用的堆内存。即两个字符串,以及动态数组。

要从 C# 处理此问题,您需要确保编组器不会尝试解除分配 PAnsiChar 字段。您可以通过在 C# 结构中将它们声明为 IntPtr 来实现。然后调用Marshal.PtrToStringAnsi转换成C#字符串。


为了编写上述代码,我不得不对 Delphi 代码和 C# 代码之间的约定做出一些假设。如果我的任何假设不正确,请更新问题,我会尝试使这个答案匹配!我希望这会有所帮助。

【讨论】:

  • 绝对完美!非常感谢。还要感谢您在当前代码中发现内存问题。还有另一个 DLL 函数可以为配方结构释放内存,但显然我在 C# 代码中错误地使用了字符串类型。
【解决方案2】:

我怀疑行话混乱,我的第一个想法很简单。

公共参数[]参数;

【讨论】:

  • 我一开始也是这么想的,但它不起作用。如果我将 Delphi 代码更改为使用静态大小的数组而不是动态的,则该代码可以完美运行。只要 Delphi 数组是动态调整大小的,我就会为参数返回垃圾。
  • 看来有比我聪明的人替你猜了。
【解决方案3】:

两个选项:要么你弄清楚动态数组是如何存储的并在 c# 端匹配它,或者更好的是仍然在 Delphi 端创建一组基本方法,可以从 c# 端调用来操作数组和记录,例如 getItem 和 setItem 等。这通常是当存在跨语言障碍的不兼容类型时所做的。我会使用后一种方法,因为您不知道将来某个时候动态数组的内存结构是否会发生变化。

对了,你为什么把 TParameter 定义为记录,你可以用 TParameter = integer ?

我发现这个链接对Delphi动态数组的结构有一些要说的:

http://www.programmersheaven.com/mb/delphikylix/262971/262971/dynamic-array-memory-storage/

这个链接有更多细节。结构比简单的数组要复杂一些。

Dynamic Array Structure

【讨论】:

  • 其实Delphi代码不是我写的。我从需要与我的 C# 应用程序交互的遗留项目继承了它。我相信 TParameter 对象一次包含更多。我被要求尝试与 Delphi 代码交互而不更改它。这就是为什么要寻找一种按原样处理动态数组的方法。我有一个非常简单的解决方案,围绕 Delphi 代码编写一个包装类,并将动态数组转换为静态数组,然后再发送回调用应用程序。不过,我希望不必这样做。
  • 好吧,我明白了,你被以前的设计困住了。我看到了赫弗南的答案,应该可以。我已经在 Delphi 和 C# 之间连接了相当复杂的接口,包括回调,您只需要小心确保编组正确。
猜你喜欢
  • 2013-03-12
  • 1970-01-01
  • 2010-09-14
  • 1970-01-01
  • 1970-01-01
  • 2020-02-19
  • 1970-01-01
  • 2010-10-28
  • 2011-02-13
相关资源
最近更新 更多