在 C#/C++ 之间编组复杂结构
Posted
技术标签:
【中文标题】在 C#/C++ 之间编组复杂结构【英文标题】:Marshalling Complex structures between C#/C++ 【发布时间】:2014-02-17 07:14:50 【问题描述】:我正在尝试从 C++ 填充结构数组并将结果传递回 C#。
我想也许创建一个具有结构数组的结构可能是前进的方向,因为我遇到的大多数示例都使用结构(但传递基本类型)。我已经尝试了以下方法,但到目前为止没有运气。
在以下位置找到了一个示例: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 <iostream>
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 < 10 ; ++i)
//
// pTestStructOuter->embedded = new struct1;
//
for (int i = 0; i < 10 ; ++i)
struct1 s1;
for (int idx = 0; idx < 10; ++idx)
s1.d1[i] = i+0.5;
s1.d2[i] = i+0.5;
pTestStructOuter->embedded[0] = s1;
这似乎不起作用我得到的错误: 参数不正确。(HRESULT 异常:0x80070057 (E_INVALIDARG))
这可能意味着它无法识别结构数组。任何想法我怎么能做到这一点?谢谢。
【问题讨论】:
您的 C++ 代码不正确,您声明了一个不占用 任何 空间的结构。数组是空的。然后,您的 testAPI2() 函数通过访问数组来调用 UB,因为它们的长度为 10 个元素。 C++ 是一种装载武器,你将它对准你的脚并扣动扳机。当你跳来跳去时接下来会发生什么是完全无法猜测的。 【参考方案1】:好的,我有一个工作样本。我将此作为另一个答案发布,因为这是一种非常不同的方法。
所以,在 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来手动管理。
【讨论】:
我会试试这个,然后回来找你。这也是很多工作。我很乐意为您安排转账,支付 - 朋友,西联汇款,您选择的任何东西(这又是很多工作!) @user3316669 别担心,这很有趣 :) 在这一行:Marshal.PtrToStructure(current,structs[i])。我得到一个错误'结构不能是一个值类。参数名称:结构'。所以我改变了那行和 structs[i] = new Struct1();到 structs[idx] = (Struct1)Marshal.PtrToStructure(current, typeof(Struct1));这似乎成功了!非常感谢,非常感谢您的帮助! 我还发现了 2 篇非常有趣的文章。您可能会喜欢,实现 ICustomMarsharler 的使用。 link1link2 @user3316669 这很有趣,对我来说效果很好。也许您使用的是不同的 .NET 版本?【参考方案2】:首先,不要使用未知长度的数组。您正在扼杀托管代码和非托管代码之间可能使用的数组 - 您无法确定所需的大小(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
是一种可能性,然后您可以简单地将长度和指针直接传递给嵌入式数组,避免不必要的编组。另一种选择是在TestStructOuter
中使用固定大小的数组(如果它需要是一个数组:)),这将解决您的许多问题并消除对手动编组的任何需要。换句话说,如果TestStructOuter
被定义为我之前提到的:
[StructLayout(LayoutKind.Sequential)]
public struct TestStructOuter
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10,
ArraySubType=UnmanagedType.Struct)]
public Struct1[] embedded;
突然间,你的整个通话和一切都变得如此简单
testAPI2(ref outer);
整个编组是自动完成的,无需手动分配或转换。
希望这会有所帮助:)
【讨论】:
嗨 Luaan,我觉得你的解决方案在隧道尽头有光明 :-)。非常渴望得到它,但是我已经尝试过TestStructOuter outer = new TestStructOuter(); outer.length = 10; outer.embedded = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Struct1)) * 10); Marshal.StructureToPtr(outer.embedded, outer.embedded, false); testAPI2(ref outer); outer = (TestStructOuter)(Marshal.PtrToStructure(outer.embedded, typeof(TestStructOuter))); Marshal.FreeHGlobal(outer.embedded);
但是程序失败了。
@user3316669 是的,您的Marshal.StructureToPtr
呼叫是错误的。您必须将准备好的结构作为第一个参数传递,而不是指向非托管结构的指针。此外,无论如何,您只发布一个实例,而不是您想要传递的 10 个。一个接一个地传递多个结构有点棘手,基本上,您需要在一个循环中执行StructureToPtr
,同时将IntPtr
增加结构大小。您能否在某处发布您的测试 API dll,以便我可以尝试使其工作?那会让一切都快一点:)
我真的很想在某处发布完整的解决方案(它非常小)。这是我第一次使用***,我不确定在哪里。并且 cmets 仅限于 ~500 个字符左右。
@user3316669 如果您不担心与未知公司共享数据,请尝试speedyshare.com 之类的方法(注意:我不以任何方式认可这个特定的文件共享网站,事实上它是第一个是我从 Google 上下来的,但我相信它对你的样本来说已经足够好了,除非你受到一些 NDA 或类似的约束)。
@user3316669 别担心,当我们弄清楚实际问题是什么时,我们可以根据需要更新问题和答案:))【参考方案3】:
提示:Leadn C++/CLI。在我的生活中,我不得不处理两次复杂的互操作——一次是使用 ISDN(AVM 开发套件让它变得更容易——遗憾的是 C++,我需要 C#),现在使用 Nanex(伟大的实时复杂和完整的市场数据源,sadl 复杂的 C++ , 我需要 C#)
在这两种情况下,我都在 C++/CLI 中制作了自己的包装器,与 C++ 对话并在 C# 中公开真实的对象模型。让我可以把事情做得更好,并处理很多我们友好的 Marshaller 无法有效处理的边缘情况。
【讨论】:
以上是关于在 C#/C++ 之间编组复杂结构的主要内容,如果未能解决你的问题,请参考以下文章