我正在用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#之间共享内存以避免每次传递引用?这只是浪费时间吗?
我读到了关于内存映射文件的内容。他们能帮助我吗?但是,你知道更合适的解决方案吗? 非常感谢你。
答案 0 :(得分:23)
一旦你知道它是如何工作的,使用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);
在你的班上:
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)
{
// 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;
};
在上面的样本中,结构仍然可以像第一个样本一样传递。这当然只是关于如何使用结构阵列和结构数组中的结构阵列处理复杂数据的概述。正如您所看到的,您需要进行大量复制以防止内存损坏。此外,如果内存是通过C ++分配的,那么如果你使用FreeHGlobal释放它将是非常糟糕的。 如果你想避免复制内存并仍然在C#中维护逻辑,你可以用你想要的访问器编写一个本机内存包装器。例如,你将有一个方法直接设置或获取第N个数组成员的subInt - 这种方式你会将你的副本保存到你所访问的内容中。
另一种选择是编写特定的C ++函数来为您执行困难的数据处理,并根据您的逻辑从C#调用它们。
最后但并非最不重要的是,您始终可以使用带有CLI界面的C ++。然而,我自己只有在必须的时候这样做 - 我不喜欢这个术语,但对于非常复杂的数据,你当然要考虑它。
修改强>
我为DllImport添加了正确的调用约定以确保完整性。请注意,DllImport属性使用的默认调用约定是Winapi(在Windows上转换为__stdcall),而C / C ++中的默认调用约定(除非您更改编译器选项)是__cdecl。
答案 1 :(得分:3)
你能做的最好的事情就是定义你的C ++代码(我假设它是不受管理的)。 然后在C ++ / CLI中为它编写一个包装器。包装器将为您提供C#的接口,并且您可以在这里处理过marchalling(将数据从非管理区域移动到托管区域)
答案 2 :(得分:1)
避免必须传递对C#和C ++代码所需的变量/数据的引用的一种方法是从本机DLL导出两个函数。除了执行工作的函数之外,还提供另一个函数,允许将引用传入并存储到文件范围静态指针,该指针在同一个.cpp文件中定义为两个函数,以便两者都可以访问。
正如您所提到的,您还可以使用内存映射文件(在这种情况下,它可能是非持久的,因为它永远不需要写入磁盘)。