2012-11-07 41 views
15

私は何回もC++アンマネージドDLLの関数を呼び出す必要があるC#でソフトウェアを書いています。C#とC++間の変数の共有

私はそのようなC++ファイルがあります:私は "私がやりたい..." を書いた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" 
     } 
    } 

} 

よう

// "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#クラスを各ラウンドには「変数」も更新されています。

私の質問は:

は、基準を毎回渡す避けるために、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#のは、このような何か:

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++の構造の割り当てや解放などのより複雑なシナリオを使用できます。しかし、単純な構造体を使用して変数を保持できる場合は、上記の方法が最も簡単です。

EDIT:それは、単純なデータの例であれば、正しく、より複雑なデータ

を処理する方法のポインターはしたがって、上記のサンプルは私の意見ではC#とC++の間で「シェア」データへの正しい道です。基本型または固定サイズのプリミティブ型の配列を保持する構造体

これは、実際にC#を使用してメモリにアクセスする方法にはほとんど制限がないと言われています。詳細については、unsafeキーワード、fixedキーワード、およびGCHandle構造体を参照してください。そして、あなたが他の構造体の配列などを含む非常に複雑なデータ構造を持っているなら、あなたはもっと複雑な仕事をしています。

上記のケースでは、「変数」をC++に更新する方法のロジックを移動することをお勧めします。 はこのような何かを探すように機能++ Cに追加:

extern "C" 
{ 
    __declspec(dllexport) void updateVariables(int bestResult) 
    { 
     // update the variables 
    } 
} 

私はまだ私は、次の方式を提案するように、グローバル変数を使用しないようお勧めします。 C++で :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 
    } 
}; 

[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#にデータを公開します。あなたは構造がまだとして渡すことができます上記のサンプルでは、​​この

[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; 
}; 

ような何かを

#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#で:私は何を意味することは、以下のように、 はあなたがC++で、このような構造を持っていると言うです最初のサンプルで。これはもちろん、構造体の配列内の構造体と構造体の配列を使用して複雑なデータを処理する方法の概要です。あなたが見ることができるように、あなたはメモリ破損から身を守るために多くのコピーが必要になります。また、メモリがC++経由で割り当てられている場合、FreeHGlobalを使用して解放すると非常に悪くなります。 メモリのコピーを避け、C#内のロジックを維持したい場合は、あなたが望むものに対してアクセサでネイティブメモリラッパーを書くことができます。例えば、N番目の配列メンバーのsubIntを直接設定または取得するメソッドがあります。あなたのコピーをあなたがアクセスしているものに正確に保ちます。

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

最後に、CLIインターフェイスで常にC++を使用できます。しかし、私は自分がそうしなければならない場合にのみそれをする - 私は言葉が好きではないが、非常に複雑なデータについては、それを考慮する必要がある。

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

+0

この明白な答えをありがとう。私はそれを打ち明けましたが、私はまだ満足していません。たぶん私の質問で私はP/invokeで既に試したことを指定していないが、このように(私が間違っていない場合)、各繰り返しですべての変数(ok、参照)を渡さなければならない。私はこれを避ける方法があるのだろうかと思っていた。 – 888

+1

@mcdg構造体への参照をまったく渡すことは問題ありません。そうすることによって、大きなオーバーヘッドはありません。多くの点で、ある種の構造やクラスを使用して状態を保持する方がはるかに優れています。こうすることで、マルチスレッド化が可能になり、グローバル変数を使用するときには可能性を制限しながら関数を再入可能にします。しかし、あなたが "変数"の仕様であなたの質問を更新したので、私は答えに複雑なケースを扱う方法のいくつかの指針を追加します –

3

あなたができることは、あなたのC++コードを定義することです(これは私が想定している管理対象外です)。 次に、C++/CLIでそのラッパーを記述します。ラッパーはC#へのインターフェースを提供し、marchalling(unmanagerdからmanagedエリアにデータを移動する)を気にするところです

+1

なぜC++/CLIを使用してラッパーを作成するのですか?あなたはC#でラッパーを簡単に書くことができます。 –

+1

C++/CLIは、管理されたコードとアンマネージコードを切り替える場合には、すべての詳細を扱う際の柔軟性がもう少しあります。 C++/CLIは、.NETとC++を組み合わせたもので、少なくともC++にはあまり使用されていないのであれば、「楽になる」というわけではありません。 C#に固執することは賢明な選択かもしれません。 – PapaAtHome

+0

私は自分自身をよりよく説明するために少し質問を変えました。 – 888

1

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

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

関連する問題