18

我正在用 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# 之间共享内存以避免每次都传递引用?这只是浪费时间吗?

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

4

3 回答 3

24

一旦你知道它是如何工作的,使用 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# 调用它们。

最后但并非最不重要的一点是,您始终可以将 C++ 与 CLI 接口一起使用。但是,我本人仅在必须时才这样做-我不喜欢术语,但是对于非常复杂的数据,您当然必须考虑它。

编辑

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

于 2012-11-09T09:34:42.430 回答
4

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

于 2012-11-07T17:54:04.393 回答
1

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

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

于 2012-11-13T01:59:42.563 回答