TN058: MFC 模組狀態實作

注意事項注意事項

由於它第一次線上文件中包含尚未更新下列技術提示。如此一來,某些程序和主題可能已經過期或不正確。如需最新資訊,建議您先搜尋線上文件索引中有興趣的主題。

這份技術提示會說明 MFC 「 模組狀態 」 建構的實作。了解的模組狀態的實作相當重要,對於使用 MFC 的共用 Dll 從 DLL (或 OLE 在處理序伺服程式)。

之後,再閱讀此附註,「 管理資料的 MFC 模組狀態 」 以參考建立的新文件、 視窗和檢視表。本文包含重要的使用方式資訊以及有關此主題的概觀資訊。

概觀

有三種 MFC 狀態資訊: 模組狀態、 處理序狀態和執行緒狀態。有時您可以結合這些狀態型別。例如,MFC 的處理常式對應是本機的模組和執行緒本機。這可讓兩個不同的模組,若要在每個他們的執行緒中有不同的對應。

處理序狀態和執行緒狀態的相似處。這些資料的項目是向來就全域變數,但需要有專屬於指定的處理序或執行緒的適當的 win32 中支援或適當的多執行緒支援的事。符合指定的資料的項目所屬的分類取決於這個項目和需要的語意,處理程序和執行緒界限與相關。

模組狀態是唯一的因為它可以包含真正的全球化的狀態或本機的處理序或執行緒本機狀態。此外,它可以快速地切換。

切換模組狀態

