编组C#/ C ++之间的复杂结构

我正在尝试从C ++填充结构数组并将结果传递回C#。

我想也许创建一个结构数组的结构可能是前进的方式,因为大多数例子我遇到了使用结构(但传递基本类型)。 我试过以下但到目前为止没有运气。

在以下url找到了一个例子: http : //limbioliong.wordpress.com/2011/08/20/passing-a-pointer-to-a-structure-from-c-to-c-part-2/?relatedposts_exclude = 542

我在C#中有以下内容

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.InteropServices; namespace CSharpDLLCall { class Program { [StructLayout(LayoutKind.Sequential,Pack=8)] public struct Struct1 { [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)] public double[] d1; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)] public double[] d2; } [StructLayout(LayoutKind.Sequential)] public struct TestStructOuter { public int length; public IntPtr embedded; } static void Main(string[] args) { Program program = new Program(); program.demoArrayOfStructs(); } public void demoArrayOfStructs() { TestStructOuter outer = new TestStructOuter(); testStructOuter.embedded = new Struct1[10]; outer.length = 10; outer.embedded = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Struct1)) * 10); Marshal.StructureToPtr(outer, outer.embedded, false); testAPI2(ref outer); outer = (TestStructOuter)(Marshal.PtrToStructure(outer.embedded, typeof(TestStructOuter))); Marshal.FreeHGlobal(outer.embedded); } [DllImport(@"C:\CPP_Projects\DLL\DLLSample\Release\DLLSample.dll")] static extern void testAPI2(IntPtr pTestStructOuter); } } 

在头文件中的C ++我有

 #ifdef DLLSAMPLE_EXPORTS #define DLLSAMPLE_API __declspec(dllexport) #else #define DLLSAMPLE_API __declspec(dllimport) #endif #include  using namespace std; #pragma pack(1) struct struct1 { double d1[]; double d2[]; }; struct TestStructOuter { struct1* embedded; }; extern "C" { DLLSAMPLE_API void __stdcall testAPI2(TestStructOuter* pTestStructOuter); } 

在cpp我有:

 #include "stdafx.h" #include "DLLSample.h" __declspec(dllexport) void __stdcall testAPI2(TestStructOuter* pTestStructOuter) { // not sure that this is necessary // for (int i = 0; i embedded = new struct1; // } for (int i = 0; i < 10 ; ++i) { struct1 s1; for (int idx = 0; idx embedded[0] = s1; } } 

这似乎不起作用我得到的错误:参数不正确。(HRESULTexception:0x80070057(E_INVALIDARG))

这可能意味着它无法识别结构数组。 我有什么想法可以做到这一点? 谢谢。

好的,我有一份工作样本。 我将此作为另一个答案发布,因为这是一种非常不同的方法。

所以,在C ++方面,我有这个头文件:

 struct Struct1 { int d1[10]; int d2[10]; }; extern "C" __declspec(dllexport) void __stdcall TestApi2(int* pLength, Struct1 **pStructures); 

以下代码:

 __declspec(dllexport) void __stdcall TestApi2(int* pLength, Struct1 **pStructures) { int len = 10; *pLength = len; *pStructures = (Struct1*)LocalAlloc(0, len * sizeof(Struct1)); Struct1 *pCur = *pStructures; for (int i = 0; i < len; i++) { for (int idx = 0; idx < 10; ++idx) { pCur->d1[idx] = i + idx; pCur->d2[idx] = i + idx; } pCur ++; } } 

现在,重要的一点是LocalAlloc 。 这实际上是我遇到所有问题的地方,因为我错误地分配了内存。 LocalAlloc是.NET调用Marshal.AllocHGlobal ,这非常方便,因为这意味着我们可以使用调用者中的内存并根据需要处理它。

现在,此方法允许您返回任意长度的结构数组。 例如,可以使用相同的方法。 返回一个结构数组的结构,你只是更深入。 关键是LocalAlloc – 你可以使用Marshal类轻松访问的内存,以及不被丢弃的内存。

你必须返回数组长度的原因是因为没有办法知道你“返回”了多少数据。 这是非托管代码中常见的“问题”,如果你曾经做过任何P / Invoking,你就会知道这一切。

而现在,C#方面。 我很努力地以一种很好的方式做到这一点,但问题是结构数组根本不是blittable,这意味着你不能简单地使用MarshalAs(UnmanagedType.LPArray, ...) 。 所以,我们必须采用IntPtr方式。

定义如下所示:

 [StructLayout(LayoutKind.Sequential)] public class Struct1 { [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)] public int[] d1; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)] public int[] d2; } [DllImport(@"Win32Project.dll", CallingConvention = CallingConvention.StdCall)] static extern void TestApi2(out int length, out IntPtr structs); 

