TN033: Versión de DLL de MFC

Esta nota describe cómo puede utilizar las bibliotecas de enlace dinámico compartidas MFCxx.DLL e MFCxxD.DLL (donde xx es el número de versión de MFC) con aplicaciones MFC y DLL de extensión de MFC. Para obtener más información sobre los archivos DLL de MFC normales, consulte Uso de MFC como parte de un archivo DLL.

En esta nota técnica se tratan tres aspectos de los archivos DLL. Los dos últimos son para los usuarios más avanzados:

Si está interesado en compilar un archivo DLL mediante MFC que se puede usar con aplicaciones que no son MFC (conocidas como DLL de MFC normales), consulte la Nota técnica 11.

Información general sobre la compatibilidad con MFCxx.DLL: terminología y archivos

DLL de MFC normal: se usa un archivo DLL de MFC normal para compilar un archivo DLL independiente mediante algunas de las clases MFC. Las interfaces a través del límite de app/DLL son interfaces "C" y la aplicación cliente no tiene que ser una aplicación MFC.

Los archivos DLL de MFC normales son la versión de archivos DLL compatibles con MFC 1.0. Se describen en la Nota técnica 11 y en el ejemplo de conceptos avanzados de DLLScreenCapMFC.

Nota:

A partir de la versión 4.0 de Visual C++, el término USRDLL está obsoleto y se ha reemplazado por un archivo DLL de MFC normal que se vincula estáticamente a MFC. También puede compilar un archivo DLL de MFC normal que se vincule dinámicamente a MFC.

MFC 3.0 (y versiones posteriores) admite archivos DLL de MFC normales con todas las nuevas funcionalidades, incluidas las clases OLE y Database.

AFXDLL: también conocido como la versión compartida de las bibliotecas MFC. Es el nuevo soporte DLL agregado en MFC 2.0. La propia biblioteca MFC está en una serie de archivos DLL (que se describen a continuación). Una aplicación cliente o DLL vincula dinámicamente los archivos DLL que requiere. Las interfaces entre el límite de la aplicación o DLL son interfaces de clase de C++/MFC. La aplicación cliente DEBE ser una aplicación MFC. Este archivo DLL admite todas las funciones de MFC 3.0 (excepción: UNICODE no se admite para las clases de base de datos).

Nota:

A partir de la versión 4.0 de Visual C++, este tipo de DLL se conoce como "DLL de extensión".

Esta nota usará MFCxx.DLL para hacer referencia a todo el conjunto de archivos DLL de MFC, que incluye:

  • Depurar: MFCxxD.DLL (combinado) y MFCSxxD.LIB (estático).

  • Versión: MFCxx.DLL (combinado) y MFCSxx.LIB (estático).

  • Depurar Unicode: MFCxxUD.DLL (combinado) y MFCSxxD.LIB (estático).

  • Versión Unicode: MFCxxU.DLL (combinado) y MFCSxxU.LIB (estático).

Nota:

Las MFCSxx[U][D].LIB bibliotecas se usan junto con los archivos DLL compartidos de MFC. Estas bibliotecas contienen código que se debe vincular estáticamente a la aplicación o DLL.

Una aplicación se vincula a las bibliotecas de importación correspondientes:

  • Debug: MFCxxD.LIB

  • Release: MFCxx.LIB

  • Depuración Unicode: MFCxxUD.LIB

  • Versión Unicode: MFCxxU.LIB

Un archivo DLL de extensión MFC es un archivo DLL que extiende (u otros archivos DLL compartidos MFCxx.DLL de MFC). Aquí se inicia la arquitectura de componentes de MFC. Si deriva una clase útil de una clase MFC o compila otro kit de herramientas similar a MFC, puede colocarlo en un archivo DLL. El archivo DLL usa MFCxx.DLL, al igual que la aplicación cliente final. Un archivo DLL de extensión MFC permite clases hoja reutilizables, clases base reutilizables y clases de documentos y vistas reutilizables.

Ventajas y desventajas

¿Por qué debe usar la versión compartida de MFC?

  • El uso de la biblioteca compartida puede dar lugar a aplicaciones más pequeñas. (Una aplicación mínima que usa la mayoría de la biblioteca MFC es inferior a 10 000).

  • La versión compartida de MFC admite archivos DLL de extensión MFC y archivos DLL de MFC normales.

  • Es más rápido compilar una aplicación que usa las bibliotecas MFC compartidas que una aplicación MFC vinculada estáticamente. Esto se debe a que no es necesario vincular MFC. Esto es especialmente cierto en las compilaciones DEBUG en las que el enlazador debe compactar la información de depuración. Cuando la aplicación se vincula a un archivo DLL que ya contiene la información de depuración, hay menos información de depuración para compactar.

