テクニカル ノート 58: MFC のモジュール状態の実装
[!メモ]
次のテクニカル ノートは、最初にオンライン ドキュメントの一部とされてから更新されていません。結果として、一部のプロシージャおよびトピックが最新でないか、不正になります。最新の情報について、オンライン ドキュメントのキーワードで関係のあるトピックを検索することをお勧めします。
このテクニカル ノートは、 MFC のモジュール状態 「」の構造の実装について説明します。モジュール状態実装の知識は、 DLL (または OLE サーバー プロセス)から MFC の共有 DLL を使用するように重大。
この説明を読む前に、 「 新しいドキュメント、ウィンドウとビューの作成の MFC のモジュール状態データを管理すること」を参照してください。この技術情報では、このサブジェクトの重要な使用方法について、概要情報が含まれます。
概要
3 種類の MFC の状態情報があります: モジュール状態、プロセスの状態およびスレッド状態。場合によっては、これらの状態の型を組み合わせることができます。たとえば、 MFC ハンドルのマップは、モジュールの同じローカル変数、およびスレッド ローカルです。これにより、 2 台のモジュールがスレッドのバリアント マップを持つようにします。
プロセス状態とスレッド状態は似ています。これらのデータ項目は、従来のグローバル変数が、適切な Win32s サポートまたは適切なマルチスレッドのサポートの特定のプロセスまたはスレッドに固有である必要があります。カテゴリに指定されたデータ項目が収まるかは、プロセスとスレッドの境界に関連する項目とその目的のセマンティクスによって。
モジュール状態は、ローカル プロセスまたはスレッド ローカルのグローバル状態を完全に含めるか、提供できます。一意です。また、すぐに切り替えることができます。
モジュール状態の切り替え
各スレッドは、 「現在」または 「実行中」モジュール状態へのポインターが格納されます (予測可能で、 MFC のスレッド ローカルの状態の一部です)。このポインターは、アプリケーションにコールバックを OLE コントロールや DLL、または OLE コントロールに呼び出すアプリケーションなど、実行中のスレッドがモジュールの境界を渡すときに変更されます。
現在のモジュール状態は AfxSetModuleStateを呼び出して切り替わります。ほとんどの場合、 API を直接取扱いません。MFC、多くの場合、のは、 (WinMain、 OLE エントリ ポイント、 AfxWndProcなど)。これは、のモジュール状態が最新になる必要があるかわかっている特別な WndProcの静的にリンクして書き込むコンポーネントで、特別な WinMain されます (または DllMain)。MFC ent dict AnyPathTillLastSlash ディレクトリの DLLMODUL.CPP か APPMODUL.CPP を参照して、このコードを確認できます。
これは、モジュール状態を設定し、をに設定することはまれです。ほとんどの場合は」と 「現在のスケジューラとしてモジュール状態を独自にする場合は、された後、元のコンテキストをもう一度 「ポップ鳴らして」これは、マクロ AFX_MANAGE_STATE および特殊なクラス AFX_MAINTAIN_STATEによってされます。
CCmdTarget のサポートのモジュール状態の切り替えの特別な機能があります。特に、 CCmdTarget は、 OLE オートメーションと OLE の COM エントリ ポイントに使用するルート クラスです。公開されるシステムの他のエントリ ポイントと同様に、これらのエントリ ポイントは正しいモジュール状態を設定する必要があります。特定の CCmdTarget がどのように 「正しい」モジュール状態の内容わかっているか。答えは後で呼び出されたときに呼び出すことができる値」現在のモジュール状態を 「に設定できる 「」現在のモジュール状態が、そのうち作成時に何を 「終了」することです。その結果、 CCmdTarget の特定のオブジェクトが関連付けられているモジュール状態は、オブジェクトが構築されたときに現在のモジュール状態です。インプロセス サーバーを読み込み、オブジェクトを作成し、メソッドを呼び出す簡単な例を取得します。
DLL は LoadLibraryを使用して OLE で読み込まれます。
RawDllMain は、最初に呼び出されます。これは、 DLL の既知の静的なモジュール状態にモジュール状態を設定します。したがって RawDllMain は、 DLL に静的にリンクされます。
このオブジェクトに関連付けられたクラス ファクトリのコンストラクターを呼び出します。COleObjectFactory は CCmdTarget から派生し、のモジュール状態をでインスタンス化されるかその結果、記憶します。これは重要です —オブジェクトを作成するためのクラス ファクトリが呼び出されたときに、現在のを行うか、モジュール状態、習得できます。
DllGetClassObject は、クラス ファクトリを取得するために呼び出されます。MFC は、このモジュールと戻りに関連付けられたクラス ファクトリの一覧で取得します。
COleObjectFactory::XClassFactory2::CreateInstance が呼び出されます。オブジェクトを作成し、手順 3 ( COleObjectFactory のインスタンスが作成されたとき)現在のレコードであったで最新の 1 のモジュール状態に、この関数のセット モジュール状態返します。これは METHOD_PROLOGUE内でされます。
オブジェクトが作成されると、非常に CCmdTarget から派生したものであり、同様に保持するモジュール状態がアクティブであった COleObjectFactory は、この新しいオブジェクトをします。現在オブジェクトが呼び出されるたびに切り替えるためのモジュール状態をを習得できます。
クライアントの呼び出し CoCreateInstance の呼び出しから受け取った OLE COM オブジェクトの関数。オブジェクトが呼び出されると、 COleObjectFactory がとしてモジュール状態を切り替えるには METHOD_PROLOGUE を使用します。
確認できるように、モジュール状態は、オブジェクトからオブジェクトに対するこれらの作成時に反映されます。これは重要モジュール状態に適切に設定されるようになります。が設定されていない場合、 DLL または COM オブジェクトは、 MFC アプリケーションに不完全にやり取りするかを呼び出している可能性がある、またはリソースが見つからないことがありますが、またはそのほかの悲惨な形で失敗することがあります。
特定の種類の DLL が特に、 「」 RawDllMain MFC 拡張 DLL のモジュール状態を切替えない。実際には、通常 RawDllMainはありません)。これは」実際に使用するアプリケーションの場合と同様に、 「実行するためです。これらは、を実行している場合は、そのアプリケーションのグローバル状態を変更することが目的ですアプリケーションの一部です。
OLE コントロールやそのほかの DLL は大きく異なります。これらは呼び出し元のアプリケーションの状態を変更したく; これらを呼び出しているアプリケーションは、 MFC アプリケーションでなく、し、そこに変更する状態ではないそうでない場合があります。これは、モジュール状態の切り替えを作成したという原因です。
DLL のダイアログ ボックスを表示し、 1 などの DLL のエクスポート関数について、関数の開始に次のコードを追加する必要があります:
AFX_MANAGE_STATE(AfxGetStaticModuleState( ))
これは、現在のスコープを閉じるまでの AfxGetStaticModuleState から返される状態を持つ現在のモジュール状態を交換します。
DLL のリソースに問題が AFX_MODULE_STATE のマクロが使用されていない場合に発生します。既定では、 MFC はリソース テンプレートを読み込むメイン アプリケーションのリソース ハンドルを使用します。このテンプレートは、 DLL に実際に格納されます。根本原因は、 MFC のモジュール状態情報が AFX_MODULE_STATE マクロで切替えられなかったことです。リソース ハンドルは、 MFC のモジュール状態から復元します。モジュール状態を切替えないと、誤ったリソース ハンドルを使用します。
AFX_MODULE_STATE は、 DLL の関数に配置される必要はありません。たとえば、 InitInstance は AFX_MODULE_STATE を含まないアプリケーションの MFC コードによって MFC が InitInstance の前に自動的にモジュール状態をシフトし、 InitInstance の戻りの後に切り替えるため呼び出すことができます。これは、すべてのメッセージ マップ ハンドラーにも当てはまります。レギュラー DLL に実際にメッセージをルーティングする前に自動的にモジュール状態を切り替える特別なマスターのウィンドウ プロシージャがあります。
プロセス ローカル データ
プロセス ローカル データは Win32s の DLL モデルの問題がない場合は、このような重要視されるはありません。Win32s のすべての DLL で複数のアプリケーションによって読み込まれる場合でもグローバル データを共有します。これは、各 DLL が DLL に接続する各プロセスのデータ領域の別のコピーの取得 「実際の」 Win32 DLL のデータ モデルと大差います。実際、複雑さに追加するには、 Win32s の DLL ヒープに割り当てられたデータ (少なくとも所有権がその範囲では)プロセス仕様です。次のコードやデータを検討する:
static CString strGlobal; // at file scope
__declspec(dllexport)
void SetGlobalString(LPCTSTR lpsz)
{
strGlobal = lpsz;
}
__declspec(dllexport)
void GetGlobalString(LPCTSTR lpsz, size_t cb)
{
StringCbCopy(lpsz, cb, strGlobal);
}
上のコードが DLL にあり、その DLL が 2 台のプロセス A と 12 C (実際は、同じアプリケーションの 2 個のインスタンスである可能性もあります)によって読み込まれる場合はどうなるかを検討します。呼び出し SetGlobalString("Hello from A")。そのため、メモリはプロセス A. のコンテキストで CString のデータのようになりました。CString 自体がグローバルで、 A と 12 C の両方から参照できることに注意してください。現在の 12 C の呼び出し GetGlobalString(sz, sizeof(sz))。12 C に設定されている A データを表示できます。これは Win32s が Win32 などのプロセス間の保護を提供しないためです。これにより、最初の問題が; 多くの場合、異なるアプリケーションが所有していると見なされる 1 アプリケーションに影響のグローバル データを持つことはお勧めしません。
そのほかの問題があります。A で終了すると仮定します。A が終了すると、 " "strGlobalの文字列によって使用されるメモリはシステムで使用できるようになります。つまり、すべてに割り当てられたメモリはを自動的によって解放され、オペレーティング システムによって処理します。これは CString のデストラクターが呼び出されるため放されません; これはまだ呼び出されていません。では、割り当てたアプリケーションはシーンを離れたため、単によって解放され。12 C が GetGlobalString(sz, sizeof(sz))を呼び出した現在は、有効なデータを招く可能性があります。他のアプリケーションでは、他の操作にこのメモリを使用する場合があります。
マニフェストに問題があります。MFC Version 3.x はスレッドローカル ストレージ (TLS) という手法を使用しました。呼び出されませんが、 MFC Version 3.x は、プロセス ローカル ストレージのインデックスとして Win32s の下に実際に機能するキーワード インデックスを割り当てます。は、その TLS インデックスに基づいてすべてのデータを参照します。これは、 Win32 でスレッドローカル データを格納するために使用した TLS インデックスに似ています (そのサブジェクトの詳細については、次を参照)。これには、 MFC DLL は、プロセスごとの少なくとも 2 つが TLS インデックスを使用します。読み込みを多くの OLE コントロールの DLL () OCXs 説明すると、すぐに TLS インデックスを使い果たします (使用できる 64 のみがあります)。また、 MFC は単一の構造の 1 か所でこのデータをすべて、入れる必要がありました。このクラスは、非常に拡張可能にし、 TLS インデックスの使用に関しては適切ではありません。
MFC 4.x は、プロセス固有である必要があるデータの周囲に 「ラップ」することができる一連のクラス テンプレートでこれに対応します。たとえば、上記された問題を記述することで解決できます:
struct CMyGlobalData : public CNoTrackObject
{
CString strGlobal;
};
CProcessLocal<CMyGlobalData> globalData;
__declspec(dllexport)
void SetGlobalString(LPCTSTR lpsz)
{
globalData->strGlobal = lpsz;
}
__declspec(dllexport)
void GetGlobalString(LPCTSTR lpsz, size_t cb)
{
StringCbCopy(lpsz, cb, globalData->strGlobal);
}
MFC は 2 ステップでこれを実行します。最初に、いくつかの DLL を持っていても、プロセスごとに 2 回 TLS インデックスだけを使用する Tls* Win32 API (TlsAlloc、 TlsSetValue、 TlsGetValueなど)の先頭にレイヤーがあります。第 2 に、 CProcessLocal テンプレートは、このデータにアクセスするために使用されます。これは、上に表示される直感的な構文を使用する状況である operator-> をオーバーライドします。CProcessLocal でラップされたすべてのオブジェクトは CNoTrackObjectから派生する必要があります。CNoTrackObject は、プロセスが終了すると MFC が自動的にプロセス ローカル オブジェクトを破棄できます。低レベルのアロケーター (LocalAlloc/LocalFree)と仮想デストラクターを、こうした提供します。このようなオブジェクトは追加クリーンアップが必要な場合は、カスタム デストラクターがある場合があります。上の例では CString の埋め込みオブジェクトを破棄するには、コンパイラが既定のデストラクターを生成するので、 1 を必要としません。
この方法に他の間の長所があります。CProcessLocal のすべてのオブジェクトは必要になるまでのみ、組み立てられません自動的に破棄されません。CProcessLocal::operator-> が呼び出されたとき、この後のインスタンスを作成し、オブジェクトに関連が。上の例では、 " "strGlobalの文字列が SetGlobalString まで最初に構築または GetGlobalString が呼び出されることを示す意味します。これにより、 DLL の起動時間を短縮することができます。
スレッド ローカル データ
データが特定のスレッドに固有である必要があるときにプロセス ローカル データ、スレッド ローカル データと同様に使用されます。つまり、そのデータ アクセスを各スレッドのデータの二つのインスタンスが必要です。これは、広範な同期機構の代わりに何度も使用できます。データを複数のスレッドで共有する必要はない場合、このような機能は高く、不要になります。ここでは CString のオブジェクトがあることを想定します (上のサンプルと同じです)。ここでは CThreadLocal テンプレートでそれをラップすることによって、スレッド ローカルにすることができます:
struct CMyThreadData : public CNoTrackObject
{
CString strThread;
};
CThreadLocal<CMyThreadData> threadData;
void MakeRandomString()
{
// a kind of card shuffle (not a great one)
CString& str = threadData->strThread;
str.Empty();
while (str.GetLength() != 52)
{
unsigned int randomNumber;
errno_t randErr;
randErr = rand_s( &randomNumber );
if ( randErr == 0 )
{
TCHAR ch = randomNumber % 52 + 1;
if (str.Find(ch) < 0)
str += ch; // not found, add it
}
}
}
MakeRandomString が 2 台の異なるスレッドから呼び出された場合、は他に干渉しないようにさまざまな方法で文字列を 「」混ぜます。これは、スレッドごとに strThread のインスタンスは、 1 種類のグローバル インスタンスではなく、実際に存在するためです。
ループ反復ごとのではなく CString アドレスを一度にキャプチャの参照が一度どのように使用するか注意してください。ループのコードは、とstr" どこでも使用されます threadData->strThread " 記述された場合、コードは実行に非常に時間がかかります。このような参照がループで発生したときにデータへの参照をキャッシュすることをお勧めします。
CThreadLocal のクラス テンプレートは CProcessLocal がと同じ実装の手法を使用して、同じ機能。