将 Delphi DLL 与 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# 调用应用程序,该代码将从数组中获取这 2 个参数项?
更新:嗯,碰巧我得到的 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;
I have a Delphi DLL that contains the following types:
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;
I need to call this DLL from C# passing a ref object corresponding to the Delphi types that the DLL will fill and return. I have defined structures in my C# code like this:
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;
}
I was doing fine until I ran into the dynamic array (Parameters: array of TParameter) in the Delphi code. I understand that dynamic arrays are a Delphi only construct, so I chose to use an IntPtr in my C# code in the hopes of just getting a pointer to the array and pulling the contents. Unfortunately, I am rather new to this interop stuff and I am not sure how to deal with the IntPtr.
Let's say the Delphi DLL populates the dynamic array with 2 parameter items. Can someone possibly show me the C# code that would get those 2 parameter items out of the array once it gets passed back from the Delphi DLL to my C# calling application?
UPDATE: Well, as it happens the Delphi code I was given was a simplified version. One of our Delphi developers thought it would be easier to get started with the simplified version than the real version, which is substantially more complex containing dynamic arrays of dynamic arrays of dynamic arrays. Anyway, I am now completely over my head. I only know enough about Delphi to be dangerous. Below is the code for the real structures in the Delphi code. Any further guidance on how to deal with these structures from my C# calling application would be greatly appreciated. It may not even be possible with the nesting of dynamic arrays such that they are.
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;
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我假设 DLL 具有释放
recipe
结构的函数。这是您不可能希望通过 C# 做到的事情。稍后将详细讨论这一点。Delphi 动态数组不是有效的互操作类型。它确实应该只在使用单个版本的编译器编译的 Delphi 代码内部使用。公开公开它类似于从 DLL 导出 C++ 类。
在理想的情况下,您将重新编写 Delphi 代码,以便它使用正确的互操作类型导出数组。然而,在这种情况下,实际上您可以相对容易地进行编组,而无需调整 Delphi 代码。
Delphi 动态数组早在 Delphi 4 中就已引入,并且从那时起它们的实现就一直保持不变。 T 动态数组变量的
数组
实际上是指向第一个元素的指针。元素在内存中按顺序排列。动态数组变量还维护(在负偏移处)引用计数和数组的大小。您可以放心地忽略这些,因为您既不修改动态数组,也不需要确定其大小。使用
IntPtr
作为Parameters
字段是完美的。由于TParameter
仅包含一个 32 位整数,因此您可以使用Marshal.Copy
将其直接复制到int[]
数组。因此,当 Delphi DLL 返回时,您可以使用
执行最后的编组步骤Marshal.Copy
。这涉及动态数组,但碰巧您的代码还存在另一个问题。您将这两个字符串声明为 C# 结构中的
string
。这意味着编组器将负责释放 Delphi DLL 在两个PAnsiChar
字段中返回的内存。它将通过调用 CoTaskMemFree 来完成此操作。我相当确定这不会与 Delphi 代码中的PAnsiChar
字段的分配相匹配。如上所述,我希望此接口的约定是您调用另一个 DLL 函数来释放
recipe
结构引用的堆内存。即两个字符串和动态数组。要在 C# 中处理此问题,您需要确保编组器不会尝试释放
PAnsiChar
字段。您可以通过在 C# 结构中将它们声明为IntPtr
来实现这一点。然后调用Marshal.PtrToStringAnsi
转换为一个 C# 字符串。为了编写上述内容,我必须对 Delphi 代码和 C# 代码之间的契约做出一些假设。如果我的任何假设不正确,请更新问题,我会尽力使这个答案匹配!我希望这有帮助。
I am assuming trust that the DLL has a function that deallocates the
recipe
struct. That's something that you can't possibly hope to do from C#. More on this point later on.A Delphi dynamic array is not a valid interop type. It really should only used internally to Delphi code compiled with a single version of the compiler. Exposing it publically is akin to exporting C++ classes from a DLL.
In an ideal world you would re-work the Delphi code so that it exported the array using a proper interop type. However, in this case it is actually relatively easy for you to do the marshalling without adjusting the Delphi code.
Delphi dynamic arrays were introduced way back in Delphi 4 and their implementation has remained unchanged since then. The
array of T
dynamic array variable is effectively a pointer to the first element. The elements are laid out sequentially in memory. The dynamic array variable also maintains (at negative offsets) a reference count and the size of the array. You can safely ignore these since you are neither modifying the dynamic array nor needing to ascertain its size.Using
IntPtr
for theParameters
field is perfect. BecauseTParameter
contains just a single 32 bit integer you can useMarshal.Copy
to copy it straight to anint[]
array.So, when the Delphi DLL returns, you can do the final marshalling step using
Marshal.Copy
.That deals with the dynamic array, but as it happens you have another problem with your code as it stands. You are declaring the two strings as
string
in the C# struct. This means that the marshaller will take responsibility for freeing the memory returned by the Delphi DLL in the twoPAnsiChar
fields. It will do so by callingCoTaskMemFree
. I'm fairly sure that's not going to match the allocation of thePAnsiChar
fields made in the Delphi code.As stated above, I would expect that the contract for this interface is that you call a further DLL function to deallocate the heap memory referenced by the
recipe
struct. That is, the two strings, and the dynamic array.To deal with this issue from C# you need to make sure that the marshaller does not attempt to deallocate the
PAnsiChar
fields. You can achieve that by declaring them asIntPtr
in the C# struct. Then callMarshal.PtrToStringAnsi
to convert to a C# string.I've had to make a few assumptions about the contract between the Delphi code and the C# code in order to write the above. If any of my assumptions are incorrect please update the question and I'll try to make this answer match! I hope this helps.
我怀疑行话混乱,我的第一个想法很简单。
公共参数[]参数;
Jargon confusion I suspect, my first thought was simply.
public parameter[] parameters;
两个选择:要么你弄清楚动态数组是如何存储的,并与 c# 端的相匹配,要么更好的是在 Delphi 端创建一组基本方法,可以从 c# 端调用这些方法来操作数组和记录,例如 getItem和 setItem 等。当存在跨越语言障碍的不兼容类型时,通常会这样做。我会使用后一种方法,因为您不知道动态数组的内存结构是否在将来的某个时刻可能会发生变化。
顺便问一下,为什么将 TParameter 定义为记录,您可以使用 TParameter = integer ?
我发现这个链接有一些关于Delphi动态数组的结构的内容:
http://www.programmersheaven.com/mb/delphikylix/262971/262971/dynamic-array-memory-storage/
此链接包含更多详细信息。该结构比简单的数组稍微复杂一些。
动态数组结构
Two options: Either you figure out exactly how dynamic arrays are stored and match that on the c# side or better still create a set of basic methods on the Delphi side that can be called from the c# side to manipulate the array and record, eg getItem and setItem etc. That's usually what is done when there are incompatible types across a language barrier. I would use the later approach because you don't know whether at some point in the future the memory structure of a dynamic array might change.
By the way, why have you defined TParameter as a record, you could have used TParameter = integer ?
I found this link which has something to say about the structure of Delphi dynamic arrays:
http://www.programmersheaven.com/mb/delphikylix/262971/262971/dynamic-array-memory-storage/
And this link has even more details. The structure is a bit more complicated than a simple array.
Dynamic Array Structure