每個執行緒都包含 「 目前 」 或 「 作用中 」 的模組狀態指標 (不要訝異,滑鼠指標是 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知道 「 正確 」 的模組狀態應該是什麼?答案是會在它 「 記住 」 什麼"current"的模組狀態是在建構,使得它可以的集合目前模組狀態,來 「 記憶 」 值更新時,它會呼叫。如此一來,該模組指明給定CCmdTarget物件有關聯就是建構物件時,目前模組狀態。需要載入耗用情形來得多伺服器、 建立物件,及呼叫其方法的簡單範例。

  1. DLL 會載入 ole 使用 LoadLibrary

  2. RawDllMain 會先呼叫。它會將模組狀態設定為已知的靜態模組狀態為 DLL。基於這個理由 RawDllMain 以靜態方式連結至 DLL。

  3. 會呼叫我們物件相關聯的類別工廠的建構函式。COleObjectFactory衍生自CCmdTarget ,如此一來,它會記得哪一個模組的狀態為執行個體化。這點很重要,當詢問 class factory 來建立物件時,請它現在知道何種成為現用的模組狀態。

  4. DllGetClassObject會取得類別廠時所呼叫的方法。MFC 會搜尋與此模組相關聯的類別處理站清單,並傳回它。

  5. COleObjectFactory::XClassFactory2::CreateInstance 呼叫。之前建立的物件,並將其傳回,此函式將模組狀態設定為 [已在步驟 3 中的目前模組狀態 (即當時最新的時機COleObjectFactory已具現化)。這是內部的 METHOD_PROLOGUE

  6. 建立物件時,它也是CCmdTarget衍生,並以相同的方式COleObjectFactory記憶中的模組狀態下,為作用中,因此沒有這個新的物件。現在物件知道的模組狀態下,若要切換到每次呼叫它。

  7. 用戶端接收到的 OLE COM 物件上呼叫函式及其CoCreateInstance呼叫。當物件被呼叫時它會使用METHOD_PROLOGUE ,就像是切換模組狀態COleObjectFactory不會。

如您所見,模組狀態是從物件傳播到物件建立時。請務必已適當地設定模組狀態。如果未設定它,則您的 DLL 或 COM 物件可能會不佳互動的 MFC 應用程式會呼叫它,或可能是找不到它自己的資源,甚至可能完全無法以其他討厭的方式。

請注意,某些類型的 Dll,特別是 「 MFC 擴充"Dll 沒有切換模組狀態,在其 RawDllMain (事實上,它們通常甚至不需要 RawDllMain)。這是因為它們為了""就像是實際出現在應用程式中使用,則它們的行為。它們是非常相似的應用程式正在執行的一部分,其中間修改該應用程式的全域狀態的項目。

OLE 控制項和其他的 Dll 是很大的差異。他們不想要修改呼叫的應用程式檢視狀態。 正在呼叫的應用程式甚至可能不是 MFC 應用程式,因此可能沒有修改的狀態。這是已發明模組狀態切換的原因。

匯出的函式的 dll,例如啟動對話方塊,在您的 DLL,您需要將下列程式碼加入函式的開頭:

AFX_MANAGE_STATE(AfxGetStaticModuleState( ))

這會交換目前模組狀態與所傳回的狀態 AfxGetStaticModuleState 到目前領域的結束為止。

如果 Dll 中的資源的問題就會發生AFX_MODULE_STATE不會使用巨集。根據預設,MFC 會使用主應用程式的資源控制代碼以載入資源樣板。這個樣板實際上儲存在 DLL 中。MFC 模組狀態資訊的沒的問題的根本原因是AFX_MODULE_STATE巨集。資源控制代碼是從 MFC 模組狀態中復原。沒有切換模組狀態會讓使用錯誤的資源控制代碼。

AFX_MODULE_STATE不需要被放在 DLL 中的每個函式。例如, InitInstance可由 MFC 中的程式碼的應用程式,而不需呼叫AFX_MODULE_STATE因為 MFC 會自動移之前的模組狀態InitInstance和再切換回原處之後InitInstance傳回。同樣適用於所有的訊息對應處理常式。標準 dll 裡實際上擁有路由任何訊息前便會自動切換模組狀態的特殊的主控視窗程序。

處理本機資料

處理本機資料就不是這類最關心它尚未在 win32 中 DLL 模型的困難度。在 win32 中所有的 Dll 共用通用的資料,甚至在多個應用程式載入時。這是非常不同的 「 真正 」 的 Win32 DLL 資料模型,其中每個 DLL 中取得其連結至 DLL 時每個處理序中的資料空間的另一個複本。若要增加複雜性,配置在堆積在 win32 中 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 會載入由兩個處理程序 a 和 B (可能,其實是兩個相同的應用程式執行個體),會發生什麼事。A calls SetGlobalString("Hello from A").如此一來,將記憶體配置給CString資料內容中的處理程序 a。請注意, CString本身為全域性質,都可以看到這兩個 a 與 b現在 b 會呼叫GetGlobalString(sz, sizeof(sz))。B 將會看到一組資料。這是因為在 win32 中提供不了 Win32 不一樣的處理序之間的任何保護。這是第一個問題。 在許多情況下並不會有一個會影響字會被視為屬於不同的應用程式的全域資料的應用程式。

有其他的問題。例如,假設現在結束。結束時 a 時,所使用的記憶體 'strGlobal' 字串可供系統 — 亦即,所有處理程序 a 所配置的記憶體會自動釋出由作業系統。無法釋放因為CString呼叫解構函式。 它還尚未被呼叫。它只會釋出因為配置它的應用程式已離開場景。現在,如果 b 呼叫GetGlobalString(sz, sizeof(sz)),它可能不會得到有效的資料。其他應用程式可能已經使用該記憶體的其他項目。

很明顯地會有問題。MFC 3.x 用稱為執行緒區域儲存區 (TLS) 的技術。MFC 3.x 會配置一個 TLS 索引,在 win32 中真的作為處理程序區域儲存區的索引,即使它並非使用此名稱,然後會參考該 TLS 索引為基礎的所有資料。這是類似於用來將執行緒區域資料儲存在 Win32 TLS 索引 (請參閱下面有關該主題的詳細資訊)。這會造成每個使用兩個以上的 TLS 索引,每個處理序的 MFC DLL。當您考慮載入 OLE 控制 Dll (的 Ocx) 時,快速地完 TLS 索引 (還有僅 64 可用)。此外,MFC 就必須將所有這些資料放在同一個地方,在單一的結構。它不是擴充性很高,且不理想的 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 實作這兩個步驟。首先是 Win32 上方的圖層 Tls 1 Api (TlsAllocTlsSetValueTlsGetValue、 等等) 會使用每個處理序,無論您有多少 Dll 的只有兩個 TLS 索引。第二個, CProcessLocal來存取此資料係依範本。它會覆寫運算子-> 這是為什麼您請參閱上述的語法。藉由被包起來之所有物件CProcessLocal必須衍生自CNoTrackObject。CNoTrackObject提供較低層級配置器 (LocalAlloc/LocalFree) 和虛擬解構函式,讓處理程序終止時,MFC 可以自動摧毀程序區域物件。這種物件可以有自訂的解構函式,如果需要其他清除。上述範例中並不需要,因為編譯器會產生預設解構函式終結內嵌CString物件。

有許多其他有趣的優點,這種方法。不只是所有CProcessLocal自動終結,物件不建構直到所需之。CProcessLocal::operator->第一次呼叫時,相關的物件和快也,將具現化。在上述範例中,這表示 'strGlobal' 字串不能建構直到第一次 SetGlobalStringGetGlobalString 呼叫。在某些情況下,這有助於縮小 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受呼叫時隨機從兩個不同的執行緒,每一個會"執行"字串以不同的方式不會干擾其他。這是因為沒有實際strThread每個執行緒,而非只是一個全域執行個體的執行個體。

請注意如何參考用來擷取CString處理一次而不是一次每個迴圈反覆運算。迴圈程式碼可能已經撰寫與threadData->strThread每個地方 'str' 使用時,但程式碼會更慢執行中。最好在迴圈中的這類參考發生時,快取資料的參考。

CThreadLocal類別樣板會使用相同的機制, CProcessLocal功能以及相同的實作技術。

請參閱

其他資源

技術的備忘稿編號

依類別的技術注意事項