Share via


Compatibilidad del vinculador con las DLL de carga retrasada

El enlazador de MSVC admite la carga retrasada de los archivos DLL. Esta característica le alivia la necesidad de usar las funciones de Windows SDK LoadLibrary y GetProcAddress para implementar la carga retrasada del archivo DLL.

Sin carga retrasada, la única manera de cargar un archivo DLL en tiempo de ejecución es mediante LoadLibrary y GetProcAddress; el sistema operativo carga el DLL cuando se carga el archivo ejecutable o el DLL mediante él.

Con carga retrasada, cuando se vincula implícitamente un archivo DLL, el enlazador proporciona opciones para retrasar la carga del DLL hasta que el programa llama a una función en ese DLL.

Una aplicación puede retrasar la carga de un DLL mediante la opción del enlazador /DELAYLOAD (Retrasar la importación de carga) con una función auxiliar. (Microsoft proporciona una implementación de función auxiliar predeterminada). La función auxiliar carga por usted el DLL a petición en tiempo de ejecución mediante una llamada a LoadLibrary y GetProcAddress.

Considere la posibilidad de retrasar la carga de un archivo DLL si:

  • Es posible que el programa no llame a una función del archivo DLL.

  • Es posible que no se llame a una función en el archivo DLL hasta que se finalice la ejecución del programa.

La carga retrasada de un archivo DLL se puede especificar durante la compilación de un proyecto EXE o DLL. Un proyecto DLL que retrasa la carga de uno o varios archivos DLL en sí no debe llamar a un punto de entrada cargado con retraso en DllMain.

Especificación de DLL para retrasar cargas

Se puede especificar de qué archivos DLL deben cargar con retraso mediante la opción del enlazador /delayload:dllname. Si no se piensa usar la propia versión de una función del asistente, se debe vincular también el programa con delayimp.lib (para las aplicaciones de escritorio) o dloadhelper.lib (para las aplicaciones UWP).

Este es un ejemplo sencillo de retraso de la carga de un archivo DLL:

// cl t.cpp user32.lib delayimp.lib  /link /DELAYLOAD:user32.dll
#include <windows.h>
// uncomment these lines to remove .libs from command line
// #pragma comment(lib, "delayimp")
// #pragma comment(lib, "user32")

int main() {
   // user32.dll will load at this point
   MessageBox(NULL, "Hello", "Hello", MB_OK);
}

Genera la versión de depuración del proyecto. Recorra el código usando el depurador y observará que el archivo user32.dll solo se carga cuando realiza la llamada a MessageBox.

Descarga explícita de DLL de carga retrasada

La opción del enlazador /delay:unload permite al código descargar explícitamente un archivo DLL que se ha cargado con retraso. De forma predeterminada, las importaciones cargadas por retraso permanecen en la tabla de direcciones de importación (IAT). Sin embargo, si usa /delay:unload en la línea de comandos del enlazador, la función auxiliar admite la descarga explícita del archivo DLL mediante una llamada a __FUnloadDelayLoadedDLL2 y restablece el IAT a su forma original. Los punteros ahora no válidos se sobrescriben. El IAT es un campo de la estructura ImgDelayDescr que contiene la dirección de una copia del IAT original, si existe.

Ejemplo de descarga de un archivo DLL de carga retrasada

Este ejemplo muestra cómo descargar explícitamente un archivo DLL, MyDll.dll, que contiene una función fnMyDll:

// link with /link /DELAYLOAD:MyDLL.dll /DELAY:UNLOAD
#include <windows.h>
#include <delayimp.h>
#include "MyDll.h"
#include <stdio.h>

#pragma comment(lib, "delayimp")
#pragma comment(lib, "MyDll")
int main()
{
    BOOL TestReturn;
    // MyDLL.DLL will load at this point
    fnMyDll();

    //MyDLL.dll will unload at this point
    TestReturn = __FUnloadDelayLoadedDLL2("MyDll.dll");

    if (TestReturn)
        printf_s("\nDLL was unloaded");
    else
        printf_s("\nDLL was not unloaded");
}

