Condividi tramite


TN058: Implementazione di stato del modulo MFC

[!NOTA]

La seguente nota tecnica non è stata aggiornata dalla prima volta che viene inclusa nella documentazione online.Di conseguenza, alcune procedure e argomenti potrebbero non essere aggiornati o errati.Per le informazioni più recenti, è consigliabile cercare l'argomento di interesseindice della documentazione online.

Questa nota tecnica descritta l'implementazione “costrutti dello stato del modulo„ MFC.Una conoscenza dell'implementazione di stato del modulo sono fondamentali per l'utilizzo di DLL condivisa MFC da un server in-process OLE o (DLL).

Prima di leggere questa nota, vedere “per gestire i dati dello stato dei moduli MFC„ in Creazione di nuovi documenti, le finestre e delle visualizzazioni.In questo articolo sono le informazioni e informazioni generali importanti di utilizzo a questo argomento.

Panoramica

Esistono tre tipi di informazioni sullo stato MFC: Stato del modulo, stato di processo e lo stato del thread.Talvolta questi tipi di stato possono essere combinate.Ad esempio, mappe di handle di MFC sono sia di modulo locale che locale del thread.In questo modo due moduli diversi di mapping diversi in ognuno dei thread.

Lo stato e lo stato del thread gestiti sono simili.Questi elementi di dati sono operazioni che è stata tradizionalmente variabili globali, ma è necessario che siano specifici di un processo o un thread specificato per il supporto appropriato di Win32 o per il supporto del multithreading appropriato.La categoria un elemento di dati specificato ha lasciato il team concordi dipende dall'elemento e dalla relativa semantica desiderata rispetto ai limiti del thread e processi.

Lo stato del modulo sono univoci in quanto può contenere lo stato realmente globale o dichiarare che è locale del processo o locale del thread.Inoltre, può essere modificato rapidamente.

Passaggio di stato del modulo

Ogni thread contiene un puntatore a “c„ o “lo stato attivo„ module in base alle aspettative, il puntatore fa parte dello stato locale del thread di MFC.Questo puntatore viene modificato quando il thread di esecuzione passa un limite del modulo, ad esempio un'applicazione chiamante un controllo OLE o DLL, o in un controllo OLE chiamante in un'applicazione.

Lo stato del modulo corrente viene passato chiamando AfxSetModuleState.In genere, non è occuperete mai direttamente API.MFC, in molti casi, la chiamata automaticamente (WinMain, i punti di ingresso OLE, AfxWndProc, e così via.).Questa operazione viene eseguita in qualsiasi componente che si scrivono collegandola in WndProcspeciale e WinMain speciale (o DllMain) che è in grado di riconoscere il stato del modulo deve essere corrente.È possibile vedere il codice di DLLMODUL.CPP o APPMODUL.CPP nella directory SRC \ MFC.

È raro che si desidera impostare lo stato del modulo e quindi non impostarlo indietro.Per la maggior parte dei casi si desidera “per chiedere„ il proprio stato del modulo come quello corrente e quindi, dopo aver utilizzato, “prelevare„ la parte finale originale di contesto.Questa operazione viene eseguita da macro AFX_MANAGE_STATE e la classe speciale AFX_MAINTAIN_STATE.

CCmdTarget dispone di funzionalità speciali per la commutazione di stato che supporta il modulo.In particolare, CCmdTarget è la classe radice utilizzata per automazione OLE e i punti di ingresso OLE COM.Come qualsiasi altro punto di ingresso esposto al sistema, questi punti di ingresso devono impostare lo stato del modulo corretto.Come CCmdTarget specificato sa quali lo stato corretto„ module deve essere?La risposta è “che si memorizza„ cosa lo stato “corrente„ module è quando viene creato, in modo che possibile impostare lo stato del modulo corrente su quello valore “ricordato„ quando successivamente viene chiamato.Di conseguenza, lo stato del modulo che un determinato oggetto di CCmdTarget è associato è lo stato del modulo che era corrente durante la costruzione dell'oggetto.Fare un esempio semplice di carico del server INPROC, di creare un oggetto e di chiamare i relativi metodi.

  1. Il caricamento della DLL da OLE tramite LoadLibrary.

  2. RawDllMain viene chiamato per primo.Imposta lo stato del modulo allo stato static noto del modulo per la DLL.Per questo motivo RawDllMain è collegato staticamente alla DLL.

  3. Il costruttore per il class factory associato all'oggetto viene chiamato.COleObjectFactory è derivato da CCmdTarget e di conseguenza, è memorizzata nello stato del modulo è stata creata un'istanza.Ciò è importante — quando il class factory viene richiesto di creare oggetti, ora riconosce che cosa stato del modulo per fare corrente.

  4. DllGetClassObject viene chiamato per ottenere il class factory.MFC cercherà l'elenco di class factory associato al modulo e lo restituisce.

  5. COleObjectFactory::XClassFactory2::CreateInstance viene chiamato.Prima di creare l'oggetto e di restituirlo, questa funzione imposta lo stato del modulo allo stato del modulo che era corrente nel passaggio 3 (che era corrente quando COleObjectFactory istanziato).Questa operazione viene eseguita in METHOD_PROLOGUE.

  6. Quando viene creato l'oggetto, anche è un derivato e allo stesso modo COleObjectFactory di CCmdTarget ricordati che lo stato del modulo era attivo, in modo da far questo nuovo oggetto.Ora l'oggetto sa quale stato del modulo da passare ogni volta che viene chiamato.

  7. Il client chiama una funzione l'oggetto COM che OLE ha ricevuto dalla chiamata di CoCreateInstance .Quando l'oggetto viene chiamato utilizza METHOD_PROLOGUE per passare lo stato del modulo come COleObjectFactory fa.

