TN058: Implementação do estado do módulo MFC

Observação:

A seguinte nota técnica não foi atualizada desde que foi incluída pela primeira vez na documentação online.sistema autônomo resultado, alguns procedimentos e tópicos podem estar desatualizado ou incorreto.Para obter informações mais recentes, é recomendável que você procurar o tópico de interesse no índice de documentação online.

Esta nota técnica descreve a implementação de construções MFC "módulo de estado".Noções básicas sobre a implementação de estado do módulo é fundamental para usar o MFC compartilhado DLLs de uma DLL (ou do servidor OLE em processo).

Antes de ler esta nota, consulte "Gerenciando o estado de MFC módulos dados" emCriação de novos documentos, Windows e exibições.Este artigo contém informações de uso 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: Estado do módulo, estado de processo e estado de thread.À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 do processo e estado de thread são semelhantes.Esses itens de dados são coisas que tradicionalmente têm sido variáveis global, mas têm precisam ser específicas para um determinado processo ou thread de Win32s adequado de suporte ou para suporte multithread correto.Qual categoria um item de dados determinado é adequado depende desse item e sua semântica desejada com relação a processar e thread limites.

Estado do módulo é o único que pode conter estado que é processo local ou estado realmente global ou thread local.Além disso, ele pode ser alternado rapidamente.

Estado do módulo de comutação

Cada thread contém um ponteiro para o estado do módulo "corrente" ou "ativo" (como era de se esperar, o ponteiro é parte do MFC thread estado local).Esse ponteiro é alterado quando o segmento de execução passa um limite de módulo, sistema autônomo um aplicativo de chamada para um controle OLE ou DLL ou um controle OLE chamar de volta em um aplicativo.

O estado corrente do módulo é comutado por chamando AfxSetModuleState.Em sua maioria, você nunca irá lidar diretamente com a API.MFC, em muitos casos, irá chamá-lo para você (em WinMain, pontos de entrada OLE, AfxWndProc, etc.).Isso é concluído em qualquer componente que você escreve vinculando estaticamente em um especial WndProce um especial WinMain (ou DllMain) que sabe qual estado do módulo deve ser corrente. Você pode ver este código, observando DLLMODUL.CPP ou APPMODUL.CPP no diretório MFC\SRC.

É raro que você deseja conjunto o estado do módulo e, em seguida, não definida-lo novamente.Na maioria das vezes que você deseja "enviar" em seu próprio módulo estado que a corrente e, em seguida, depois de concluído, "pop" contexto original novamente.Para fazer isso, a macro AFX_MANAGE_STATE e a classe especial AFX_MAINTAIN_STATE.

CCmdTarget possui recursos especiais para oferecer suporte a alternância de estado do módulo. Em particular, um CCmdTarget é que a classe raiz usado para automação OLE e OLE COM pontos de entrada. Como qualquer Outros ponto de entrada expostos ao sistema, esses pontos de entrada devem conjunto o estado do módulo correto.Como o de um determinado CCmdTargetsaber o que o estado do módulo "correto" deve ser?A resposta é que ele "memoriza" qual é o estado do módulo "corrente" quando é construído, de modo que ele pode conjunto o estado corrente do módulo que "lembrada" valor quando for posterior chamado.sistema autônomo resultado, o módulo de estado que um determinado CCmdTarget objeto está associado com é o estado do módulo que era o corrente quando o objeto foi construído. Dê um exemplo simples de carregamento de um servidor INPROC, criação de um objeto e chamando seus métodos.

  1. A DLL é carregada por OLE usando LoadLibrary.

  2. RawDllMain é chamado pela primeira vez.Ele define o estado do módulo para o estado de módulo estático conhecido para a DLL.Para esta razão RawDllMain está vinculada estaticamente a DLL.

  3. O construtor para o fábrica de classes associado ao nosso objeto é chamado.COleObjectFactory é derivado de CCmdTarget e sistema autônomo um resultado, ele lembra ter em qual estado do módulo-w sistema autônomo instanciado. Isso é importante — quando o classe fábrica é solicitada a criar objetos, ele agora sabe qual estado do módulo para tornar corrente.

  4. DllGetClassObject é chamado para obter a fábrica de classes. MFC pesquisa a lista de fábrica de classes associada a este módulo e o retorna.

  5. COleObjectFactory::XClassFactory2::CreateInstance é chamado.Antes de criar o objeto e retorná-lo, essa função define o estado do módulo para o estado do módulo que era corrente na etapa 3 (aquele que era o corrente quando o COleObjectFactory foi instanciado). Isso é concluído dentro de METHOD_PROLOGUE.

  6. Quando o objeto é criado, é muito um CCmdTarget derivada e da mesma forma COleObjectFactory memorizadas qual estado do módulo estava ativo, aumenta esse novo objeto. Agora, o objeto sabe qual estado do módulo para comutador para sempre que for chamado.

  7. O cliente chama uma função sobre o OLE objeto COM it recebida do seu CoCreateInstance Chame. Quando o objeto é chamado usa METHOD_PROLOGUE Para comutador o módulo de estado como COleObjectFactory não.

