TN033: versão DLL do MFC

Esta observação descreve como você pode usar as bibliotecas de vínculo dinâmico compartilhadas MFCxx.DLL e MFCxxD.DLL (onde xx é o número da versão do MFC) com aplicativos do MFC e DLLs de extensão MFC. Para obter mais informações sobre DLLs MFC regulares, consulte Usar MFC como parte de uma DLL.

Esta nota técnica aborda três aspectos das DLLs. Os dois últimos são para os usuários mais avançados:

Se você estiver interessado em compilar uma DLL usando MFC que possa ser usada com aplicativos não MFC (conhecidos como DLL regular do MFC), consulte a Nota Técnica 11.

Visão geral do suporte MFCxx.DLL: terminologia e arquivos

DLL regular do MFC: você usa uma DLL regular do MFC para compilar uma DLL autônoma usando algumas das classes do MFC. As interfaces entre o limite de Aplicativo/DLL são interfaces "C" e o aplicativo cliente não precisa ser um aplicativo do MFC.

DLLs regulares do MFC são a versão de DLLs com suporte no MFC 1.0. Eles estão descritos na Nota Técnica 11 e no exemplo DLLScreenCap de Conceitos Avançados do MFC.

Observação

A partir da versão 4.0 do Visual C++, o termo USRDLL é obsoleto e foi substituído por uma DLL regular do MFC que é vinculada estaticamente ao MFC. Você também pode compilar uma DLL regular do MFC que se vincula dinamicamente ao MFC.

O MFC 3.0 (e acima) dá suporte a DLLs regulares do MFC com todas as novas funcionalidades, incluindo as classes OLE e Banco de Dados.

AFXDLL: também conhecida como a versão compartilhada das bibliotecas do MFC. É o novo suporte de DLL adicionado ao MFC 2.0. A própria biblioteca do MFC está em várias DLLs (descritas abaixo). Um aplicativo cliente ou DLL vincula dinamicamente as DLLs necessárias. Interfaces entre o limite de aplicativo/DLL são interfaces de classe C++/MFC. O aplicativo cliente DEVE ser um aplicativo do MFC. Essa DLL dá suporte a todas as funcionalidades do MFC 3.0 (exceção: NÃO há suporte para UNICODE para as classes de banco de dados).

Observação

A partir da versão 4.0 do Visual C++, esse tipo de DLL é chamado de "DLL de extensão".

Esta observação usará MFCxx.DLL para fazer referência a todo o conjunto de DLL do MFC, que inclui:

  • Depuração: MFCxxD.DLL (combinado) e MFCSxxD.LIB (estático).

  • Liberação: MFCxx.DLL (combinado) e MFCSxx.LIB (estático).

  • Depuração Unicode: MFCxxUD.DLL (combinado) e MFCSxxD.LIB (estático).

  • Liberação Unicode: MFCxxU.DLL (combinado) e MFCSxxU.LIB (estático).

Observação

As bibliotecas MFCSxx[U][D].LIB são usadas em conjunto com as DLLs compartilhadas do MFC. Essas bibliotecas contêm código que deve ser vinculado estaticamente ao aplicativo ou à DLL.

Um aplicativo é vinculado às bibliotecas de importação correspondentes:

  • Depurar: MFCxxD.LIB

  • Versão: MFCxx.LIB

  • Depuração Unicode: MFCxxUD.LIB

  • Liberação Unicode: MFCxxU.LIB

Uma DLL de extensão do MFC é uma DLL que estende MFCxx.DLL (ou as outras DLLs compartilhadas do MFC). Aqui, a arquitetura de componente do MFC entra em ação. Se você derivar uma classe útil de uma classe MFC ou criar outro kit de ferramentas semelhante a MFC, poderá colocá-la em uma DLL. Sua DLL usa MFCxx.DLL, assim como o aplicativo cliente final. Uma DLL de extensão do MFC permite classes folha reutilizáveis, classes base reutilizáveis e classes de documento e exibição reutilizável.

Prós e contras

Por que você deve usar a versão compartilhada do MFC

  • O uso da biblioteca compartilhada pode resultar em aplicativos menores. (Um aplicativo mínimo que usa a maior parte da biblioteca do MFC é menor que 10 mil).

  • A versão compartilhada do MFC dá suporte a DLLs de extensão do MFC e DLLs regulares do MFC.

  • É mais rápido criar um aplicativo que usa as bibliotecas do MFC compartilhadas do que um aplicativo do MFC vinculado estaticamente. Isso ocorre porque não é necessário vincular o MFC. Isso é especialmente verdadeiro em builds DEBUG em que o vinculador deve compactar as informações de depuração. Quando o aplicativo é vinculado a uma DLL que já contém as informações de depuração, há menos informações de depuração para serem compactadas.

