我正在写在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#之间共享内存? 难道仅仅是浪费时间?
我读到内存映射文件。 他们能帮助我吗? 但是,你知道更合适的解决方案?
非常感谢你。
这是没有问题分享C#和C ++使用P / Invoke之间存储,一旦你知道它是如何工作的。 我建议阅读有关MSDN编组。 您可能还需要阅读有关使用不安全的关键字和固定的记忆。
这里是假设你的变量可以被描述为一个简单的结构示例:
在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#访问内存的方式很少限制。 欲了解更多信息,看看到不安全的关键词,固定关键字和结构的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;
};
另外,在上述样品的结构仍可以作为第一样品中被传递。 当然,这仅仅是如何处理与结构的阵列内的结构araays和结构数组复杂的数据轮廓。 正如你可以看到你将需要大量复制到从内存损坏守护自己。 此外,如果内存是通过C ++分配它将如果你使用FreeHGlobal释放它是非常糟糕的。 如果你想避免复制记忆,仍然保持C#中的逻辑,你可以写与存取你想要比如什么都你将不得不直接设置的方法或得到第N阵列成员的subInt本机内存包装 - 这样你一定要让你复制到你访问什么。
另一种选择是编写特定的C ++函数做艰难数据为你办理,并根据您的逻辑从C#调用它们。
最后但并非最不重要的,你总是可以使用C ++与CLI界面。 不过我自己做的只有我必须 - 我不喜欢行话但对于非常复杂的数据,你一定要考虑它。
编辑
我加了正确的调用约定到的DllImport的完整性。 请注意,DllImport属性使用的默认调用约定是WINAPI(这在Windows转化为__stdcall),而在C / C默认调用约定++(除非你改变了编译器选项)是__cdecl。
你能做的最好的事情是定义你的C ++代码(这将是我的非托管承担)。 然后写在C ++ / CLI为它的包装。 该包装将提供给C#的接口,是在那里你可以照顾marchalling(移动从unmanagerd数据管理区域的)的地方
一种方式,以避免具有传递到变量/数据,无论是C#和C ++代码需要是从本地DLL导出两个函数的参考。 除了你的函数,它的工作,提供了另一种功能,它允许参考传递和存储到在同一个.cpp文件作为这两个功能定义,这样是既可以访问的文件范围的静态指针。
正如你所说,你也可以使用内存映射文件(在这种情况下,它可以是非持久,因为它永远不需要被写入到磁盘)。