Sdílet prostřednictvím


TN058: Implementace stavu modulu MFC

[!POZNÁMKA]

Následující technická poznámka nebyla aktualizována, protože byla poprvé zahrnuta v dokumentaci online.V důsledku toho některé postupy a témata mohou být nesprávné nebo zastaralé.Pro nejnovější informace je vhodné vyhledat téma zájmu v dokumentaci online index.

Tato technická Poznámka popisuje provádění konstrukce "Stavu modulu knihovny MFC".Znalost stavu implementace modulu je velmi důležitá pro použití knihovny MFC sdílené knihovny DLL z knihovny DLL (nebo server OLE v rámci procesu).

Před čtením této poznámky, přejděte k "Správa stavu dat modulů knihovny MFC" v Windows, vytváření nových dokumentů a zobrazení.Tento článek obsahuje důležité informace a přehled informací o této problematice.

Přehled

Existují tři typy informací o stavu MFC: stav modulu stav procesu a stav podprocesu.Někdy je možné kombinovat tyto typy státu.Mapování zpracování knihovny MFC jsou například modul Místní a podproces místní.To umožňuje dva různé moduly mít různé mapy ve všech jejich vláken.

Stav procesu a stav podprocesu jsou podobné.Tyto datové položky jsou věci, které byly tradičně globální proměnné, ale mají třeba specifické pro daný proces nebo podproces pro podporu řádné Win32s nebo řádné podpoře multithreadingu.Kategorie, které odpovídá dané datové položky v závisí na dané zboží a jeho požadovaná Sémantika pro hranice procesů a podprocesů.

Stav modulu je jedinečná v tom, že může obsahovat skutečně globální státu nebo státu, který je místní proces nebo podproces místní.Kromě toho je možné vypnout rychle.

Přepnutí stavu modulu

Každé vlákno obsahuje ukazatel stavu modulu "aktuální" nebo "aktivní" (Není divu, ukazatel je součástí místního stavu vlákna knihovny MFC).Tento ukazatel se změní, když podproces spuštění prochází modul hranice, jako je například aplikace volání do OLE ovládacího prvku nebo knihovny DLL nebo OLE řízení volání zpět do aplikace.

Aktuální stav modulu se přepne voláním AfxSetModuleState.Z větší části jste se nikdy čelit přímo rozhraní API.Knihovny MFC, v mnoha případech bude volat vám (v WinMain OLE-míst, AfxWndProcatd.). To se provádí v libovolné součásti můžete napsat pomocí statického propojení ve speciální WndProca speciální WinMain (nebo DllMain), ví, že stát který modul by měl být aktuální.Tento kód lze zobrazit pohledem na DLLMODUL.CPP nebo APPMODUL.CPP do adresáře MFC\SRC.

Je vzácné, že chcete nastavit stav modulu a pak není nastavte ji zpět.Ve většině případů má vlastní modul "push" znít jako aktuální a potom po dokončení "pop" původního kontextu.To se provádí pomocí makra AFX_MANAGE_STATE a speciální třídy AFX_MAINTAIN_STATE.

CCmdTargetobsahuje zvláštní funkce pro podporu přepnutí stavu modulu.Zejména CCmdTarget je kořenová třída pro automatizaci OLE a OLE COM vstupních bodů.Jako další vstupní bod vystaven k systému, musí tyto vstupní body nastavit správný stav modulu.Jak se dané CCmdTarget vědět, co by mělo být "správný" stav modulu?Odpovědí je, že "pamatuje" co "aktuální" stav modulu je, když je vytvořen, tak, aby jej můžete nastavit aktuální stav modulu, který "pamatovat" hodnota při vyšší jen.V důsledku toho modul uvedeno, že dané CCmdTarget objekt je spojen s je stav modulu, který byl aktuální při byl objekt vytvořen.Přijmout jednoduchý příklad načítání serveru INPROC, vytvoření objektu a voláme její metody.

  1. Knihovna DLL je načtena pomocí OLE pomocí LoadLibrary.

  2. RawDllMain jako první.Nastaví stav modulu Stav známý statického modulu pro knihovnu DLL.Z tohoto důvodu RawDllMain je staticky propojena s knihovnou DLL.

  3. Volání konstruktoru pro zdroj tříd přidružené našeho objektu.COleObjectFactoryje odvozen od CCmdTarget a v důsledku toho pamatuje stát, ve kterém modul byla vytvořena instance.To je důležité-třídy factory je dotaz, chcete-li vytvořit objekty, nyní ví jaký stav modulu chcete změnit na aktuální.

  4. DllGetClassObjectChcete-li získat zdroj tříd se nazývá.Knihovna MFC hledá seznam factory třídy spojené s tímto modulem a vrátí jej.

  5. COleObjectFactory::XClassFactory2::CreateInstance se nazývá.Ještě před vytvořením objektu a jeho vrácení, tato funkce nastaví stav modulu stav modulu, který byl aktuální v kroku 3 (ten, který byl aktuální, když COleObjectFactory byla vytvořena instance).To se provádí uvnitř METHOD_PROLOGUE.

  6. Při vytvoření objektu příliš je CCmdTarget derivátů a stejným způsobem jako COleObjectFactory pamatovat, které stav modulu byl aktivní, tak se tento nový objekt.Nyní objekt ví, které stavu modulu pro přepnutí do vždy, když je volána.

  7. Klient volá funkci objektu OLE COM obdržel od jeho CoCreateInstance volání.Objekt se nazývá používá METHOD_PROLOGUE k přepnutí stavu modulu stejně jako COleObjectFactory nemá.

