从 C 到 VB.NET 编组可变长度结构数组






4.67/5 (4投票s)
如何将可变长度的结构数组从 C 传递到 VB.NET。
引言
本文提供了一个示例,说明如何创建一个 VB.NET 程序,该程序从用 C 编写的 DLL 文件中检索可变长度结构数组形式的数据。这里的挑战是如何在公共语言运行时 (CLR) 执行的安全托管代码世界和非托管代码之间传递数据。
背景
使用 .NET Framework 开发的代码称为托管代码。此代码由 CLR 在托管代码执行的帮助下直接执行。用 .NET Framework 编写的任何代码都是托管代码。托管代码使用 CLR,它通过管理内存、处理安全性、允许跨语言调试等方式来照顾您的应用程序。在 .NET Framework 之外开发的代码称为非托管代码。不在 CLR 控制下运行的应用程序被称为非托管的,因此没有 CLR 的好处。我不会深入探讨托管代码和非托管代码的定义,因为这超出了本文的范围,而且互联网上有大量的资源。只需在 Google 上搜索即可 :)
封送
在托管代码和非托管代码之间传递数据的解决方案当然是编组。使这项任务有点棘手的是,一些非托管函数返回一个结构数组,而我在调用该函数时并不知道它的大小。
代码
我将从非托管 DLL 开始。这是 C 中的结构声明
typedef struct {
Char id1[10];
Char id2[20];
}unmanagedStruct;
这是函数的声明
extern "C" __declspec(dllexport) unmanagedStruct* unmanagedFunction(int &size)
{
unmanagedStruct *outStruct = new
unmanagedStruct[myStructSize];
size = myStructSize;
//Do whatever your function is supposed to do
return outStruct;
}
正如您所看到的,该函数返回一个指针,因为我在托管端需要的是指向我已分配的非托管内存的指针。接下来要注意的是,我将整数大小作为引用调用。这个变量使我能够在托管端获得数组的大小。当我展示托管代码时,将详细解释如何检索数组的元素。现在,我将只展示非托管函数的相关部分。当然,该函数应该做更多的事情,例如用值填充数组等等,但这超出了本文的范围。因此,正如您所看到的,我声明了我的结构数组并为其分配了内存。然后我将 size
变量设置为数组的大小,以便托管代码能够知道该大小。最后,我将指针返回给函数的调用者。
现在在托管端,在 Visual Studio 中,我首先声明我打算使用的结构。请注意,我正在使用 MarshalAs
属性类来指定结构中字段的长度。这些长度必须与非托管 DLL 中声明的长度相同。
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> _
Public Structure managedStruct
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=10)> _
Public id1 As String
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=20)> _
Public id2 As String
End Structure
我继续声明一个 DllImport
到我的非托管 DLL 和我打算使用的函数。
<DllImport("myUnmanagedDll.dll")> _
Public Shared Function unmanagedFunction(ByRef size As Integer) As IntPtr
End Function
最后,这是我从托管代码调用非托管函数、检索指向非托管结构数组的指针以及检索值的部分。
Dim p1 As IntPtr 'My struct pointer
'pointer to be used to cleanup the unmanaged memory
Dim cleanUpPtr as IntPtr
Dim numberOfResults As Integer 'Size of array from dll
Dim resultArray() As managedStruct
'The array where i will store the retrieved values
'Call the unmanaged function and get intpointer
p1 = unmanagedFunction(numberOfResults)
'Set the pointer to be used for cleanup to point on the same memory block
cleanUpPtr = p1
ReDim resultArray(numberOfResults - 1) 'Resize my array
Dim size As Integer = Marshal.SizeOf(GetType(managedStruct))
Dim start As Integer = (CType(p1, Integer) + _
(Marshal.SizeOf(GetType(managedStruct)) - size))
p1 = New IntPtr(start) 'Set the pointer at the first element of array
Dim i As Integer = 0
Do While (i < numberOfResults)
resultArray(i) = CType(Marshal.PtrToStructure(p1, _
GetType(managedStruct)), managedStruct)
If (i < (count - 1)) The
p1 = New IntPtr((CType(p1, Integer) + size))
End If
i = (i + 1)
Loop
调用非托管函数后,我得到了在数组中检索到的帖子数量。在名为 size
的变量中,我存储每个数组元素的大小,这样我就知道必须递增多少字节才能检索到下一个帖子。然后我继续将指针 p1
设置为第一个数组元素,最后使用 Marshal.PtrToStructure
来检索值。一旦我检索到一个数组元素,我就会继续将指针设置为下一个元素。重复此步骤,直到检索到所有元素,现在我在托管内存中拥有了整个非托管数组。
现在作为最后一件事,请记住清理非托管内存。这就是为什么我创建了一个额外的指针,称为 cleanupPtr
,因为我将使用原始指针循环遍历数组。 cleanUpPtr
仍然指向非托管内存的开头。可以通过使用 CoTaskMemFree()
属性直接从托管内存执行清理操作,或者通过在非托管代码中使用 delete[]
运算符来执行清理操作。
结束语
最后,我想回答我想你们中的许多人都会问的问题:为什么要在 VB.NET 中这样做,答案是这是我接到任务时的要求的一部分。如果由我决定,我会在 C# 中完成。我是一个 C# 爱好者。
历史
- 2011 年 7 月 24 日:修复了文章代码部分中的一些复制粘贴错误。