c# - 将 Delphi DLL 与来自 C# 的动态数组一起使用

标签 c# delphi pinvoke

我有一个包含以下类型的 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 个参数项填充动态数组。有人可以向我展示 C# 代码,一旦它从 Delphi DLL 传回我的 C# 调用应用程序,就会从数组中取出这 2 个参数项吗?

更新:好吧,碰巧我得到的 Delphi 代码是一个简化版本。我们的一位 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;

最佳答案

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

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

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

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

Parameters 字段使用 IntPtr 是完美的。因为 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 字段的分配相匹配。

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

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


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

关于c# - 将 Delphi DLL 与来自 C# 的动态数组一起使用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8109026/

相关文章:

c# - c# 中的 char[] 数组等价物

c# - HttpWebRequest.Headers.Add ("Cookie",值)与 HttpWebRequest.CookieContainer

c# - 两个 ViewModel 中的相同属性和属性更改

c# - 如何在 WebBrowser 实例中在 WPF 和 JavaScript 之间进行通信?

delphi - 帮助基于数据库表(脚手架?)生成 Delphi DFM

delphi - 如何对 TCollection 中的项目重新排序?

.net - AttachConsole(-1),但 Console.WriteLine 不会输出到父命令提示符?

c# - 编码变量参数 - __arglist 或替代

c# - 在 C# 中销毁非托管对象

sql-server - delphi dbgrid bool 值,接受 f fa fal fals false ,如何接受更多值?