Jak můžete vidět stav modulu z objektu rozšíří do objektu při jejich vytvoření.Je důležité, aby byly odpovídajícím způsobem nastavit stav modulu.Pokud není nastaven, může objekt modelu COM nebo knihovny DLL s MFC aplikace, která volá, možná nebudete moci najít své vlastní zdroje nebo může dojít k selhání jinými způsoby miserable špatně pracovat.

Všimněte si, že určité druhy knihoven DLL, konkrétně "MFC rozšiřující" knihovny DLL není přepnutí stavu modulu v jejich RawDllMain (ve skutečnosti, obvykle ještě nemají RawDllMain).Je to proto, že jsou určeny k chování "jako kdyby" byly skutečně přítomen v aplikaci, která je používá.Jsou velmi dobře část aplikace, která je spuštěna a je jejich úmyslu změnit globální stav dané aplikace.

Ovládací prvky ACTIVEX a další knihovny DLL se velmi liší.Není potřeba měnit stav volání aplikace; aplikace, který je volá možná i aplikace knihovny MFC a tak mohou existovat žádný stav změnit.To je důvod, že byla vyvinuta přepnutí stavu modulu.

Exportované funkce z knihovny DLL, jako například spustí dialogové okno v DLL musíte na začátek funkce přidejte následující kód:

AFX_MANAGE_STATE(AfxGetStaticModuleState( ))

To Zamění aktuální stav modulu s stav vrácený z AfxGetStaticModuleState až do konce aktuálního oboru.

Problémy se zdroji v DLL dojde-li AFX_MODULE_STATE makro není použit.Knihovna MFC používá ve výchozím nastavení popisovač prostředku hlavní aplikace načíst šablonu zdroje.Tato šablona je skutečně uloženy v knihovně DLL.Hlavní příčina je, že informace o stavu modulu knihovny MFC nebyl přepínány AFX_MODULE_STATE makra.Popisovač prostředku je obnovit ze stavu modulu knihovny MFC.Není přepnutí stavu modulu způsobí nesprávné zdroje popisovač má být použit.

AFX_MODULE_STATEnení nutné umístit všechny funkce v knihovně DLL.Například InitInstance může být volána kód knihovny MFC do aplikace bez AFX_MODULE_STATE vzhledem k tomu, že knihovna MFC automaticky přepne stav modulu před InitInstance a pak ji zpět po InitInstance vrátí.Totéž platí pro všechny mapy obslužné rutiny zpráv.Běžné knihovny DLL mají skutečně zvláštní hlavní okno postup, který automaticky přepne stav modulu před směrováním zprávy.

Zpracování místních dat

Procesu místní data by takové velké znepokojení, kdyby byly potíže Win32s DLL modelu.V Win32s sdílet všechny soubory DLL globální data, i když několik aplikací načtena.To se velmi liší od "reálný" Win32 DLL datového modelu, kde každou knihovnu DLL obdrží samostatnou kopii jeho místo dat u každého procesu, který se připojuje ke knihovně DLL.Přidání složitost, údaje přiděleny na haldě v knihovně DLL Win32s je ve skutečnosti zvláštní proces (alespoň pokud přejde vlastnictví).Zvažte následující data a kód:

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);
}

Zvažte, co se stane, pokud výše uvedený kód je v umístěn v knihovně DLL a aby knihovna DLL načtena dva procesy A a B (může ve skutečnosti být dvě instance stejné aplikace).A calls SetGlobalString("Hello from A").V důsledku toho je přidělena paměť pro CString dat v rámci procesu A.Mějte na paměti, že CString sám je globální a je viditelný i a B.Nyní volá B GetGlobalString(sz, sizeof(sz)).B budou moci zobrazit data A nastavení.Je to proto, že Win32s nenabízí žádnou ochranu mezi procesy jako Win32.To je první problém; v mnoha případech není žádoucí, aby jedna aplikace ovlivňují globální data, která se považuje za být vlastněn jinou aplikací.

