Partilhar via


Entender a função auxiliar de atrasar carregamento

A função auxiliar para carregamento atrasado com suporte de vinculador é o que realmente carrega a DLL em tempo de execução. Você pode modificar a função auxiliar para personalizar seu comportamento. Em vez de usar a função auxiliar fornecida em delayimp.lib, escreva sua própria função e vincule-a ao seu programa. Uma função auxiliar atende a todas as DLLs carregadas com atraso.

Você pode fornecer sua própria versão da função auxiliar se desejar fazer um processamento específico com base nos nomes da DLL ou importações.

A função auxiliar executa estas ações:

  • Verifica o identificador armazenado para a biblioteca para ver se já foi carregado

  • Chama LoadLibrary para tentar carregar a DLL

  • Chama GetProcAddress para tentar obter o endereço do procedimento

  • Retorna à conversão de carregamento de importação de atraso para chamar o ponto de entrada agora carregado

A função auxiliar pode chamar de volta para um gancho de notificação em seu programa após cada uma das seguintes ações:

  • Quando a função auxiliar inicia

  • Pouco antes LoadLibrary é chamado na função auxiliar

  • Pouco antes GetProcAddress é chamado na função auxiliar

  • Se a chamada para LoadLibrary na função auxiliar falhar

  • Se a chamada para GetProcAddress na função auxiliar falhar

  • Depois que a função auxiliar terminar de processar

Cada um desses pontos de gancho pode retornar um valor que altera o processamento normal da rotina auxiliar de alguma maneira, exceto o retorno à conversão de carregamento de importação de atraso.

O código auxiliar padrão pode ser encontrado em delayhlp.cpp e delayimp.h no diretório MSVC include. Ele é compilado em delayimp.lib no diretório MSVC lib para sua arquitetura de destino. Você precisará incluir essa biblioteca em suas compilações, a menos que escreva sua própria função auxiliar.

Convenções de chamada do auxiliar de carregamento de atraso, parâmetros e tipo de retorno

O protótipo para a rotina auxiliar de carregamento de atraso é:

FARPROC WINAPI __delayLoadHelper2(
    PCImgDelayDescr pidd,
    FARPROC * ppfnIATEntry
);

Parâmetros

pidd
Um ponteiro const para um ImgDelayDescr que contém os deslocamentos de vários dados relacionados à importação, um carimbo de data/hora para informações de vinculação e um conjunto de atributos que fornecem informações adicionais sobre o conteúdo do descritor. Atualmente há apenas um atributo, dlattrRva, que indica que os endereços no descritor são endereços virtuais relativos. Para obter mais informações, consulte as declarações em delayimp.h.

Os ponteiros no descritor de atraso (ImgDelayDescr em delayimp.h) usam endereços virtuais relativos (RVAs) para funcionar conforme o esperado em programas de 32 bits e 64 bits. Para usá-los, converta esses RVAs de volta em ponteiros usando a função PFromRva, encontrada em delayhlp.cpp. Você pode usar essa função em cada um dos campos no descritor para convertê-los de volta em ponteiros de 32 ou 64 bits. A função auxiliar de carregamento de atraso padrão é um bom modelo para usar como exemplo.

Para a definição da estrutura PCImgDelayDescr, consulte Definições de estrutura e constante.

ppfnIATEntry
Um ponteiro para um slot na tabela de endereços de importação de carregamento de atraso (IAT). É o slot que é atualizado com o endereço da função importada. A rotina auxiliar precisa armazenar o mesmo valor que retorna neste local.

Valores de retorno esperados

Se a função auxiliar for bem-sucedida, ela retornará o endereço da função importada.

Se a função falhar, ela gera uma exceção estruturada e retorna 0. Há três tipos de exceções possíveis:

  • Parâmetro inválido, que acontece quando os parâmetros presentes em pidd não são especificados corretamente. Trate isso como um erro irrecuperável.

  • LoadLibrary falhou na DLL (biblioteca de vínculo dinâmico) especificada.

  • Falha de GetProcAddress.

Lidar com essas exceções é sua responsabilidade. Para obter mais informações, consulte Tratamento e notificação de erros.

Comentários

A convenção de chamada da função do auxiliar é __stdcall. O tipo de valor retornado não é relevante, portanto FARPROC é usado. Essa função tem vinculação C, o que significa que ela precisa ser encapsulada por extern "C" quando declarada no código C++. A macro ExternC cuida desse wrapper para você.

Para usar a rotina auxiliar como um gancho de notificação, seu código deverá especificar o ponteiro de função apropriado a ser retornado. O código de conversão gerado pelo vinculador usa o valor retornado como destino real da importação e pula diretamente para ele. Se não quiser usar sua rotina auxiliar como um gancho de notificação, armazene o valor retornado da função auxiliar em ppfnIATEntry, no local do ponteiro da função passada.

Exemplo de função de gancho

O código a seguir mostra como implementar uma função de gancho básica.