Por que você não deve usar a versão compartilhada do MFC:

  • O envio de um aplicativo que usa a biblioteca compartilhada requer que você envie MFCxx.DLL e outras bibliotecas com seu programa. MFCxx.DLL pode ser redistribuída livremente como diversas DLLs, mas você ainda deverá instalar a DLL em seu programa de instalação. Além disso, você precisará enviar as outras bibliotecas redistribuíveis usadas pelo programa e pelas DLLs do MFC.

Como gravar uma DLL de extensão do MFC

Uma DLL de extensão do MFC é uma DLL que contém classes e funções para expandir a funcionalidade das classes do MFC. Uma DLL de extensão do MFC usa as DLLs do MFC compartilhadas da mesma forma que um aplicativo as usa, com algumas considerações adicionais:

  • O processo de build é semelhante à compilação de um aplicativo que usa as bibliotecas do MFC compartilhadas com algumas opções adicionais de compilador e vinculador.

  • Uma DLL de extensão do MFC não tem uma classe derivada de CWinApp.

  • Uma DLL de extensão do MFC deve fornecer uma DllMain especial. O AppWizard fornece uma função DllMain que você pode modificar.

  • Uma DLL de extensão do MFC normalmente fornece uma rotina de inicialização para criar uma CDynLinkLibrary, se a DLL de extensão do MFC exportar CRuntimeClass tipos ou recursos para o aplicativo. Uma classe derivada de CDynLinkLibrary pode ser usada se os dados por aplicativo precisarem ser mantidos pela DLL de extensão do MFC.

Essas considerações estão descritas abaixo de forma detalhada. Consulte também o exemplo DLLHUSK de Conceitos Avançados do MFC. Ele mostra como:

  • Compile um aplicativo usando as bibliotecas compartilhadas. (DLLHUSK.EXE é um aplicativo do MFC que se vincula dinamicamente às bibliotecas do MFC e outras DLLs.)

  • Compile uma DLL de extensão do MFC. (Ele mostra como sinalizadores especiais, como _AFXEXT são usados ao criar uma DLL de extensão do MFC.)

  • Compile dois exemplos de DLLs de extensão do MFC. Uma mostra a estrutura básica de uma DLL de extensão do MFC com exportações limitadas (TESTDLL1) e a outra mostra a exportação de uma interface de classe inteira (TESTDLL2).

O aplicativo cliente e quaisquer DLLs de extensão da MFC devem usar a mesma versão de MFCxx.DLL. Siga as convenções das DLLs do MFC e forneça uma versão de depuração e liberação (/release) de sua DLL de extensão do MFC. Essa prática permite que os programas cliente compilem versões de depuração e liberação de seus aplicativos e as vinculem à versão de depuração ou liberação apropriada de todas as DLLs.

Observação

Como problemas de exportação e desconfiguração de nome C++, a lista de exportação de uma DLL de extensão do MFC pode ser diferente entre as versões de depuração e versão da mesma DLL e DLLs para plataformas diferentes. A liberação MFCxx.DLL tem cerca de 2.000 pontos de entrada exportados; a depuração MFCxxD.DLL tem cerca de 3.000 pontos de entrada exportados.

Anotação rápida sobre o gerenciamento de memória

A seção intitulada "Gerenciamento de Memória", próxima ao final desta nota técnica, descreve a implementação da MFCxx.DLL com a versão compartilhada do MFC. As informações que você precisa saber para implementar apenas uma DLL de extensão do MFC estão descritas aqui.

MFCxx.DLL e todas as DLLs de extensão do MFC carregadas no espaço de endereço de um aplicativo cliente usarão o mesmo alocador de memória, o carregamento de recursos e outros estados "globais" do MFC como se estivessem no mesmo aplicativo. Isso é significativo porque as bibliotecas DLL não MFC e as DLLs regulares do MFC que se vinculam estaticamente ao MFC fazem exatamente o oposto: cada DLL aloca fora de seu próprio pool de memória.

Se uma DLL de extensão do MFC alocar memória, essa memória poderá se misturar livremente com qualquer outro objeto alocado pelo aplicativo. Além disso, se um aplicativo que usa as bibliotecas do MFC compartilhadas falhar, o sistema operacional manterá a integridade de qualquer outro aplicativo do MFC que compartilhe a DLL.

Da mesma forma, outros estados "globais" do MFC, como o arquivo executável atual do qual os recursos devem ser carregados, também são compartilhados entre o aplicativo cliente, todas as DLLs de extensão do MFC e MFCxx.DLL.

Como compilar uma DLL de extensão do MFC

Você pode usar o AppWizard para criar um projeto de DLL de extensão do MFC e ele gera automaticamente as configurações apropriadas do compilador e do vinculador. Ele também gera uma função DllMain que você pode modificar.

