web-dev-qa-db-ja.com

C#と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#の間でメモリを共有することは可能ですか?時間の無駄ですか?

メモリマップトファイルについて読みました。彼らは私を助けてくれますか?しかし、あなたはより適切な解決策を知っていますか?
どうもありがとうございました。

17
888

動作方法がわかれば、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を直接設定または取得するメソッドがあります-この方法あなたはあなたがアクセスするものに正確にあなたのコピーを保持します。

もう1つのオプションは、特定のC++関数を記述して、難しいデータ処理を実行し、ロジックに従ってC#からそれらを呼び出すことです。

そして最後になりましたが、CLIインターフェイスでいつでもC++を使用できます。しかし、私自身は必要な場合にのみそれを行います-私は用語が好きではありませんが、非常に複雑なデータの場合は確かにそれを考慮する必要があります。

[〜#〜]編集[〜#〜]

完全を期すために、正しい呼び出し規約をDllImportに追加しました。 DllImport属性で使用されるデフォルトの呼び出し規約はWinapi(Windowsでは__stdcallに変換されます)であるのに対し、C/C++のデフォルトの呼び出し規約(コンパイラオプションを変更しない限り)は__cdeclであることに注意してください。

23
Sebastian Cabot

あなたができる最善のことはあなたのC++コードを定義することです(それは私が推測する管理されていないでしょう)。次に、C++/CLIでラッパーを記述します。ラッパーはC#へのインターフェイスを提供し、マーチャリング(管理されていない領域から管理された領域へのデータの移動)を処理できる場所です。

3
PapaAtHome

C#コードとC++コードの両方が必要とする変数/データへの参照を渡す必要を回避する1つの方法は、ネイティブDLLから2つの関数をエクスポートすることです。作業を行う関数に加えて、両方の関数と同じ.cppファイルで定義されているファイルスコープの静的ポインターに参照を渡して格納できるようにする別の関数を提供して、両方にアクセスできるようにします。

あなたが言ったように、メモリマップトファイルを使用することもできます(この場合、ディスクに書き込む必要がないため、永続化されない可能性があります)。

1
Josh Heitzman