¿Por qué debe usar la versión compartida de MFC?

  • El envío de una aplicación que usa la biblioteca compartida requiere que envíe MFCxx.DLL y otras bibliotecas con el programa. MFCxx.DLL se pueden redistribuir libremente como muchos archivos DLL, pero aun así deben instalarse en el programa de INSTALACIÓN. Además, tendrá que enviar las otras bibliotecas redistribuibles que usa el programa y los archivos DLL de MFC.

Cómo escribir un archivo DLL de extensión MFC

Un archivo DLL de extensión MFC es un archivo DLL que contiene clases y funciones para expandir la funcionalidad de las clases MFC. Un archivo DLL de extensión de MFC utiliza una versión compartida de MFC de la misma manera que una aplicación las usa, con algunas consideraciones adicionales:

  • El proceso de compilación es similar a compilar una aplicación que usa las bibliotecas MFC compartidas con algunas opciones adicionales del compilador y del vinculador.

  • Un archivo DLL de extensión MFC no tiene una CWinAppclase derivada.

  • Un archivo DLL de extensión MFC debe proporcionar un elemento especial DllMain. AppWizard proporciona una DllMain función que se puede modificar.

  • Normalmente, un archivo DLL de extensión MFC proporciona una rutina de inicialización para crear un CDynLinkLibrary, si el archivo DLL de extensión MFC exporta CRuntimeClass tipos o recursos a la aplicación. Se puede usar una clase derivada de si el archivo DLL de CDynLinkLibrary extensión MFC debe mantener los datos por aplicación.

Las operaciones se describen con más detalle a continuación. Consulte también el ejemplo de conceptos avanzados de DLLHUSKMFC. Muestra cómo:

  • Compile una aplicación mediante las bibliotecas compartidas. (DLLHUSK.EXE es una aplicación MFC que se vincula dinámicamente a las bibliotecas de MFC y a otros archivos DLL).

  • Creación de un archivo DLL de extensión MFC. (Muestra cómo se usan las marcas especiales, como _AFXEXT, por ejemplo, al compilar un archivo DLL de extensión MFC).

  • Cree dos ejemplos de archivos DLL de extensión MFC. Una muestra la estructura básica de un archivo DLL de extensión MFC con exportaciones limitadas (TESTDLL1) y la otra muestra la exportación de una interfaz de clase completa (TESTDLL2).

La aplicación de cliente y el archivo DLL de extensión de MFC deben usar la misma versión de MFCxx.DLL. Siga las convenciones de los archivos DLL de MFC y proporcione una versión de depuración y versión (/release) del archivo DLL de extensión de MFC. Esta práctica permite a los programas cliente compilar versiones de depuración y lanzamiento de sus aplicaciones y vincularlas con la versión de depuración o versión adecuada de todos los archivos DLL.

Nota:

A causa de la eliminación de nombres de C++ y los problemas de exportación, la lista de exportación de un archivo DLL de extensión de MFC puede ser distinta en las versiones de depuración y de lanzamiento del mismo archivo DLL y para archivos DLL de distintas plataformas. La versión de lanzamiento MFCxx.DLL tiene alrededor de 2000 puntos de entrada exportados; la versión de depuración MFCxxD.DLL tiene unos 3000 puntos de entrada exportados.

Nota rápida sobre la administración de memoria

La sección titulada "Administración de memoria", cerca del final de esta nota técnica, describe la implementación de MFCxx.DLL con la versión compartida de MFC. La información que necesita saber para implementar solo un archivo DLL de extensión MFC se describe aquí.

MFCxx.DLL y todos los archivos DLL de extensión de MFC cargados en el espacio de direcciones de la aplicación cliente usarán el mismo asignador de memoria, la misma carga de recursos y otros estados globales de MFC, como si estuvieran en la misma aplicación. Esto es importante, ya que las bibliotecas DLL que no están basadas en MFC y los archivos DLL estándar de MFC realizan funciones exactamente opuestas y cada uno de sus archivos DLL asigna fuera de su propio bloque de memoria.

Si un archivo DLL de extensión de MFC asigna memoria, esta memoria puede combinarse libremente con cualquier otro objeto asignado por la aplicación. Además, si una aplicación que usa las bibliotecas MFC compartidas se bloquea, el sistema operativo mantiene la integridad de cualquier otra aplicación MFC que comparta el archivo DLL.

De manera similar, otros estados globales de MFC, como el archivo ejecutable actual desde el que se cargan recursos, también se comparten entre la aplicación cliente y todos los archivos DLL de extensión de MFC, así como el propio MFCxx.DLL.

