以前の提案は本質的にかなり一般的なものであるため、特定のコード例、この例外を発生させるために実装した背景の変更、およびその解決方法を使用して、この例外に対する独自の戦いを投稿するのに役立つと思いました。
まず、TL; DRバージョン:C ++(管理されていない)で記述された社内dllを使用していました。.NET実行可能ファイルから特定のサイズの配列を渡しました。アンマネージコードは、マネージコードによって割り当てられていないアレイの場所に書き込もうとしました。これにより、後でガベージコレクションされるように設定されたメモリの破損が発生しました。ガベージコレクタは、メモリを収集する準備をするときに、最初にメモリのステータス(および境界)をチェックします。破損が見つかったら、BOOM。
今詳細なバージョン:
社内で開発され、C++で記述されたアンマネージDLLを使用しています。私自身のGUI開発はC#.Net4.0です。私はそれらのさまざまなアンマネージドメソッドを呼び出しています。そのdllは効果的に私のデータソースとして機能します。dllからのextern定義の例:
[DllImport(@"C:\Program Files\MyCompany\dataSource.dll",
EntryPoint = "get_sel_list",
CallingConvention = CallingConvention.Winapi)]
private static extern int ExternGetSelectionList(
uint parameterNumber,
uint[] list,
uint[] limits,
ref int size);
次に、プロジェクト全体で使用できるように、メソッドを独自のインターフェイスでラップします。
/// <summary>
/// Get the data for a ComboBox (Drop down selection).
/// </summary>
/// <param name="parameterNumber"> The parameter number</param>
/// <param name="messageList"> Message number </param>
/// <param name="valueLimits"> The limits </param>
/// <param name="size"> The maximum size of the memory buffer to
/// allocate for the data </param>
/// <returns> 0 - If successful, something else otherwise. </returns>
public int GetSelectionList(uint parameterNumber,
ref uint[] messageList,
ref uint[] valueLimits,
int size)
{
int returnValue = -1;
returnValue = ExternGetSelectionList(parameterNumber,
messageList,
valueLimits,
ref size);
return returnValue;
}
このメソッドの呼び出し例:
uint[] messageList = new uint[3];
uint[] valueLimits = new uint[3];
int dataReferenceParameter = 1;
// BUFFERSIZE = 255.
MainNavigationWindow.MainNavigationProperty.DataSourceWrapper.GetSelectionList(
dataReferenceParameter,
ref messageList,
ref valueLimits,
BUFFERSIZE);
GUIでは、さまざまなグラフィックスとユーザー入力を含むさまざまなページをナビゲートします。以前の方法では、データを取得してデータを入力することができましたComboBoxes
。この例外の前の時点でのナビゲーション設定と呼び出しの例:
ホストウィンドウで、プロパティを設定します。
/// <summary>
/// Gets or sets the User interface page
/// </summary>
internal UserInterfacePage UserInterfacePageProperty
{
get
{
if (this.userInterfacePage == null)
{
this.userInterfacePage = new UserInterfacePage();
}
return this.userInterfacePage;
}
set { this.userInterfacePage = value; }
}
次に、必要に応じて、次のページに移動します。
MainNavigationWindow.MainNavigationProperty.Navigate(
MainNavigation.MainNavigationProperty.UserInterfacePageProperty);
私はいくつかの深刻な忍び寄る問題を抱えていましたが、すべてが十分にうまくいきました。オブジェクト(NavigationService.Navigateメソッド(オブジェクト)IsKeepAlive
)を使用してナビゲートする場合、プロパティのデフォルト設定はですtrue
。しかし、問題はそれよりももっと悪質です。IsKeepAlive
そのページのコンストラクターで値を具体的にに設定した場合false
でも、ガベージコレクターによって値がそのまま残されます。true
。今、私のページの多くにとって、これは大したことではありませんでした。彼らはメモリフットプリントが小さく、それほど多くは起こっていませんでした。しかし、これらの他の多くのページには、説明のために非常に詳細な大きなグラフィックがいくつかありました。私たちの機器のオペレーターによるこのインターフェースの通常の使用がメモリの膨大な割り当てを引き起こし、それがクリアされることはなく、最終的にマシン上のすべてのプロセスを詰まらせるまで、それほど長くはありませんでした。津波から海嘯へと初期開発のラッシュが収まった後、私はついにメモリリークに完全に取り組むことにしました。メモリをクリーンアップするために実装したすべてのトリックの詳細については説明しません(WeakReferenceを画像に、Unload()でイベントハンドラーをフック解除し、IWeakEventListenerを実装するカスタムタイマーを使用します)インターフェイスなど)。私が行った主な変更は、オブジェクトの代わりにUriを使用してページに移動することでした(NavigationService.Navigateメソッド(Uri))。このタイプのナビゲーションを使用する場合、2つの重要な違いがあります。
IsKeepAlive
デフォルトでに設定さfalse
れています。
- ガベージコレクターは、
IsKeepAlive
に設定されているかのようにナビゲーションオブジェクトをクリーンアップしようとしfalse
ます。
だから今私のナビゲーションは次のようになります:
MainNavigation.MainNavigationProperty.Navigate(
new Uri("/Pages/UserInterfacePage.xaml", UriKind.Relative));
ここで注意すべき点:これは、オブジェクトがガベージコレクターによってクリーンアップされる方法に影響するだけでなく、すぐにわかるように、オブジェクトがメモリに最初に割り当てられる方法にも影響します。
すべてがうまくいったようです。グラフィックを多用するページをナビゲートすると、この特定のページにアクセスしてdataSource dllを呼び出し、いくつかのコンボボックスに入力するまで、メモリはすぐに初期状態近くまでクリーンアップされました。それから私はこの厄介なものを手に入れましたFatalEngineExecutionError
。何日にもわたって調査し、漠然とした提案、または私には当てはまらない非常に具体的な解決策を見つけ、個人的なプログラミング兵器のほぼすべてのデバッグ兵器を解き放った後、私は最終的にこれを釘付けにする唯一の方法を決定しましたダウンは、この特定のページの正確なコピーを、要素ごと、メソッドごと、行ごとに再構築するという極端な手段でしたが、最終的にこの例外をスローするコードに出くわしました。それは私が示唆しているのと同じくらい退屈で苦痛でした、しかし私はついにそれを突き止めました。
管理されていないdllが、データを取り込むために送信していたアレイにデータを書き込むためにメモリを割り当てていた方法であることが判明しました。その特定のメソッドは実際にパラメーター番号を調べ、その情報から、送信した配列に書き込むと予想されるデータの量に基づいて特定のサイズの配列を割り当てます。クラッシュしたコード:
uint[] messageList = new uint[2];
uint[] valueLimits = new uint[2];
int dataReferenceParameter = 1;
// BUFFERSIZE = 255.
MainNavigationWindow.MainNavigationProperty.DataSourceWrapper.GetSelectionList(
dataReferenceParameter,
ref messageList,
ref valueLimits,
BUFFERSIZE);
このコードは上記のサンプルと同じように見えるかもしれませんが、わずかな違いが1つあります。私が割り当てる配列サイズは3ではなく2です。これを行ったのは、この特定のComboBoxには、すべて3つの選択項目があるページ上の他のComboBoxとは対照的に、2つの選択項目しかないことがわかっていたためです。しかし、アンマネージコードは私が見たように物事を見ることができませんでした。渡した配列を取得し、サイズ3の配列をサイズ2の割り当てに書き込もうとしました。それだけです。*バン!* *クラッシュ!*割り当てサイズを3に変更したところ、エラーはなくなりました。
現在、この特定のコードは、少なくとも1年間、このエラーなしですでに実行されていました。Uri
しかし、とは対照的に、を介してこのページに移動するという単純な行為Object
により、クラッシュが表示されました。これは、私が使用したナビゲーション方法のために、最初のオブジェクトを別の方法で割り当てる必要があることを意味します。私の古いナビゲーション方法では、記憶はちょうどその場に積み上げられ、永遠にふさわしいと思ったので、それが1つか2つの小さな場所で少し壊れていても問題ではなかったようです。ガベージコレクターが実際にそのメモリで何かを実行する必要があると(クリーンアップなど)、メモリの破損を検出して例外をスローしました。皮肉なことに、私の主要なメモリリークは、致命的なメモリエラーを隠蔽することでした。
明らかに、将来このようなクラッシュを引き起こすような単純な仮定を回避するために、このインターフェイスを確認します。これが、他の人が自分のコードで何が起こっているのかを知るのに役立つことを願っています。