Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Anmärkning
Följande tekniska anmärkning har inte uppdaterats sedan den först inkluderades i onlinedokumentationen. Därför kan vissa procedurer och ämnen vara inaktuella eller felaktiga. För den senaste informationen rekommenderar vi att du söker efter det intressanta ämnet i onlinedokumentationsindexet.
Den här tekniska anmärkningen beskriver implementeringen av MFC-konstruktioner för "modultillstånd". En förståelse för implementeringen av modultillståndet är viktig för att använda de MFC-delade DLL:erna från en DLL (eller OLE-in-process-server).
Innan du läser den här anteckningen läser du "Hantera tillståndsdata för MFC-moduler" i Skapa nya dokument, Windows och vyer. Den här artikeln innehåller viktig användningsinformation och översiktsinformation om det här ämnet.
Översikt
Det finns tre typer av MFC-tillståndsinformation: modultillstånd, processtillstånd och trådtillstånd. Ibland kan dessa tillståndstyper kombineras. MFC:s referenskartor är till exempel både modullokala och trådlokala. På så sätt kan två olika moduler ha olika kartor i var och en av sina trådar.
Processtillstånd och trådtillstånd är liknande. Dessa dataobjekt är saker som traditionellt har varit globala variabler, men som måste vara specifika för en viss process eller tråd för korrekt Win32s-stöd eller för korrekt stöd för multitrådning. Vilken kategori ett visst dataobjekt passar in beror på objektet och dess önskade semantik när det gäller process- och trådgränser.
Modultillståndet är unikt eftersom det kan innehålla antingen verkligt globalt tillstånd eller tillstånd som bearbetas lokalt eller trådlokalt. Dessutom kan den växlas snabbt.
Växling av modultillstånd
Varje tråd innehåller en pekare till modultillståndet "aktuell" eller "aktiv" (inte överraskande är pekaren en del av MFC:s lokala trådtillstånd). Den här pekaren ändras när körningstråden passerar en modulgräns, till exempel ett program som anropar till en OLE-kontroll eller DLL eller en OLE-kontroll som anropar tillbaka till ett program.
Det aktuella modultillståndet växlas genom att anropa AfxSetModuleState. För det mesta kommer du aldrig att hantera API:et direkt. MFC kommer i många fall att anropa det för dig (vid WinMain, OLE-startpunkter, AfxWndProc, osv.). Detta görs i alla komponenter som du skriver genom att statiskt länka i en särskild WndProc, och en särskild WinMain (eller DllMain) som vet vilket modultillstånd som ska vara aktuellt. Du kan se den här koden genom att titta på DLLMODUL. CPP eller APPMODUL. CPP i katalogen MFC\SRC.
Det är ovanligt att du vill ange modultillståndet och sedan inte ställa in det igen. För det mesta vill du "push" ditt eget modultillstånd som det nuvarande och sedan, när du är klar, "pop" tillbaka den ursprungliga kontexten. Detta görs av makrot AFX_MANAGE_STATE och specialklassen AFX_MAINTAIN_STATE.
              CCmdTarget har särskilda funktioner för att stödja modultillståndsväxling. I synnerhet är en CCmdTarget rotklassen som används för OLE-automatisering och OLE COM-startpunkter. Precis som andra startpunkter som exponeras för systemet måste dessa startpunkter ange rätt modultillstånd. Hur vet en viss vad CCmdTarget "rätt" modultillstånd ska vara Svaret är att den "kommer ihåg" vad det "aktuella" modultillståndet är när den konstrueras, så att den kan ange det aktuella modultillståndet till det "ihågkomna" värdet när det senare anropas. Därför är modultillståndet som ett visst CCmdTarget objekt är associerat med modultillståndet som var aktuellt när objektet konstruerades. Ta ett enkelt exempel på hur du läser in en INPROC-server, skapar ett objekt och anropar dess metoder.
- DLL:en läses in av OLE med hjälp av - LoadLibrary.
- RawDllMainanropas först. Den anger modultillståndet till det kända statiska modultillståndet för DLL:en. Därför- RawDllMainär statiskt länkad till DLL:en.
- Konstruktorn för klassfabriken som är associerad med vårt objekt anropas. - COleObjectFactoryhärleds från- CCmdTargetoch som ett resultat kommer den ihåg i vilket modultillstånd den instansierades. Det här är viktigt – när klassfabriken uppmanas att skapa objekt vet den nu vilket modultillstånd som ska vara aktuellt.
- DllGetClassObjectanropas för att hämta klassfabriken. MFC söker igenom den klassfabrikslista som är kopplad till denna modul och returnerar den.
- COleObjectFactory::XClassFactory2::CreateInstancekallas. Innan du skapar objektet och returnerar det anger den här funktionen modultillståndet till modultillståndet som var aktuellt i steg 3 (det som var aktuellt när instansierades- COleObjectFactory). Detta görs i METHOD_PROLOGUE.
- När objektet skapas är det också ett - CCmdTargetderivat och på samma sätt- COleObjectFactorykom ihåg vilket modultillstånd som var aktivt, så gör även det här nya objektet. Nu vet objektet vilket modultillstånd som ska växlas till när det anropas.
- Klienten anropar en funktion på OLE COM-objektet som den tog emot från - CoCreateInstanceanropet. När objektet anropas används- METHOD_PROLOGUEdet för att växla modultillstånd precis som- COleObjectFactorydet gör.
Som du ser sprids modultillståndet från objekt till objekt när de skapas. Det är viktigt att modultillståndet anges på rätt sätt. Om det inte har angetts kan ditt DLL- eller COM-objekt interagera dåligt med ett MFC-program som anropar det, eller kanske inte kan hitta sina egna resurser eller misslyckas på andra eländiga sätt.
Observera att vissa typer av DLL:er, särskilt "MFC-tillägg" DLL:er inte växlar modultillståndet i deras RawDllMain (faktiskt har de vanligtvis inte ens en RawDllMain). Detta beror på att de är avsedda att bete sig "som om" de faktiskt fanns i programmet som använder dem. De spelar en betydande roll i det program som körs och deras avsikt är att ändra den globala statusen för programmet.
OLE-kontroller och andra DLL:er skiljer sig mycket åt. De vill inte ändra det anropande programmets tillstånd. programmet som anropar dem kanske inte ens är ett MFC-program och därför kanske det inte finns något tillstånd att ändra. Det är anledningen till att modultillståndsväxlingen uppfanns.
För exporterade funktioner från en DLL, till exempel en som startar en dialogruta i din DLL, måste du lägga till följande kod i början av funktionen:
AFX_MANAGE_STATE(AfxGetStaticModuleState())
Detta byter det aktuella modultillståndet mot det tillstånd som returneras från AfxGetStaticModuleState tills det aktuella omfånget avslutas.
Problem med resurser i DLL:er uppstår om det AFX_MODULE_STATE makrot inte används. Som standard använder MFC resurshandtaget för huvudprogrammet för att läsa in resursmallen. Den här mallen lagras faktiskt i DLL-filen. Rotorsaken är att MFC:s modultillståndsinformation inte har växlats av AFX_MODULE_STATE makrot. Resurshandtaget återställs från MFC:s modultillstånd. Om du inte växlar modultillståndet används fel resursreferens.
AFX_MODULE_STATE behöver inte placeras i varje funktion i DLL-filen. Kan till exempel InitInstance anropas av MFC-koden i programmet utan AFX_MODULE_STATE eftersom MFC automatiskt ändrar modultillståndet före InitInstance och sedan växlar tillbaka det efter att InitInstance har returnerat. Detsamma gäller för alla meddelandemappningshanterare. Vanliga MFC-DLL:er har faktiskt en särskild huvudfönsterprocedur som automatiskt växlar modultillståndet innan du dirigerar något meddelande.
Bearbeta lokala data
Lokala processdata skulle inte vara så bekymmersamt om det inte hade varit för svårigheten med Win32s DLL-modellen. I Win32s delar alla DLL:er sina globala data, även när de läses in av flera program. Detta skiljer sig mycket från den "riktiga" Win32 DLL-datamodellen, där varje DLL får en separat kopia av sitt datautrymme i varje process som kopplas till DLL:en. För att lägga till komplexitet är data som allokerats på heapen i en Win32s DLL faktiskt processspecifik (åtminstone när det gäller ägarskap). Överväg följande data och 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);
}
Tänk på vad som händer om koden ovan finns i en DLL och att DLL läses in av två processer A och B (det kan faktiskt vara två instanser av samma program). En anropar SetGlobalString("Hello from A"). Därför allokeras minne för data i kontexten av processen A. Tänk på att CString är globalt och synligt för både A och B. Nu anropar B CString. B kommer att kunna se de data som A anger. Det beror på att Win32s inte erbjuder något skydd mellan processer som Win32 gör. Det är det första problemet. I många fall är det inte önskvärt att ett program påverkar globala data som anses vara ägda av ett annat program.
Det finns ytterligare problem också. Låt oss säga att A nu går ut. När A avslutas görs det minne som används av strängenstrGlobal tillgänglig för systemet , det vill vill: allt minne som allokeras av process A frigörs automatiskt av operativsystemet. Det frigörs inte eftersom CString destructor kallas; det har inte kallats ännu. Det frigörs bara för att programmet som allokerade det har lämnat scenen. Om B nu anropade GetGlobalString(sz, sizeof(sz))kanske det inte får giltiga data. Något annat program kan ha använt det minnet för något annat.
Det är uppenbart att det finns ett problem. MFC 3.x använde en teknik som kallas trådlokal lagring (TLS). MFC 3.x skulle allokera ett TLS-index som under Win32s verkligen fungerar som ett processlokalt lagringsindex, även om det inte kallas det och sedan refererar till alla data baserat på det TLS-indexet. Detta liknar TLS-indexet som användes för att lagra trådlokala data på Win32 (se nedan för mer information om det ämnet). Detta gjorde att varje MFC DLL-fil använder minst två TLS-index per process. När du tar hänsyn till inläsningen av många OLE Control DLL:er (OCX) får du snabbt slut på TLS-index (det finns bara 64 tillgängliga). Dessutom var MFC tvungen att placera alla dessa data på ett ställe, i en enda struktur. Det var inte särskilt utökningsbart och var inte idealiskt när det gäller dess användning av TLS-index.
MFC 4.x adresserar detta med en uppsättning klassmallar som du kan "omsluta" runt de data som ska bearbetas lokalt. Till exempel kan problemet som nämns ovan åtgärdas genom att skriva:
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 implementerar detta i två steg. Först finns det ett lager ovanpå Win32 Tls* API:er (TlsAlloc, TlsSetValue, TlsGetValue osv.) som endast använder två TLS-index per process, oavsett hur många DLL:er du har. För det andra tillhandahålls mallen CProcessLocal för åtkomst till dessa data. Den åsidosätter operatorn ,> vilket är det som tillåter den intuitiva syntax som du ser ovan. Alla objekt som omsluts av CProcessLocal måste härledas från CNoTrackObject. 
              CNoTrackObject tillhandahåller en allokerare på lägre nivå (LocalAlloc/LocalFree) och en virtuell destruktor som gör att MFC automatiskt kan förstöra processens lokala objekt när processen avslutas. Sådana objekt kan ha en anpassad destruktor om ytterligare städning krävs. Exemplet ovan kräver inte något eftersom kompilatorn genererar en standarddestruktor för att förstöra det inbäddade CString objektet.
Det finns andra intressanta fördelar med den här metoden. Alla objekt förstörs CProcessLocal inte bara automatiskt, de konstrueras inte förrän de behövs. 
              CProcessLocal::operator-> instansierar det associerade objektet första gången det anropas och inte tidigare. I exemplet ovan innebär det att strängen "strGlobal" inte kommer att konstrueras förrän första gången SetGlobalString eller GetGlobalString anropas. I vissa fall kan detta bidra till att minska DLL-starttiden.
Trådnära variabler
På samma sätt som för att bearbeta lokala data används trådlokala data när data måste vara lokala för en viss tråd. Du behöver alltså en separat instans av data för varje tråd som kommer åt dessa data. Detta kan många gånger användas i stället för omfattande synkroniseringsmekanismer. Om data inte behöver delas av flera trådar kan sådana mekanismer vara dyra och onödiga. Anta att vi hade ett CString objekt (ungefär som exemplet ovan). Vi kan göra tråden lokal genom att omsluta den med en CThreadLocal mall:
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
        }
    }
}
Om MakeRandomString anropades från två olika trådar skulle var och en "blanda" strängen på olika sätt utan att störa den andra. Det beror på att det faktiskt finns en strThread instans per tråd i stället för bara en global instans.
Observera hur en referens används för att fånga CString adressen en gång, istället för en gång per loopvarv. Loopkoden kunde ha skrivits med threadData->strThread överallt där "str" används, men koden skulle vara mycket långsammare under körning. Det är bäst att cachelagra en datareferens när sådana referenser förekommer i loopar.
Klassmallen CThreadLocal använder samma mekanismer som CProcessLocal gör det och samma implementeringstekniker.
Se även
              tekniska anteckningar efter nummer
              tekniska anteckningar efter kategori