Creación de un archivo DLL de extensión MFC

Puede usar AppWizard para crear un proyecto dll de extensión MFC y genera automáticamente la configuración adecuada del compilador y del vinculador. También genera una DllMain función que se puede modificar.

Si va a convertir un proyecto existente en un archivo DLL de extensión MFC, comience con la configuración estándar que se compila mediante la versión compartida de MFC. A continuación, realice los siguientes cambios:

  • Agregue /D_AFXEXT a las marcas del compilador. En el cuadro de diálogo Propiedades del proyecto, seleccione la categoría Preprocesador de >C/C++. Agregue _AFXEXT al campo Definir macros, separando cada uno de los elementos con punto y coma.

  • Retire el modificador /Gy del compilador. En el cuadro de diálogo Propiedades del proyecto, seleccione la categoría C/C++>Generación de código. Asegúrese de que la propiedad Enable Function-Level Linking no está habilitada. Esta configuración facilita la exportación de clases, ya que el enlazador no quitará funciones sin referencia. Si el proyecto original creó un archivo DLL de MFC normal que está vinculado estáticamente a MFC, cambie la /MT opción del compilador (o /MTd) a /MD (o /MDd).

  • Cree una biblioteca de exportación con la /DLL opción vincular. Esta opción se establece al crear un nuevo destino y especificar Win32 Dynamic-Link Library como tipo de destino.

Cambiar los archivos de encabezado

El objetivo habitual de un archivo DLL de extensión MFC es exportar alguna funcionalidad común a una o varias aplicaciones que pueden usar esa funcionalidad. Básicamente, el archivo DLL exporta clases y funciones globales para que las usen las aplicaciones cliente.

Para asegurarse de que cada función miembro se marca para importar o exportar según corresponda, use las declaraciones especiales __declspec(dllexport) y __declspec(dllimport). Cuando las aplicaciones cliente usan las clases, quiere que se declaren como __declspec(dllimport). Cuando se compila el archivo DLL de extensión MFC, las funciones deben declararse como __declspec(dllexport). El archivo DLL compilado también debe exportar las funciones para que los programas cliente puedan enlazarlos en tiempo de carga.

Para exportar toda la clase, use AFX_EXT_CLASS en la definición de clase. El marco define esta macro como __declspec(dllexport) cuándo _AFXDLL y _AFXEXT se define, pero la define como __declspec(dllimport) cuando _AFXEXT no se define. _AFXEXT solo se define al compilar el archivo DLL de extensión MFC. Por ejemplo:

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

No se exporta la clase completa

A veces es posible que quiera exportar solo los miembros individuales necesarios de la clase. Por ejemplo, si va a exportar una clase derivada de CDialog, es posible que solo necesite exportar el constructor y la llamada a DoModal. Puede exportar estos miembros mediante el archivo DEF del archivo DLL, pero también puede usar AFX_EXT_CLASS de la misma manera en los miembros individuales que necesita exportar.

Por ejemplo:

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

Cuando lo hace, puede encontrarse con un problema adicional porque no exporta todos los miembros de la clase. El problema es la forma en que funcionan las macros de MFC. Algunas de las macros de asistente de MFC declaran o definen miembros de datos. El archivo DLL también debe exportar estos miembros de datos.

Por ejemplo, la macro DECLARE_DYNAMIC se define como sigue al compilar un archivo DLL de extensión de MFC:

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

La línea que comienza static AFX_DATA declara un objeto estático dentro de la clase. Para exportar esta clase correctamente y acceder a la información en tiempo de ejecución desde un archivo EXE de cliente, debe exportar este objeto estático. Dado que el objeto estático se declara con el modificador AFX_DATA, solo es necesario definir AFX_DATA como __declspec(dllexport) al compilar el archivo DLL. Defina como __declspec(dllimport) cuando compile el ejecutable del cliente.

Como se ha explicado anteriormente, AFX_EXT_CLASS ya está definido de esta manera. Solo tiene que volver a definir AFX_DATA para que sea igual que AFX_EXT_CLASS en la definición de la clase.

Por ejemplo:

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

Como MFC siempre usa el símbolo AFX_DATA en los elementos de datos que define dentro de sus macros, esta técnica funciona en todos estos escenarios. Por ejemplo, funcionará para DECLARE_MESSAGE_MAP.

Nota:

Si va a exportar toda la clase en lugar de miembros concretos de ella, los miembros de datos estáticos se exportan de forma automática.

Puede usar la misma técnica para exportar automáticamente el CArchive operador de extracción para las clases que usan las macros DECLARE_SERIAL y IMPLEMENT_SERIAL. Exporte el operador archive entre corchetes las declaraciones de clase (ubicadas en el archivo de encabezado) con el código siguiente:

#undef AFX_API
#define AFX_API AFX_EXT_CLASS

/* your class declarations here */

#undef AFX_API
#define AFX_API

Limitaciones de _AFXEXT

Puede usar el símbolo de preprocesador _AFXEXT para los archivos DLL de extensión de MFC, siempre y cuando no disponga de varios niveles de ellos. Si tiene archivos DLL de extensión de MFC que llaman o se derivan de clases de archivos DLL de extensión de MFC propios, que a su vez se derivan de las clases MFC, tendrá que usar un símbolo de preprocesador propio para evitar ambigüedades.

El problema es que, en Win32, debe declarar de forma explícita cualquier dato como __declspec(dllexport) si se va a exportar desde un archivo DLL y como __declspec(dllimport) para importarlo desde un archivo DLL. Al definir _AFXEXT, los encabezados de MFC se aseguran de que AFX_EXT_CLASS está definido correctamente.

Cuando tiene varias capas, un símbolo como AFX_EXT_CLASS no es suficiente: un archivo DLL de extensión MFC puede exportar sus propias clases e importar también otras clases desde otro archivo DLL de extensión MFC. Para solucionar este problema, use un símbolo de preprocesador especial que indique que se va a compilar el propio archivo DLL, no a usarlo. Por ejemplo, imagine dos archivo DLL de extensión A.DLL y B.DLL. Cada uno exporta algunas clases de A.H y B.H, respectivamente. B.DLL usa las clases de A.DLL. Los archivos de encabezado serían similares a los siguientes:

/* 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 ... */ };

Cuando A.DLL se ha compilado, se compila con /DA_IMPL y, cuando se compila B.DLL es compilado con /DB_IMPL. Al usar símbolos independientes para cada archivo DLL, se exporta CExampleB y se importa CExampleA al compilar B.DLL. CExampleA se exporta al compilar A.DLL y se importa cuando lo usa B.DLL (u otro cliente).

Este tipo de distribución en capas no se puede realizar cuando se usa el compilado en los símbolos de preprocesador AFX_EXT_CLASS y _AFXEXT. La técnica descrita anteriormente resuelve este problema de la misma manera que lo hace MFC. MFC usa esta técnica al compilar sus archivos DLL de extensión OLE, Database y Network MFC.

Sigue sin exportarse la clase completa

De nuevo, tendrá que tener especial cuidado cuando no exporte toda una clase. Asegúrese de que los elementos de datos necesarios creados por las macros de MFC se exporten correctamente. Para hacerlo, vuelva a definir AFX_DATA la macro de la clase específica. Vuelva a definirlo siempre que no exporte toda la clase.

Por ejemplo:

// 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

Este es el código que debe colocar en el archivo de código fuente principal para el archivo DLL de extensión MFC. Debe venir después de incluir el estándar. Cuando se usa AppWizard para crear archivos de inicio para un archivo DLL de extensión MFC, le proporciona un DllMain.

#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
}

La llamada a AfxInitExtensionModule captura las clases de tiempo de ejecución del módulo (estructuras CRuntimeClass), así como sus generadores de objetos (objetos COleObjectFactory) para su uso cuando se crea el objeto CDynLinkLibrary. La llamada (opcional) a AfxTermExtensionModule permite que MFC limpie el archivo DLL de extensión de MFC cuando cada proceso se desasocia del archivo DLL de extensión de MFC (lo que sucede cuando el proceso finaliza o cuando el archivo DLL se descarga como resultado de una llamada a FreeLibrary). Dado que la mayoría de los archivos DLL de extensión MFC no se cargan dinámicamente (normalmente, se vinculan a través de sus bibliotecas de importación), la llamada a AfxTermExtensionModule normalmente no es necesaria.

Si la aplicación carga y libera archivos DLL de extensión MFC dinámicamente, asegúrese de llamar AfxTermExtensionModule a como se muestra anteriormente. Asegúrese también de usar AfxLoadLibrary y AfxFreeLibrary (en lugar de las funciones LoadLibrary y FreeLibrary de Win32) si la aplicación usa varios subprocesos o si carga dinámicamente un archivo DLL de extensión MFC. El uso de AfxLoadLibrary y AfxFreeLibrary garantiza que el código de inicio y apagado que se ejecuta cuando se carga y descarga el archivo DLL de extensión de MFC no daña el estado global de MFC.

El archivo de encabezado AFXDLLX.H contiene definiciones especiales para estructuras que se usan en archivos DLL de extensión de MFC, como la definición de AFX_EXTENSION_MODULE y CDynLinkLibrary.

