TN058: implementacja stanu modułu MFC
[!UWAGA]
Następująca uwaga techniczna nie został zaktualizowana od pierwszego uwzględnienia jej w dokumentacji online.W rezultacie niektóre procedury i tematy może być nieaktualne lub nieprawidłowe.Aby uzyskać najnowsze informacje, zaleca się wyszukać temat w indeksie dokumentacji online.
Ta uwaga techniczna zawiera opis stosowania konstrukcje MFC "stanu modułu".Zrozumienie implementacji stanu modułu ma kluczowe znaczenie przy użyciu biblioteki MFC współużytkowane biblioteki dll z biblioteki DLL (lub serwer OLE w procesie).
Przed przeczytaniem tej notatki, odnoszą się do "Zarządzanie Państwo dane z biblioteki MFC moduły" w Tworzenie nowych dokumentów, Windows i widoki.Ten artykuł zawiera ważne informacje użytkowania i przegląd informacji na ten temat.
Omówienie
Istnieją trzy rodzaje informacji o stanie MFC: stan modułu, stan procesu i stan wątku.Czasami można łączyć tych typów stanu.Na przykład mapy uchwyt MFC są zarówno moduł lokalnych, jak i lokalnej wątku.Pozwala to dwa różne moduły mają różne mapy we wszystkich swoich wątków.
Stan procesu i stan wątku są podobne.Te elementy danych są rzeczy, które są tradycyjnie zmienne globalne, ale mają muszą być specyficzne dla danego procesu lub wątku do właściwego Win32s obsługuje lub odpowiednie wsparcie wielowątkowości.Element danych danego mieści się w kategorii zależy od danego elementu i jego pożądanych semantykę w odniesieniu do granic procesów i wątków.
Stan modułu jest unikatowy w tym może zawierać prawdziwie globalną Państwo lub Państwa, które jest procesem lokalnym lub lokalnej wątku.Ponadto to mogą być szybko przełączane.
Przełączanie w stan modułu
Każdy wątek zawiera wskaźnik do stanu modułu "bieżący" lub "aktywne" (Nic dziwnego, wskaźnik jest częścią lokalnego stan wątku MFC).Ten wskaźnik jest zmieniany podczas wątku wykonywania przechodzi obwiedni modułu, takich jak aplikacja, wzywając do formantu OLE lub biblioteki DLL lub formantu OLE wywołaniem zwrotnym do aplikacji.
Aktualny stan modułu jest włączane przez wywołanie AfxSetModuleState.W większości nigdy nie poradzi sobie bezpośrednio przy użyciu interfejsu API.MFC, w wielu przypadkach nazywają to dla Ciebie (w WinMain OLE punktów wejścia, AfxWndProc, itp.). Jest to wykonywane w dowolny składnik piszesz łącząc statycznie w specjalnym WndProci specjalne WinMain (lub DllMain) który wie, które Państwo moduł powinno być bieżącym.Ten kod można zobaczyć, patrząc na DLLMODUL.CPP lub APPMODUL.CPP w katalogu MFC\SRC.
Jest to rzadko, że chcesz ustawić stan modułu i nie ustawić ją ponownie.W większości przypadków mają być "push" własnego modułu podać jako bieżąca i następnie, po zakończeniu "pop" powrót oryginalnego kontekstu.Jest to realizowane przez makro AFX_MANAGE_STATE i specjalnej klasy AFX_MAINTAIN_STATE.
CCmdTargetudostępnia specjalne funkcje do obsługi przełączania stanu modułu.W szczególności CCmdTarget jest klasa główny używany do automatyzacji OLE i modelu COM OLE punktów wejścia.Się inne punkty wejścia narażone na system, te punkty wejścia, należy ustawić stan do modułu.Jak czy dany CCmdTarget wie, co powinno być stanu "Popraw" moduł?Odpowiedź jest to "zapamiętuje" co "bieżący" Stan modułu jest, gdy jest skonstruowany, tak, że można go o nazwie zestaw bieżącego stanu modułu do tego "pamiętał" wartość gdy jest nowszy.W rezultacie moduł stwierdzać, że danej CCmdTarget obiektu jest skojarzony z jest stanu modułu, w którym były aktualne w chwili obiektu została skonstruowana.Weź prosty przykład ładowania serwera INPROC, tworzenia obiektu i wywołanie jego metody.
Biblioteka DLL jest ładowany przez OLE za pomocą LoadLibrary.
RawDllMain jest wywoływana pierwszy.Ustawia stan modułu Państwu znane statycznych modułu DLL.Z tego powodu RawDllMain jest łączony statycznie do biblioteki DLL.
Konstruktor dla fabryki klas skojarzonych z naszych obiektu jest wywoływana.COleObjectFactorypochodzi z CCmdTarget i w efekcie pamięta Państwa, w którym moduł został skonkretyzowany.Jest to ważne — pytany fabryki klas do tworzenia obiektów, teraz wie jakie Państwo moduł ma stać się bieżącym.
DllGetClassObjectnazywa się uzyskać fabryki klas.MFC przeszukuje listy fabryka klas skojarzonych z tym modułem i zwraca go.
COleObjectFactory::XClassFactory2::CreateInstance nazywa się.Przed utworzeniem obiektu i zwraca go, ta funkcja ustawia stan modułu do stanu modułu, które były aktualne w kroku 3 (taki, który był bieżącego, kiedy COleObjectFactory został skonkretyzowany).Odbywa się wewnątrz METHOD_PROLOGUE.
Po utworzeniu obiektu to też jest CCmdTarget pochodnych i w ten sam sposób COleObjectFactory pamiętać, które stwierdzają, moduł był aktywny, co powoduje to nowy obiekt.Teraz obiekt wie stanowiących moduł, aby przełączyć się do ilekroć jest nazywany.
Klient wywołuje funkcję na obiekcie OLE COM otrzymała od jej CoCreateInstance zadzwonić.Gdy obiekt jest nazywany korzysta z METHOD_PROLOGUE do tak jak przełączać stan modułu COleObjectFactory czy.
Jak widać, stan modułu są propagowane z obiektu do obiektu, przy ich tworzeniu.Należy odpowiednio ustawić stanu modułu.Jeśli nie jest ustawiona, obiekt biblioteki DLL lub COM może źle współdziała z aplikacji MFC, która wywołuje go, nie będą mogli znaleźć swoich własnych zasobów lub może się nie powieść w inny sposób nieszczęśliwy.
Należy zwrócić uwagę niektórych rodzajów plików dll, w szczególności "MFC rozszerzenie" dll nie Przełączaj stan modułu w ich RawDllMain (w rzeczywistości zwykle nie mają nawet RawDllMain).Jest to spowodowane przeznaczone są do "tak, jakby" były one faktycznie znajduje się w aplikacji, która ich używa.Są one bardzo częścią aplikacji, która działa i jest ich zamiar zmiany stanu globalnego tej aplikacji.
Formanty OLE i inne biblioteki DLL są bardzo różne.Nie chcą zmodyfikować stan aplikacji wywołującej; aplikacji, która je wywołuje, nie można nawet aplikacji MFC i dlatego nie może być żadne Państwo do modyfikowania.To jest powód, że moduł stanu przełączania został opracowany.
Dla eksportowanych funkcji z biblioteki DLL, który uruchamia okno dialogowe w bibliotece DLL należy dodać poniższy kod do początku funkcji:
AFX_MANAGE_STATE(AfxGetStaticModuleState( ))
To zamienia bieżący stan modułu z Państwem zwrócony z AfxGetStaticModuleState do końca bieżącego zakresu.
Problemy z zasobami w bibliotekach DLL wystąpi, jeśli AFX_MODULE_STATE makro nie jest używany.Domyślnie MFC używa uchwytu zasobów dla głównej aplikacji do załadowania zasobu szablonu.Ten szablon jest faktycznie przechowywane w bibliotece DLL.Przyczyną jest, że informacje o stanie modułu MFC nie został przełączony przez AFX_MODULE_STATE makro.Uchwyt zasobów jest odbierana od stanu modułu MFC.Nie przełączenie stanu modułu powoduje uchwyt źle zasobów ma być używany.
AFX_MODULE_STATEnie musi znajdować się w każdej funkcji w bibliotece DLL.Na przykład InitInstance może być wywoływana przez kod MFC w aplikacji bez AFX_MODULE_STATE ponieważ MFC automatycznie przełącza stan modułu przed InitInstance i następnie przełączniki go z powrotem po InitInstance zwraca.To samo dotyczy dla wszystkich osób zajmujących się mapę wiadomość.Regularne bibliotek DLL faktycznie mają specjalne okno główne procedury, która automatycznie przełącza stan modułu przed przesłaniem wiadomości.
Proces danych lokalnych
Dane lokalne procesu nie byłoby takich wielkim problemem nie byli w trudnej sytuacji Win32s DLL modelu.W Win32s wszystkich plików dll udostępniać swoich danych globalnych, nawet wtedy, gdy jest ładowany przez wiele aplikacji.Jest to bardzo różni się od "real" model danych biblioteki DLL systemu Win32, gdzie każdej biblioteki DLL pobiera oddzielną kopię swojej przestrzeni danych w każdym procesie, który dołącza się do pliku DLL.Aby dodać do złożoności, danych na stercie w bibliotece DLL Win32s przydzielana jest w rzeczywistości procesu określonego (co najmniej w zakresie jak idzie własności).Należy wziąć pod uwagę następujące dane i kod:
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);
}
Należy rozważyć, co się dzieje, jeśli powyższy kod w znajduje się w bibliotece DLL i że DLL jest ładowany przez dwa procesy, A i B (może w rzeczywistości być dwa wystąpienia tej samej aplikacji).A calls SetGlobalString("Hello from A").W rezultacie jest rezerwowana pamięć na CString danych w kontekście procesu A.Należy pamiętać, że CString sam ma charakter globalny i jest widoczna zarówno A i B.Teraz wywołuje B GetGlobalString(sz, sizeof(sz)).B będą mogli wyświetlać dane, które zestaw.Jest to spowodowane Win32s oferuje bez ochrony między procesami, tak jak robi Win32.To jest pierwszy problem; w wielu przypadkach nie jest pożądane, aby mieć jeden wniosek wpływa na danych globalnych, która jest uważana za własność przez inną aplikację.
Istnieją również dodatkowe problemy.Załóżmy, że teraz kończy pracę.Kiedy A kończy pracę, pamięci używanej przez "strGlobal" ciąg staje się dostępny do systemu — czyli całej pamięci przydzielonej przez proces A jest zwalniane automatycznie przez system operacyjny.Nie zostanie zwolniona, ponieważ CString jest wywoływana destruktora; nie została wywołana jeszcze.Jest zwalniane po prostu ponieważ aplikacji, które przydzielają jej opuścił sceny.Teraz, jeśli B o nazwie GetGlobalString(sz, sizeof(sz)), nie może pobrać prawidłowych danych.Inna aplikacja może użyto tej pamięci do czegoś innego.
Wyraźnie istnieje problem.MFC 3.x używana jest metoda zwana pamięci lokalnej wątku (TLS).MFC 3.x czy przydzielić indeks TLS, który pod Win32s naprawdę działa jako indeks Magazyn lokalny proces, mimo że nie jest wywoływana który i następnie odwołanie wszystkich danych w oparciu o tym indeksie TLS.Jest to podobne do indeksu TLS, który był używany do przechowywania danych lokalnej wątku na Win32 (Aby uzyskać więcej informacji na ten temat patrz poniżej).Przyczyną co MFC DLL wykorzystać co najmniej dwa wskaźniki TLS dla jednego procesu.Jeśli masz konto na załadowanie wielu kontroli bibliotek DLL OLE (formanty ocx), szybko uruchamiać z indeksami TLS (Brak dostępnych tylko 64).Ponadto MFC musiał umieścić te dane w jednym miejscu w jedną strukturę.Nie było bardzo extensible i nie jest idealnym rozwiązaniem w zakresie jego użycie indeksów TLS.
MFC 4.x rozwiązuje ten problem z zestawu szablonów klasy użytkownik może "zawinięcia" dane, które powinny być procesu lokalnego.Na przykład ten problem wymienionych powyżej może być ustalona przez pisania:
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 implementuje to w dwóch etapach.Pierwszy z nich to warstwa powyżej Win32 Tls * interfejsów API (TlsAlloc, TlsSetValue, Funkcje TlsGetValue, itd.) używających tylko dwa indeksy TLS dla jednego procesu, bez względu na to, jak wiele bibliotek DLL masz.Po drugie, CProcessLocal znajduje się szablon do dostępu do tych danych.Ustawienie to zastępuje operator -> co jest, co pozwala intuicyjny składni, zobacz powyżej.Wszystkie obiekty, które są zapakowane przez CProcessLocal musi pochodzić z CNoTrackObject.CNoTrackObjectzapewnia alokatora niższego poziomu (Funkcja LocalAlloc/funkcji LocalFree) i wirtualny destruktor takie, że MFC automatycznie może zniszczyć obiektów lokalnych procesów, gdy proces się zakończy.Takie obiekty mogą mieć niestandardowe destruktor, jeśli wymagane jest dodatkowe oczyszczanie.Powyższy przykład nie wymaga jednego, ponieważ kompilator wygeneruje destruktor domyślny do zniszczenia osadzonego CString obiektu.
Istnieją inne ciekawe zalety takiego podejścia.Są nie tylko wszystkie CProcessLocal obiekty zniszczone automatycznie, które nie są zbudowane dopóki nie są one potrzebne.CProcessLocal::operator->będzie utworzyć wystąpienia obiektu skojarzonego nazywa się po raz pierwszy, a nie wcześniej.W powyższym przykładzie, oznacza to, że "strGlobal' nie można skonstruować ciąg aż po raz pierwszy SetGlobalString lub GetGlobalString nazywa się.W niektórych przypadkach może to pomóc zmniejszyć czas uruchamiania biblioteki DLL.
Dane lokalne wątku
Podobne do przetwarzania danych lokalnych, dane lokalne wątku służy dane muszą być lokalne dla danego wątku.Oznacza to czego potrzebujesz osobne wystąpienie danych dla każdego wątku, który uzyskuje dostęp do tych danych.To wiele razy można zamiast mechanizmów rozległe synchronizacji.Jeśli dane nie musi być współużytkowane przez wiele wątków, takich mechanizmów może być kosztowne i niepotrzebne.Załóżmy, że trzeba było CString obiektu (podobnie jak z powyższego przykładu).Możemy sprawić, że wątek lokalnym przez owijanie go z CThreadLocal szablonu:
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
}
}
}
Jeśli MakeRandomString została wywołana z dwóch różnych wątków, każdy będzie "shuffle" ciąg na różne sposoby nie kolidowało z innymi.To dlatego, że rzeczywiście strThread wystąpienie na wątku, a nie tylko jedno wystąpienie globalnego.
Należy zwrócić uwagę, jak odwołanie jest używany do przechwytywania CString adres raz zamiast raz na iteracji pętli.Kod pętla może zostały zapisane z threadData->strThread wszędzie "str" jest używany, ale kod będzie znacznie mniejsza w realizacji.Najlepiej buforowania odwołania do danych w wystąpieniu takiego odniesienia w pętli.
CThreadLocal Szablonu klasy używa tych samych mechanizmów który CProcessLocal nie i z tych samych metod implementacji.