在C#和C ++之间共享变量

我正在用c#编写一个软件,它需要多次调用,并且需要多个线程调用c ++非托管dll中的函数。

我有一个这样的C ++文件:

// "variables" which consist in some simple variables (int, double) // and in some complex variables (structs containing arrays of structs) extern "C" { __declspec(dllexport) int function1() { // some work depending on random and on the "variables" } } 

和那样的C#类

 public class class1 { // "variables" <--- the "same" as the C++ file's ones // Dll import <--- ok public void method1() { int [] result; for(int i=0; i<many_times; i++) { result = new int[number_of_parallel_tasks]; Parallel.For(0, number_of_parallel_tasks, delegate(int j) { // I would like to do result[j] = function1() }); // choose best result // then update "variables" } } } 

我写了“我想做…”,因为每一轮的c ++函数都需要更新“变量”。

我的问题是:

是否可以在C ++和C#之间共享内存以避免每次传递引用? 这只是浪费时间吗?

我读到了内存映射文件。 他们能帮助我吗? 但是,您知道更合适的解决方案吗?
非常感谢你。

一旦你知道它是如何工作的,使用P / Invoke在C#和C ++之间共享内存是没有问题的。 我建议阅读有关MSDN编组的内容。 您可能还想阅读有关使用unsafe关键字和修复内存的信息。

这是一个假设您的变量可以描述为简单结构的示例:

在C ++中声明您的函数如下:

 #pragma pack(1) typedef struct VARIABLES { /* Use simple variables, avoid pointers If you need to use arrays use fixed size ones */ }variables_t; #pragma pack() extern "C" { __declspec(dllexport) int function1(void * variables) { // some work depending on random and on the "variables" } } 

在C#中做这样的事情:

 [StructLayout(LayoutKind.Sequential, Pack=1)] struct variables_t { /* Place the exact same definition as in C++ remember that long long in c++ is long in c# use MarshalAs for fixed size arrays */ }; [DllExport("YourDll.dll", CallingConvention=CallingConvention.Cdecl)] static extern int function(ref variables_t variables); 

在你的class上:

 variables_t variables = new variables_t(); //Initialize variables here for(int i=0; i 

您可以使用更复杂的方案,例如在c ++中分配和释放结构,使用其他forms的编组来获取数据,就像构建自己的类一样直接读取和写入非托管内存。 但是如果你可以使用一个简单的结构来保存你的变量,那么上面的方法是最简单的。

编辑:关于如何正确处理更复杂数据的指针

所以上面的示例在我看来是在C#和C ++之间“共享”数据的正确方法,如果它是简单的数据,例如。 包含原始类型或固定大小的基本类型数组的结构。

这就是说,使用C#访问内存的方式实际上几乎没有限制。 有关更多信息,请查看unsafe关键字,fixed关键字和GCHandle结构。 而且如果你有一个非常复杂的数据结构包含其他结构的数组等,那么你的工作就会更复杂。

在上面的例子中,我建议移动关于如何将“变量”更新为C ++的逻辑。 在C ++中添加一个看起来像这样的函数:

 extern "C" { __declspec(dllexport) void updateVariables(int bestResult) { // update the variables } } 

我仍然建议不要使用全局变量,所以我提出以下方案。 在C ++中:

 typedef struct MYVERYCOMPLEXDATA { /* Some very complex data structure */ }variables_t; extern "C" { __declspec(dllexport) variables_t * AllocVariables() { // Alloc the variables; } __declspec(dllexport) void ReleaseVariables(variables_t * variables) { // Free the variables; } __declspec(dllexport) int function1(variables_t const * variables) { // Do some work depending on variables; } __declspec(dllexport) void updateVariables(variables_t * variables, int bestResult) { // update the variables } }; 

在C#中:

 [DllExport("YourDll.dll", CallingConvention=CallingConvention.Cdecl)] static extern IntPtr AllocVariables(); [DllExport("YourDll.dll", CallingConvention=CallingConvention.Cdecl)] static extern void ReleaseVariables(IntPtr variables); [DllExport("YourDll.dll", CallingConvention=CallingConvention.Cdecl)] static extern int function1(IntPtr variables); [DllExport("YourDll.dll", CallingConvention=CallingConvention.Cdecl)] static extern void updateVariables(IntPtr variables, int bestResult); 

如果您仍想在C#中维护逻辑,则必须执行以下操作:创建一个类来保存从C ++返回的内存并编写自己的内存访问逻辑。 使用复制语义将数据公开给C#。 我的意思如下,假设您在C ++中有这样的结构:

 #pragma pack(1) typedef struct SUBSTRUCT { int subInt; double subDouble; }subvar_t; typedef struct COMPLEXDATA { int int0; double double0; int subdata_length; subvar_t * subdata; }variables_t; #pragma pack() 

在C#你做这样的事情

 [DllImport("kernel32.dll")] static extern void CopyMemory(IntPtr dst, IntPtr src, uint size); [StructLayout((LayoutKind.Sequential, Pack=1)] struct variable_t { public int int0; public double double0; public int subdata_length; private IntPtr subdata; public SubData[] subdata { get { SubData[] ret = new SubData[subdata_length]; GCHandle gcH = GCHandle.Alloc(ret, GCHandleType.Pinned); CopyMemory(gcH.AddrOfPinnedObject(), subdata, (uint)Marshal.SizeOf(typeof(SubData))*subdata_length); gcH.Free(); return ret; } set { if(value == null || value.Length == 0) { subdata_length = 0; subdata = IntPtr.Zero; }else { GCHandle gcH = GCHandle.Alloc(value, GCHandleType.Pinned); subdata_length = value.Length; if(subdata != IntPtr.Zero) Marshal.FreeHGlobal(subdata); subdata = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(SubData))*subdata_length); CopyMemory(subdata, gcH.AddrOfPinnedObject(),(uint)Marshal.SizeOf(typeof(SubData))*subdata_length); gcH.Free(); } } } }; [StructLayout((LayoutKind.Sequential, Pack=1)] sturct SubData { public int subInt; public double subDouble; }; 

在上面的样品中,结构仍然可以像第一个样品一样通过。 这当然只是关于如何使用结构arrays中的结构和结构arrays处理复杂数据的概述。 正如您所看到的,您需要进行大量复制以防止内存损坏。 此外,如果内存是通过C ++分配的,那么如果你使用FreeHGlobal来释放它将是非常糟糕的。 如果你想避免复制内存并仍然在C#中维护逻辑,你可以编写一个本机内存包装器,带有你想要的访问器。例如,你将有一个方法来直接设置或获取第N个数组成员的subInt - 这种方式您将保留您的副本到您访问的确切内容。

另一个选择是编写特定的C ++函数来为您执行困难的数据处理,并根据您的逻辑从C#调用它们。

最后但并非最不重要的是,您始终可以使用带有CLI界面的C ++。 然而,我自己只有在必须的时候才这样做 - 我不喜欢这种语言,但对于非常复杂的数据,你当然必须考虑它。

编辑

为了完整性,我向DllImport添加了正确的调用约定。 请注意,DllImport属性使用的默认调用约定是Winapi(在Windows上转换为__stdcall),而C / C ++中的默认调用约定(除非您更改编译器选项)是__cdecl。

你能做的最好的事情就是定义你的C ++代码(我假设它是不受管理的)。 然后在C ++ / CLI中为它编写一个包装器。 包装器将为您提供C#的接口,并且您可以在这里处理marchalling(将数据从非管理区域移动到托管区域)

避免必须传递对C#和C ++代码所需的变量/数据的引用的一种方法是从本机DLL导出两个函数。 除了执行工作的函数之外,还提供另一个函数,该函数允许将引用传入并存储到文件范围静态指针,该指针在与两个函数相同的.cpp文件中定义,以便两者都可以访问。

正如您所提到的,您还可以使用内存映射文件(在这种情况下,它可能是非持久的,因为它永远不需要写入磁盘)。