Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Observação
A nota técnica a seguir não foi atualizada desde que foi incluída pela primeira vez na documentação on-line. Como resultado, alguns procedimentos e tópicos podem estar desatualizados ou incorretos. Para obter as informações mais recentes, recomenda-se que pesquise o tópico de interesse no índice de documentação online.
Esta nota técnica descreve a implementação de construções de "estado do módulo" do MFC. Uma compreensão da implementação do estado do módulo é crítica para usar as DLLs compartilhadas MFC de uma DLL (ou servidor OLE em processo).
Antes de ler esta nota, consulte "Gerenciando os dados de estado dos módulos MFC" em Criando novos documentos, janelas e modos de exibição. Este artigo contém informações importantes sobre o uso e informações gerais sobre este assunto.
Visão geral
Há três tipos de informações de estado MFC: Estado do módulo, Estado do processo e Estado do thread. Às vezes, esses tipos de estado podem ser combinados. Por exemplo, os mapas de identificador do MFC são tanto o módulo local quanto o thread local. Isso permite que dois módulos diferentes tenham mapas diferentes em cada um de seus threads.
O Estado do Processo e o Estado do Thread são semelhantes. Esses itens de dados são coisas que tradicionalmente são variáveis globais, mas precisam ser específicas para um determinado processo ou thread para suporte adequado a Win32s ou para suporte adequado a multithreading. A categoria em que um determinado item de dados se encaixa depende desse item e de sua semântica desejada em relação aos limites de processo e thread.
O Estado do Módulo é único porque pode conter um estado verdadeiramente global ou um estado que é local por processo ou local por thread. Além disso, pode ser trocado rapidamente.
Comutação de estado do módulo
Cada thread contém um ponteiro para o estado do módulo "atual" ou "ativo" (não surpreendentemente, o ponteiro faz parte do estado local do thread do MFC). Este ponteiro é alterado quando o thread de execução passa um limite de módulo, como uma aplicação chamando um controlo OLE ou DLL, ou um controlo OLE chamando de volta uma aplicação.
O estado atual do módulo é modificado chamando AfxSetModuleState. Na maioria das vezes, você nunca lidará diretamente com a API. MFC, em muitos casos, irá chamá-lo para você (em WinMain, OLE pontos de entrada, AfxWndProcetc.). Isto é feito em qualquer componente que se escreve através da ligação estática de um especial WndProc e um especial WinMain (ou DllMain) que sabe qual estado do módulo deve ser atual. Você pode ver esse código olhando para DLLMODUL. CPP ou APPMODUL. CPP no diretório MFC\SRC.
É raro que você queira definir o estado do módulo e, em seguida, não defini-lo de volta. Na maioria das vezes, pretendes "empurrar" o estado do teu próprio módulo para o topo e, de seguida, após terminares, "retirar" de volta o contexto original. Isso é feito pelo AFX_MANAGE_STATE macro e pela classe AFX_MAINTAIN_STATEespecial.
CCmdTarget tem características especiais para suportar a comutação de estado do módulo. Em particular, a CCmdTarget é a classe raiz usada para automação OLE e pontos de entrada OLE COM. Como qualquer outro ponto de entrada exposto ao sistema, esses pontos de entrada devem definir o estado correto do módulo. Como um dado CCmdTarget sabe qual deve ser o estado "correto" do módulo A resposta é que ele "lembra" qual é o estado do módulo "atual" quando ele é construído, de modo que ele pode definir o estado do módulo atual para esse valor "lembrado" quando ele é chamado posteriormente. Como resultado, o estado do módulo ao qual um determinado CCmdTarget objeto está associado é o estado do módulo que estava atual quando o objeto foi construído. Tomemos um exemplo simples de carregar um servidor INPROC, criar um objeto e chamar seus métodos.
A DLL é carregada pelo OLE usando
LoadLibrary.RawDllMainé chamado primeiro. Define o estado do módulo ao estado estático conhecido do módulo para a DLL. Por esta razãoRawDllMainestá estaticamente ligado à DLL.O construtor da classe factory associada ao nosso objeto é chamado.
COleObjectFactoryé derivado deCCmdTargete, como resultado, ele se lembra em qual estado do módulo ele foi instanciado. Isso é importante — agora, quando a fábrica de classes é solicitada a criar objetos, ela sabe qual estado do módulo deve ser tornado atual.DllGetClassObjecté chamado para obter a fábrica de classe. MFC pesquisa a lista de fábrica de classes associada a este módulo e a retorna.COleObjectFactory::XClassFactory2::CreateInstanceé invocado. Antes de criar o objeto e retorná-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 oCOleObjectFactoryfoi instanciado). Isso é feito dentro de METHOD_PROLOGUE.Quando o objeto é criado, ele também é um
CCmdTargetderivado e, da mesma forma, lembra-se que estado do módulo estava ativo, o mesmo se aplica a este novo objeto. Agora, o objeto sabe para qual estado do módulo alternar sempre que for chamado.O cliente chama uma função no objeto OLE COM que recebeu a partir da sua chamada
CoCreateInstance. Quando o objeto é chamado, ele usaMETHOD_PROLOGUEpara alternar o estado do módulo da mesma formaCOleObjectFactory.
Como você pode ver, o estado do módulo é propagado de objeto para objeto à medida que são criados. É importante ter o estado do módulo definido adequadamente. Se não estiver definido, seu objeto DLL ou COM pode interagir mal com um aplicativo MFC que está chamando-o, ou pode ser incapaz de encontrar seus próprios recursos, ou pode falhar de outras maneiras miseráveis.
Observe que certos tipos de DLLs, especificamente DLLs "MFC Extension", não alteram o estado do módulo em sua RawDllMain (na verdade, geralmente nem têm um RawDllMain). Isto porque se destinam a comportar-se "como se" estivessem realmente presentes na aplicação que os utiliza. Eles são uma parte integrante da aplicação que está a ser executada, e é sua intenção modificar o estado global dessa aplicação.
Controles OLE e outras DLLs são muito diferentes. Eles não querem modificar o estado do aplicativo chamador; o aplicativo que está chamando-os pode nem ser um aplicativo MFC e, portanto, pode não haver nenhum estado para modificar. Esta é a razão pela qual a mudança de estado do módulo foi inventada.
Para funções exportadas de uma DLL, como uma que inicia uma caixa de diálogo na DLL, você precisa adicionar o seguinte código ao início da função:
AFX_MANAGE_STATE(AfxGetStaticModuleState())
Isso troca o estado atual do módulo pelo estado retornado de AfxGetStaticModuleState até o final do escopo atual.
Problemas com recursos em DLLs ocorrerão se a macro AFX_MODULE_STATE não for 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 principal é que as informações de estado do módulo do MFC não foram atualizadas pela macro AFX_MODULE_STATE. O identificador de recurso é recuperado a partir do estado do módulo do MFC. Não alternar o estado do módulo faz com que o identificador de recurso errado seja usado.
AFX_MODULE_STATE não precisa ser colocado em todas as funções na DLL. Por exemplo, InitInstance pode ser chamado pelo código MFC no aplicativo sem AFX_MODULE_STATE porque a MFC muda automaticamente o estado do módulo antes de InitInstance, e, em seguida, alterna-o de volta após o retorno de InitInstance. O mesmo é verdadeiro para todos os manipuladores de mapa de mensagens. DLLs MFC regulares realmente têm um procedimento de janela mestra especial que alterna automaticamente o estado do módulo antes de rotear qualquer mensagem.
Processar dados locais
Processar dados locais não seria de grande preocupação se não fosse a dificuldade do modelo DLL do Win32s. No Win32s, todas as DLLs compartilham seus dados globais, mesmo quando carregadas por vários aplicativos. Isso é muito diferente do modelo de dados DLL Win32 "real", onde cada DLL obtém uma cópia separada de seu espaço de dados em cada processo que se anexa à DLL. Para aumentar a complexidade, os dados alocados no heap num DLL do Win32s são realmente específicos do processo (pelo menos no que se refere à 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 estiver localizado em uma DLL e essa DLL for carregada por dois processos A e B (poderia, na verdade, ser duas instâncias do mesmo aplicativo). Uma chamada SetGlobalString("Hello from A"). Como resultado, a memória é alocada para os CString dados no contexto do processo A. Tenha em mente que o CString próprio é global e é visível para A e B. Agora B chama GetGlobalString(sz, sizeof(sz)). B será capaz de ver os dados que A definiu. Isso ocorre porque o Win32s não oferece proteção entre processos como o Win32 faz. Esse é o primeiro problema; Em muitos casos, não é desejável que um aplicativo afete dados globais que são considerados de propriedade de um aplicativo diferente.
Há também problemas adicionais. Digamos que A agora sai. Quando A sai, a memória usada pela string 'strGlobal' é disponibilizada para o sistema — ou seja, toda a memória alocada pelo processo A é liberada automaticamente pelo sistema operacional. Não é libertado porque o CString destruidor está sendo chamado, ainda não foi chamado. É libertado simplesmente porque a aplicação que o alocou foi encerrada. Agora, se B ligou GetGlobalString(sz, sizeof(sz)), pode não obter dados válidos. Algum outro aplicativo pode ter usado essa memória para outra coisa.
É evidente que existe um problema. MFC 3.x usou uma técnica chamada thread-local storage (TLS). MFC 3.x alocaria um índice TLS que sob Win32s realmente atua como um índice de armazenamento local de processo, mesmo que não seja chamado assim e, em seguida, faria referência a todos os dados com base nesse índice TLS. Isso é semelhante ao índice TLS que foi usado para armazenar dados de thread-local no Win32 (veja abaixo para obter mais informações sobre esse assunto). Isso fez com que cada DLL MFC utilizasse pelo menos dois índices TLS por processo. Ao carregar muitas DLLs de controle OLE (OCXs), rapidamente fica sem índices TLS (há apenas 64 disponíveis). Além disso, a MFC teve que colocar todos esses dados em um só lugar, em uma única estrutura. Não era muito extensível e não era ideal no que diz respeito ao uso de índices TLS.
MFC 4.x aborda isso com um conjunto de modelos de classe que você pode "envolver" em torno dos dados que devem ser processados localmente. Por exemplo, o problema mencionado acima poderia ser corrigido por escrito:
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 sobre as APIs Win32 Tls* (TlsAlloc, TlsSetValue, TlsGetValue, etc.) que usam apenas dois índices TLS por processo, não importa quantas DLLs você tenha. Em segundo lugar, o CProcessLocal modelo é fornecido para acessar esses dados. Ele substitui o operador -> que é o que permite a sintaxe intuitiva que você vê acima. Todos os objetos encapsulados por CProcessLocal devem ser derivados de CNoTrackObject.
CNoTrackObject fornece um alocador de nível inferior (LocalAlloc/LocalFree) e um destruidor virtual tal que o MFC pode destruir automaticamente os objetos locais do processo quando o processo é encerrado. Esses objetos podem ter um destruidor personalizado se for necessária uma limpeza adicional. O exemplo acima não requer um, uma vez que o compilador irá gerar um destruidor padrão para destruir o objeto incorporado CString .
Há outras vantagens interessantes nesta abordagem. Não só todos os CProcessLocal objetos são destruídos automaticamente, como também não são construídos até que sejam necessários.
CProcessLocal::operator-> irá instanciar o objeto associado na primeira vez que for chamado, e não antes. No exemplo acima, isso significa que a string 'strGlobal' não será construída até a primeira vez SetGlobalString ou GetGlobalString é chamada. Em alguns casos, isso pode ajudar a diminuir o tempo de inicialização da DLL.
Dados Locais à Thread
Semelhante aos dados locais de processo, os dados locais de thread são usados quando os dados devem ser locais para uma determinada thread. Ou seja, você precisa de uma instância separada dos dados para cada thread que acessa esses dados. Isso pode muitas vezes ser usado em vez de mecanismos de sincronização extensos. Se os dados não precisam ser compartilhados por vários threads, tais mecanismos podem ser caros e desnecessários. Suponhamos que tínhamos um CString objeto (muito parecido com o exemplo acima). Podemos torná-lo thread local envolvendo-o 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 fosse chamado a partir de dois threads diferentes, cada um iria "embaralhar" a string de maneiras diferentes sem interferir com o outro. Isso ocorre porque há realmente uma strThread instância por thread em vez de apenas uma instância global.
Observe como uma referência é usada para capturar o endereço CString uma única vez, em vez de em cada iteração do loop de repetição. O código de loop poderia ter sido escrito usando threadData->strThread em todos os lugares em que 'str' é usado, mas o código seria muito mais lento na sua execução. É melhor armazenar em cache uma referência aos dados quando essas referências ocorrem em loops.
O CThreadLocal modelo de classe usa os mesmos mecanismos que CProcessLocal e as mesmas técnicas de implementação.