Come si può notare, lo stato del modulo viene propagata da oggetto in un oggetto quando vengono creati.È importante eseguire lo stato del modulo impostare in modo appropriato.Se non è impostata, la DLL o oggetto COM può interagire correttamente con un'applicazione MFC che è chiamante, o potrebbe non essere in grado di trovare le proprie risorse, oppure può avere esito negativo in altre modalità misere.

Si noti che determinati tipi di DLL, in particolare “DLL di estensione MFC„ non superano lo stato del modulo nel RawDllMain (in realtà, in genere non hanno RawDllMain).In quanto sono destinati per comportarsi come “if„ siano stati effettivamente presenti nell'applicazione che li utilizza.In sono disponibili una parte dell'applicazione in esecuzione e è la propria intenzione modificare che lo stato complessivo dell'applicazione.

I controlli OLE e altre DLL sono molto diverse.Non desidera modificare lo stato dell'applicazione chiamante, l'applicazione che si sta chiamando questi elementi può non essere un'applicazione MFC e pertanto non può essere stato da modificare.Questo è il motivo per cui la commutazione di stato del modulo è stata inventata.

Per le funzioni esportate da una DLL, come uno che avvia una finestra di dialogo nella DLL, è necessario aggiungere il codice seguente all'inizio della funzione:

AFX_MANAGE_STATE(AfxGetStaticModuleState( ))

Ciò consente di scambiare lo stato corrente del modulo con lo stato restituito da AfxGetStaticModuleState fino alla fine dell'ambito corrente.

I problemi con le risorse nelle DLL si verificheranno se la macro di AFX_MODULE_STATE non viene utilizzata.Per impostazione predefinita, MFC utilizza l'handle di risorsa dell'applicazione principale caricare il modello di risorse.Questo modello viene effettivamente archiviato nella DLL.La causa principale è che le informazioni sullo stato del modulo MFC non è stato passato dalla macro di AFX_MODULE_STATE .L'handle delle risorse vengono recuperate dallo stato del modulo MFC.Non passare lo stato del modulo consente a un handle di risorsa errato per essere utilizzato.

AFX_MODULE_STATE non deve essere inserito in ogni funzione nella DLL.Ad esempio, InitInstance può essere chiamato da codice MFC nell'applicazione senza AFX_MODULE_STATE perché MFC automaticamente scorre lo stato del modulo prima di InitInstance quindi le opzioni di nuovo dopo il completamento di InitInstance .Lo stesso vale per tutti i gestori della mappa messaggi.Le DLL regolari effettivamente dispongono di una routine della finestra di master speciale che passa automaticamente lo stato del modulo prima di gestire qualsiasi messaggio.

Dati locali considerati

I dati locali considerati non sarebbero di tale grande preoccupazione se non fosse stato per la difficoltà del modello di DLL Win32.In tutte le DLL Win32 condividere i dati globali, anche se caricato da più applicazioni.Ciò è molto diverso dal modello di dati “reale„ DLL Win32, in cui ogni DLL ottiene una copia distinta dello spazio di dati in ogni processo che associa alla DLL.Per aggiungere alla complessità, i dati allocati nell'heap in una DLL Win32 sono in effetti specifico processo (almeno per quanto la proprietà va).Considerare i seguenti dati e codice:

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

Si consideri ciò che si verifica se il codice precedente è in trova in una DLL e il caricamento della DLL da due processi A e B (possibile, infatti, essere due istanze della stessa applicazione).Chiamate SetGlobalString("Hello from A").Di conseguenza, è allocata la memoria per i dati di CString nel contesto del processo In.Tenere presente che CString stesso è globale e è visibile sia per che per B.Ora B chiama GetGlobalString(sz, sizeof(sz)).B potrà visualizzare i dati che A impostato.Questo perché i Win32 non offre protezione tra processi quale Win32 fa.Questo è il primo problema; in molti casi non si desidera disporre di un dati globale effetti dell'applicazione che sono considerati come proprietà da un'applicazione diversa.

