4

職場では、専用のフラット ファイル データベースの読み取りと書き込みを担当するネイティブ C コードがあります。P/Invoke 呼び出しを OO モデルにカプセル化する C# で記述されたラッパーがあります。P/Invoke 呼び出しのマネージ ラッパーは、プロジェクトが開始されてからかなり複雑になりました。逸話的には、現在のラッパーは問題なく動作していますが、正しい動作を確保するには、実際にはさらに多くのことを行う必要があると考えています。

回答によってもたらされたいくつかのメモ:

  1. おそらくキープアライブは必要ありません
  2. おそらくGCHandleのピン留めは必要ありません
  3. GCHandle を使用する場合は、試してみてください...最終的にそのビジネス (ただし、CER の質問は解決されていません)

修正されたコードの例を次に示します。

[DllImport(@"somedll", EntryPoint="ADD", CharSet=CharSet.Ansi,
           ThrowOnUnmappableChar=true, BestFitMapping=false,
           SetLastError=false)]
[ReliabilityContract(Consistency.MayCorruptProcess, Cer.None)]
internal static extern void ADD(
    [In] ref Int32 id,
    [In] [MarshalAs(UnmanagedType.LPStr)] string key,
    [In] byte[] data, // formerly IntPtr
    [In] [MarshalAs(UnmanagedType.LPArray, SizeConst=10)] Int32[] details,
    [In] [MarshalAs(UnmanagedType.LPArray, SizeConst=2)] Int32[] status);

public void Add(FileId file, string key, TypedBuffer buffer)
{
    // ...Arguments get checked

    int[] status = new int[2] { 0, 0 };
    int[] details = new int[10];

    // ...Make the details array

    lock (OPERATION_LOCK)
    {
        ADD(file.Id, key, buffer.GetBytes(), details, status);
        // the byte[], details, and status should be auto
        // pinned/keepalive'd

        if ((status[0] != 0) || (status[1] != 0))
            throw new OurDatabaseException(file, key, status);

        // we no longer KeepAlive the data because it should be auto
        // pinned we DO however KeepAlive our 'file' object since 
        // we're passing it the Id property which will not preserve
        // a reference to 'file' the exception getting thrown 
        // kinda preserves it, but being explicit won't hurt us
        GC.KeepAlive(file);
    }
}

私の(修正された)質問は次のとおりです。

  1. データ、詳細、ステータスは自動固定/キープアライブされますか?
  2. これが正しく動作するために必要なものが他にありませんか?

編集: 最近、私の好奇心をかき立てた図を見つけました。基本的には、P/Invoke メソッドを呼び出すと、 GC がネイティブ コードをプリエンプトできると述べています。そのため、ネイティブ呼び出しは同期的に行われる可能性がありますが、GCはメモリを実行して移動/削除することを選択できます。私は今、自動ピン留めで十分かどうか(またはそれが実行されるかどうか)疑問に思っていると思います。

4

4 に答える 4

2

アンマネージ コードがメモリを直接操作していない限り、オブジェクトを固定する必要はないと思います。ピニングは基本的に、コレクション サイクルのコンパクト フェーズ中にそのオブジェクトをメモリ内で移動してはならないことを GC に通知します。これは、アンマネージ コードがデータが常に渡されたときと同じ場所にあることを期待しているアンマネージ メモリ アクセスの場合にのみ重要です。どちらのモードでもピン留めの動作規則が適用されるためです。.NET のマーシャリング インフラストラクチャは、マネージ コードとアンマネージ コードの間でデータをマーシャリングする方法を賢くしようとします。この特定のケースでは、作成している 2 つの配列は、マーシャリング プロセス中に自動的に固定されます。

アンマネージ ADD メソッドが非同期でない限り、GC.KeepAlive の呼び出しもおそらく必要ありません。GC.KeepAlive は、GC が長時間実行されている操作中に死んでいると考えるオブジェクトを再利用するのを防ぐことのみを目的としています。file はパラメーターとして渡されるため、マネージ Add 関数の呼び出し後にコード内の別の場所で使用される可能性が高いため、GC.KeepAlive 呼び出しは必要ありません。

コード サンプルを編集し、GCHandle.Alloc() と Free() の呼び出しを削除しましたが、それはコードがそれらを使用しなくなったことを意味しますか? まだ使用している場合は、lock(OPERATION_LOCK) ブロック内のコードも try/finally ブロックでラップする必要があります。最終ブロックでは、おそらく次のようなことをしたいと思うでしょう:

if (dataHandle.IsAllocated)
{
   dataHandle.Free();
}

また、呼び出し GCHandle.Alloc() がロック内にあってはならないことを確認することもできます。ロックの外に置くことで、複数のスレッドがメモリの割り当てを行うことになります。

自動ピン留めに関しては、マーシャリング プロセス中にデータが自動的にピン留めされる場合、データはピン留めされ、アンマネージ コードの実行中に GC コレクション サイクルが発生した場合でも移動されません。GC.KeepAlive を呼び出し続ける理由についてのコード コメントを完全に理解しているかどうかはわかりません。管理されていないコードは実際に file.Id フィールドの値を設定しますか?

于 2009-02-09T15:37:57.993 に答える
1
  1. あなたはすでに GCHandle を解放しているので、あなたの KeepAlive のポイントが何であるかわかりません - その時点でデータはもう必要ないようです?
  2. #1 と同様に、なぜ KeepAlive を呼び出す必要があると思いますか? あなたが投稿したコード以外に、私たちが見ていないものはありますか?
  3. おそらくそうではありません。これが同期 P/Invoke である場合、マーシャラーは実際には、返されるまで着信変数を固定します。実際、おそらくデータをピン留めする必要もありません (これが非同期である場合を除きますが、構成がそうではないことを示唆している場合を除きます)。
  4. いいえ、何も逃しませんでした。実際には必要以上に追加したと思います。

元の質問の編集とコメントに応じて編集します。

この図は、GCモードが変更されることを示しているだけです。モードは固定されたオブジェクトには影響しません。型は、型に応じてマーシャリング中に固定またはコピーされます。この場合、バイト配列を使用しています。これは、ドキュメントによると blittable typeです。また、「最適化として、blittable メンバーのみを含む blittable 型およびクラスの配列は、マーシャリング中にコピーされるのではなく固定される」ことも具体的に述べていることがわかります。つまり、呼び出し中はデータが固定され、GC が実行されると、配列を移動したり解放したりできなくなります。ステータスについても同様です。

渡される文字列は少し異なり、文字列データがコピーされ、ポインターがスタックに渡されます。この動作により、コレクションと圧縮の影響も受けなくなります。GC はコピーに触れることができず (それについて何も知らない)、ポインターはスタック上にあり、GC は影響しません。

KeepAlive を呼び出す意味がまだわかりません。おそらく、ファイルはメソッドに渡され、それを維持する他のルート (宣言された場所) があるため、コレクションに使用できません。

于 2009-02-09T15:40:09.503 に答える
0

マネージド コードとネイティブ コードの相互運用性のベスト プラクティスを読み、 PInvoke Interop Assistantを使用する

于 2010-02-26T17:00:06.990 に答える