[英]Using Delphi DLL with dynamic array from C#
我有一个包含以下类型的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;
我假设DLL有一个解除recipe
结构的函数。 这是你不可能希望用C#做的事情。 稍后将详细介绍这一点。
Delphi动态数组不是有效的互操作类型。 它实际上应该只在内部用于使用单个版本的编译器编译的Delphi代码。 公开公开它类似于从DLL导出C ++类。
在理想的世界中,您将重新处理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
。 这意味着编组器将负责释放两个PAnsiChar
字段中Delphi DLL返回的内存。 它将通过调用CoTaskMemFree
。 我很确定这不会与Delphi代码中的PAnsiChar
字段的分配相匹配。
如上所述,我希望这个接口的合同是你调用另一个DLL函数来释放recipe
结构引用的堆内存。 也就是说,两个字符串和动态数组。
要从C#处理此问题,您需要确保marshaller不会尝试释放PAnsiChar
字段。 您可以通过在C#结构中将它们声明为IntPtr
来实现这一点。 然后调用Marshal.PtrToStringAnsi
转换为C#字符串。
为了写上面的内容,我必须对Delphi代码和C#代码之间的契约做一些假设。 如果我的任何假设不正确,请更新问题,我会尽力使这个答案匹配! 我希望这有帮助。
我怀疑的行话混乱,我的第一个想法很简单。
public parameter []参数;
有两个选项:要么你弄清楚动态数组是如何存储的,要么在c#端匹配,或者更好地在Delphi端创建一组基本方法,可以从c#端调用来操作数组和记录,例如getItem和setItem等。当语言障碍中存在不兼容的类型时,通常会执行此操作。 我会使用后面的方法,因为你不知道在未来的某个时候动态数组的内存结构是否会发生变化。
顺便说一句,为什么你将TParameter定义为记录,你可以使用TParameter = integer?
我发现这个链接有关于Delphi动态数组结构的说法:
http://www.programmersheaven.com/mb/delphikylix/262971/262971/dynamic-array-memory-storage/
此链接有更多细节。 结构比简单的数组复杂一点。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.