sistema autônomo você pode ver, o estado do módulo é propagado de objeto ao objeto, sistema autônomo são criadas.É importante ter o estado do módulo conjunto apropriadamente.Se não for conjunto, seu objeto DLL ou COM pode interagir mal com um aplicativo MFC que está chamando, ou talvez não consiga encontrar seus próprios recursos ou pode falhar de outras maneiras péssima.

Observe que certos tipos de DLLs, especificamente "Extensão do MFC" DLLs não alternam o estado do módulo seusRawDllMain (na verdade, geralmente não ainda têm um RawDllMain).Isso ocorre porque eles se destinam a se comportar "sistema autônomo se" fosse realmente presentes no aplicativo que utiliza.Eles são muito mais uma parte do aplicativo que está sendo executado 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 pode não ser até mesmo um aplicativo MFC e então, não pode haver nenhum estado para modificar.Esse é o motivo de switching de estado do módulo foi inventado.

Funções exportadas de uma DLL, por exemplo, um que inicia uma caixa de diálogo em sua DLL, você precisará adicione o código a seguir ao início da função:

AFX_MANAGE_STATE(AfxGetStaticModuleState( ))

Isso alterna o estado corrente do módulo com o estado retornado de AfxGetStaticModuleState até o participante do escopo corrente.

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, na verdade, é armazenado na DLL.A causa é que informações de estado do módulo do MFC não foi alternadas, a AFX_MODULE_STATE macro. O identificador de recurso é recuperado do estado do 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_STATE nã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 fica automaticamente o estado do módulo anterior InitInstance e switches-lo novamente após InitInstance Retorna. O mesmo é verdadeiro para todos os manipuladores de MAP de mensagens.DLLs normais, na verdade, têm um procedimento de janela mestre especial que alterne automaticamente o estado do módulo antes de qualquer mensagem de roteamento.

Processo de dados locais