La extensión globalDLL debe declararse como se muestra. A diferencia de la versión de MFC de 16 bits, puede asignar memoria y llamar a funciones de MFC durante este tiempo, ya que el MFCxx.DLL se inicializa por completo para cuando se llama a DllMain.

Compartir recursos y clases

Los archivos DLL de extensión MFC simples solo necesitan exportar algunas funciones de ancho de banda bajo a la aplicación cliente y nada más. Las DLLs más intensivas en interfaz de usuario pueden querer exportar recursos y clases C++ a la aplicación cliente.

La exportación de recursos se realiza mediante una lista de recursos. En cada aplicación hay una lista vinculada resaltada de objetos CDynLinkLibrary. Al buscar un recurso, la mayoría de las implementaciones MFC estándar que cargan recursos buscan primero en el módulo de recursos actual (AfxGetResourceHandle) y, si no se encuentra el recurso, recorren la lista de objetos CDynLinkLibrary para intentar cargar el recurso solicitado.

La creación dinámica de objetos de C++ dado un nombre de clase de C++ es similar. El mecanismo de deserialización de objetos de MFC requiere que todos los objetos CRuntimeClass estén registrados para poder reconstruir creando dinámicamente objetos C++ del tipo requerido a partir de lo que estaba almacenado antes.

Si desea que la aplicación cliente use clases en el archivo DLL de extensión MFC que son DECLARE_SERIAL, deberá exportar las clases para que sean visibles para la aplicación cliente. También se hace caminando la CDynLinkLibrary lista.

En el caso del ejemplo DLLHUSK de MFC, la lista tiene la siguiente apariencia:

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

La entrada MFCxx.DLL suele estar al final de la lista de recursos y clases. MFCxx.DLL incluye todos los recursos estándar de MFC, incluidas las cadenas de texto de los identificadores de comando estándar. Si lo coloca al final de la lista, permitirá que los archivos DLL y la aplicación del cliente dependan de los recursos compartidos en MFCxx.DLL, en vez de tener sus propias copias.

Combinar los recursos y los nombres de clase de todos los archivos DLL en el espacio de nombres de la aplicación de cliente tiene la desventaja de que requiere ser cuidadoso al seleccionar identificadores o nombres. Para deshabilitar esta característica, no exporte los recursos ni un CDynLinkLibrary objeto a la aplicación cliente. El DLLHUSK ejemplo administra el espacio de nombres de recurso compartido mediante varios archivos de encabezado. Consulte la Nota técnica 35 para obtener más sugerencias sobre el uso de archivos de recursos compartidos.

Inicializar el DLL

Como se mencionó anteriormente, normalmente querrá crear un CDynLinkLibrary objeto para exportar los recursos y clases a la aplicación cliente. Deberá proporcionar un punto de entrada exportado para inicializar el archivo DLL. Mínimamente, es una void rutina que no toma ningún argumento y no devuelve nada, pero puede ser cualquier cosa que quiera.

Cada aplicación cliente que quiera usar el archivo DLL debe llamar a esta rutina de inicialización, si usa este enfoque. También puede asignar este objeto CDynLinkLibrary en el justo DllMain después de llamar a AfxInitExtensionModule.

La rutina de inicialización debe crear un CDynLinkLibrary objeto en el montón de la aplicación actual, conectado a la información del archivo DLL de extensión de MFC. Puede hacerlo definiendo una función como esta:

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

El nombre de rutina, InitXxxDLL en este ejemplo, puede ser cualquier cosa que desee. No es necesario que sea extern "C", pero facilita el mantenimiento de la lista de exportación.

Nota:

Si usa el archivo DLL de extensión MFC desde un archivo DLL de MFC normal, debe exportar esta función de inicialización. Se debe llamar a esta función desde el archivo DLL de MFC normal antes de usar cualquier clase o recursos DLL de extensión de MFC.

Exportar entradas

La manera sencilla de exportar las clases es usar __declspec(dllimport) y __declspec(dllexport) en cada clase y función global que desee exportar. Es mucho más fácil, pero es menos eficaz que asignar un nombre a cada punto de entrada en un archivo DEF, como se describe a continuación. Esto se debe a que tiene menos control sobre qué funciones se exportan. Y no se pueden exportar las funciones por ordinal. TESTDLL1 y TESTDLL2 usan este método para exportar sus entradas.

Un método más eficaz consiste en exportar cada entrada asignando un nombre en el archivo DEF. Este método lo usa MFCxx.DLL. Puesto que estamos exportando selectivamente desde nuestro archivo DLL, debemos decidir qué interfaces concretas queremos exportar. Es difícil, ya que debe especificar los nombres codificados en el enlazador en forma de entradas en el archivo DEF. No exporte ninguna clase de C++ a menos que realmente necesite tener un vínculo simbólico para ella.