Existují i další problémy.Řekněme, že nyní ukončí.Když A opustí, paměti používané 'strGlobal' řetězec je k dispozici pro systém – to znamená všechny paměti přidělené procesu A uvolní automaticky operačním systémem.Není uvolněna, protože CString zavolán destruktor; nebyla ještě volána.Je jednoduše uvolnit vzhledem k tomu, že aplikace, která je přidělena opustil scénu.Nyní, pokud je volána B GetGlobalString(sz, sizeof(sz)), ale nemusí získat platná data.Jiná aplikace může použít tuto paměť pro něco jiného.

Jasně problém existuje.MFC 3.x používá techniku zvanou podproces místní úložiště (TLS).MFC 3.x by přidělení indexu TLS, fungující pod Win32s skutečně jako proces místního úložiště indexu, i když to není volána a potom by odkazovat všechny data v závislosti na daném indexu TLS.To je podobné protokolu TLS index, který byl použit při ukládání dat místní Win32 (Další informace o této problematice viz níže).Příčinou každé knihovny MFC DLL pro využití alespoň dva indexy TLS jeden proces.Pokud je účet pro načítání mnoho OLE ovládací prvek knihovny DLL (soubory OCX), je rychle dostatek indexy TLS (pouze 64 k dispozici jsou).Navíc bylo umístění těchto dat na jednom místě, v jedné struktuře MFC.To nebyl velmi rozšiřitelné a nebyl ideální, pokud jde o jeho použití indexů TLS.

MFC 4.x řeší tuto sadu můžete "zabalit" kolem data, která by měla být místní proces šablony třídy.Výše uvedené potíže může například stanoví napsáním:

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);
}

Knihovna MFC implementuje to ve dvou krocích.První je vrstva nad Win32 Tls * rozhraní API (TlsAlloc, TlsSetValue, TlsGetValue, atd) využívající pouze dva rejstříky TLS jednotlivé procesy, bez ohledu na to, jak mnoho knihoven DLL, které používáte.Druhý, CProcessLocal šablona slouží k přístupu k těmto datům.Přepíše operátor -> což je to, co umožňuje intuitivní syntaxe, viz výše.Všechny objekty, které jsou zabaleny ve CProcessLocal musí být odvozen od CNoTrackObject.CNoTrackObjectposkytuje nižší úrovni přidělování (LocalAlloc/LocalFree) a virtuální destruktor tak, aby knihovny MFC lze automaticky zničit procesu místní objekty při ukončení procesu.Tyto objekty mohou mít vlastní destruktor, pokud je požadováno další čištění.Výše uvedený příklad nevyžaduje, protože kompilátor vygeneruje destruktor zničit vložený výchozí CString objektu.

Existují další zajímavé výhody tohoto přístupu.Jsou všechny CProcessLocal objekty automaticky, zničení jejich nejsou vyrobeny, dokud není potřeba.CProcessLocal::operator->konkretizovat přidružený objekt je volána poprvé a dříve.V předchozím příkladu to znamená, že "strGlobal' řetězec nebude konstruován do první SetGlobalString nebo GetGlobalString se nazývá.V některých případech to může pomoci snížit čas spuštění knihovny DLL.

Podproces místní Data

Podobně jako místní data zpracovat podproces místní data se používá při data musí být umístěna na dané vlákno.Samostatné instance dat, musíte pro každý podproces, který přistupuje k datům.To tolikrát, kolikrát lze namísto rozsáhlé synchronizačními mechanismy.Tyto mechanismy data nemusí být sdíleny více vláken, může být nákladné a zbytečné.Předpokládejme, že jsme měli CString objektu, (podobně jako výše uvedené ukázkové).Můžeme vytvořit podproces místní obalením s CThreadLocal šablony:

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
      }
   }
}

Pokud MakeRandomString byla volána ze dvou různých podprocesů, každý by "náhodně" řetězec jiným způsobem bez zasahování jiných.Důvodem je, že je skutečně na strThread instance pro vlákno, nikoli pouze jeden globální instance.

Všimněte si, jak odkaz se používá k zachycení CString adresa jednou namísto jednou za iteraci smyčky.Kód smyčky by byly zapsány s threadData->strThread všude 'str"se používá, ale kód bude mnohem pomalejší při spuštění.Doporučujeme odkaz na data v mezipaměti při výskytu takových odkazů ve smyčkách.

CThreadLocal Třídy šablona používá stejné mechanismy, CProcessLocal nefunguje a stejné techniky provedení.

Viz také

Další zdroje

Technické poznámky podle čísel

Technické poznámky podle kategorií