Notas importantes sobre la descarga de un archivo DLL de carga retrasada:

  • Se puede encontrar la implementación de la función __FUnloadDelayLoadedDLL2 en el archivo delayhlp.cpp, en el directorio MSVC include. Para más información, consulte Descripción de la función auxiliar de carga retrasada.

  • El parámetro name de la función __FUnloadDelayLoadedDLL2 debe coincidir exactamente (incluido el caso) que contiene la biblioteca de importación. (Esa cadena también se encuentra en la tabla de importación de la imagen). Se puede ver el contenido de la biblioteca de importación mediante DUMPBIN /DEPENDENTS. Si se prefiere una coincidencia de cadena sin distinción entre mayúsculas y minúsculas, se puede actualizar __FUnloadDelayLoadedDLL2 para usar una de las funciones de cadena de CRT que no distinguen mayúsculas de minúsculas o una llamada a la API de Windows.

Enlace de importaciones de carga retrasada

El comportamiento predeterminado del enlazador es crear una tabla de direcciones de importación enlazable (IAT) para el archivo DLL cargado con retraso. Si el archivo DLL está enlazado, la función auxiliar intenta usar la información enlazada en vez de llamar a GetProcAddress en cada una de las importaciones referenciadas. Si la marca de tiempo o la dirección preferida no coinciden con la del archivo DLL cargado, la función auxiliar asume que la tabla de direcciones de importación enlazada no está actualizada. Continúa como si la IAT no existiera.

Si no existe la intención de enlazar las importaciones de carga retrasada de un archivo DLL, especifique /delay:nobind en la línea de comandos del enlazador. El enlazador no generará la tabla de direcciones de importación enlazada, que ahorra espacio en el archivo de imagen.

Carga de todas las importaciones de DLL de carga retrasada

La función __HrLoadAllImportsForDll, que se define en delayhlp.cpp, indica al enlazador que cargue todas las importaciones de un archivo DLL que se especificó con la opción /delayload del enlazador.

Al cargar todas las importaciones a la vez, se puede centralizar el control de errores en un solo lugar. Se puede evitar el control estructurado de excepciones en torno a todas las llamadas reales a las importaciones. También se evita una situación en la que la aplicación produce un error parcial en un proceso: por ejemplo, si el código auxiliar no puede cargar una importación, después de cargar correctamente otros.

La llamada a __HrLoadAllImportsForDll no cambia el comportamiento de los enlaces y el control de errores. Para más información, consulte Control de errores y notificación.

__HrLoadAllImportsForDll hace una comparación que distingue mayúsculas de minúsculas con el nombre almacenado dentro del propio archivo DLL.

Este es un ejemplo que usa __HrLoadAllImportsForDll en una función llamada TryDelayLoadAllImports para intentar cargar un archivo DLL con un nombre. Usa una función CheckDelayException, para determinar el comportamiento de la excepción.

int CheckDelayException(int exception_value)
{
    if (exception_value == VcppException(ERROR_SEVERITY_ERROR, ERROR_MOD_NOT_FOUND) ||
        exception_value == VcppException(ERROR_SEVERITY_ERROR, ERROR_PROC_NOT_FOUND))
    {
        // This example just executes the handler.
        return EXCEPTION_EXECUTE_HANDLER;
    }
    // Don't attempt to handle other errors
    return EXCEPTION_CONTINUE_SEARCH;
}

bool TryDelayLoadAllImports(LPCSTR szDll)
{
    __try
    {
        HRESULT hr = __HrLoadAllImportsForDll(szDll);
        if (FAILED(hr))
        {
            // printf_s("Failed to delay load functions from %s\n", szDll);
            return false;
        }
    }
    __except (CheckDelayException(GetExceptionCode()))
    {
        // printf_s("Delay load exception for %s\n", szDll);
        return false;
    }
    // printf_s("Delay load completed for %s\n", szDll);
    return true;
}

