Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Al compilar una biblioteca de vínculos dinámicos (DLL) mediante Visual Studio, de forma predeterminada, el enlazador incluye la biblioteca en tiempo de ejecución de Visual C++ (VCRuntime). VCRuntime contiene el código necesario para inicializar y finalizar un ejecutable de C/C++. Cuando se vincula en un archivo DLL, el código de VCRuntime proporciona una función interna de punto de entrada para la DLL denominada _DllMainCRTStartup que gestiona los mensajes del sistema operativo Windows para adjuntar o desadjuntar la DLL a un proceso o subproceso. La _DllMainCRTStartup función realiza tareas esenciales, como la configuración de seguridad del búfer de pila, la inicialización y terminación de la biblioteca en tiempo de ejecución de C (CRT), y llama a constructores y destructores para objetos estáticos y globales.
_DllMainCRTStartup también llama a funciones hook para que otras bibliotecas como WinRT, MFC y ATL realicen su propia inicialización y finalización. Sin esta inicialización, CRT y otras bibliotecas, y las variables estáticas, se dejarían en un estado no inicializado. Se llama a las mismas rutinas de inicialización y finalización internas de VCRuntime, independientemente de si el DLL utiliza un CRT estáticamente vinculado o un CRT DLL dinámicamente vinculado.
Punto de entrada de DLL predeterminado _DllMainCRTStartup
En Windows, todos los archivos DLL pueden contener una función de punto de entrada opcional, normalmente denominada DllMain, a la que se llama para la inicialización y la finalización. Esto le ofrece la oportunidad de asignar o liberar otros recursos según sea necesario. Windows llama a la función de punto de entrada en cuatro situaciones: asociación de procesos, desasociación de procesos, asociación de subprocesos y desasociación de subprocesos. Cuando se carga un archivo DLL en un espacio de direcciones de proceso, ya sea cuando se carga una aplicación que lo usa, o cuando la aplicación solicita el archivo DLL en tiempo de ejecución, el sistema operativo crea una copia independiente de los datos DLL. Esto se denomina asociación de procesos. La asociación de subprocesos se produce cuando el proceso en el que se carga el archivo DLL crea un subproceso. La desasociación de subprocesos se produce cuando finaliza el subproceso y la desasociación del proceso, cuando el archivo DLL ya no es necesario y se libera en una aplicación. El sistema operativo realiza una llamada independiente al punto de entrada de DLL para cada uno de estos eventos, y pasa un argumento de motivo para cada tipo de evento. Por ejemplo, el sistema operativo envía DLL_PROCESS_ATTACH como argumento de motivo para indicar la asociación de procesos.
La biblioteca VCRuntime proporciona una función de punto de entrada llamada _DllMainCRTStartup para administrar las operaciones predeterminadas de inicialización y finalización. Al adjuntar el proceso, la función _DllMainCRTStartup configura comprobaciones de seguridad de búfer, inicializa la CRT y otras bibliotecas, inicializa la información de tipo en tiempo de ejecución, inicializa y llama a constructores para datos estáticos y no locales, inicializa el almacenamiento local de subprocesos, incrementa un contador estático interno por cada adjunto, y a continuación, llama a una función proporcionada por el usuario o la biblioteca DllMain. Al desasociar el proceso, la función realiza estos pasos en orden inverso. Llama a DllMain, disminuye el contador interno, llama a destructores, a las funciones de finalización de CRT y a las funciones de atexit registradas, y notifica la finalización a cualquier otra biblioteca. Cuando el contador de datos adjuntos llega a cero, la función devuelve FALSE para indicar a Windows que el archivo DLL se puede descargar. La función _DllMainCRTStartup también se llama durante la adjunción y desvinculación de subprocesos. En estos casos, el código VCRuntime no realiza ninguna otra inicialización o terminación por sí sola y solo llama DllMain a para pasar el mensaje. Si DllMain devuelve FALSE de la asociación de procesos, indicando un error, _DllMainCRTStartup vuelve a llamar a DllMain y pasa DLL_PROCESS_DETACH como argumento de motivo, luego pasa por el resto del proceso de finalización.
Al compilar archivos DLL en Visual Studio, el punto de entrada predeterminado _DllMainCRTStartup que proporciona VCRuntime se vincula de forma automática. No es necesario especificar una función de punto de entrada para el archivo DLL mediante la /ENTRY opción del enlazador (símbolo de punto de entrada).
Nota:
Aunque es posible especificar otra función de punto de entrada para un archivo DLL mediante la /ENTRYopción : enlazador, no se recomienda, ya que la función de punto de entrada tendría que duplicar todo lo que _DllMainCRTStartup hace, en el mismo orden. VCRuntime proporciona funciones que permiten duplicar su comportamiento. Por ejemplo, puede llamar a __security_init_cookie inmediatamente en el adjunto del proceso para admitir la opción /GS (comprobación de seguridad del búfer). Puede llamar a la función _CRT_INIT y pasar los mismos parámetros de la función de punto de entrada, para realizar el resto de las funciones de inicialización o finalización de DLL.
Inicialización de un archivo DLL
Es posible que en el archivo DLL haya código de inicialización que se tenga que ejecutar cuando se cargue el archivo DLL. Para que pueda realizar sus propias funciones de inicialización y finalización de DLL, _DllMainCRTStartup llama a una función denominada DllMain que puede proporcionar.
DllMain debe tener la firma requerida para un punto de entrada de DLL. La función de punto de entrada predeterminada _DllMainCRTStartup llama a DllMain con los mismos parámetros pasados por Windows. De forma predeterminada, si no proporcionas una DllMain función, Visual Studio te proporciona una y la enlaza para que _DllMainCRTStartup siempre tenga algo a lo que llamar. Esto significa que si no necesita inicializar el archivo DLL, no hay nada especial que debe hacer al compilar el archivo DLL.
Esta es la firma que se usa para DllMain:
#include <windows.h>
extern "C" BOOL WINAPI DllMain (
HINSTANCE const instance, // handle to DLL module
DWORD const reason, // reason for calling function
LPVOID const reserved); // reserved
Algunas bibliotecas encapsulan la función DllMain de forma automática. Por ejemplo, en un archivo DLL de MFC estándar, implemente las funciones miembro CWinApp y InitInstance del objeto ExitInstance para realizar la inicialización y la finalización que requiere el archivo DLL. Para obtener más información, consulte la sección Inicializar archivos DLL de MFC normales .
Advertencia
Hay límites importantes sobre lo que se puede hacer de forma segura en un punto de entrada del archivo DLL. Para obtener más información sobre las API específicas de Windows a las que no es seguro llamar en DllMain, vea Procedimientos recomendados generales. Si necesita algo más que la inicialización más sencilla, hazlo en una función de inicialización para la DLL. Puede requerir que las aplicaciones llamen a la función de inicialización después de que se haya ejecutado DllMain y antes de llamar a otras funciones del archivo DLL.
Inicialización de DLL estándar (no basadas en MFC)
Para realizar la inicialización propia en DLL estándar (no basadas en MFC) que usan el punto de entrada _DllMainCRTStartup proporcionado por VCRuntime, el código fuente de la DLL debe contener una función llamada DllMain. En el código siguiente se presenta una estructura básica en la que se muestra el aspecto de la definición de DllMain:
#include <windows.h>
extern "C" BOOL WINAPI DllMain (
HINSTANCE const instance, // handle to DLL module
DWORD const reason, // reason for calling function
LPVOID const reserved) // reserved
{
// Perform actions based on the reason for calling.
switch (reason)
{
case DLL_PROCESS_ATTACH:
// Initialize once for each new process.
// Return FALSE to fail DLL load.
break;
case DLL_THREAD_ATTACH:
// Do thread-specific initialization.
break;
case DLL_THREAD_DETACH:
// Do thread-specific cleanup.
break;
case DLL_PROCESS_DETACH:
// Perform any necessary cleanup.
break;
}
return TRUE; // Successful DLL_PROCESS_ATTACH.
}
Nota:
La documentación anterior de Windows SDK indica que el nombre real de la función de punto de entrada DLL debe especificarse en la línea de comandos del enlazador con la /ENTRY opción . Con Visual Studio, no es necesario usar la /ENTRY opción si el nombre de la función de punto de entrada es DllMain. De hecho, si utiliza la opción /ENTRY y asigna a la función de punto de entrada un nombre distinto de DllMain, el CRT no se inicializa correctamente a menos que la función de punto de entrada realice las mismas llamadas de inicialización que realiza _DllMainCRTStartup.
Inicialización manual de CRT con _CRT_INIT
Al compilar un archivo DLL que use cualquiera de las bibliotecas en tiempo de ejecución de C, para asegurarse de que el CRT se inicializa correctamente, ya sea:
- La función de inicialización debe denominarse
DllMain()y el punto de entrada debe especificarse con la opción-entry:_DllMainCRTStartup@12del enlazador Este es el comportamiento predeterminado al compilar un archivo DLL (solo@12x86 porque esa plataforma usastdcall) o - El punto de entrada del archivo DLL debe llamar explícitamente a
_CRT_INIT()al adjuntar y desasociar procesos. Esto solo es relevante si usa/NOENTRYo tiene un punto de entrada personalizado. No se recomienda usar/NOENTRYni un punto de entrada personalizado, si es posible, porque tendría que duplicar todo el código de inicialización y finalización que_DllMainCRTStartupproporciona, en el mismo orden.
Esto permite que las bibliotecas de C en tiempo de ejecución asignen e inicialicen adecuadamente los datos de C en tiempo de ejecución cuando un proceso o subproceso se vincula al DLL, y que limpien correctamente los datos de C en tiempo de ejecución cuando un proceso se desvincula del DLL, y que los objetos globales de C++ en el DLL se construyan y destruyan adecuadamente.
Todos los ejemplos del SDK de Win32 usan el primer método. Consulte la referencia del programador de Win32 para DllEntryPoint() y la documentación de Visual C++ para DllMain().
DllMainCRTStartup() llama a _CRT_INIT() y _CRT_INIT() llama a la aplicación DllMain(), si existe.
Si desea usar el segundo método y llamar al código de inicialización de CRT usted mismo, en lugar de usar DllMainCRTStartup() y DllMain(), hay dos técnicas:
Si tiene su propio punto de entrada DLL, haga lo siguiente en ese punto de entrada:
a) Use este prototipo (definido en
process.hcuando_DECL_DLLMAINse define) para_CRT_INIT():BOOL WINAPI _CRT_INIT(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved);Para obtener información sobre
_CRT_INIT()los valores devueltos, consulte la documentación deDllEntryPoint; se devuelven los mismos valores.b. En
DLL_PROCESS_ATTACHyDLL_THREAD_ATTACH, llame_CRT_INIT()primero, antes de que se llame a cualquier función en tiempo de ejecución de C o se realicen las operaciones de punto flotante.c. Llame a su propio código de inicialización o finalización de procesos o subprocesos.
d. En
DLL_PROCESS_DETACHyDLL_THREAD_DETACH, llame a_CRT_INIT()al final, después de haber llamado a todas las funciones de tiempo de ejecución de C y completado todas las operaciones de punto flotante.
Asegúrese de pasar a _CRT_INIT() todos los parámetros del punto de entrada; _CRT_INIT() espera esos parámetros, por lo que es posible que las cosas no funcionen de forma confiable si se omiten (en particular, fdwReason es necesario determinar si se necesita la inicialización o la terminación del proceso).
A continuación, se muestra una función de ejemplo de punto de entrada que ilustra cuándo y cómo realizar estas llamadas a _CRT_INIT() en el punto de entrada de la DLL.
BOOL WINAPI DllEntryPoint(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
if (fdwReason == DLL_PROCESS_ATTACH || fdwReason == DLL_THREAD_ATTACH)
if (!_CRT_INIT(hinstDLL, fdwReason, lpReserved))
return(FALSE);
if (fdwReason == DLL_PROCESS_DETACH || fdwReason == DLL_THREAD_DETACH)
if (!_CRT_INIT(hinstDLL, fdwReason, lpReserved))
return(FALSE);
return(TRUE);
}
Nota:
Esto no es necesario si usa DllMain() y -entry:_DllMainCRTStartup@12.
Inicialización de archivos DLL de MFC estándar
Como los archivos DLL de MFC estándar tienen un objeto CWinApp, deben realizar sus tareas de inicialización y finalización en la misma ubicación que una aplicación MFC: en las funciones miembro InitInstance y ExitInstance de la clase derivada de CWinApp de la DLL. Debido a que MFC proporciona una función DllMain que es llamada por _DllMainCRTStartup para DLL_PROCESS_ATTACH y DLL_PROCESS_DETACH, no debería escribir su propia función DllMain. La función DllMain proporcionada por MFC llama a InitInstance cuando se carga la DLL y a ExitInstance antes de que se descargue.
Un archivo DLL de MFC estándar puede realizar el seguimiento de varios subprocesos mediante la llamada a TlsAlloc y TlsGetValue en su función InitInstance. Estas funciones permiten que la DLL realice el seguimiento de datos específicos del subproceso.
En su archivo DLL MFC regular que se vincula dinámicamente a MFC, si está utilizando soporte para MFC OLE, base de datos MFC (o DAO), o sockets de MFC, respectivamente, los DLL de extensión de depuración MFCOversionD.dll, MFCDversionD.dll, y MFCNversionD.dll (donde version es el número de versión) se vinculan automáticamente. Debe llamar a una de las siguientes funciones de inicialización predefinidas para cada uno de estos archivos DLL que está usando en el archivo DLL CWinApp::InitInstance estándar de MFC.
| Tipo de compatibilidad con MFC | Función de inicialización a la que se llama |
|---|---|
| MFC OLE (MFCOversionD.dll) | AfxOleInitModule |
| Base de datos de MFC (MFCDversiónD.dll) | AfxDbInitModule |
| Sockets de MFC (MFCNversiónD.dll) | AfxNetInitModule |
Inicialización de archivos DLL de extensión MFC
Dado que los archivos DLL de extensión MFC no tienen un CWinAppobjeto derivado (como los archivos DLL de MFC normales), debe agregar el código de inicialización y finalización a la DllMain función que genera el Asistente para archivos DLL de MFC.
El asistente proporciona el código siguiente para los archivos DLL de extensión de MFC. En el código, PROJNAME es un marcador de posición para el nombre del proyecto.
#include "pch.h" // For Visual Studio 2017 and earlier, use "stdafx.h"
#include <afxdllx.h>
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
static AFX_EXTENSION_MODULE PROJNAMEDLL;
extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
if (dwReason == DLL_PROCESS_ATTACH)
{
TRACE0("PROJNAME.DLL Initializing!\n");
// MFC extension DLL one-time initialization
AfxInitExtensionModule(PROJNAMEDLL,
hInstance);
// Insert this DLL into the resource chain
new CDynLinkLibrary(Dll3DLL);
}
else if (dwReason == DLL_PROCESS_DETACH)
{
TRACE0("PROJNAME.DLL Terminating!\n");
}
return 1; // ok
}
La creación de un objeto CDynLinkLibrary durante la inicialización permite al archivo DLL de extensión de MFC exportar objetos CRuntimeClass o recursos a la aplicación cliente.
Si va a usar el archivo DLL de extensión MFC desde uno o varios archivos DLL normales de MFC, debe exportar una función de inicialización que cree un objeto CDynLinkLibrary. Se debe llamar a esa función desde cada una de las DLLs regulares de MFC que utilizan la DLL de extensión MFC. Un lugar adecuado para llamar a esta función de inicialización es la función miembro InitInstance del objeto derivado de CWinApp de un DLL de MFC regular antes de usar cualquiera de las clases o funciones exportadas de un DLL de extensión de MFC.
En el DllMain que genera el Asistente para archivos DLL de MFC, la llamada a AfxInitExtensionModule captura las clases en tiempo de ejecución (CRuntimeClass estructuras) del módulo y sus generadores de objetos (COleObjectFactory objetos) para su uso cuando se crea el CDynLinkLibrary objeto. Debe comprobar el valor devuelto de AfxInitExtensionModule; si AfxInitExtensionModule devuelve un valor cero, devuelva cero desde la función DllMain.
Si tu DLL de extensión MFC está vinculada explícitamente a un ejecutable (lo que significa que el ejecutable llama a AfxLoadLibrary para vincularse al DLL), deberías añadir una llamada a AfxTermExtensionModule en DLL_PROCESS_DETACH. Esta función permite que MFC limpie el archivo DLL de extensión de MFC cuando cada proceso se desasocie 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 AfxFreeLibrary). Si el archivo DLL de extensión MFC está vinculado implícitamente a la aplicación, la llamada a AfxTermExtensionModule no es necesaria.
Las aplicaciones que se vinculan de forma explícita a archivos DLL de extensión de MFC deben llamar a AfxTermExtensionModule al liberar la DLL. También deben usar AfxLoadLibrary y AfxFreeLibrary (en lugar de las funciones LoadLibrary y FreeLibrary de Win32) si la aplicación usa varios subprocesos. 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.
Como MFCx0.dll se inicializa por completo cuando se llama a DllMain, puede asignar memoria y llamar a funciones de MFC en DllMain (a diferencia de la versión de MFC de 16 bits).
Los archivos DLL de extensión se pueden encargar del multithreading mediante el control de los casos de DLL_THREAD_ATTACH y DLL_THREAD_DETACH en la función DllMain. Estos casos se pasan a DllMain cuando los subprocesos se conectan y desconectan de la DLL. Llamar a TlsAlloc cuando una DLL se adjunta permite que la DLL mantenga los índices de almacenamiento local de subprocesos (TLS) para cada subproceso conectado a la DLL.
El archivo Afxdllx.h de encabezado contiene definiciones especiales para estructuras usadas en archivos DLL de extensión MFC, como la definición de AFX_EXTENSION_MODULE y CDynLinkLibrary. Debe incluir este archivo de encabezado en la DLL de extensión de MFC.
Nota:
Es importante que no defina ni desdefina los _AFX_NO_XXX macros en pch.h (stdafx.h en Visual Studio 2017 y versiones anteriores). Estas macros solo existen para comprobar si una plataforma de destino determinada admite esa característica o no. Puede escribir el programa para que compruebe estas macros (por ejemplo, #ifndef _AFX_NO_OLE_SUPPORT), pero nunca debe definirlas ni anular su definición.
En Windows SDK, en Uso de almacenamiento local para el subproceso en una biblioteca de vínculos dinámicos, se incluye una función de inicialización de ejemplo que controla el multithreading. Tenga en cuenta que el ejemplo contiene una función de punto de entrada denominada LibMain, pero debe asignar un nombre a esta función DllMain para que funcione con las bibliotecas en tiempo de ejecución de MFC y C.
Consulte también
Creación de archivos DLL de C/C++ en Visual Studio
Punto de entrada de DllMain
Procedimientos recomendados de la biblioteca de vínculos dinámicos