Si ha intentado exportar clases de C++ con un archivo DEF antes, puede que desee desarrollar una herramienta para generar esta lista automáticamente. Se puede realizar mediante un proceso de vínculo de dos fases. Vincule el archivo DLL una vez sin exportaciones y permita que el enlazador genere un archivo MAP. El archivo MAP contiene una lista de funciones que se deben exportar. Con alguna reorganización, puede usarla para generar las entradas EXPORT para el archivo DEF. La lista de exportación para MFCxx.DLL y los archivos DLL de extensión OLE y Database MFC, varios miles de números, se generó con este proceso (aunque no es totalmente automático, y requiere un ajuste manual cada vez en un tiempo).

CWinApp vs. CDynLinkLibrary

Un archivo DLL de extensión MFC no tiene un CWinAppobjeto derivado de sí mismo. Debe funcionar con el objeto derivado CWinApp de la aplicación de cliente. Esto significa que la aplicación de cliente es propietaria del suministro principal de mensajes, el bucle de inactividad, etc.

Si su DLL de extensión MFC necesita mantener datos adicionales para cada aplicación, puede derivar una nueva clase de CDynLinkLibrary y crearla en la rutina InitXxxDLL descrita anteriormente. Cuando se ejecuta, el archivo DLL puede comprobar la lista de objetos de CDynLinkLibrary la aplicación actual para buscar el archivo DLL de extensión MFC concreto.

Uso de recursos en la implementación de DLL

Como se mencionó anteriormente, la carga de recursos predeterminada guiará la lista de CDynLinkLibrary objetos que buscan el primer archivo EXE o DLL que tiene el recurso solicitado. Todas las API de MFC y todo el código interno usan AfxFindResourceHandle para recorrer la lista de recursos para buscar cualquier recurso, independientemente de dónde se encuentre.

Si desea cargar sólo recursos de un lugar específico, utilice las APIs AfxGetResourceHandle y AfxSetResourceHandle para guardar el antiguo identificador y establecer el nuevo identificador. Asegúrese de restaurar el identificador de recurso antiguo antes de volver a la aplicación de cliente. El ejemplo TESTDLL2 usa este enfoque para cargar explícitamente un menú.

Recorrer la lista tiene algunas desventajas: es ligeramente más lento y requiere gestionar rangos de ID de recursos. Tiene la ventaja de que una aplicación cliente que se vincula a varios archivos DLL de extensión de MFC puede usar cualquier recurso proporcionado por un archivo DLL sin tener que especificar el identificador de instancia del archivo DLL. AfxFindResourceHandle es una API que se usa para recorrer la lista de recursos a fin de buscar una coincidencia determinada. Toma el nombre y el tipo de un recurso, y devuelve el identificador del recurso donde lo encuentra por primera vez, o NULL.

Escribir una aplicación que use la versión de DLL

Requisitos de las aplicaciones

Una aplicación que use la versión compartida de MFC debe seguir algunas reglas básicas:

  • Debe tener un CWinApp objeto y seguir las reglas estándar para un bombeo de mensajes.

  • Debe compilarse con un conjunto de marcas de compilador necesarias (consulte a continuación).

  • Debe vincularse con las bibliotecas de importación MFCxx. Al establecer las marcas del compilador necesarias, los encabezados MFC determinan en el momento del vínculo con qué biblioteca debe vincularse la aplicación.

  • Para activar el archivo ejecutable, MFCxx.DLL debe estar en la ruta de acceso o en el directorio del sistema de Windows.

Compilación con el entorno de desarrollo

Si estás usando el archivo Make interno con la mayoría de los valores por defecto, puedes cambiar fácilmente el proyecto para construir la versión DLL.

En el paso siguiente se supone que tiene una aplicación MFC que funciona correctamente vinculada NAFXCWD.LIB (para depurar) y NAFXCW.LIB (para la versión) y quiere convertirla para usar la versión compartida de la biblioteca MFC. Está ejecutando el entorno de Visual Studio y tiene un archivo de proyecto interno.

  1. En el menú Proyecto, seleccione Propiedades. En la página General , en Valores predeterminados del proyecto, establezca Microsoft Foundation Classes en Usar MFC en un archivo DLL compartido (MFCxx(d).dll).

Creación con NMAKE

Si usa la característica makefile externa del enlazador o usa NMAKE directamente, tendrá que editar el archivo make para admitir las opciones necesarias del compilador y del enlazador.

Marcas de compilador necesarias:

  • /D_AFXDLL /MD /D_AFXDLL

Los encabezados MFC estándar necesitan definir el _AFXDLL símbolo.

  • /MD La aplicación debe usar la versión DLL de la biblioteca en tiempo de ejecución de C.

Todas las demás marcas del compilador siguen los valores predeterminados de MFC (por ejemplo, _DEBUG para depurar).

Editar la lista del enlazador de bibliotecas. Cambie NAFXCWD.LIB a MFCxxD.LIB y cambie NAFXCW.LIB a MFCxx.LIB. Reemplace LIBC.LIB por MSVCRT.LIB. Al igual que con cualquier otra biblioteca MFC, es importante que MFCxxD.LIB se coloque antes de cualquier biblioteca en tiempo de ejecución C.

Opcionalmente, agregue /D_AFXDLL a las opciones del compilador de recursos de versión y depuración (la que realmente compila los recursos con /R). Esta opción hace que el archivo ejecutable final sea más pequeño al compartir los recursos que están presentes en los archivos DLL de MFC.

Se requiere una recompilación completa después de realizar estos cambios.

Compilación de los ejemplos

La mayoría de los programas de ejemplo de MFC se pueden compilar desde Visual C++ o desde un MAKEFILE compatible con NMAKE compartido desde la línea de comandos.

Para convertir cualquiera de estos ejemplos para usar MFCxx.DLL, puede cargar el archivo MAK en Visual C++ y establecer las opciones de Project como se describió anteriormente. Si usa la compilación de NMAKE, puede especificar AFXDLL=1 en la línea de comandos de NMAKE y que compilará el ejemplo mediante las bibliotecas de MFC compartidas.

El archivo DLLHUSK de ejemplo de conceptos avanzados de MFC se compila con la versión DLL de MFC. En este ejemplo no solo se muestra cómo compilar una aplicación vinculada a MFCxx.DLL, sino que también se muestran otras características de la opción de empaquetado DLL de MFC, como archivos DLL de extensión MFC que se describen más adelante en esta nota técnica.

Notas de empaquetado

Las versiones de lanzamiento de los archivos DLL (MFCxx.DLL y MFCxxU.DLL) son redistribuibles libremente. Las versiones de depuración de los archivos DLL no son redistribuibles libremente y solo se deben usar durante el desarrollo de la aplicación.

Los archivos DLL de depuración se proporcionan con información de depuración. Mediante el depurador de Visual C++, puede realizar un seguimiento de la ejecución de la aplicación y del archivo DLL. Los archivos DLL de versión (MFCxx.DLL y MFCxxU.DLL) no contienen información de depuración.

Si personaliza o vuelve a generar los archivos DLL, debe llamarlos algo distinto de "MFCxx". El archivo MFCDLL.MAK SRC de MFC describe las opciones de compilación y contiene la lógica para cambiar el nombre del archivo DLL. Es necesario cambiar el nombre de los archivos, ya que es posible que muchas aplicaciones MFC compartan estos archivos DLL. Hacer que la versión personalizada de los archivos DLL de MFC reemplace las instaladas en el sistema puede interrumpir otra aplicación MFC mediante los archivos DLL de MFC compartidos.

No se recomienda recompilar los archivos DLL de MFC.

Cómo se implementa la MFCxx.DLL

En la sección siguiente se describe cómo se implementa la DLL de MFC (MFCxx.DLL y MFCxxD.DLL). Comprender los detalles aquí tampoco son importantes si todo lo que desea hacer es usar el archivo DLL de MFC con la aplicación. Los detalles aquí no son esenciales para comprender cómo escribir un archivo DLL de extensión MFC, pero comprender esta implementación puede ayudarle a escribir su propio archivo DLL.

Información general sobre la implementación

El archivo DLL de MFC es realmente un caso especial de un archivo DLL de extensión MFC, como se ha descrito anteriormente. Tiene un gran número de exportaciones para un gran número de clases. Hay algunas cosas adicionales que hacemos en el archivo DLL de MFC que hacen que sea aún más especial que un archivo DLL de extensión MFC normal.

Win32 realiza la mayoría del trabajo

La versión de 16 bits de MFC necesitaba una serie de técnicas especiales, incluidos los datos por aplicación en el segmento de pila, segmentos especiales creados por un código de ensamblado de 80x86, contextos de excepción por proceso y otras técnicas. Win32 admite directamente los datos por proceso en un archivo DLL, que es lo que más quieres. Por lo general MFCxx.DLL, solo NAFXCW.LIB se empaqueta en un archivo DLL. Si observa el código fuente de MFC, encontrará algunos #ifdef _AFXDLL casos, ya que no hay muchos casos especiales que se deben realizar. Los casos especiales que hay en concreto para tratar con Win32 en Windows 3.1 (también conocido como Win32s). Win32s no admite datos DLL por proceso directamente. El archivo DLL de MFC debe usar las API win32 de almacenamiento local de subprocesos (TLS) para obtener los datos locales del proceso.

