使用Delphi DLL与c#实现动态数组

本文关键字:实现 动态 数组 Delphi DLL 使用 | 更新日期: 2023-09-27 18:14:29

我有一个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代码中实际结构的代码。如果能从我的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#实现动态数组

我假设信任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#代码之间的契约做一些假设。如果我的任何假设是不正确的,请更新问题,我会尽量使这个答案匹配!我希望这对你有帮助。

术语混淆我怀疑,我的第一个想法很简单。

有两个选择:要么你弄清楚动态数组是如何存储的,并与c#端相匹配,要么在Delphi端创建一组基本方法,可以从c#端调用这些方法来操作数组和记录,例如getItem和setItem等。当存在跨越语言障碍的不兼容类型时,通常就是这样做的。我将使用后一种方法,因为你不知道在未来的某个时刻动态数组的内存结构是否会改变。

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

我找到了这个链接,它有一些关于Delphi动态数组结构的说明:

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

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

动态数组结构