TN058: Estado do módulo MFC implementação

Esta anotação técnica descreve a implementação do MFC "módulo estado" construções. Um entendimento da implementação de estado de módulo é crítico para usando o MFC compartilhada DLLs de uma DLL (ou OLE em processo servidor).

Antes de ler esta nota, por favor, consulte "Managing the dados da MFC módulos de estado" na Creating New documentos, Windows e vistas no Guia do programador do Visual C++. Este artigo contém informações de visão geral sobre este assunto e informações de utilização importantes.

Visão geral

Existem três tipos de informações de estado do MFC: Estado do módulo, estado do processo e Thread estado. Às vezes, esses tipos de estado podem ser combinados. Por exemplo, mapas de identificador do MFC são módulo local e segmento local. Isso permite que dois módulos diferentes ter mapas diferentes em cada um dos seus segmentos.

Estado do processo e Thread estado são semelhantes. Estes itens de dados são coisas que têm sido, tradicionalmente, variáveis globais, mas precisam ser específico para um determinado processo ou thread de suporte Win32s adequada ou para suporte a multithreading adequada. Que categoria se encaixa um item de dados fornecidos depende desse item e sua semântica desejada aos limites de processo e thread.

Estado do módulo é único em que ele pode conter um Estado verdadeiramente global ou Estado que é local de processo ou thread local. Também 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 chamado em um controle OLE ou DLL ou um controle OLE chamado voltar para um aplicativo.

O estado atual do módulo é comutado por chamado AfxSetModuleState. Na maior parte, você nunca irá lidar diretamente com a API. MFC, em muitos casos, vai chamá-lo para você (em WinMain, pontos de entrada OLE, AfxWndProc, etc.). Isso é feito em qualquer componente você escrever vinculando estaticamente em um especial WndProce um especial WinMain (ou DllMain) que sabe qual estado módulo deve ser atual. Você pode ver este código dando uma olhada em DLLMODUL.CPP ou APPMODUL.CPP no diretório MFC\SRC.

É raro que você deseja definir o estado de módulo e, em seguida, não defini-lo novamente. Na maioria das vezes que você quer "empurrar" seu próprio módulo de Estado como o actual e, em seguida, após você é feito, "pop" o contexto original de volta. Para isso, a macro AFX_MANAGE_STATE e classe especial AFX_MAINTAIN_STATE.

CCmdTarget tem características especiais de apoio à mudança de estado do módulo. Em particular, um CCmdTarget é que a classe de raiz usado para automação de OLE e OLE COM pontos de entrada. Como qualquer outro ponto de entrada expostos para o sistema, esses pontos de entrada devem definir o estado de módulo correcto. Como um determinado CCmdTarget sabe qual deve ser o estado do módulo "correto"? A resposta é que ele "lembra" o que é o estado "atual" do módulo quando ele é criado, tal que ele pode definir o estado atual do módulo para que "lembrado" valor quando é mais tarde chamado. Como resultado, o estado do módulo que está associado a um determinado objeto CCmdTarget é o estado do módulo que foi atual quando o objeto foi construído. Vamos dar um exemplo simples de carregar um servidor INPROC, criando um objeto e chamar seus métodos.

  1. O DLL é carregado pelo OLE usando LoadLibrary.

  2. Função RawDllMain é chamado primeiro. Ele define o estado do módulo para o estado do módulo estático conhecido para a DLL. Por esta razão função RawDllMain esteja vinculada estaticamente a DLL.

  3. O construtor para a fábrica de classe associado com nosso objeto é chamado. COleObjectFactory é derivado de CCmdTarget e como resultado, ela se lembra em qual estado de módulo foi instanciado. Isto é importante — quando o classe factory é solicitado para criar objetos, ele agora sabe qual estado de módulo para tornar atual.

  4. DllGetClassObject é chamado para obter a fábrica de classes. MFC procura na lista de fábrica de classe associada com este módulo e retorna-lo.

  5. 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 que estava atual na etapa 3 (aquele que era atual quando o COleObjectFactory foi instanciado). Isso é feito dentro de METHOD_PROLOGUE.

  6. Quando o objeto é criado, ele também é um derivado de CCmdTarget e da mesma forma COleObjectFactory lembrado qual estado de módulo estava ativo, faz assim este novo objeto. Agora o objeto sabe qual estado de módulo para mudar para sempre que é chamado.

  7. O cliente chama uma função no objeto OLE COM que ele recebeu do seu chamar CoCreateInstance . Quando o objeto é chamado ele usa METHOD_PROLOGUE para alternar o estado de módulo exatamente como COleObjectFactory.