FARPROC WINAPI delayHook(unsigned dliNotify, PDelayLoadInfo pdli)
{
    switch (dliNotify) {
        case dliStartProcessing :

            // If you want to return control to the helper, return 0.
            // Otherwise, return a pointer to a FARPROC helper function
            // that will be used instead, thereby bypassing the rest
            // of the helper.

            break;

        case dliNotePreLoadLibrary :

            // If you want to return control to the helper, return 0.
            // Otherwise, return your own HMODULE to be used by the
            // helper instead of having it call LoadLibrary itself.

            break;

        case dliNotePreGetProcAddress :

            // If you want to return control to the helper, return 0.
            // If you choose you may supply your own FARPROC function
            // address and bypass the helper's call to GetProcAddress.

            break;

        case dliFailLoadLib :

            // LoadLibrary failed.
            // If you don't want to handle this failure yourself, return 0.
            // In this case the helper will raise an exception
            // (ERROR_MOD_NOT_FOUND) and exit.
            // If you want to handle the failure by loading an alternate
            // DLL (for example), then return the HMODULE for
            // the alternate DLL. The helper will continue execution with
            // this alternate DLL and attempt to find the
            // requested entrypoint via GetProcAddress.

            break;

        case dliFailGetProc :

            // GetProcAddress failed.
            // If you don't want to handle this failure yourself, return 0.
            // In this case the helper will raise an exception
            // (ERROR_PROC_NOT_FOUND) and exit.
            // If you choose, you may handle the failure by returning
            // an alternate FARPROC function address.

            break;

        case dliNoteEndProcessing :

            // This notification is called after all processing is done.
            // There is no opportunity for modifying the helper's behavior
            // at this point except by longjmp()/throw()/RaiseException.
            // No return value is processed.

            break;

        default :

            return NULL;
    }

    return NULL;
}

/*
and then at global scope somewhere:

ExternC const PfnDliHook __pfnDliNotifyHook2 = delayHook;
ExternC const PfnDliHook __pfnDliFailureHook2 = delayHook;
*/

Definições de estrutura atrasar carga e constantes

A rotina de auxiliar de atrasar carga padrão usa várias estruturas para se comunicar com as funções de gancho e durante exceções. Essas estruturas são definidas em delayimp.h. Seguem as macros, typedefs, valores de notificação e falha, estruturas de informações e o tipo de função ponteiro a gancho passado para os ganchos:

#define _DELAY_IMP_VER  2

#if defined(__cplusplus)
#define ExternC extern "C"
#else
#define ExternC extern
#endif

typedef IMAGE_THUNK_DATA *          PImgThunkData;
typedef const IMAGE_THUNK_DATA *    PCImgThunkData;
typedef DWORD                       RVA;

typedef struct ImgDelayDescr {
    DWORD           grAttrs;        // attributes
    RVA             rvaDLLName;     // RVA to dll name
    RVA             rvaHmod;        // RVA of module handle
    RVA             rvaIAT;         // RVA of the IAT
    RVA             rvaINT;         // RVA of the INT
    RVA             rvaBoundIAT;    // RVA of the optional bound IAT
    RVA             rvaUnloadIAT;   // RVA of optional copy of original IAT
    DWORD           dwTimeStamp;    // 0 if not bound,
                                    // O.W. date/time stamp of DLL bound to (Old BIND)
    } ImgDelayDescr, * PImgDelayDescr;

typedef const ImgDelayDescr *   PCImgDelayDescr;

enum DLAttr {                   // Delay Load Attributes
    dlattrRva = 0x1,                // RVAs are used instead of pointers
                                    // Having this set indicates a VC7.0
                                    // and above delay load descriptor.
    };

//
// Delay load import hook notifications
//
enum {
    dliStartProcessing,             // used to bypass or note helper only
    dliNoteStartProcessing = dliStartProcessing,

    dliNotePreLoadLibrary,          // called just before LoadLibrary, can
                                    //  override w/ new HMODULE return val
    dliNotePreGetProcAddress,       // called just before GetProcAddress, can
                                    //  override w/ new FARPROC return value
    dliFailLoadLib,                 // failed to load library, fix it by
                                    //  returning a valid HMODULE
    dliFailGetProc,                 // failed to get proc address, fix it by
                                    //  returning a valid FARPROC
    dliNoteEndProcessing,           // called after all processing is done, no
                                    //  bypass possible at this point except
                                    //  by longjmp()/throw()/RaiseException.
    };

typedef struct DelayLoadProc {
    BOOL                fImportByName;
    union {
        LPCSTR          szProcName;
        DWORD           dwOrdinal;
        };
    } DelayLoadProc;

typedef struct DelayLoadInfo {
    DWORD               cb;         // size of structure
    PCImgDelayDescr     pidd;       // raw form of data (everything is there)
    FARPROC *           ppfn;       // points to address of function to load
    LPCSTR              szDll;      // name of dll
    DelayLoadProc       dlp;        // name or ordinal of procedure
    HMODULE             hmodCur;    // the hInstance of the library we have loaded
    FARPROC             pfnCur;     // the actual function that will be called
    DWORD               dwLastError;// error received (if an error notification)
    } DelayLoadInfo, * PDelayLoadInfo;

