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

mey*_*ann 3 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;
Run Code Online (Sandbox Code Playgroud)

我需要从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;
}
Run Code Online (Sandbox Code Playgroud)

我一直很好,直到我在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;
Run Code Online (Sandbox Code Playgroud)

Dav*_*nan 5

我假设DLL有一个解除分配recipe结构的函数.这是你不可能希望用C#做的事情.稍后将详细介绍这一点.

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

在理想的世界中,您将重新处理Delphi代码,以便使用适当的互操作类型导出数组.但是,在这种情况下,在不调整Delphi代码的情况下进行编组实际上相对容易.

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

IntPtrParameters场上使用是完美的.因为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
}
Run Code Online (Sandbox Code Playgroud)

这涉及动态数组,但实际上你的代码存在另一个问题.您正在声明stringC#结构中的两个字符串.这意味着marshaller将负责释放Delphi DLL在两个PAnsiChar字段中返回的内存.它会通过电话来实现CoTaskMemFree.我很确定这不会PAnsiChar与Delphi代码中的字段分配相匹配.

如上所述,我希望这个接口的合同是你调用另一个DLL函数来释放recipestruct 引用的堆内存.也就是说,两个字符串和动态数组.

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


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