Se você estiver convertendo um projeto existente em uma DLL de extensão do MFC, comece com as configurações padrão criadas usando a versão compartilhada do MFC. Depois, faça as seguintes alterações:

  • Adicione /D_AFXEXT aos sinalizadores do compilador. No diálogo Propriedades do Projeto, selecione a categoria C/C++>Pré-processador. Adicione _AFXEXT ao campo Definir Macros, separando cada um dos itens com ponto e vírgula.

  • Remova a opção de compilador /Gy. No diálogo Propriedades do Projeto, selecione a categoria C/C++>Geração de código. Verifique se a propriedade Habilitar Vinculação do Nível de Função não está habilitada. Essa configuração facilita a exportação de classes, pois o vinculador não removerá funções não referenciadas. Se o projeto original tiver criado uma DLL regular do MFC vinculada estaticamente ao MFC, altere a opção /MT (ou /MTd) do compilador para /MD (ou /MDd).

  • Crie uma biblioteca de exportação com a opção /DLL para LINK. Essa opção é definida quando você cria um novo destino e especifica a biblioteca de vínculo dinâmico do Win32 como o tipo de destino.

Alteração dos arquivos de cabeçalho

O objetivo usual de uma DLL de extensão do MFC é exportar algumas funcionalidades comuns para um ou mais aplicativos que podem usar essa funcionalidade. Essencialmente, a DLL exporta classes e funções globais para uso pelos aplicativos cliente.

Para garantir que cada função membro seja marcada para importação ou exportação conforme apropriado, use as declarações especiais __declspec(dllexport) e __declspec(dllimport). Quando os aplicativos cliente usam suas classes, você deseja que eles sejam declarados como __declspec(dllimport). Quando a DLL da extensão do MFC for criada, as funções deverão ser declaradas como __declspec(dllexport). A DLL criada também deve exportar as funções para que os programas cliente possam ser associados a elas no tempo de carregamento.

Para exportar toda a classe, use AFX_EXT_CLASS na definição de classe. A estrutura define essa macro como __declspec(dllexport) quando _AFXDLL e _AFXEXT são definidos, mas a define como __declspec(dllimport) quando _AFXEXT não é definido. _AFXEXT só é definido ao criar sua DLL de extensão do MFC. Por exemplo:

class AFX_EXT_CLASS CExampleExport : public CObject
{ /* ... class definition ... */ };

Sem exportação da classe inteira

Às vezes, talvez você queira exportar apenas os membros individuais necessários da sua classe. Por exemplo, se você exportar uma classe derivada de CDialog, talvez seja necessário exportar apenas o construtor e a chamada DoModal. Você pode exportar esses membros usando o arquivo DEF da DLL, mas também pode usar AFX_EXT_CLASS da mesma maneira nos membros individuais necessários para exportar.

Por exemplo:

class CExampleDialog : public CDialog
{
public:
    AFX_EXT_CLASS CExampleDialog();
    AFX_EXT_CLASS int DoModal();
    // rest of class definition
    // ...
};

Ao fazer isso, você pode ter um problema adicional porque não exporta todos os membros da classe. O problema está na maneira como as macros do MFC funcionam. Várias macros auxiliares do MFC realmente declaram ou definem membros de dados. Sua DLL também precisa exportar esses membros de dados.

Por exemplo, a macro DECLARE_DYNAMIC é definida da seguinte maneira ao compilar uma DLL de extensão do MFC:

#define DECLARE_DYNAMIC(class_name) \
protected: \
    static CRuntimeClass* PASCAL _GetBaseClass(); \
    public: \
    static AFX_DATA CRuntimeClass class##class_name; \
    virtual CRuntimeClass* GetRuntimeClass() const; \

A linha que começa com static AFX_DATA declara um objeto estático dentro de sua classe. Para exportar essa classe corretamente e acessar as informações de runtime de um EXE de cliente, é necessário exportar esse objeto estático. Como o objeto estático é declarado com o modificador AFX_DATA, você só precisa definir AFX_DATA como __declspec(dllexport) quando compila sua DLL. Defina como __declspec(dllimport) ao compilar o executável do cliente.

Conforme discutido acima, AFX_EXT_CLASS já está definido dessa forma. Você só precisa redefinir AFX_DATA para ser igual a AFX_EXT_CLASS em torno de sua definição de classe.

Por exemplo:

#undef  AFX_DATA
#define AFX_DATA AFX_EXT_CLASS
class CExampleView : public CView
{
    DECLARE_DYNAMIC()
    // ... class definition ...
};
#undef  AFX_DATA
#define AFX_DATA

O MFC sempre usa o símbolo AFX_DATA em itens de dados que define dentro de suas macros, portanto, essa técnica funcionará para todos esses cenários. Por exemplo, ele funcionará para DECLARE_MESSAGE_MAP.

Observação

Se você estiver exportando toda a classe em vez de membros selecionados da classe, os membros de dados estáticos serão exportados automaticamente.

Você pode usar a mesma técnica para exportar automaticamente o operador de extração CArchive para classes que usam as macros DECLARE_SERIAL e IMPLEMENT_SERIAL. Exporte o operador de arquivo agrupando as declarações de classe (localizadas no arquivo de cabeçalho) com o seguinte código:

#undef AFX_API
#define AFX_API AFX_EXT_CLASS

/* your class declarations here */

#undef AFX_API
#define AFX_API

Limitações de _AFXEXT

Você pode usar o símbolo de pré-processador _AFXEXT para suas DLLs de extensão do MFC, desde que não tenha várias camadas de DLLs de extensão do MFC. Se você tiver DLLs de extensão do MFC que chamam ou derivam de classes em suas próprias DLLs de extensão do MFC, que, então, derivam das classes do MFC, você deve usar seu próprio símbolo de pré-processador para evitar ambiguidade.

O problema é que, no Win32, você deve declarar explicitamente todos os dados como __declspec(dllexport) para exportá-los de uma DLL e __declspec(dllimport) para importá-los de uma DLL. Quando você define _AFXEXT, os cabeçalhos do MFC garantem que AFX_EXT_CLASS seja definido corretamente.

Quando você tem várias camadas, um símbolo como AFX_EXT_CLASS não é suficiente: uma DLL de extensão do MFC pode exportar suas próprias classes e também importar outras classes de outra DLL de extensão do MFC. Para lidar com esse problema, use um símbolo de pré-processador especial que indica que você está compilando a própria DLL, em vez de usar a DLL. Por exemplo, imagine duas DLLs de extensão do MFC A.DLL e B.DLL. Cada uma delas exporta algumas classes em A.H e B.H, respectivamente. B.DLL usa as classes de A.DLL. Os arquivos de cabeçalho devem ser parecidos com este:

/* A.H */
#ifdef A_IMPL
    #define CLASS_DECL_A   __declspec(dllexport)
#else
    #define CLASS_DECL_A   __declspec(dllimport)
#endif

class CLASS_DECL_A CExampleA : public CObject
{ /* ... class definition ... */ };

/* B.H */
#ifdef B_IMPL
    #define CLASS_DECL_B   __declspec(dllexport)
#else
    #define CLASS_DECL_B   __declspec(dllimport)
#endif

class CLASS_DECL_B CExampleB : public CExampleA
{ /* ... class definition ... */ };

Quando A.DLL é compilado, ele é compilado com /DA_IMPL e quando B.DLL é compilado, ele é compilado com /DB_IMPL. Ao usar símbolos separados para cada DLL, CExampleB é exportado e CExampleA importado ao compilar B.DLL. CExampleA é exportado ao compilar A.DLL e importado quando usado por B.DLL ou outro cliente.

Esse tipo de disposição em camadas não pode ser feito ao usar os símbolos internos do pré-processador AFX_EXT_CLASS e _AFXEXT. A técnica descrita acima resolve esse problema da mesma forma que o MFC. O MFC usa essa técnica ao criar suas DLLs de extensão do MFC de OLE, Banco de Dados e Rede.

Ainda sem exportação da classe inteira

Você também terá que tomar cuidado quando não estiver exportando uma classe inteira. Verifique se os itens de dados necessários criados pelas macros do MFC são exportados corretamente. Você pode fazer isso redefinindo AFX_DATA para a macro de sua classe específica. Redefina-o sempre que você não estiver exportando a classe inteira.

Por exemplo:

// A.H
#ifdef A_IMPL
    #define CLASS_DECL_A  _declspec(dllexport)
#else
    #define CLASS_DECL_A  _declspec(dllimport)
#endif

#undef  AFX_DATA
#define AFX_DATA CLASS_DECL_A

class CExampleA : public CObject
{
    DECLARE_DYNAMIC()
    CLASS_DECL_A int SomeFunction();
    // class definition
    // ...
};

#undef AFX_DATA
#define AFX_DATA

DllMain

Aqui está o código que você deve colocar em seu arquivo de origem principal para sua DLL de extensão do MFC. Ele deve vir depois que o padrão incluir. Quando você usa o AppWizard para criar arquivos de início para uma DLL de extensão do MFC, ele fornece um DllMain para você.

#include "afxdllx.h"

static AFX_EXTENSION_MODULE extensionDLL;

extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID)
{
   if (dwReason == DLL_PROCESS_ATTACH)
   {
      // MFC extension DLL one-time initialization
      if (!AfxInitExtensionModule(
             extensionDLL, hInstance))
         return 0;

      // TODO: perform other initialization tasks here
   }
   else if (dwReason == DLL_PROCESS_DETACH)
   {
      // MFC extension DLL per-process termination
      AfxTermExtensionModule(extensionDLL);

      // TODO: perform other cleanup tasks here
   }
   return 1;   // ok
}

A chamada para AfxInitExtensionModule captura as classes de runtime do módulo (CRuntimeClass estruturas) e suas fábricas de objetos (COleObjectFactory objetos) para uso posterior quando o objeto CDynLinkLibrary é criado. A chamada (opcional) para AfxTermExtensionModule permite que o MFC limpe a DLL de extensão de MFC quando cada processo é desanexado (o que acontece quando o processo é encerrado ou quando a DLL é descarregada por uma chamada FreeLibrary) da DLL de extensão do MFC. Como a maioria das DLLs de extensão de MFC não são carregadas dinamicamente (normalmente, elas são vinculadas por meio de suas bibliotecas de importação), a chamada para AfxTermExtensionModule geralmente não é necessária.

