このテクニカル ノートでは、MFC の「モジュール状態」の実装について説明します。MFC を使用する Dll、DLL から共有のモジュール状態の実装の理解が重要です (または OLE イン プロセス サーバー)。
このノートを読む前に、「管理、状態データの MFC モジュールに」でご覧ください を作成する新しいドキュメント ウィンドウ、およびビューでVisual の C++ プログラマのガイド。この記事には重要な情報、このテーマに関する概要情報が含まれています。
概要
MFC の状態情報の 3 種類があります。 モジュール状態、状態、プロセスとスレッドの状態。時々 これらの状態の種類を組み合わせることができます。たとえば、MFC のハンドル マップは、ローカルのモジュールとスレッド ローカルです。これは、スレッドの各で別のマップに 2 つの異なるモジュール。
プロセスの状態とスレッドの状態と似ています。これらのデータ項目は、グローバル変数、従来されているが、特定のプロセスを特定するまたはスレッド Win32s をサポートまたは適切なマルチ スレッドのサポートに必要ことです。アイテムとその目的のセマンティクスのプロセスとスレッドの境界を特定のデータ項目に適合では、カテゴリによって異なります。
モジュール状態は、真にグローバルな状態またはローカルのプロセスまたはスレッド ローカルな状態のいずれかを含めることができますでユニークです。また、それはすぐに切り替えることができます。
モジュール状態の切り替え
各スレッドに「現在」または「アクティブ」のモジュール状態へポインターが含まれています (当然のことながら、ポインターが MFC のスレッド ローカル状態の一部です)。実行のスレッド OLE コントロールまたは DLL、または、OLE コントロールをアプリケーションにコールバックを呼び出すアプリケーションなど、モジュール境界を通過するときにこのポインターが変更されます。
現在のモジュール状態は、 AfxSetModuleStateを呼び出すことによって切り替えられます。ほとんどの部分については、API を直接を対処することは決してされます。MFC は、多くの場合、呼ぶそれを (WinMain では、OLE エントリ ポイント、 AfxWndProc、等)。あなたの書き込みは特別なを静的にリンクすることによって、コンポーネントのWndProcと特別なWinMain (またはDllMain) これは、モジュール状態を現在にする必要があります知っています。DLLMODUL を見てすることによって、このコードを見ることができます。CPP または APPMODUL。CPP MFC\SRC ディレクトリ。
モジュール状態を設定し、バックアップ設定いない場合はまれです。」「あなた自身モジュールをプッシュする時間のほとんど、現在の状態、が終了したら、その後、「元のコンテキストをポップ」します。AFX_MANAGE_STATEマクロと特別クラスAFX_MAINTAIN_STATEでは。
CCmdTargetモジュール状態の切り替えをサポートするための特別な機能があります。特に、 CCmdTargetは、ルート クラスを使用する OLE オートメーションおよび OLE COM のエントリ ポイントです。他のエントリ ポイントのようにシステムを公開、これらのエントリ ポイントは正しいモジュール状態を設定する必要があります。どのように特定のCCmdTarget何「正しい」のモジュール状態をする必要があります知っていますか?答えは、"それが構築されるとき「現在」のモジュール状態は覚えている」設定できます、現在のモジュール状態を「後の場合の値と呼ばれる記憶」。この結果、 CCmdTargetオブジェクトに関連付けられているモジュール状態オブジェクトが構築されたときに現在のモジュール状態です。INPROC サーバーを読み込んで、オブジェクトを作成し、そのメソッドを呼び出すは簡単な例を見てみましょう。
ご覧のとおり、作成時に、モジュール状態からオブジェクトが「オブジェクトに伝達されます。モジュールが適切に設定することが重要です。設定されていない場合は、DLL または COM オブジェクト悪い、それを呼び出しているまたは独自のリソースを検索することができないまたは他悲惨な形で失敗する可能性があります。、MFC アプリケーションと対話可能性があります。
Dll、特に「MFC 拡張」Dll 特定種類のRawDllMainのモジュール状態を切り替えるしないメモ (実際彼らは通常、 RawDllMainもない).これは、彼らはそれらを使用するアプリケーションでは、実際に存在していた「ように」動作を意図しているためです。非常に実行しているアプリケーションの一部であるし、は、アプリケーションのグローバル状態を変更する意思。
OLE コントロールとその他の Dll 非常に異なっています。彼らは、呼び出し元のアプリケーションの状態を変更したくはない;それらを呼び出しているアプリケーションが MFC アプリケーションもできない場合があります、のである可能性がありますしない変更する状態。これは、モジュール状態の切り替えが採用された理由です。
1 つは、ダイアログ ボックス、DLL の起動など、DLL からエクスポートされた関数の関数の先頭に次のコードを追加する必要があります。:
AFX_MANAGE_STATE (AfxGetStaticModuleState ())
これは、現在のモジュール状態を現在のスコープの末尾までのAfxGetStaticModuleStateから返される状態を入れ替えます。
AFX_MODULE_STATEマクロを使用していない場合は、Dll 内のリソースの問題が発生します。既定では、MFC はメイン アプリケーションのリソース ハンドルを使用して、リソース テンプレートを読み込みます。このテンプレートは、実際に、DLL に格納されます。根本的な原因は、MFC のモジュール状態情報が、 AFX_MODULE_STATEマクロで切り替えされていないことです。リソース ハンドルは MFC のモジュール状態から復元されます。モジュール状態が切り替えられていないため、間違ったリソース ハンドルをが。
AFX_MODULE_STATEは、すべての関数が DLL 内にする必要はありません。たとえば、 InitInstance MFC が自動的に前に、のモジュール状態をシフトため、MFC アプリケーションのコードでAFX_MODULE_STATEなしで呼び出すことができます InitInstance とそれをバックアップ後スイッチ、 InitInstance を返します。すべてのメッセージ マップ ハンドラーのも同じことが当てはまります。通常の Dll が実際に任意のメッセージをルーティングする前に、モジュール状態を自動的にスイッチは特殊なマスター ウィンドウ プロシージャがあります。
プロセス ローカルなデータ
プロセス ローカルなデータで、Win32s DLL モデルの難易度をされていないこのような偉大な関心のではないです。Win32s では、でも複数のアプリケーションで読み込まれたときにグローバル データすべての Dll を共有します。これは各 DLL、DLL にアタッチする各プロセスでデータ空間のコピーを取得、「実質の」の Win32 DLL データ モデルから完全に異なっています。複雑さを追加するには、Win32s DLL にヒープに割り当てられるデータ実際に特定のプロセス (少なくとも限り所有権を行く) です。次のデータとコードを検討します。:
静的 CString strGlobal;//ファイル スコープ__declspec(dllexport) SetGlobalString(LPCTSTR lpsz) を無効します。{strGlobal = lpsz;}__declspec(dllexport)void GetGlobalString (LPCTSTR lpsz、int cb){lstrcpyn (lpsz、strGlobal、cb);}
何が起こるか、上記のコードは DLL であるかどうか、2 つのプロセス A と B の (それは実際には、同じアプリケーションの 2 つのインスタンスことができる、) によって DLL が読み込まれていることを検討してください。呼び出し SetGlobalString("Hello from A") 。CStringデータ プロセス A. を維持、 CString自体グローバル両方を目に見えるだと心のコンテキストでのメモリの割り当て、結果としては、a と B今すぐ B を呼び出す GetGlobalString(sz, sizeof(sz)) 。B セット データを表示することができます。これは、Win32s が Win32 のようなプロセス間の保護を提供しないためにです。だから、最初の問題である;多くの場合、別のアプリケーションによって所有されていると見なされますグローバル データに影響を与える 1 つのアプリケーションに好ましくないです。
しかし待ってください-より多くの問題があります。今終了するとしましょう。A の終了する、によって使用されるメモリが ' strGlobal ' システムの文字列が使用可能 ? つまり、プロセス A によって割り当てられたすべてのメモリは自動的にオペレーティング システムによって解放されます。CStringのデストラクターが呼び出されているので、それは解放されません;それはまだ呼び出されていません。それが単に解放されるそれを割り当て、アプリケーションが終了したため。場合は B と呼ばれる今 GetGlobalString(sz, sizeof(sz)) 、有効なデータを得ることはできません。いくつかの他のアプリケーションがメモリ何かを使用している可能性があります。
明らかにここでの問題です。MFC 3.x スレッド ローカル ストレージ (TLS) と呼ばれる技術を使用します。MFC 3.x が呼ばれるではないにもかかわらず win32s は本当に、プロセス ローカル ストレージのインデックスとして機能する TLS インデックスを割り当てることし、TLS インデックスに基づいてすべてのデータを参照します。Win32 でのスレッド ローカル データを格納するために使用された TLS インデックスに似ています (下記は、件名の詳細を参照)。これはプロセスごとに少なくとも 2 つの TLS のインデックスを利用するには、すべて MFC DLL が原因。あなた多く OLE コントロール Dll (Ocx) を読み込むと、(がのみ 64) TLS インデックスを迅速に実行します。また、MFC、1 つの構造で、1 つの場所ですべてのデータを配置していた。それは非常に拡張可能ではなかったし、TLS インデックスの使用に理想的ではなかった。
MFC 4.x これ、「プロセス ローカルにする必要がありますデータ周りを包むことができる「クラス テンプレート セットを持つアドレスします。たとえば、上の問題を記述して修正される可能性があります。:
構造体 CMyGlobalData: パブリック CNoTrackObject{CString の strGlobal;};CProcessLocallt;CMyGlobalData > globalData;__declspec(dllexport) SetGlobalString(LPCTSTR lpsz) を無効します。{globalData - > strGlobal lpsz; =}__declspec(dllexport)void GetGlobalString (LPCTSTR lpsz、int cb){lstrcpyn (lpsz、globalData - > strGlobal、cb);}
MFC これは 2 つの手順で実装します。最初に、レイヤーの上、Win32 Tls ※のみ 2 つの TLS インデックス プロセス、どんなにあなたがどのように多くの Dll を使用して Api (TlsAlloc TlsSetValue TlsGetValueなど) です。第二に、 CProcessLocal テンプレートを提供して、このデータにアクセスします。演算子 gt によってオーバーライドされます。どのような直感的な構文は上記を参照できます。ラップされたすべてのオブジェクト CProcessLocal から派生する必要があります CNoTrackObject . CNoTrackObject は、低レベルのアロケーターを提供 (LocalAlloc/LocalFree) および仮想デストラクター、プロセスが終了すると、MFC は、プロセス ローカル オブジェクトを自動的に破棄することができますように。追加のクリーンアップが必要な場合などのオブジェクトをカスタムのデストラクターを持つことができます。埋め込みのCStringオブジェクトを破棄するには、既定のデストラクターは、コンパイラが生成されるため、上記の例を 1 つは、必要としません。(&G)。
このアプローチへの他の興味深い利点があります。だけでなく、すべては CProcessLocal オブジェクトを自動的に破棄必要になるまでには構築されません。 CProcessLocal::operator-gt; は関連オブジェクトと呼ばれる、最初の時間とない早くをインスタンス化します。上記の例では、つまり、' strGlobal ' 文字列はSetGlobalStringまたはGetGlobalStringと呼ばれる、最初の時間までない構築されます。いくつかのインスタンスでは、この DLL の起動時間を短縮することができます。(&G)。
スレッド ローカル データ
データは、特定のスレッドに対してローカルである必要がありますとプロセス ローカルなデータと同様、スレッドのローカル データが使用されます。つまり、そのデータにアクセスする各スレッドは、データの別のインスタンス必要があります。これは何回も広範な同期メカニズムの代わりに使用できます。データを複数のスレッドで共有する必要がない場合は、このようなメカニズムは高価で、不要なことができます。私たち (上記のサンプル) と同様に多くのCStringオブジェクトがあるとします。我々 はスレッド ローカルそれをラップすることができます、 CThreadLocal テンプレート:
構造体 CMyThreadData: パブリック CNoTrackObject{CString つの strThread;};CThreadLocallt;CMyThreadData > threadData;void MakeRandomString(){・ ・ カード シャッフル (偉大なものではない) の種類CString & str = threadData - > つの strThread;str。Empty();(str 中。GetLength() ! = 52){TCHAR ch = rand() %52 + 1;場合 (str。Find(ch) < 0)str = ch;・ ・が見つからない、追加}}
場合は MakeRandomString と呼ばれていた 2 つの異なるスレッドから各」文字列の異なる方法で、他と干渉することなくがシャッフル」。あるのでこれは実際には、 strThread インスタンスあたり 1 つだけのグローバル インスタンスではなくスレッドに。
参照を使用して、1 回ループの反復処理ごとに 1 回の代わりにCStringアドレスをキャプチャする方法に注意してください。ループ コードで書かれていること threadData-gt;strThread どこでも ' str ' 使用されますが、コードの実行は遅くなりますが。このような参照でループが発生すると、データへの参照をキャッシュするが最適です。(&G)。
CThreadLocalクラス テンプレートは、同じメカニズムを使用して、 CProcessLocal は、同じの実装技術。
番号順テクニカル ノート|nbsp;カテゴリ別テクニカル ノート(&N)