使用Delphi DLL和C#中的动态数组

时间:2011-11-13 01:53:27

标签: 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#调用应用程序,就会从数组中获取这两个参数项吗?

更新:好吧,因为它发生的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;

3 个答案:

答案 0 :(得分:5)

我假设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#代码之间的契约做出一些假设。如果我的任何假设不正确,请更新问题,我将尝试使此答案匹配!我希望这会有所帮助。

答案 1 :(得分:0)

我怀疑的术语混乱,我的第一个念头就是。

public parameter []参数;

答案 2 :(得分:0)

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

顺便说一下,为什么要将TParameter定义为记录,你可以使用TParameter = integer?

我发现这个链接有关于Delphi动态数组结构的说法:

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

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

Dynamic Array Structure