簡體   English   中英

在C#和C ++之間共享變量

[英]Sharing variables between C# and C++

我正在用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#之間共享內存以避免每次傳遞引用? 這只是浪費時間嗎?

我讀到了內存映射文件。 他們能幫助我嗎? 但是,您知道更合適的解決方案嗎?
非常感謝你。

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

你能做的最好的事情就是定義你的C ++代碼(我假設它是不受管理的)。 然后在C ++ / CLI中為它編寫一個包裝器。 包裝器將為您提供C#的接口,並且您可以在這里處理游戲(將數據從非管理區域移動到管理區域)

避免必須傳遞對C#和C ++代碼所需的變量/數據的引用的一種方法是從本機DLL導出兩個函數。 除了執行工作的函數之外,還提供另一個函數,該函數允許將引用傳入並存儲到文件范圍靜態指針,該指針在同一.cpp文件中定義為兩個函數,以便兩者都可以訪問。

正如您所提到的,您還可以使用內存映射文件(在這種情況下,它可能是非持久的,因為它永遠不需要寫入磁盤)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM