Questa nota tecnica descrive l'implementazione dei costrutti di "Stato del modulo" MFC. La comprensione dell'implementazione dello stato del modulo è critica per usando MFC dll da una DLL condivisa (o server in-process OLE).
Prima di leggere questa nota, consultare "Managing the stato dei dati di MFC moduli" in creazione di nuovi documenti, Windows e viste in manuale del programmatore di Visual C++. Questo articolo contiene informazioni importanti sull'utilizzo e informazioni generali su questo argomento.
Panoramica
Ci sono tre tipi di informazioni sullo stato MFC: stato del modulo, stato del processo e lo stato Thread. A volte questi tipi di stato possono essere combinati. Ad esempio, mappe di handle di MFC sono sia modulo locale e thread locale. Questo permette di due diversi moduli di avere diverse mappe in ciascuna delle loro discussioni.
Stato del processo e lo stato Thread sono simili. Questi elementi di dati sono cose che sono stati tradizionalmente le variabili globali, ma hanno bisogno di essere specifiche per un determinato processo o thread per Win32s adeguato supporto o per un adeguato sostegno multithread. Quale categoria di un elemento di dati si adatta in dipende da tale elemento e la semantica desiderata in materia di limiti di processi e thread.
Stato del modulo è unico in quanto può contenere sia stato veramente globale o stato, che è il processo locale o il locale di thread. Inoltre si possono essere commutato velocemente.
Stato del modulo di commutazione
Ogni thread contiene un puntatore allo stato del modulo "attiva" o "corrente" (non sorprendentemente, il puntatore è parte dello stato locale del thread di MFC). Questo puntatore è cambiato quando il thread di esecuzione passa un limite del modulo, ad esempio un'applicazione chiamata in un controllo OLE o DLL o di un controllo OLE rimettere indietro in un'applicazione.
Stato corrente del modulo è acceso chiamando AfxSetModuleState. Per la maggior parte, non tratterà mai direttamente con l'API. MFC, in molti casi, chiamano per te (a WinMain, OLE punti di ingresso, AfxWndProc, ecc.). Questo viene fatto in qualsiasi componente scrivere collegando in modo statico in una speciale WndProce una speciale WinMain (o DllMain) che sa quale stato del modulo deve essere corrente. Si può vedere questo codice da dare un'occhiata a DLLMODUL.CPP o APPMODUL.CPP nella directory MFC\SRC.
È raro che si desidera impostare lo stato del modulo e quindi non impostare indietro. Il più delle volte che si desidera "spingere" il proprio modulo di stato come quello attuale e poi, dopo che siete fatto, "pop" torna il contesto originale. Questo viene fatto con la macro AFX_MANAGE_STATE e la classe speciale AFX_MAINTAIN_STATE.
CCmdTarget ha caratteristiche particolari per sostenere modulo dello stato di commutazione. In particolare, un CCmdTarget è che la classe principale utilizzato per l'automazione OLE e OLE COM punti di ingresso. Come qualsiasi altro punto di entrata esposto al sistema, questi punti di ingresso devono impostare stato del modulo corretto. Come un dato CCmdTarget sapere quello che dovrebbe essere stato del modulo "corretto"? La risposta è che "ricorda" che cosa è stato "corrente" del modulo quando viene creato, tale che è possibile impostare lo stato corrente del modulo a quella "ricordato" valore quando è più tardi chiamato. Di conseguenza, stato del modulo in cui è associato un determinato oggetto CCmdTarget è stato del modulo che è stato corrente quando l'oggetto è stato costruito. Diamo un semplice esempio di caricamento di un server INPROC, creando un oggetto e chiamando i metodi.
Come potete vedere, stato del modulo viene propagato da oggetto a oggetto, come essi vengono creati. È importante avere stato del modulo impostato in modo appropriato. Se non è impostata, l'oggetto DLL o COM possa interagire scarsamente con un'applicazione MFC che sta chiamando, non sia in grado di trovare le proprie risorse o potrebbe non riuscire in altri modi miserabili.
Nota che certi tipi di dll, specificamente "MFC Extension" dll non passano lo stato del modulo nel loro RawDllMain fornita (in realtà, essi di solito non hanno nemmeno un RawDllMain fornita). Questo è perché essi sono destinati a comportarsi "come se" fossero effettivamente presenti nell'applicazione che li utilizza. Essi sono molto una parte dell'applicazione che esegue ed è loro intenzione di modifica dello stato globale di quell'applicazione.
OLE controlli e altre dll sono molto diversi. Essi non vogliono modifica dello stato dell'applicazione chiamante; l'applicazione che li chiama non può anche essere un'applicazione MFC e così non ci può essere nessun stato per modificare. Questo è il motivo che è stato inventato la commutazione di modulo dello stato.
Per funzioni esportate da una DLL, ad esempio uno che lancia una finestra di dialogo nella DLL, è necessario aggiungere il codice seguente all'inizio della funzione:
AFX_MANAGE_STATE (AfxGetStaticModuleState ())
Questo swap stato corrente del modulo con lo stato restituito da AfxGetStaticModuleState fino alla fine dell'ambito corrente.
Problemi con risorse nelle dll si verificherà se non viene utilizzata la macro AFX_MODULE_STATE . Per impostazione predefinita, MFC utilizza l'handle di risorsa dell'applicazione principale per caricare il modello di risorse. Questo modello è effettivamente memorizzato nella DLL. La causa principale è se le informazioni sullo stato MFC modulo non sono stati attivati dalla macro AFX_MODULE_STATE . L'handle di risorsa è recuperato da stato del modulo di MFC. Stato del modulo di commutazione non provoca l'handle di risorsa errato essere utilizzato.
AFX_MODULE_STATE non ha bisogno di essere messo in ogni funzione nella DLL. Ad esempio, InitInstance può essere chiamato dal codice dell'applicazione senza AFX_MODULE_STATE MFC perché MFC sposta automaticamente lo stato del modulo prima di InitInstance e poi interruttori indietro dopo InitInstance restituisce. Lo stesso è vero per tutti i gestori di mappa del messaggio. DLL regolari in realtà hanno una routine di finestra maestro speciale che passa automaticamente lo stato del modulo prima di qualsiasi messaggio di routing.
Dati di processo locali
Processo di dati locali non sarebbe così grande preoccupazione se non fosse stato per la difficoltà del modello Win32s DLL. In Win32s tutte le dll di condividono i propri dati globali, anche se a carico di più applicazioni. Questo è totalmente diverso dal modello di dati DLL Win32 "reale", dove ogni DLL ottiene una copia del suo spazio dati in ogni processo che attribuisce alla DLL. Per aggiungere la complessità, i dati allocati sull'heap in una DLL Win32s sono infatti processo specifico (almeno per quanto va proprietà). Prendere in considerazione i seguenti dati e codice:
strGlobal CString statico; / / nell'ambito del file
__declspec(dllexport) public static void SetGlobalString(LPCTSTR lpsz)
{
strGlobal = lpsz;
}
__declspec(dllexport)
public static void GetGlobalString (LPCTSTR lpsz, int cb)
{
lstrcpyn (lpsz, strGlobal, cb);
}
Si consideri che cosa succede se il codice precedente è in situato in una DLL e che la DLL viene caricata da due processi a e B (potrebbe, infatti, essere due istanze della stessa applicazione). Un chiamate SetGlobalString("Hello from A") . Di conseguenza, di memoria allocata per i dati di CString nel contesto del processo A. tenere a mente che la stessa CString è globale ed è visibile ad entrambi a e b. B chiede ora GetGlobalString(sz, sizeof(sz)) . B sarà in grado di visualizzare i dati a impostare. Questo è perché Win32s non offre alcuna protezione tra processi come Win32 non fa. Quindi questo è il primo problema; in molti casi non è desiderabile avere una sola applicazione influiscono sui dati globali che sono considerati essere di proprietà di un'altra applicazione.
Ma l'attesa — non ci sono più problemi. Diciamo che ora viene chiuso. Quando esce A, la memoria utilizzata dai ' strGlobal ' stringa viene reso disponibile per il sistema — cioè, tutta la memoria allocata da un processo viene liberata automaticamente dal sistema operativo. Non si è liberata perché il distruttore CString viene chiamato; Questo non è stato definito ancora. Esso viene liberata semplicemente perché l'applicazione che destinarlo ha lasciato la scena. Ora, se b chiamato GetGlobalString(sz, sizeof(sz)) , non possono ottenere dati validi. Un'altra applicazione che potrebbe essere utilizzato che la memoria per qualcos'altro.
C'è chiaramente un problema qui. 3. X MFC utilizzato una tecnica denominata archiviazione locale di thread (TLS). 3. X MFC sarebbe allocare un indice TLS che sotto Win32s davvero agisce come un indice di archiviazione processo locale, anche se esso non è chiamato che e quindi vorrei fare riferimento a tutti i dati in base a quell'indice TLS. Questo è simile all'indice TLS che è stato utilizzato per memorizzare i dati locali del thread su Win32 (vedi sotto per maggiori informazioni su quell'argomento). Questo ha causato ogni DLL MFC di utilizzare almeno due indici TLS per ogni processo. Quando rappresentano il caricamento molti OLE Control dll (ocx), correte rapidamente fuori di indici TLS (ci sono solo 64 disponibile). Inoltre, MFC dovuto inserire tutti questi dati in un unico luogo, in una singola struttura. Non era molto estensibile e non era l'ideale per l'uso di indici TLS.
4. X MFC questo si rivolge con un set di modelli di classe può "avvolgere" i dati che dovrebbero essere un processo locale. Ad esempio, il problema di cui sopra potrebbe essere risolto scrivendo:
struct CMyGlobalData: pubblico CNoTrackObject
{
CString strGlobal;
};
CProcessLocallt;CMyGlobalData > globalData;
__declspec(dllexport) public static void SetGlobalString(LPCTSTR lpsz)
{
globalData - > strGlobal = lpsz;
}
__declspec(dllexport)
public static void GetGlobalString (LPCTSTR lpsz, int cb)
{
lstrcpyn (lpsz, globalData - > strGlobal, cb);
}
MFC implementa questo in due fasi. In primo luogo, c'è uno strato sulla cima di Win32 Tls * API (TlsAlloc, TlsSetValue, Tls&GetValue, ecc.) che utilizzano solo due gli indici TLS per ogni processo, non importa quante le dll che avete. Secondo, il CProcessLocal modello viene fornito per accedere a questi dati. Esegue l'override di operatore-gt; che è ciò che permette la sintassi intuitiva che vedete qui sopra. Tutti gli oggetti che sono avvolti da CProcessLocal deve derivare da CNoTrackObject . CNoTrackObject fornisce un allocatore di livello inferiore (LocalAlloc/LocalFree) e un distruttore virtuale tale che MFC automaticamente può distruggere gli oggetti locali del processo, quando il processo è terminato. Tali oggetti possono avere un distruttore personalizzato se è necessaria la pulizia supplementare. L'esempio sopra non richiede uno, poiché il compilatore genererà un distruttore predefinito per distruggere l'oggetto CString incorporato.
Ci sono altre interessanti vantaggi di questo approccio. Non solo sono tutti CProcessLocal oggetti distrutti automaticamente, non sono costruiti fino a quando sono necessari. CProcessLocal::operator-gt; si crea un'istanza di oggetto associato, la prima volta che viene chiamato e non prima. Nell'esempio precedente, ciò significa che la ' str&Global ' stringa non sarà costruito fino a quando la prima volta che viene chiamato SetGlobalString o GetGlobalString . In alcuni casi, questo può aiutare a diminuire il tempo di avvio DLL.
Dati locale di thread
Simile a elaborare i dati locali, dati locale di thread viene utilizzati quando i dati devono essere locali a un thread specifico. Che è, avete bisogno di un'istanza separata dei dati per ogni thread che accede a tali dati. Molte volte consente invece di meccanismi di sincronizzazione estesa. Se i dati non devono essere condivisi da più thread, tali meccanismi possono essere costosi e inutili. Si supponga che abbiamo avuto un oggetto CString (molto simile l'esempio precedente). Noi possiamo rendere thread locale avvolgendolo con una CThreadLocal modello:
struct CMyThreadData: pubblico CNoTrackObject
{
CString strThread;
};
CThreadLocallt;CMyThreadData > threadData;
public static void MakeRandomString()
{
/ / a kind of shuffle carta (non un grande)
CString & str = threadData - > strThread;
Str.Returna;
mentre (str.GetLength()! = 52)
{
TCHAR ch = Rand () % 52 + 1;
Se (str.Find(ch) < 0)
Str + = ch; / / non trovato, aggiungerla
}
}
Se MakeRandomString è stato chiamato da due thread diversi, ognuno avrebbe "shuffle" la stringa in modi diversi senza interferire con l'altro. Questo è perché non c'è in realtà un strThread istanza per ogni thread invece di una sola istanza globale.
Si noti come un riferimento viene utilizzato per acquisire l'indirizzo CStrin&g una volta invece di una volta per iterazione del ciclo. Il codice del ciclo sono stati scritti con threadData-gt;strThread ovunque ' str ' è usato, ma il codice sarebbe stato molto più lento in esecuzione. Si consiglia di memorizzare nella cache un riferimento ai dati quando tali riferimenti si verificano in loop.
La CThreadLocal classe modello utilizza gli stessi meccanismi che CProcessLocal fa e le stesse tecniche di attuazione.
&Note tecniche per numero |nbsp; Note tecniche per la categoria