Se o aplicativo carregar e liberar DLLs de extensão de MFC dinamicamente, certifique-se de chamar AfxTermExtensionModule, conforme mostrado acima. Certifique-se também de usar AfxLoadLibrary e AfxFreeLibrary (em vez das funções LoadLibrary e FreeLibrary do Win32) se o aplicativo usar vários threads ou se ele carregar dinamicamente uma DLL de extensão MFC. Usar AfxLoadLibrary e AfxFreeLibrary garante que o código de inicialização e desligamento executado quando a DLL da extensão MFC é carregada e descarregada não corrompa o estado MFC global.

O arquivo de cabeçalho AFXDLLX.H contém definições especiais para estruturas usadas em DLLs de extensão MFC, como a definição de AFX_EXTENSION_MODULE e CDynLinkLibrary.

O extensionDLL global deve ser declarado conforme mostrado. Ao contrário da versão de 16 bits do MFC, você pode alocar memória e chamar funções do MFC durante esse tempo, já que MFCxx.DLL é totalmente inicializado quando DllMain chamado.

Compartilhando recursos e classes

As DLLs de extensão do MFC simples precisam exportar apenas algumas funções de baixa largura de banda para o aplicativo cliente e nada mais. Mais DLLs intensivas de interface do usuário podem querer exportar recursos e classes C++ para o aplicativo cliente.

A exportação de recursos é feita por meio de uma lista de recursos. Em cada aplicativo há uma lista vinculada de objetos CDynLinkLibrary. Ao procurar um recurso, a maioria das implementações padrão do MFC que carregam recursos olham primeiro para o módulo de recurso atual (AfxGetResourceHandle) e, se não for encontrado, navegue na lista de objetos CDynLinkLibrary tentando carregar o recurso solicitado.

A criação dinâmica de objetos C++ dado um nome de classe C++ é semelhante. O mecanismo de desserialização do objeto do MFC precisa ter todos os objetos CRuntimeClass registrados para que ele possa ser reconstruído através da criação dinâmica de objeto C++ do tipo necessário com base no que foi armazenado anteriormente.

Se você quiser que o aplicativo cliente use classes em sua DLL de extensão do MFC que são DECLARE_SERIAL, então você precisará exportar suas classes para torná-las visíveis para o aplicativo cliente. Isso também é feito navegando pela lista CDynLinkLibrary.

No exemplo DLLHUSK de Conceitos Avançados do MFC, a lista é semelhante a:

head ->   DLLHUSK.EXE   - or - DLLHUSK.EXE
               |                    |
          TESTDLL2.DLL         TESTDLL2.DLL
               |                    |
          TESTDLL1.DLL         TESTDLL1.DLL
               |                    |
               |                    |
           MFC90D.DLL           MFC90.DLL

A entrada MFCxx.DLL geralmente é a última na lista de recursos e classes. MFCxx.DLL inclui todos os recursos MFC padrão, incluindo cadeias de caracteres de prompt para todas as IDs de comando padrão. Colocá-lo no final da lista permite que as DLLs e o próprio aplicativo cliente confiem nos recursos compartilhados em MFCxx.DLL, em vez de terem suas próprias cópias.

Mesclar os recursos e os nomes de classe de todas as DLLs no espaço de nome do aplicativo cliente tem a desvantagem de exigir que você tenha cuidado com a escolha das IDs ou nomes. Você pode desabilitar esse recurso não exportando seus recursos ou um objeto CDynLinkLibrary para o aplicativo cliente. O exemplo DLLHUSK gerencia o espaço de nome de recurso compartilhado ao usar vários arquivos de cabeçalho. Consulte a Nota Técnica 35 para obter mais dicas sobre como usar arquivos de recursos compartilhados.

Inicializando a DLL

Conforme mencionado acima, você geralmente deseja criar um objeto CDynLinkLibrary para exportar seus recursos e classes para o aplicativo cliente. Você precisará fornecer um ponto de entrada exportado para inicializar a DLL. Minimamente, é uma rotina void que não usa argumentos e não retorna nada, mas pode ser qualquer coisa que você gosta.

Cada aplicativo cliente que deseja usar a DLL deve chamar essa rotina de inicialização se você usar essa abordagem. Você também pode alocar esse objeto CDynLinkLibrary no DllMain logo após chamar AfxInitExtensionModule.

A rotina de inicialização deve criar um objeto CDynLinkLibrary no heap do aplicativo atual, conectado às informações de DLL da extensão do MFC. Isso pode ser feito definindo uma função como esta:

extern "C" extern void WINAPI InitXxxDLL()
{
    new CDynLinkLibrary(extensionDLL);
}

O nome de rotina, InitXxxDLL neste exemplo, pode ser o que você quiser. Não precisa ser extern "C", mas facilita a manutenção da lista de exportação.

Observação

Se você usar a DLL de extensão do MFC de uma DLL regular do MFC, deverá exportar essa função de inicialização. Essa função deve ser chamada da DLL regular do MFC antes de usar quaisquer classes ou recursos de DLL de extensão do MFC.

Exportando entradas

A maneira simples de exportar suas classes é usar __declspec(dllimport) e __declspec(dllexport) em cada classe e função global que você deseja exportar. É muito mais fácil, mas menos eficiente do que nomear cada ponto de entrada em um arquivo DEF, conforme descrito abaixo. Isso ocorre porque você tem menos controle sobre quais funções são exportadas. E você não pode exportar as funções por ordinal. TESTDLL1 e TESTDLL2 usam esse método para exportar suas entradas.

Um método mais eficiente é exportar cada entrada nomeando-a no arquivo DEF. Esse método é usado por MFCxx.DLL. Como estamos exportando seletivamente de nossa DLL, devemos decidir quais interfaces específicas desejamos exportar. É difícil, pois você deve especificar os nomes danificados para o vinculador na forma de entradas no arquivo DEF. Não exporte nenhuma classe C++ a menos que você realmente precise ter um link simbólico para ela.

Se você já tentou exportar classes C++ com um arquivo DEF, talvez queira desenvolver uma ferramenta para gerar essa lista automaticamente. Isso pode ser feito usando um processo de link em dois estágios. Vincule sua DLL uma vez sem exportações e permita que o vinculador gere um arquivo MAP. O arquivo MAP contém uma lista de funções que devem ser exportadas. Com algumas reorganizações, você pode usá-la para gerar as entradas EXPORT para o arquivo DEF. A lista de exportação para MFCxx.DLL e as DLLs de extensão do MFC do OLE e do Banco de Dados, vários milhares em número, foram geradas com esse processo (embora não seja totalmente automático e exija um ajuste manual de vez em quando).

CWinApp vs. CDynLinkLibrary

Uma DLL de extensão MFC não tem um objeto derivado de CWinApp próprio. Em vez disso, ele deve funcionar com o objeto derivado de CWinApp do aplicativo cliente. Isso significa que o aplicativo cliente possui a bomba de mensagem principal, o loop ocioso, etc.

Se a DLL da extensão do MFC precisar manter dados extras para cada aplicativo, você poderá derivar uma nova classe de CDynLinkLibrary e criá-la na rotina InitXxxDLL descrita acima. Ao ser executada, a DLL pode verificar a lista de objetos CDynLinkLibrary do aplicativo atual para localizar a DLL de extensão do MFC específica.

Usando recursos na implementação de DLL

Conforme mencionado acima, a carga de recursos padrão percorrerá a lista de objetos CDynLinkLibrary que procuram o primeiro EXE ou DLL com o recurso solicitado. Todas as APIs MFC e todo o código interno usam AfxFindResourceHandle para percorrer a lista de recursos para localizar qualquer recurso, independentemente de onde ele esteja localizado.

Se você quiser carregar apenas recursos de um local específico, use as APIs AfxGetResourceHandle e AfxSetResourceHandle para salvar o identificador antigo e definir o novo identificador. Confirme se restaura o identificador de recurso antigo antes de retornar ao aplicativo cliente. O exemplo TESTDLL2 usa essa abordagem para carregar um menu explicitamente.

A navegação pela lista tem algumas desvantagens: é um pouco mais lenta e requer o gerenciamento de intervalos de ID de recursos. Ele tem a vantagem de que um aplicativo cliente que é vinculado a várias DLLs de extensão do MFC pode usar qualquer recurso fornecido por DLL sem precisar especificar o identificador da instância de DLL. AfxFindResourceHandle é uma API usada para percorrer a lista de recursos para procurar uma determinada correspondência. O nome e o tipo de um recurso são usados e retornam o identificador de recurso onde o recurso é encontrado pela primeira vez ou NULL.

Gravação de um aplicativo que usa a versão de DLL

Requisitos do aplicativo

Um aplicativo que usa a versão compartilhada do MFC deve seguir algumas regras básicas:

  • Ele deve ter um objeto CWinApp e seguir as regras padrão de uma bomba de mensagens.

  • Ele deve ser compilado com um conjunto de sinalizadores de compilador necessários (veja abaixo).

  • Ele deve vincular-se às bibliotecas de importação do MFCxx. Ao definir os sinalizadores do compilador necessários, os cabeçalhos do MFC determinam no tempo de vinculação à qual biblioteca o aplicativo deve vincular.

  • Para executar o executável, MFCxx.DLL deve estar no caminho ou no diretório do sistema do Windows.

Compilando com o Ambiente de Desenvolvimento