Como você pode ver, o estado do módulo é propagado de objeto para objeto conforme eles são criados. É importante ter o estado de módulo definido adequadamente. Se ele não estiver definido, seu objeto COM ou DLL pode interagir mal com um aplicativo do MFC que está chamando, ou pode ser incapaz de encontrar seus próprios recursos ou pode falhar em outras maneiras miseráveis.

Nota que certos tipos de DLLs, especificamente "De extensão do MFC" DLLs não alternar o estado de módulo em sua função RawDllMain (na verdade, eles geralmente não têm sequer uma função RawDllMain). Isso é porque se destinam a comportar-se "como se" estivessem realmente presentes no aplicativo que usa-los. Eles são muito uma parte do aplicativo que está sendo executado e é sua intenção de modificar o estado global do aplicativo.

OLE controles e outras DLLs são muito diferentes. Eles não querem modificar o estado do aplicativo de chamada; o aplicativo que está chamando não pode mesmo ser um aplicativo do MFC e assim não pode haver nenhum Estado para modificar. Esta é a razão que o módulo de comutação de Estado foi inventado.

Para funções exportadas de uma DLL, tal como um que inicia uma caixa de diálogo em sua DLL, você precisará adicionar o código a seguir para o início da função:

AFX_MANAGE_STATE (AfxGetStaticModuleState ())

Isso alterna 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 não se utilizar a macro AFX_MODULE_STATE . 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 raiz é que informações de estado de módulo do MFC não foi mudadas, a macro AFX_MODULE_STATE . O identificador de recurso é recuperado de estado de módulo do MFC. Não alternar o estado de módulo faz com que o identificador de recurso errado para 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 mudará automaticamente o estado de módulo antes de InitInstance e, em seguida, os switches-lo de volta depois de InitInstance retorna. O mesmo é verdadeiro para todos os manipuladores de mapa de mensagem. DLLs regulares realmente têm um procedimento de janela mestre especial que alterne automaticamente entre o estado de módulo antes roteamento qualquer mensagem.

Processo Local de dados

Dados locais do processo não seria tão grande preocupação se não fosse a dificuldade do modelo Win32s DLL. No Win32s todas as DLLs de compartilham seus dados globais, mesmo quando carregado por Múltiplo aplicativos. Isso é totalmente diferente do modelo de dados Win32 DLL "real", onde cada DLL recebe uma cópia separada do seu espaço de dados em cada processo que atribui para a DLL. Para aumentar a complexidade, o dados alocados no heap em um DLL Win32s na verdade são processo específico (pelo menos tanto quanto vai de propriedade). Considere os seguintes dados e código:

estático CString I; / / no escopo do arquivo

dllexport void SetGlobalString(LPCTSTR lpsz)
{
   I = lpsz;
}

dllexport
privatevoid GetGlobalString (LPCTSTR lpsz, int cb)
{
   lstrcpyn (lpsz, I, cb);
}

Considere o que acontece se o código acima está em localizado em uma DLL e que DLL é carregado por dois processos a e B (que poderia, de facto, ser duas instâncias do mesmo aplicativo). Um chamadas SetGlobalString("Hello from A") . Como resultado, a memória é alocada para os dados de CString no contexto do processo r. Keep em mente que o CString propriamente dito é global e é visível tanto a e b. Agora b chama GetGlobalString(sz, sizeof(sz)) . B será capaz de ver os dados de um conjunto. Isso ocorre porque Win32s não oferece nenhuma proteção entre processos como o Win32. Assim que é o primeiro problema; em muitos casos não é desejável ter um aplicativo afetam dados globais que são considerados para ser possuído por um aplicativo diferente.

Mas espere — há mais problemas. Digamos que um agora sai. Quando a é encerrado, a memória usada pelo ' strGlobal ' Cadeia de caracteres é disponibilizada para o sistema — ou seja, toda a memória alocada por um processo é libertada automaticamente pelo sistema operacional. Ela não é liberada porque está sendo chamado o destruidor de CString ; Ele não foi chamado ainda. Ele é liberada simplesmente porque o aplicativo que alocou deixou a cena. Agora, se b chamado GetGlobalString(sz, sizeof(sz)) , ele não pode obter dados válidos. Algum outro aplicativo pode ter usado essa memória para outra coisa.

