使用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;
我假设信任DLL有一个释放recipe
结构体的函数。这是你不可能指望用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/这个链接有更多的细节。该结构比简单的数组要复杂一些。
动态数组结构