Se você estiver usando o makefile interno com a maioria dos padrões, será possível alterar facilmente o projeto para compilar a versão de DLL.

A etapa a seguir pressupõe que você tenha um aplicativo do MFC funcionando corretamente vinculado a NAFXCWD.LIB (para depuração) e NAFXCW.LIB (para liberação) e deseja convertê-lo para usar a versão compartilhada da biblioteca do MFC. Você está executando o ambiente do Visual Studio e tem um arquivo de projeto interno.

  1. No menu Projetos, selecione Propriedades. Na página Geral em Padrões do Projeto, defina Microsoft Foundation Classes como Use MFC em uma DLL Compartilhada (MFCxx(d).dll).

Compilando com NMAKE

Se você estiver usando o recurso de makefile externo do compilador ou estiver usando o NMAKE diretamente, será necessário editar o makefile para dar suporte às opções necessárias de compilador e vinculador.

Sinalizadores do compilador necessários:

  • /D_AFXDLL /MD /D_AFXDLL

O simbolo _AFXDLL deve ser definido para os cabeçalhos MFC padrão.

  • /MD O aplicativo deve usar a versão DLL da biblioteca de runtime do C.

Todos os outros sinalizadores do compilador seguem os padrões do MFC (por exemplo, _DEBUG para depuração).

Edite a lista de bibliotecas do vinculador. Altere NAFXCWD.LIB para MFCxxD.LIB e NAFXCW.LIB para MFCxx.LIB. Substitua LIBC.LIB por MSVCRT.LIB. Assim como acontece com qualquer outra biblioteca do MFC, é importante que MFCxxD.LIB seja colocado antes de qualquer biblioteca de runtime do C.

Opcionalmente, adicione /D_AFXDLL às opções do compilador de recursos de liberação e depuração (aquela que realmente compila os recursos com /R). Essa opção torna o executável final menor compartilhando os recursos presentes nas DLLs do MFC.

Uma recompilação completa é necessária depois que essas alterações forem feitas.

Construindo as amostras

A maioria dos programas de exemplo do MFC pode ser criada a partir do Visual C++ ou de um MAKEFILE compatível com NMAKE compartilhado da linha de comando.

Para converter qualquer um desses exemplos para usar MFCxx.DLL, você pode carregar o arquivo MAK no Visual C++ e definir as opções do Projeto, conforme descrito acima. Se você estiver usando o build do NMAKE, poderá especificar AFXDLL=1 na linha de comando NMAKE e que compilará o exemplo usando as bibliotecas do MFC compartilhadas.

O DLLHUSK de exemplo de Conceitos Avançados do MFC é criado com a versão DLL do MFC. Este exemplo ilustra não apenas como compilar um aplicativo vinculado MFCxx.DLL, mas também ilustra outros recursos da opção de empacotamento de DLL do MFC, como DLLs de extensão do MFC descritas posteriormente nesta nota técnica.

Anotações de empacotamento

As versões de lançamento das DLLs (MFCxx.DLL e MFCxxU.DLL) são livremente redistribuíveis. As versões de depuração das DLLs não são redistribuíveis livremente e devem ser usadas somente durante o desenvolvimento do aplicativo.

As DLLs de depuração são fornecidas com informações de depuração. Ao usar o depurador do Visual C++, você pode rastrear a execução do aplicativo e da DLL. As DLLs de liberfação (MFCxx.DLL e MFCxxU.DLL) não contêm informações de depuração.

Se você personalizar ou recompilar as DLLs, deverá chamá-las de algo diferente de "MFCxx". O arquivo MFCDLL.MAK no SRC do MFC descreve as opções de build e contém a lógica para renomear a DLL. Renomear os arquivos é necessário, pois essas DLLs são potencialmente compartilhadas por muitos aplicativos do MFC. Fazer com que a versão personalizada das DLLs do MFC substitua as instaladas no sistema, pode interromper outro aplicativo do MFC usando as DLLs compartilhadas do MFC.

Não é recomendável recompilar as DLLs do MFC.

Como o MFCxx.DLL é implementado

A seção a seguir descreve como a DLL do MFC (MFCxx.DLL e MFCxxD.DLL) é implementada. Entender os detalhes aqui também não é importante se tudo o que você deseja fazer é usar a DLL do MFC com seu aplicativo. Os detalhes aqui não são essenciais para entender como gravar uma DLL de extensão do MFC, mas entender essa implementação pode ajudá-lo a gravar sua própria DLL.

Visão geral da implementação

A DLL do MFC é realmente um caso especial de uma DLL de extensão do MFC, conforme descrito acima. Ela tem um grande número de exportações para um grande número de classes. Há algumas coisas adicionais que fazemos na DLL do MFC que a tornam ainda mais especial do que uma DLL de extensão regular do MFC.

O Win32 faz a maior parte do trabalho

