TN058: Estado do módulo MFC implementação
Observação |
---|
A seguinte nota técnica não foi atualizada desde que foi incluída pela primeira vez na documentação online.Como resultado, alguns procedimentos e tópicos podem estar desatualizado ou incorreto.As informações mais recentes, é recomendável que você procure o tópico de interesse no índice de documentação on-line. |
Esta nota técnica descreve a implementação do MFC "módulo estado" construções.Noções básicas sobre a implementação de estado do módulo é crítico para usar o MFC DLLs de uma DLL compartilhada (ou servidor OLE em processo).
Antes de ler esta nota, consulte "Gerenciando a dados de estado do MFC módulos" em exibições, Windows e criar novos documentos.Este artigo contém informações de utilização importantes e informações de visão geral sobre esse assunto.
Visão Geral
Há três tipos de informações de estado do MFC: módulo de estado, estado de processo e estado do segmento.Às vezes, esses tipos de estado podem ser combinados.Por exemplo, mapas de identificador do MFC são thread local e módulo local.Isso permite que dois módulos diferentes ter mapas diferentes em cada um dos seus segmentos.
Estado de processo e Thread são semelhantes.Esses itens de dados são coisas que tem sido tradicionalmente variáveis globais, mas tem precisam ser específicas para um determinado processo ou thread de suporte do Win32s adequada ou para suporte a multithreading adequada.Qual categoria se encaixa um item de dados determinado depende do item e sua semântica desejada com relação aos limites de processo e segmento.
Estado do módulo é o único que pode conter estado local de processo ou thread local ou estado verdadeiramente global.Além disso, ele pode ser comutado rapidamente.
Estado do módulo de comutação
Cada segmento contém um ponteiro para o estado do módulo "atual" ou "ativo" (não surpreendentemente, o ponteiro é parte do estado local de thread do MFC).Esse ponteiro é alterado quando o thread de execução passa um limite de módulo, como um aplicativo chamando um controle OLE ou DLL ou um controle OLE chamar novamente um aplicativo.
O estado atual do módulo é comutado chamando AfxSetModuleState.Na maior parte, você nunca irá lidar diretamente com a API.O MFC, em muitos casos, chamará para você (em WinMain, pontos de entrada do OLE, AfxWndProc, etc.).Isso é feito em qualquer componente escrever vinculando estaticamente em um especial WndProce um especial WinMain (ou DllMain) que sabe qual estado do módulo deve ser atual.Você pode ver este código observando DLLMODUL.CPP ou APPMODUL.CPP no diretório MFC\SRC.
É raro que você deseja definir o estado do módulo e, em seguida, não defini-la novamente.Na maioria das vezes que você deseja "enviar" em seu próprio módulo state como atual e em seguida, depois de feito, "pop" o contexto original de volta.Isso é feito pela macro AFX_MANAGE_STATE e a classe especial AFX_MAINTAIN_STATE.
CCmdTargetpossui recursos especiais para troca de estado do módulo de suporte.Em particular, um CCmdTarget é a classe raiz usado para automação de OLE e do OLE COM pontos de entrada.Outro ponto de entrada, como exposto no sistema, esses pontos de entrada devem definir o estado do módulo correto.Como oferece um determinado CCmdTarget souber qual deve ser o estado do módulo "corretos"?A resposta é que ele "lembra" o que é o estado "atual" do módulo quando ele é construído, de modo que ele pode definir o estado atual do módulo para que "lembrada" valor quando for posterior chamado.Como resultado, o módulo de estado que um determinado CCmdTarget objeto é associado com é o estado do módulo atual quando o objeto foi construído.Levar um exemplo simples de carregar um servidor INPROC, criando um objeto e chamar seus métodos.
A DLL é carregada pelo OLE usando LoadLibrary.
RawDllMain é chamado primeiro.Ele define o estado do módulo para o estado de módulo estático conhecido para a DLL.Por esse motivo RawDllMain está vinculada estaticamente para a DLL.
O construtor da fábrica de classe associado ao nosso objeto é chamado.COleObjectFactoryderivado de CCmdTarget e, conseqüentemente, lembra em qual estado do módulo foi instanciado.Isso é importante — quando é solicitada a fábrica de classes para criar objetos, agora sabe qual estado do módulo para tornar atual.
DllGetClassObjecté chamado para obter a fábrica de classes.Pesquisa a lista de fábrica de classe associada com esse módulo MFC e o retorna.
COleObjectFactory::XClassFactory2::CreateInstance é chamado.Antes de criar o objeto e devolvê-lo, essa função define o estado do módulo para o estado do módulo atual na etapa 3 (que era atual quando o COleObjectFactory foi instanciado).Isso é feito dentro de METHOD_PROLOGUE.
Quando o objeto é criado, ele é muito um CCmdTarget e derivado da mesma maneira COleObjectFactory lembrada qual estado módulo estava ativo, aumenta esse novo objeto.Agora o objeto sabe qual estado do módulo para alternar para sempre que é chamado.
O cliente chama uma função no objeto OLE COM ele recebeu do seu CoCreateInstance de chamada.Quando o objeto é chamado, ele usa METHOD_PROLOGUE para alternar o estado do módulo como COleObjectFactory oferece.
Como você pode ver o estado do módulo é propagado de objeto para objeto conforme eles são criados.É importante que o estado do módulo definido apropriadamente.Se não for definida, seu objeto DLL ou COM pode interagir mal com um aplicativo MFC que está chamando, ou pode não conseguir localizar seus próprios recursos ou pode falhar de outras maneiras péssima.
Observe que certos tipos de DLLs, especificamente as DLLs de "extensão do MFC" não alternar o estado do módulo de RawDllMain (na verdade, eles geralmente não ainda têm um RawDllMain).Isso é porque são projetados para se comportar "como se" fosse realmente presentes no aplicativo que usa-los.Eles são muito uma parte do aplicativo que está executando e é sua intenção de modificar o estado global do aplicativo.
Controles OLE e outras DLLs são muito diferentes.Eles não desejam modificar o estado do aplicativo de chamada; o aplicativo que está chamando não pode ser até mesmo um aplicativo MFC e portanto, não pode haver nenhum estado para modificar.Este é o motivo de troca de estado do módulo foi inventado.
Funções exportadas de uma DLL, como um que inicia uma caixa de diálogo na sua DLL, você precisará adicionar o código a seguir ao início da função:
AFX_MANAGE_STATE(AfxGetStaticModuleState( ))
Essa troca o estado atual do módulo com o estado retornado de AfxGetStaticModuleState até o final do escopo atual.
Problemas com recursos em DLLs ocorrerá se o AFX_MODULE_STATE macro não é usada.Por padrão, o MFC usa o identificador de recurso do aplicativo principal para carregar o modelo de recurso.Este modelo é realmente armazenado na DLL.A causa é que informações de estado do módulo do MFC não foi comutadas pelo AFX_MODULE_STATE macro.O identificador de recurso é recuperado do estado de módulo do MFC.O estado do módulo de switching não faz com que o identificador de recurso incorreto ser usado.
AFX_MODULE_STATEnão precisa ser colocado em cada função na DLL.Por exemplo, InitInstance pode ser chamado pelo código do MFC no aplicativo sem AFX_MODULE_STATE porque MFC mudará automaticamente o estado de módulo antes de InitInstance e, em seguida, os switches volta após InitInstance retorna.O mesmo é verdadeiro para todos os manipuladores de mapa da mensagem.DLLs regulares realmente têm um procedimento de janela mestre especial que automaticamente alterna o estado de módulo antes roteamento qualquer mensagem.
Local de dados do processo
Dados de processo local não seria de tal grande preocupação não era para a dificuldade do modelo DLL Win32s.No Win32s todas as DLLs compartilham seus dados globais, mesmo quando carregado por vários aplicativos.Isso é muito diferente do modelo de dados DLL Win32 "real", onde cada DLL obtém uma cópia separada do espaço de dados em cada processo anexa a DLL.Para adicionar a complexidade, dados alocados na pilha em uma DLL Win32s são na verdade processo específico (até pelo menos onde vai de propriedade).Considere os seguintes dados e código:
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);
}
Considere o que acontece se o código acima é no localizado em uma DLL e DLL é carregada por dois processos a e B (na verdade, ele poderia ser duas instâncias do mesmo aplicativo).A calls SetGlobalString("Hello from A").Como resultado, a memória é alocada para o CString dados no contexto da processo.Lembre-se de que o CString si é global e está visível para os dois a e b.Agora b chama GetGlobalString(sz, sizeof(sz)).B poderão ver os dados de um conjunto.Isso ocorre porque o Win32s não oferece nenhuma proteção entre processos como o Win32.Esse é o primeiro problema; em muitos casos não é desejável ter um aplicativo afetam dados globais são considerados para ser pertencente a um aplicativo diferente.
Há também problemas adicionais.Digamos que a agora sai.Quando um sai, a memória usada pelo 'strGlobal' seqüência de caracteres é disponibilizada para o sistema — isto é, toda a memória alocada por um processo é liberada automaticamente pelo sistema operacional.Não é liberado porque a CString chamado de destruidor; não foi chamado ainda.Ele é liberado simplesmente porque o aplicativo que alocou deixou a cena.Agora se chamado b GetGlobalString(sz, sizeof(sz)), ele não pode obter dados válidos.Outro aplicativo pode ter usado o que a memória para outra coisa.
Claramente existe um problema.3. X MFC usada uma técnica chamada armazenamento thread local (TLS).3. X MFC alocará um índice de TLS que em Win32s realmente funciona como um índice de armazenamento local do processo, mesmo que não é chamado e, em seguida, faria referência a todos os dados com base no índice TLS.Isso é semelhante ao índice TLS que foi usado para armazenar dados de local de thread Win32 (consulte abaixo para obter mais informações sobre o assunto).Isso causou cada DLL do MFC utilizar pelo menos dois índices TLS por processo.Quando você conta para carregar muitos controle DLLs OLE (OCXs), rapidamente executar fora de índices TLS (existem apenas 64 disponível).Além disso, o MFC tinha colocar todos os dados em um lugar em uma estrutura única.Ele não era muito extensível e não era ideal com relação ao seu uso de índices TLS.
4. X MFC soluciona isso com um conjunto de modelos de classe, você pode "quebrar" em torno de dados que devem ser um processo local.Por exemplo, o problema mencionado acima foi corrigido, escrevendo:
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 isso em duas etapas.Primeiro, há uma camada na parte superior do Win32 Tls * APIs (TlsAlloc, TlsSetValue, TlsGetValue, etc.) que usam apenas dois índices TLS por processo, não importa quantas DLLs que você tem.Segundo, o CProcessLocal modelo é fornecido para acessar esses dados.Ela substitui o operador - > qual é o que permite que a sintaxe intuitiva que ver acima.Todos os objetos são delimitados por CProcessLocal deve ser derivado de CNoTrackObject.CNoTrackObjectFornece um alocador de nível inferior (LocalAlloc/LocalFree) e um destruidor virtual que MFC automaticamente pode destruir os objetos de processo local quando o processo é encerrado.Esses objetos podem ter um destruidor personalizado se adicionais de limpeza é necessária.O exemplo acima não requer um, pois o compilador gerará um destruidor padrão para destruir o incorporado CString objeto.
Há outras vantagens interessantes para essa abordagem.Não só são todos CProcessLocal objetos destruídos automaticamente, até que eles são necessários não estão construídas.CProcessLocal::operator->instanciará o objeto associado na primeira vez que ele é chamado e não antes.No exemplo acima, isso significa que o 'strGlobal' seqüência de caracteres não será construída até a primeira vez SetGlobalString ou GetGlobalString é chamado.Em alguns casos, isso pode ajudar a diminuir o tempo de inicialização da DLL.
Local de dados de segmento
Semelhante ao processar dados locais, dados de local de thread são usados quando os dados devem ser locais para um determinado segmento.Ou seja, você precisa de uma instância separada dos dados para cada thread que acessa dados.Isso pode muitas vezes ser usado no lugar de mecanismos de sincronização abrangente.Se os dados não precisam ser compartilhado por vários threads, tais mecanismos podem ser caros e desnecessários.Suponhamos que tivéssemos uma CString objeto (muito parecido com o exemplo acima).Podemos fazê-lo thread local encapsulando-com um CThreadLocal modelo:
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 foi chamado de dois segmentos diferentes, cada um seria "embaralhar" seqüência de maneiras diferentes sem interferir com o outro.Isso ocorre porque há realmente um strThread instância por thread em vez de apenas uma instância global.
Observe como uma referência é usada para capturar a CString endereço uma vez em vez de uma vez por iteração do loop.O código do loop foi escrito com threadData->strThread todos os lugares 'str' é usado, mas o código seria muito mais lento em execução.É melhor para armazenar em cache uma referência para os dados quando ocorrem tais referências em loops.
O CThreadLocal modelo de classe utiliza os mesmos mecanismos que CProcessLocal faz e as mesmas técnicas de implementação.