Puede usar el resultado de TryDelayLoadAllImports para controlar si se llama a las funciones de importación o no.

Notificación y control de errores

Si el programa usa archivos DLL cargados con retraso,se deben controlar los errores de forma sólida. Los errores que se producen mientras se ejecuta el programa producirán excepciones no controladas. Para obtener más información sobre el control y la notificación de errores de carga retrasada de DLL, consulte Control de errores y notificación.

Volcado de memoria de importaciones de carga retrasada

Las importaciones cargadas con retraso se pueden volcar con DUMPBIN /IMPORTS. Estas importaciones se muestran con información ligeramente diferente a la de las importaciones estándar. Se separan en su propia sección de la lista /imports y se etiquetan explícitamente como importaciones con carga retrasada. Si hay información de descarga presente en la imagen, esta se indica. Si hay información de enlace presente, se indica la marca de fecha y hora del archivo DLL de destino junto con las direcciones enlazadas de las importaciones.

Restricciones en archivos DLL de carga retrasada

Hay varias restricciones en la carga retrasada de las importaciones DLL.

  • No se pueden admitir las importaciones de datos. Una solución consiste en administrar uno mismo la importación de datos de forma explícita con LoadLibrary (o usando GetModuleHandle después de saber que el asistente de carga retrasada cargó el archivo DLL) y GetProcAddress.

  • No se admite la carga con retraso Kernel32.dll. Este archivo DLL debe cargarse para que las rutinas auxiliares de carga retrasada funcionen.

  • No se admite el enlace de puntos de entrada reenviados.

  • Un proceso puede tener un comportamiento diferente si un archivo DLL se carga con retraso, en lugar de cargarse en el inicio. Se puede ver si hay inicializaciones por proceso que ocurren en el punto de entrada del archivo DLL cargado por retraso. Entre otros casos, está el TLS (almacenamiento local para el subproceso) estático, que se declara con __declspec(thread), el cual no se administra al cargar la DLL con LoadLibrary. El TLS dinámico, con TlsAlloc, TlsFree, TlsGetValue y TlsSetValue, sigue disponible para su uso en DLL estáticas o de carga retrasada.

  • Reinicie los punteros de las funciones globales estáticas a las funciones importadas después de la primera llamada de cada función. Esto es necesario porque el primer uso de un puntero de función apunta a un código thunk, no a la función cargada.

  • Actualmente no hay una manera de retrasar la carga de procedimientos específicos de un archivo DLL solamente.

  • No se admiten las convenciones de llamada personalizadas (como, por ejemplo, el uso de códigos de condición en las arquitecturas x86). Además, los registros de punto flotante no se guardan en ninguna plataforma. Tenga cuidado si la rutina del asistente personalizada o las rutinas de enlace usan tipos de punto flotante: las rutinas tendrán que guardar y restaurar por completo el estado de punto flotante en las máquinas con convenciones de llamada de registro con parámetros de punto flotante. Tenga cuidado al realizar la carga retrasada del archivo DLL de CRT especialmente si llama a funciones de CRT que tomen parámetros de punto flotante en una pila de procesador de datos numéricos (NDP) en la función de ayuda.

Descripción de la función del asistente para retrasar cargas

La función auxiliar para la carga retrasada compatible con el enlazador es lo que realmente carga el archivo DLL en el runtime. Se puede modificar la función auxiliar para personalizar su comportamiento. En lugar de usar la función auxiliar proporcionada en delayimp.lib, escriba su propia función y vincule al programa. Una función auxiliar sirve a todas las DLL cargadas con retraso. Para obtener más información, consulte Descripción de la función auxiliar de carga retrasada y Desarrollo de su propia función auxiliar.

Consulte también

Creación de archivos DLL de C/C++ en Visual Studio
Referencia del enlazador MSVC