A versão de 16 bits do MFC precisava de várias técnicas especiais, incluindo dados por aplicativo no segmento de pilha, segmentos especiais criados por um código de assembly 80x86, contextos de exceção por processo e outras técnicas. O Win32 dá suporte diretamente a dados por processo em uma DLL, que é o que você deseja na maioria das vezes. Na maioria das vezes MFCxx.DLL é apenas NAFXCW.LIB empacotado em uma DLL. Se você examinar o código-fonte MFC, encontrará alguns casos #ifdef _AFXDLL, pois não há muitos casos especiais que precisam ser feitos. Os casos especiais que existem são especificamente para lidar com o Win32 no Windows 3.1 (também conhecido como Win32s). O Win32s não dá suporte diretamente a dados de DLL por processo. A DLL do MFC deve usar as APIs do Win32 do TLS (armazenamento local de thread) para obter dados locais do processo.

Impacto nas fontes da biblioteca, arquivos adicionais

O impacto da versão _AFXDLL nas fontes e cabeçalhos normais da biblioteca de classes MFC é relativamente menor. Há um arquivo de versão especial (AFXV_DLL.H) e um arquivo de cabeçalho adicional (AFXDLL_.H) incluído pelo cabeçalho principal AFXWIN.H. O cabeçalho AFXDLL_.H inclui a classe CDynLinkLibrary e outros detalhes de implementação de aplicativos _AFXDLL e DLLs de extensão do MFC. O cabeçalho AFXDLLX.H é fornecido para a compilação de DLLs de extensão do MFC (consulte acima para obter detalhes).

As fontes regulares para a biblioteca do MFC no SRC do MFC têm algum código condicional adicional no _AFXDLL #ifdef. Um arquivo de origem adicional (DLLINIT.CPP) contém o código de inicialização de DLL extra e outras colas para a versão compartilhada do MFC.

Para compilar a versão compartilhada do MFC, arquivos adicionais são fornecidos. (Confira abaixo detalhes sobre como criar a DLL.)

  • Dois arquivos DEF são usados para exportar os pontos de entrada de DLL do MFC para versões de depuração (MFCxxD.DEF) e liberação (MFCxx.DEF) da DLL.

  • Um arquivo RC (MFCDLL.RC) contém todos os recursos MFC padrão e um recurso VERSIONINFO para a DLL.

  • Um arquivo CLW (MFCDLL.CLW) é fornecido para permitir a navegação nas classes MFC usando ClassWizard. Esse recurso não é específico para a versão DLL do MFC.

Gerenciamento de memória

Um aplicativo que usa MFCxx.DLL usa um alocador de memória comum fornecido por MSVCRTxx.DLL, a DLL de runtime de C compartilhado. O aplicativo, as DLLs de extensão do MFC e as DLLs do MFC usam esse alocador de memória compartilhado. Usando uma DLL compartilhada para alocação de memória, as DLLs do MFC podem alocar memória liberada posteriormente pelo aplicativo ou vice-versa. Como o aplicativo e a DLL devem usar o mesmo alocador, você não deve substituir o C++ global operator new ou operator delete. As mesmas regras se aplicam ao restante das rotinas de alocação de memória em runtime de C (como malloc, realloc, free e outras).

Ordinals e nomes de class __declspec(dllexport) e DLL

Não usamos a funcionalidade class__declspec(dllexport) do compilador C++. Em vez disso, uma lista de exportações é incluída com as fontes da biblioteca de classes (MFCxx.DEF e MFCxxD.DEF). Somente um conjunto selecionado de pontos de entrada (funções e dados) são exportados. Outros símbolos, como funções ou classes de implementação privada do MFC, não são exportados. Todas as exportações são feitas por ordinal sem um nome de cadeia de caracteres na tabela de nomes residente ou não residente.

O uso de class__declspec(dllexport) pode ser uma alternativa viável para a compilação de DLLs menores, mas em uma DLL grande como o MFC, o mecanismo de exportação padrão tem limites de eficiência e capacidade.

Isso significa que podemos empacotar uma grande quantidade de funcionalidade na versão MFCxx.DLL que é apenas cerca de 800 KB sem comprometer muita execução ou velocidade de carregamento. MFCxx.DLL teria sido 100 KB maior se essa técnica não tivesse sido usada. A técnica possibilita adicionar pontos de entrada adicionais no final do arquivo DEF. Ela permite o controle de versão simples sem comprometer a eficiência de velocidade e tamanho da exportação por ordinal. As revisões da versão principal na biblioteca de classes do MFC alterarão o nome da biblioteca. Ou seja, MFC30.DLL é a DLL redistribuível que contém a versão 3.0 da biblioteca de classes MFC. Em uma atualização dessa DLL, em um MFC 3.1 hipotético, a DLL seria nomeada MFC31.DLL. Se você modificar o código-fonte do MFC para produzir uma versão personalizada da DLL do MFC, use um nome diferente (preferencialmente sem "MFC" no nome).

Confira também

Observações técnicas por número
Observações técnicas por categoria