私が取り組んでいる C++/CLI (および C#) プロジェクトの早い段階で呼び出されているように見えるファイナライザーに問題があります。これは非常に複雑な問題のようで、コードから多くの異なるクラスと型について言及します。幸いなことに、これはオープン ソースであり、次のリンクをたどることができます: Pstsdk.Net (mercurial リポジトリ) また、必要に応じてファイル ブラウザーに直接リンクすることも試みたので、読みながらコードを表示できます。扱うコードのほとんどはpstsdk.mcpp
、リポジトリのフォルダーにあります。
現在、コードはかなりひどい状態にあり (私はそれに取り組んでいます)、私が取り組んでいるコードの現在のバージョンはFinalization fixes (UNSTABLE!)
ブランチにあります。そのブランチには 2 つの変更セットがあり、私の長々とした質問を理解するには、両方に対処する必要があります。(変更セット: ee6a002df36fおよびa12e9f5ea9fe )
背景として、このプロジェクトは C++ で書かれたアンマネージ ライブラリの C++/CLI ラッパーです。私はプロジェクトのコーディネーターではありません。また、コードを見た多くの人が同意するであろうと確信しているように、私が同意しない設計上の決定がいくつかありますが、余談になります。元のライブラリのレイヤーの多くを C++/CLI dll にラップしていますが、使いやすい API は C# dll で公開しています。これは、プロジェクトの目的がライブラリ全体をマネージ C# コードに変換することであるためです。
コードをコンパイルできる場合は、このテスト コードを使用して問題を再現できます。
問題
というタイトルの最新の変更セットは、moved resource management code to finalizers, to show bug
私が抱えていた元の問題を示しています。このコードのすべてのクラスは、同じパターンを使用してアンマネージ リソースを解放します。以下に例を示します (C++/CLI):
DBContext::~DBContext()
{
this->!DBContext();
GC::SuppressFinalize(this);
}
DBContext::!DBContext()
{
if(_pst.get() != nullptr)
_pst.reset(); // _pst is a clr_scoped_ptr (managed type)
// that wraps a shared_ptr<T>.
}
このコードには 2 つの利点があります。まず、このようなクラスがusing
ステートメントにある場合、リソースはすぐに適切に解放されます。第 2 に、ユーザーが破棄を忘れた場合、GC が最終的にクラスのファイナライズを決定したときに、管理されていないリソースが解放されます。
ここに、このアプローチの問題点があります。私が理解できないのは、時折、ファイル内のデータを列挙するために使用されるクラスの一部を GC がファイナライズすることを決定することです。これは多くの異なる PST ファイルで発生し、クラスがまだ使用されているにもかかわらず、Finalize メソッドが呼び出されたことに関係があると判断できました。
このファイル (ダウンロード) 1で一貫して発生させることができます。早期に呼び出されるファイナライザーは、 DBAccessor.cppファイル内のNodeIdCollection
クラスにあります。上記にリンクされたコードを実行できる場合 (このプロジェクトは、boost ライブラリへの依存関係のためにセットアップが難しい場合があります)、アプリケーションは例外で失敗します。これは、リストが null に設定され、ポインターがファイナライザーの実行の結果としてリセットされます。_nodes
_db_
1) クラスの列挙コードに、NodeIdCollection
使用中に GC がこのクラスをファイナライズするような明らかな問題はありますか?
以下で説明する回避策を使用して、コードを適切に実行することしかできませんでした。
見苦しい回避策
!classname
ここで、すべてのリソース管理コードを各ファイナライザー ( ) からデストラクタ ( )に移動することで、この問題を回避することができました~classname
。これで問題は解決しましたが、なぜクラスが早期にファイナライズされるのかという私の好奇心は解決されませんでした。
ただし、アプローチには問題があり、それよりも設計の問題であることは認めます。コード内でポインターが頻繁に使用されるため、ほぼすべてのクラスが独自のリソースを処理し、各クラスを破棄する必要があります。これにより、列挙型の使用が非常に見苦しくなります (C#):
foreach (var msg in pst.Messages)
{
// If this using statement were removed, we would have
// memory leaks
using (msg)
{
// code here
}
}
コレクション内のアイテムに作用する using ステートメントは、私には間違っているように思えますが、このアプローチでは、メモリ リークを防ぐことが非常に必要です。これがないと、pst クラスの dispose メソッドが呼び出されても、dispose が呼び出されず、メモリが解放されません。
私はこのデザインを変更しようとするあらゆる意図を持っています。このコードが最初に書かれたときの根本的な問題は、私が C++/CLI についてほとんどまたはまったく知らなかったという事実に加えて、ネイティブ クラスをマネージ クラス内に配置できなかったことです。クラスが使用されなくなったときにメモリを自動的に解放するスコープ付きポインターを使用できると思いますが、それが有効な方法であるかどうか、または機能するかどうかはわかりません。だから、私の2番目の質問は次のとおりです。
2) 管理されたクラスの管理されていないリソースを簡単に処理するための最良の方法は何ですか?
詳しく説明すると、ネイティブ ポインターをclr_scoped_ptr
最近コードに追加されたラッパー (このstackexchangeの質問の clr_scoped_ptr.h )に置き換えることができますか? または、ネイティブ ポインターを or のようなものでラップする必要がありますか?scoped_ptr<T>
smart_ptr<T>
これをすべて読んでくれてありがとう、私はそれがたくさんあったことを知っています。私よりも少し経験豊富な人からいくつかの洞察を得ることができるように、私は十分に明確であったことを願っています. それは非常に大きな質問です。私も可能であれば報奨金を追加するつもりです。うまくいけば、誰かが助けることができます。
ありがとう!
1このファイルは、自由に利用できるPST ファイルのエンロン データセットの一部です。