仕組みがわかれば、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 であることに注意してください。