typedef FARPROC (WINAPI *PfnDliHook)(
    unsigned        dliNotify,
    PDelayLoadInfo  pdli
    );

Calcular os valores necessários para o carregamento de atraso

A rotina auxiliar de carga de atraso precisa calcular duas informações críticas. Para ajudar, há duas funções embutidas em delayhlp.cpp para calcular essas informações.

  • A primeira, IndexFromPImgThunkData, calcula o índice da importação atual para as três tabelas diferentes – IAT (tabela de endereços de importação), BIAT (tabela de endereços de importação associada) e UIAT (tabela de endereços de importação não associada).

  • A segunda, CountOfImports, conta o número de importações em uma IAT válida.

// utility function for calculating the index of the current import
// for all the tables (INT, BIAT, UIAT, and IAT).
__inline unsigned
IndexFromPImgThunkData(PCImgThunkData pitdCur, PCImgThunkData pitdBase) {
    return pitdCur - pitdBase;
    }

// utility function for calculating the count of imports given the base
// of the IAT. NB: this only works on a valid IAT!
__inline unsigned
CountOfImports(PCImgThunkData pitdBase) {
    unsigned        cRet = 0;
    PCImgThunkData  pitd = pitdBase;
    while (pitd->u1.Function) {
        pitd++;
        cRet++;
        }
    return cRet;
    }

Dar suporte ao descarregamento de uma DLL carregada por atraso

Quando uma DLL carregada com atraso é carregada, o auxiliar de carregamento com atraso padrão verifica se os descritores de carregamento com atraso têm um ponteiro e uma cópia da tabela de endereços de importação original (IAT) no campo pUnloadIAT. Nesse caso, o auxiliar salva um ponteiro em uma lista para o descritor de atraso de importação. Essa entrada permite que a função auxiliar encontre a DLL pelo nome, para ajudar a descarregar essa DLL explicitamente.

Aqui estão as estruturas e funções associadas para descarregar explicitamente uma DLL carregada com atraso:

//
// Unload support from delayimp.h
//

// routine definition; takes a pointer to a name to unload

ExternC
BOOL WINAPI
__FUnloadDelayLoadedDLL2(LPCSTR szDll);

// structure definitions for the list of unload records
typedef struct UnloadInfo * PUnloadInfo;
typedef struct UnloadInfo {
    PUnloadInfo     puiNext;
    PCImgDelayDescr pidd;
    } UnloadInfo;

// from delayhlp.cpp
// the default delay load helper places the unloadinfo records in the
// list headed by the following pointer.
ExternC
PUnloadInfo __puiHead;

A estrutura UnloadInfo é implementada usando uma classe C++ que usa implementações LocalAlloc e LocalFree como seus operator new e operator delete, respectivamente. Essas opções são mantidas em uma lista vinculada padrão que usa __puiHead como o início da lista.

Quando você chama __FUnloadDelayLoadedDLL, ele tenta localizar o nome fornecido na lista de DLLs carregadas. (Uma correspondência exata é necessária). Se encontrado, a cópia do IAT in pUnloadIAT é copiada sobre a parte superior da IAT em execução para restaurar os ponteiros de thunk. Em seguida, a biblioteca é liberada usando FreeLibrary, o registro UnloadInfo correspondente é desvinculado da lista e excluído e TRUE é retornado.

O argumento para a função __FUnloadDelayLoadedDLL2 diferencia maiúsculas de minúsculas. Por exemplo, você pode especificar:

__FUnloadDelayLoadedDLL2("user32.dll");

e não:

__FUnloadDelayLoadedDLL2("User32.DLL");

Para obter um exemplo de descarregamento de uma DLL carregada com atraso, consulte Descarregar uma DLL carregada com atraso explicitamente.

Desenvolver sua própria função auxiliar de carga com atraso

Talvez você queira fornecer sua própria versão da rotina auxiliar de carga com atraso. Em sua própria rotina, você pode fazer um processamento específico baseado nos nomes das DLLs ou importações. Existem duas maneiras de inserir seu próprio código: codifique sua própria função auxiliar, possivelmente com base no código fornecido. Ou, ligue o auxiliar fornecido para chamar sua própria função usando os ganchos de notificação.

Codifique seu próprio auxiliar

Criar sua própria rotina auxiliar é simples. Você pode usar o código existente como um guia para sua nova função. Sua função deve usar as mesmas convenções de chamada que o auxiliar existente. E, se ele retornar às conversões geradas pelo vinculador, ele deverá retornar um ponteiro de função adequado. Depois de criar seu código, você pode atender à chamada ou sair da chamada, como preferir.

Use o gancho de notificação de início de processamento

Provavelmente será mais fácil fornecer um novo ponteiro para uma função de gancho de notificação fornecida pelo usuário que tenha os mesmos valores que o auxiliar padrão para a notificação dliStartProcessing. Nesse ponto, a função de gancho pode, essencialmente, se tornar a nova função auxiliar, porque um retorno bem-sucedido ao auxiliar padrão ignora todo o processamento adicional no auxiliar padrão.

Confira também

Suporte ao vinculador para DLLs carregadas com atraso