Impacto en orígenes de biblioteca, archivos adicionales

El impacto de la _AFXDLL versión en los orígenes y encabezados normales de la biblioteca de clases MFC es relativamente menor. Hay un archivo de versión especial (AFXV_DLL.H) y un archivo de encabezado adicional (AFXDLL_.H) incluido por el encabezado principal AFXWIN.H. El AFXDLL_.H encabezado incluye la CDynLinkLibrary clase y otros detalles de implementación de las _AFXDLL aplicaciones y los archivos DLL de extensión de MFC. El AFXDLLX.H encabezado se proporciona para compilar archivos DLL de extensión MFC (consulte más arriba para obtener más información).

Los orígenes normales de la biblioteca MFC en MFC SRC tienen código condicional adicional en el _AFXDLL #ifdef. Un archivo de código fuente adicional (DLLINIT.CPP) contiene el código de inicialización de DLL adicional y otro pegamento para la versión compartida de MFC.

Para crear la versión compartida de MFC, se proporcionan archivos adicionales. (Consulte a continuación para obtener más información sobre cómo compilar el archivo DLL).

  • Se usan dos archivos DEF para exportar los puntos de entrada del archivo DLL de MFC para las versiones de depuración (MFCxxD.DEF) y versión (MFCxx.DEF) del archivo DLL.

  • Un archivo RC (MFCDLL.RC) contiene todos los recursos de MFC estándar y un VERSIONINFO recurso para el archivo DLL.

  • Se proporciona un archivo CLW (MFCDLL.CLW) para permitir la exploración de las clases MFC mediante ClassWizard. Esta característica no es específica de la versión DLL de MFC.

Administración de memoria

Una aplicación que usa MFCxx.DLL un asignador de memoria común proporcionado por MSVCRTxx.DLL, el archivo DLL de tiempo de ejecución de C compartido. La aplicación, los archivos DLL de extensión de MFC y los archivos DLL de MFC usan este asignador de memoria compartida. Mediante el uso de un archivo DLL compartido para la asignación de memoria, los archivos DLL de MFC pueden asignar memoria que la aplicación libera más adelante o viceversa. Dado que tanto la aplicación como el archivo DLL deben usar el mismo asignador, no debe invalidar el global operator new de C++ o operator delete. Las mismas reglas se aplican al resto de las rutinas de asignación de memoria en tiempo de ejecución de C (como malloc, realloc, freey otras).

Ordinales y nombres de clase __declspec(dllexport) y DLL

No usamos la class__declspec(dllexport) funcionalidad del compilador de C++. En su lugar, se incluye una lista de exportaciones con los orígenes de la biblioteca de clases (MFCxx.DEF y MFCxxD.DEF). Solo se exporta un conjunto seleccionado de puntos de entrada (funciones y datos). Otros símbolos, como las clases o las funciones de implementación privada de MFC, no se exportan. Todas las exportaciones se realizan por ordinal sin un nombre de cadena en la tabla de nombres residentes o no residentes.

El uso class__declspec(dllexport) de puede ser una alternativa viable para compilar archivos DLL más pequeños, pero en un archivo DLL grande como MFC, el mecanismo de exportación predeterminado tiene límites de eficacia y capacidad.

Lo único que significa es que podemos empaquetar una gran cantidad de funcionalidades en la versión MFCxx.DLL que solo tiene alrededor de 800 KB sin poner en peligro la ejecución ni la velocidad de carga. MFCxx.DLL habría sido de 100 KB de mayor si esta técnica no se hubiera usado. La técnica permite agregar puntos de entrada adicionales al final del archivo DEF. Permite el control de versiones simple sin poner en peligro la velocidad y la eficiencia del tamaño de la exportación por ordinal. Las revisiones de la versión principal de la biblioteca de clases MFC cambiarán el nombre de la biblioteca. Es decir, MFC30.DLL es el archivo DLL redistribuible que contiene la versión 3.0 de la biblioteca de clases MFC. Una actualización de este archivo DLL, por ejemplo, en un MFC 3.1 hipotético, el archivo DLL se denominaría MFC31.DLL en su lugar. De nuevo, si modifica el código fuente de MFC para generar una versión personalizada del archivo DLL de MFC, use un nombre diferente (y preferiblemente uno sin "MFC" en el nombre).

Consulte también

Notas técnicas por número
Notas técnicas por categoría