一旦了解其工作原理,使用P/Invoke在C#和C++之间共享内存没有问题。建议阅读MSDN关于封送的文章。您还可以阅读有关使用unsafe关键字和修复内存的文章。
以下是一个示例,假设您的变量可以描述为简单的结构体:
在C ++中声明您的函数如下:
#pragma pack(1)
typedef struct VARIABLES
{
}variables_t;
#pragma pack()
extern "C"
{
__declspec(dllexport) int function1(void * 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)
并且在你的类中:
variables_t variables = new variables_t();
//Initialize variables here
for(int i=0; i<many_times; i++)
{
int[] result = new int[number_of_parallel_tasks];
Parallel.For(0, number_of_parallel_tasks, delegate(int j)
{
result[j] = function1(ref variables)
});
// choose best result
// then update "variables"
}
你可以使用更复杂的场景,比如在C ++中分配和释放结构体,在获取数据时使用其他形式的编组,比如构建自己的类以直接读写非托管内存。但是,如果你可以使用一个简单的结构体来保存变量,上述方法是最简单的方法。
编辑:处理更复杂数据的指针
因此,我的观点是上面的示例是在C#和C++之间“共享”数据的正确方式,如果它是简单的数据,例如包含基本类型或固定大小的基本类型数组的结构体。
话虽如此,实际上你可以用C#访问内存的方式有很少限制。要了解更多信息,请查看unsafe关键字、fixed关键字和GCHandle结构。即使有非常复杂的包含其他结构体数组等的数据结构,你仍然需要进行更复杂的任务。
在上述情况下,我建议将更新“变量”的逻辑移动到C++中,并在C++中添加一个类似以下的函数:
extern "C"
{
__declspec(dllexport) void updateVariables(int bestResult)
{
}
}
我仍建议不要使用全局变量,因此我提出以下方案。
在C++中:
typedef struct MYVERYCOMPLEXDATA
{
}variables_t;
extern "C"
{
__declspec(dllexport) variables_t * AllocVariables()
{
}
__declspec(dllexport) void ReleaseVariables(variables_t * variables)
{
}
__declspec(dllexport) int function1(variables_t const * variables)
{
}
__declspec(dllexport) void updateVariables(variables_t * variables, int bestResult)
{
}
};
在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;
};
在上面的示例中,结构仍然可以像在第一个示例中那样传递。当然,这只是关于如何处理带有结构数组和结构数组的复杂数据的概述。正如您所看到的,您需要大量复制以保护自己免受内存损坏。此外,如果内存是通过C ++分配的,则如果使用FreeHGlobal来释放它,情况将非常糟糕。
如果您想避免复制内存并仍然保持C#内部的逻辑,则可以编写本机内存包装器,并为所需内容编写访问器。例如,您将拥有一种方法来直接设置或获取第N个数组成员的subInt-这样,您将仅保留到您访问的内容的副本。
另一个选择是编写特定的C ++函数来处理您的困难数据,并根据您的逻辑从C#调用它们。
最后但并非最不重要的是,您始终可以使用具有CLI界面的C ++。但是,我自己只有在必须时才会这样做-我不喜欢这种术语,但对于非常复杂的数据,您确实必须考虑它。
编辑
我添加了正确的DllImport调用约定以完整性。请注意,默认DllImport属性使用的调用约定是Winapi(在Windows上转换为__stdcall),而C / C ++中的默认调用约定(除非更改编译器选项)是__cdecl。