2012-11-07 267 views
15

我在c#中编写了一个软件,它需要多次调用,并被很多线程调用C++非托管dll中的函数。在C#和C++之间共享变量

我有一个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#之间共享内存?这只是浪费时间吗?

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

+1

我把你的问题upvoted,但你应该真的指定“变量”的性质来得到一个很好的答案 –

+0

我刚刚编辑。谢谢。 – 888

回答

22

一旦您使用P/Invoke,在C#和C++之间共享内存没有任何问题知道它是如何工作的。我会建议阅读有关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++这样的结构:在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() 

你做这样的事情

[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个数组成员的子类 - 这你会保持你的副本到你访问的方式。

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

最后但并非最不重要的是,您始终可以使用C++与CLI接口。不过,我自己做这件事只有当我必须 - 我不喜欢这个术语,但对于非常复杂的数据,你当然必须考虑它。

编辑

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

+0

谢谢你这个明确的答案。我高举了它,但我一点都不满意。也许在我的问题中,我错过了指定我已经尝试过使用P/invoke,但以这种方式(如果我没有错),我必须在每次迭代时通过所有变量(ok,引用)。我想知道是否有办法避免这种情况。 – 888

+1

@mcdg通过引用结构完全没有问题。这样做没有重大的开销。在许多方面,使用某种结构或阶级来保持国家的做法更好。通过这种方式,您可以使用多线程,并且使您的函数可重入,而在使用全局变量时,可以限制您的可能性。然而,既然你已经用“变量”的规范更新了你的问题,我将在答案中加入一些关于如何处理更复杂的情况的指针 –

3

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

+1

为什么他们会使用C++/CLI来创建包装?你可以很容易地用C#编写包装器。 –

+1

C++/CLI在处理所有细节方面稍微灵活一点,如果您想在托管代码和非托管代码之间切换,那么就特别重要。结合.NET和C++的C++/CLI,如果你至少不习惯C++(但这仅仅是我的观点),并不容易。然后坚持C#可能是一个明智的选择。 – PapaAtHome

+0

我改变了一些问题来更好地解释自己。 – 888

1

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

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