基本上,我们得到一个指向“数组”长度的指针,以及指向数组第一个元素的指针。 这就是我们读取数据所需的全部内容。

代码如下:

 int length; IntPtr pStructs; TestApi2(out length, out pStructs); // Prepare the C#-side array to copy the data to Struct1[] structs = new Struct1[length]; IntPtr current = pStructs; for (int i = 0; i < length; i++) { // Create a new struct and copy the unmanaged one to it structs[i] = new Struct1(); Marshal.PtrToStructure(current, structs[i]); // Clean-up Marshal.DestroyStructure(current, typeof(Struct1)); // And move to the next structure in the array current = (IntPtr)((long)current + Marshal.SizeOf(structs[i])); } // And finally, dispose of the whole block of unmanaged memory. Marshal.FreeHGlobal(pStructs); 

如果你想真正返回结构数组的结构,唯一改变的是方法参数进入“包装”结构。 方便的是.NET可以自动处理包装器的编组,不太方便的是它无法处理内部数组,所以你必须再次使用length + IntPtr来手动管理它。

首先,不要使用未知长度的数组。 你杀死托管和非托管代码之间任何可能的数组使用 – 你不能告诉所需的大小( Marshal.SizeOf是无用的),你必须手动传递数组长度等。如果它不是一个固定长度的数组,使用IntPtr

 [StructLayout(LayoutKind.Sequential,Pack=8)] public struct Struct1 { [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)] public double[] d1; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)] public double[] d2; } [StructLayout(LayoutKind.Sequential)] public struct TestStructOuter { public int length; public IntPtr embedded; } 

(如果你的embedded数组是固定长度的,可以随意忽略它,但是要包含ByValArray, SizeConst位。你还必须将ArraySubType=System.Runtime.InteropServices.UnmanagedType.Struct添加到属性中)。

正如您可能已经注意到的那样, TestStructOuter在这种情况下几乎无用,它只会增加复杂性(并且请注意,与本机语言相比,编组是最昂贵的操作之一,因此如果您经常调用它,则可能是保持简单的好主意)。

现在,您只为外部结构分配内存。 即使在你的代码中, Marshal.SizeOf也不知道结构应该有多大; 使用IntPtr ,这更是如此(或者,更准确地说,您只需要一个IntPtr,即大约4-8个字节+长度)。 大多数情况下,你想直接传递IntPtr ,而不是做这种包装(在C ++ / CLI中使用相同的东西完全是另一回事,尽管对你的情况来说这是不必要的)。

您的方法的签名将是这样的:

 [DllImport(@"C:\CPP_Projects\DLL\DLLSample\Release\DLLSample.dll", CallingConvention=System.Runtime.InteropServices.CallingConvention.StdCall)] public static extern void testAPI2(ref TestStructOuter pTestStructOuter) ; 

现在,如果您使用IntPtr方法,您希望手动分配内存,只需要正确执行,例如:

 TestStructOuter outer = new TestStructOuter(); testStructOuter.length = 1; testStructOuter.embedded = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Struct1))); Marshal.StructureToPtr(embedded, testStructOuter.embedded, false); 

最后,你调用方法:

 // The marshalling is done automatically testAPI2(ref outer); 

不要忘记释放内存,理想情况是在内存分配后try绕过所有内容的finally子句:

 Marshal.FreeHGlobal(outer.embedded); 

现在,这过于复杂,并不完全是最佳的。 抛出TestStructOuter是一种可能性,然后您可以直接将长度和指针直接传递给嵌入式arrays,避免一次不必要的编组。 另一个选择是在TestStructOuter使用固定大小的数组(如果它需要一个数组:)),这将解决你的许多麻烦,并消除任何手动编组的需要。 换句话说,如果TestStructOuter定义为我之前提到过:

 [StructLayout(LayoutKind.Sequential)] public struct TestStructOuter { [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10, ArraySubType=UnmanagedType.Struct)] public Struct1[] embedded; } 

突然间,你的整个电话和一切都变得如此简单

 testAPI2(ref outer); 

整个编组是自动完成的,无需手动分配或转换。

希望这可以帮助 :)

提示:Leadn C ++ / CLI。 在我的生活中,我不得不两次处理复杂的互操作 – 一次使用ISDN(AVM devkits使它变得更容易 – 可悲的是C ++,我需要C#),现在使用Nanex(非常实时的复杂和完整的市场ata feed,sadl complex C ++ ,我需要C#)

在这两种情况下,我都在C ++ / CLI中创建自己的包装器,向下谈论C ++并在C#中展示一个真实的对象模型。 允许我使事情变得更好,并处理我们友好的Marshaller无法有效处理的许多边缘情况。