Claramente, há um problema aqui. 3. X MFC usado uma técnica chamada armazenamento thread local (TLS). MFC 3. x iria alocar um índice TLS que sob Win32s realmente funciona como um índice de armazenamento local do processo, mesmo que ele não é chamado que e, em seguida, consultaria todos os dados com base no índice TLS. Isso é semelhante ao índice TLS que foi usado para armazenar dados de thread-local em Win32 (consulte abaixo para obter mais informações sobre esse assunto). Isto causou cada DLL do MFC utilizar pelo menos dois índices TLS por processo. Quando você conta para carregar muitos controle DLLs OLE (ocx), rapidamente executar fora de índices TLS (existem apenas 64 disponível). Além disso, MFC teve de colocar todos os dados em um único lugar, em uma única estrutura. Não foi muito extensível e não foi ideal com relação ao uso de índices TLS.

4. X MFC resolve isso com um conjunto de modelos de classe você pode "quebrar" em torno dos dados que devem ser processo local. Por exemplo, o problema mencionado acima poderia ser corrigido por escrito:

struct CMyGlobalData: CNoTrackObject pública
{
   CString I;
};
CProcessLocallt;CMyGlobalData > globalData;

dllexport void SetGlobalString(LPCTSTR lpsz)
{
   globalData - > I = lpsz;
}

dllexport
privatevoid GetGlobalString (LPCTSTR lpsz, int cb)
{
   lstrcpyn (lpsz, globalData - > I, cb);
}

MFC implementa isso em duas etapas. Em primeiro lugar, há uma camada sobre o Win32 Tls * APIs (TlsAlloc, TlsSetValue, Tls&GetValue, etc.) que utilizam apenas dois índices TLS por processo, não importa quantas DLLs que você tem. Segundo, o CProcessLocal modelo é fornecido para acessar esses dados. Ele substitui operador-gt; o que é o que permite que a sintaxe intuitiva que você vê acima. Todos os objetos que são enrolados por CProcessLocal deve ser derivado de CNoTrackObject . CNoTrackObject fornece um alocador de nível inferior (LocalAlloc/LocalFree) e um destruidor virtual tais que MFC automaticamente pode destruir os objetos locais do processo quando o processo é encerrado. Esses objetos podem ter um destruidor personalizado se limpeza adicional é necessária. O exemplo acima não exige um, desde que o compilador irá gerar um destruidor padrão para destruir o objeto de CString incorporado.

Há outras vantagens interessantes para esta abordagem. Não só são todos CProcessLocal objetos destruídos automaticamente, eles não são criados até que forem necessários. CProcessLocal::operator-gt; instanciará o objeto associado a primeira vez que ele é chamado e não antes. No exemplo acima, isso significa que o ' str&Global ' Cadeia de caracteres não será construída até a primeira vez que é chamado de SetGlobalString ou GetGlobalString . Em alguns casos, isso pode ajudar a diminuir o tempo de inicialização 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 os dados. Isso pode muitas vezes ser usado no lugar de mecanismos de sincronização extensa. Se os dados não precisam ser compartilhado por vários threads, tais mecanismos podem ser caros e desnecessários. Suponha que nós tivemos um objeto de CString (muito semelhante ao exemplo acima). Podemos fazer thread local envolvendo-o com uma CThreadLocal modelo:

struct CMyThreadData: CNoTrackObject pública
{
   CString strThread;
};
CThreadLocallt;CMyThreadData > threadData;

privatevoid MakeRandomString()
{
   / / a tipo de shuffle cartão (não um grande)
   CString & str = threadData - > strThread;
   Str.Empty ();
   ao mesmo tempo (str.GetLength()! = 52)
   {
      TCHAR ch = Rand () % 52 + 1;
      se (str.Find(CH) < 0)
         Str + = ch; / / não encontrado, adicioná-lo
   }
}

Se MakeRandomString foi chamado de dois segmentos diferentes, cada um seria "shuffle" a Cadeia de caracteres em formas diferentes sem interferir uns com os outros. Isso é porque há na verdade um strThread instância por thread em vez de apenas uma instância global.

Observe como uma referência é usada para capturar o endereço CStrin&g uma vez em vez de uma vez por iteração do loop. O código do loop poderia ter sido escrito com threadData-gt;strThread em 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 tais referências ocorrem em loops.

O CThreadLocal modelo de classe utiliza os mesmos mecanismos que CProcessLocal faz e as mesmas técnicas de execução.

Técnico anotações por número |nbsp; &Notas técnicas por categoria

Index