Esistono problemi aggiuntivi anche.Supponiamo si debbano chiusura di un oggetto ora.Al termine di un oggetto, la memoria utilizzata dalla stringa “distrGlobal„ viene resa disponibile per il sistema, ovvero tutta la memoria allocata da elaborare A viene liberato automaticamente dal sistema operativo.Non viene liberato perché il distruttore di CString viene chiamato; non è stato chiamato nuovamente.Viene liberato semplicemente perché l'applicazione che lo ha allocato consentire alla scena.Ora se B chiami GetGlobalString(sz, sizeof(sz)), non può ottenere i dati validi.Un'altra applicazione possibile utilizzare tale memoria per il altri elementi.

Un problema esiste in modo significativo.MFC 3.x è stata utilizzata una tecnica denominata l'archiviazione locale di (TLS) thread.MFC 3.x allocherebbe un indice di TLS rispetto ai Win32 in realtà funge da indice di archiviazione del processo-locale, anche se non viene chiamato che quindi farebbe riferimento a tutti i dati basati su tale indice di TLS.Questa operazione è simile all'indice di TLS utilizzato per archiviare i dati di thread locale su Win32 (vedere di seguito per ulteriori informazioni su tale oggetto).Ciò ha causato l'errore di ogni DLL MFC a utilizzare almeno due indici di TLS per processo.Quando il servizio per il caricamento di diverse DLL del controllo OLE (OCXs), si esaurisce rapidamente gli indici di TLS (esistono solo 64 disponibili).Inoltre, MFC è pertanto necessario inserire tutti questi dati in un luogo, in una singola struttura.Non è molto estendibile e non è ideale per quanto riguarda il relativo utilizzo degli indici di TLS.

MFC 4.x risolve questo con un set di modelli che la classe è possibile “ritorno a„ intorno ai dati che devono essere locale del processo.Ad esempio, il problema accennato in precedenza può essere corretto scrivendo:

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 implementa questo in due passaggi.Innanzitutto, è presente un livello su Win32 Tls* le API (TlsAlloc, TlsSetValue, TlsGetValue, e così via.) che utilizzano solo due indici di TLS per processo, indipendentemente dal numero di DLL è.In secondo luogo, il modello di CProcessLocal viene fornito per accedere a tali dati.Esegue l'override del operator-> che è quanto consente la sintassi intuitiva che viene visualizzato su.Tutti gli oggetti di cui viene eseguito il wrapping da CProcessLocal devono essere derivati da CNoTrackObject.CNoTrackObject fornisce un allocatore di livello inferiore (LocalAlloc/LocalFree) e un distruttore virtuale in modo che il MFC è automaticamente eliminare gli oggetti locali considerati quando il processo è terminata.Tali oggetti possono avere un distruttore personalizzato se la pulizia aggiuntiva è obbligatoria.L'esempio precedente non richiede uno, poiché il compilatore genererà un distruttore predefinito per eliminare l'oggetto incorporato di CString .

Esistono altri vantaggi interessanti di questo approccio.Non solo tutti gli oggetti di CProcessLocal vengono eliminati automaticamente, non vengono creati finché non sono necessari.CProcessLocal::operator-> creare un'istanza dell'oggetto associato la prima volta che viene chiamato e non in precedenza.Nell'esempio precedente, significa che la stringa “strGlobal„ non verrà creata la prima volta fino a SetGlobalString o GetGlobalString viene chiamato.In alcuni casi, questo consente di ridurre i tempi di avvio della DLL.

Dati locali di thread

Analogamente ai dati locali dei processi, dati locali di thread viene utilizzato quando i dati devono essere locali del thread specificato.Ovvero sono necessari un'istanza separata dei dati per ogni thread che accede ai dati.Ciò può spesso possibile utilizzare anziché estesi meccanismi di sincronizzazione.Se i dati non devono essere condivisi da più thread, tali meccanismi possono rivelarsi costosi e non necessari.Si supponga che si disponga un oggetto di CString (simile all'esempio precedente).È possibile renderlo il locale di thread ne esegue il wrapping con un modello di 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
      }
   }
}

Se MakeRandomString viene chiamato da due thread differenti, ognuno “mescolerebbe„ la stringa in modi diversi senza interferire con l'altro.Questo perché esiste davvero un'istanza di strThread per thread anziché un'istanza globale.

Nota di come riferimento viene utilizzato per acquisire una volta l'indirizzo di CString anziché una volta per ogni iterazione del ciclo.Il codice del ciclo potrebbe essere scritto con threadData->strThread ovunque “str„ viene utilizzato, ma il codice sarebbe molto più lento nell'esecuzione.Si consiglia di memorizzare nella cache un riferimento ai dati quando tali riferimenti si verificano nei cicli.

Il modello della classe di CThreadLocal utilizza gli stessi meccanismi che CProcessLocal fa e le stesse tecniche di implementazione.

Vedere anche

Altre risorse

Note tecniche del numero

Note tecniche per categoria