Processo de dados local não seria tais grande preocupação não fosse para a dificuldade de modelo do Win32s DLL.Win32s todas as DLLs compartilham seus dados global, mesmo quando carregado por vários aplicativos.Isso é muito diferente do modelo de dados Win32 DLL "real", onde cada DLL obtém uma cópia separada do seu espaço de dados em cada processo que conecta-se a DLL.Para adicionar a complexidade, sistema autônomo dados alocados no heap em uma DLL Win32s estão em fato processo específico (pelo menos tão longe conforme passa da 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 está em uma DLL e que a DLL é carregada por dois processos A e B (poderia, na verdade, ser duas instâncias do mesmo aplicativo).Um chamadas SetGlobalString("Hello from A"). sistema autônomo resultado, a memória é alocada para o CString dados no contexto do processo a. Lembre-se de que o CString propriamente dito é global e é visível para os dois A e B. Agora B chama GetGlobalString(sz, sizeof(sz)). B poderão ver os dados A configurada.Isso ocorre porque o Win32s não oferece nenhuma proteção entre processos, como Win32.Esse é o primeiro problema; em muitos casos não é desejável que um aplicativo afeta dados global são considerados como pertencer a um aplicativo diferente.

Há também problemas adicionais.Digamos que A agora é encerrado.Quando A é encerrado, a memória usada pelo ' strGlobal' seqüência de caracteres é disponibilizada para o sistema — ou seja, toda a memória alocada pelo processo A é liberada automaticamente pelo sistema operacional. Não é liberado porque o CString destruidor está sendo chamado; ele ainda não foi chamado ainda. Ele é liberado simplesmente porque o aplicativo que alocada a ele deixou a cena.Agora se B chamado GetGlobalString(sz, sizeof(sz)), ele não pode obter dados válido. Outros aplicativo pode ter usado que a memória para Outros coisa.

Existe claramente um problema.MFC 3.x utilizada uma técnica chamada armazenamento thread local (TLS).3.X MFC poderia alocar um índice TLS que em Win32s realmente funciona sistema autônomo um índice de armazenamento local de processo, mesmo que não é chamado e, em seguida, faria referência a todos sistema autônomo dados com base em desse índice TLS.Isso é semelhante ao índice TLS que foi usado para armazenar thread-dados locais no Win32 (consulte a seguir para obter mais informações sobre esse assunto).Isso causou cada DLL da MFC utilizar pelo menos dois índices TLS por processo.Quando você conta para o carregamento de várias OLE controle DLLs (OCXs), rapidamente executar fora de índices TLS (existem apenas 64 disponível).Além disso, a MFC tinha que colocar todos os dados em um único local, em uma única estrutura.Não era muito extensível e não era ideal com relação ao seu uso de índices TLS.

MFC 4.x soluciona isso com um conjunto de modelos de classe, você pode "quebrar" em torno de dados que devem ser um processo local.Por exemplo, foi possível corrigir o problema mencionado acima, 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.Em primeiro lugar, há uma camada na parte superior do Win32 TLS * APIs (TlsAlloc, TlsSetValue, TlsGetValue, etc.) apenas dois índices TLS por processo, não importa quantos DLLs, você tem que usar.Segundo, a CProcessLocal modelo é fornecido para acessar esses dados. Ela substitui o operador-> qual é o que permite que a sintaxe intuitiva que consulte acima.Todos os objetos que são delimitados por CProcessLocal deve ser derivado de CNoTrackObject. CNoTrackObject Fornece um alocador de nível inferior (LocalAlloc/LocalFree) e um destruidor virtual de modo que a MFC automaticamente pode destruir os objetos de processo local quando o processo é terminado.Esses objetos podem ter um destruidor personalizado se adicionais de limpeza é necessária.O exemplo acima não requer um, desde que o compilador gerará um destruidor padrão para destruir o incorporados CString objeto.

Há outras vantagens dessa abordagem interessantes.Não só são todos os CProcessLocal objetos destruídos automaticamente, até que eles sejam necessários não estão construídas. CProcessLocal::operator-> irá criar uma instância do objeto associado a primeira time é chamado e não mais cedo. No exemplo acima, isso significa que o ' strGlobal' seqüência de caracteres não será construída até a primeira vez SetGlobalString or GetGlobalString é chamado.Em alguns casos, isso pode ajudar a diminuir o time de inicialização da DLL.

Dados de locais de thread

Semelhante ao processar dados locais, dados de locais 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 esses dados.Isso pode muitas vezes ser usado no lugar de mecanismos de sincronização abrangentes.Se os dados não precisam ser compartilhado por vários threads, esses mecanismos podem ser caro e desnecessários.Suponhamos que tivéssemos um CString objeto (muito semelhante ao exemplo acima). Nós pode torná-lo de segmento local, encapsulá-la 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
      }
   }
}

If MakeRandomStringfoi chamado de dois segmentos diferentes, cada seria "embaralhar" a seqüência de caracteres de maneiras diferentes sem interferir com o Outros. 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 o CString endereço uma vez em vez de uma vez por iteração do loop. O código de loop poderia ter sido escrito com threadData->strThread todos os lugares 'str' for usado, mas o código seria muito mais lento em execução. É melhor armazenar em cache uma referência aos dados quando essas referências ocorrem em loops.

The CThreadLocal modelo de classe usa os mesmos mecanismos que CProcessLocal faz e as mesmas técnicas de implementação.

Consulte também

Outros recursos

Notas técnicas por número

Notas técnicas por categoria