zoukankan      html  css  js  c++  java
  • 第20章 DLL高级技术(2)

    20.3 延迟载入DLL

    20.3.1延迟载入的目的

    (1)如果应用程序使用了多个DLL,那么它的初始化可能比慢,因为加载程序要将所有必需的DLL映射到进程的地址空间。→利用延迟加载可将载入过程延伸到执行过程时

    (2)如果我们的代码调用的操作系统的一个新函数,但程序又试图在老版本的操作系统运行。这时程序会被终止。这时可以有两种解决方法:一种是判断利用GetVersionEx操作系统,在老系统中使用旧函数而不使用新函数。另一种是通过延迟载入,通过SEH来捕获异

    20.3.2延迟载入技术

    (1)延迟载入是针对隐式链接DLL的

    (2)一个导出了字段(如即全局变量)的DLL是无法延迟载入的

    (3)Kernel32.dll模块是无法延迟载入的,因为必须载入该模块才能调用LoadLibrary和GetProcAddress。

    (4)不应在DllMain入口函数中调用一个延迟载入的函数,这可能导致程序崩溃

    20.3.3 使用方法及相关说明

     (1)使用方法

      ①常规建立DLL和可执行模块

      ②链接可执行模块时,添加延迟加载开关

      A.为了延迟加载Dll,还需要在解决方案的该项目“属性”->“配置属性”->“链接器”->“输入”->“延迟加载的Dll”中输入MyDll.dll(注意/DelayLoad:MyDll.dll这个开关不能用#pragma comment(linker, "/DelayLoad:MyDll.dll")来设置。

      B.增加/Lib:DelayImp.lib开关:这可以用#include <delayimp.h>和#pragma comment(lib, "Delayimp.lib")。这个开关告诉链接器将delayimp中的__delayLoadHelper2函数嵌入到我们的可执行文件中。

      C.如果需要手动卸载Dll,则需在可选“链接器”→“高级”中指定“卸载延迟加载的DLL”中输入“MyDll.dll”。但要注意两点:一是卸载时只能调用__FUnloadDelayLoadedDll2(PCSTR szDll)函数,而不能调用FreeLibrary。二是该卸载操作是可选的,不是必需的,只有在需要手动卸载Dll时才设置。

     (2)/Lib:DelayImp.lib此时链接器将执行下列的事项

      ①将MyDll.dll从.exe的导入段去除,这样操作系统就不会隐式载入该DLL

      ②在.exe中嵌 入一个新的延迟载入段(Delay Import Section,称为.didata)表示要从MyDll.dll中导入哪些函数。

      ③对延迟载入函数的调用会跳转到__delayLoadHelper2函数,来完成对延迟载入函数的解析。

    (3)其他说明

      ①应用程序对延迟载入函数的调用实际上会调用__delayLoadHelper2函数,该函数会引用那个特殊的延迟载入段,并用LoadLibrary和GetProcAddress得到延迟载入函数的地址,然后修改对该函数的调用,这样以后将直接调用该延迟载入函数。

      ②同一个DLL中的其他函数仍然必须在第一次被调用的时修复,即其他函数第1次调用时仍然会LoadLibrary+GetProcAddess并修复函数地址。

     【Export/ImportDelay程序】演示延迟载入Dll

    注意图中第1次列出模块中没有20_ExportDelay.dll,而第2次有且执行了其DllMain函数

    //Dll源文件

    /************************************************************************
    Module: ExportDelay.h
    ************************************************************************/
    #pragma  once
    
    #ifdef DELAYLIB_EXPORT
    //MYLIB_EXPORT必须在Dll源文件包含该头件前被定义
    #define DELAYAPI extern "C" __declspec(dllexport)
    //本例中所有的函数和变量都会被导出
    #else
    #define DELAYAPI extern "C" __declspec(dllimport)
    #endif
    
    //定义要导出的函数的原型
    DELAYAPI int Func_A(int iVal);
    DELAYAPI int Func_B(int iVal1, int iVal2);
    DELAYAPI int Func_C(int iVal1, int iVal2,int iVal3);
    #include <windows.h>
    #include <tchar.h>
    #include <locale.h>
    
    //在这个DLL源文件定义要导出的函数和变量
    #define DELAYLIB_EXPORT   //这个源文件中须定义这个宏,以告诉编译器函数要
    //__declspect(dllexport),这个宏须在包含MyLib.h
    //之前被定义
    
    #include "ExportDelay.h"
    
    BOOL APIENTRY DllMain(HMODULE hDllHandle, DWORD dwReason, LPVOID lpreserved)
    {
        static TCHAR pModuleName[MAX_PATH] = {};
    
        switch (dwReason)
        {
        case DLL_PROCESS_ATTACH:
            _tsetlocale(LC_ALL, _T("chs"));
            GetModuleFileName(hDllHandle, pModuleName, MAX_PATH);
            _tprintf(_T("进程[%u]调用线程[0x%X]加载DLL[0x%08X]:%s
    "),
                     GetCurrentProcessId(),GetCurrentThreadId(),hDllHandle,pModuleName);
            break;
    
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
            break;
    
        case DLL_PROCESS_DETACH:
            //GetModuleFileName(hDllHandle, pModuleName, MAX_PATH);
            _tprintf(_T("进程[%u]调用线程[0x%X]卸载DLL[0x%08X]:%s
    "),
                     GetCurrentProcessId(), GetCurrentThreadId(), hDllHandle, pModuleName);
            break;
        }
        return TRUE;
    }
    
    int Func_A(int iVal)
    {
        return iVal;
    }
    
    int Func_B(int iVal1, int iVal2)
    {
        return iVal1 + iVal2;
    }
    
    int Func_C(int iVal1, int iVal2, int iVal3)
    {
        return iVal1 + iVal2 + iVal3;
    }

    //exe源文件

    #include <windows.h>
    #include <tchar.h>
    #include <strsafe.h>
    #include <locale.h>
    #include <Psapi.h> //For EnumProcessModules函数
    #include "../../Chap20/20_ExportDelay/ExportDelay.h"
    
    //#include <delayimp.h>  
    //#pragma comment(lib, "Delayimp.lib")  //Vs2013这两行可要可不要了。
    
    #pragma comment(lib,"psapi")
    #pragma comment(lib,"../../Debug/20_ExportDelay.lib")
    //1、延迟加载是针对Dll的隐式链接的
    //2、为了延迟加载Dll,还需要在解决方案的该项目“属性”->“配置属性”->
    //“链接器”->“输入”->“延迟加载的Dll”中输入20_ExportDelay.dll
    
    void PrintModules(DWORD dwProcessID){
        HMODULE* phMods = NULL;
        HANDLE hProcess = NULL;
        DWORD dwNeeded = 0;
        TCHAR szModName[MAX_PATH] = {};
    
        hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
                               FALSE, dwProcessID);
        if (NULL == hProcess){
            _tprintf(_T("不能打开进程[ID:0x%X],错误码:[%u]
    "), dwProcessID,
                     GetLastError());
            return;
        }
    
        EnumProcessModules(hProcess, NULL, 0, &dwNeeded);
        phMods = (HMODULE*)malloc(dwNeeded);
    
        if (EnumProcessModules(hProcess,phMods,dwNeeded,&dwNeeded)){
            for (DWORD i = 0; i < (dwNeeded / sizeof(HMODULE));i++){
                ZeroMemory(szModName, MAX_PATH*sizeof(TCHAR));
                if (GetModuleFileNameEx(hProcess,phMods[i],szModName,MAX_PATH)){
                    _tprintf(_T("	(0x%08X)	%s
    "), phMods[i], szModName);            
                }
            }
        }
    
        free(phMods);
        CloseHandle(hProcess);
    }
    
    int _tmain(){
    
        _tsetlocale(LC_ALL, _T("chs"));
        //显示进程中己加载的模块(此时不含(ExportDelay.dll)
        PrintModules(GetCurrentProcessId());
        _tsystem(_T("PAUSE"));
    
        int iVal1 = 10;
        int iVal2 = 20;
        int iVal3 = 30;
        _tprintf(_T("Func_A(%d)=%d
    "),iVal1,Func_A(iVal1));
        _tprintf(_T("Func_B(%d,%d)=%d
    "), iVal1,iVal2,Func_B(iVal1,iVal2));
        _tprintf(_T("Func_C(%d,%d,%d)=%d
    "), iVal1,iVal2,iVal3, Func_C(iVal1,iVal2,iVal3));
    
        PrintModules(GetCurrentProcessId());
        _tsystem(_T("PAUSE"));
    
        return 0;
    }

    (4)卸载延迟载入的DLL

      ①构建EXE时必须指定一个额外的链接器开关(/Delay:unload)。该开关告诉链接器在文件中嵌入另一个段。这个段包含用来记录己经调用过的函数。__FUnloadDelayLoadedDll2会引用这个段,将该DLL所有的函数地址重置,然后调用FreeLibrary来卸载该DLL。添加这个开关的方法:项目属性→配置属性→链接器→高级→“卸载延迟加载的DLL”中选择“是(/Delay:unload)”。

      ②基于以上原因,只能用__FUnloadDelayLoadedDLL2来卸载,而不能手工调用FreeLibrary。(注意,调用__FUnloadDelayLoadedDLL2时只需传入DLL的名称,而不必包含路径)。

      ③如果不打算卸载一个延迟载入的DLL,就不必指定/Delay:unload链接器开关,这样可减小EXE的大小(因为少了一个段)

    20.3.4 延时载入时的异常处理

    (1)异常码和异常过滤函数

       //只处理后面的两个异常码,其余异常则将异常向外抛
       LONG WINAPI  DelayLoadDllExceptionFilter(PEXCEPTION_POINTERS pep) {
    
       //假定是认识的异常码,则后面会自行处理这些异常
       LONG lDisposition = EXCEPTION_EXECUTE_HANDLER; 
    
       //如果是延迟加载错误,则ExceptionInformation[0]指向一个DelayLoadInfo结构体,其中包含了额外的错误信息。
       PDelayLoadInfo pdli =
          PDelayLoadInfo(pep->ExceptionRecord->ExceptionInformation[0]);
    
       // Create a buffer where we construct error messages
       char sz[500] = { 0 };
     
       switch (pep->ExceptionRecord->ExceptionCode) {
    
       case VcppException(ERROR_SEVERITY_ERROR, ERROR_MOD_NOT_FOUND):
    
          // 找不到指定的DLL模块
          StringCchPrintfA(sz, _countof(sz), "Dll not found: %s", pdli->szDll);
          break;
    
       case VcppException(ERROR_SEVERITY_ERROR, ERROR_PROC_NOT_FOUND):
    
          // 在DLL模块中找不到指定的函数
          if (pdli->dlp.fImportByName) { //名称导入
             StringCchPrintfA(sz, _countof(sz), "Function %s was not found in %s",pdli->dlp.szProcName, pdli->szDll);
          } else {   //序号导入
             StringCchPrintfA(sz, _countof(sz), "Function ordinal %d was not found in %s",pdli->dlp.dwOrdinal, pdli->szDll);
          }
          break;
    
       default:
          //其余异常,则向外抛。
          lDisposition = EXCEPTION_CONTINUE_SEARCH; 
          break;
       }
    
       if (lDisposition == EXCEPTION_EXECUTE_HANDLER) {
          // 如果是我们认识的那两个异常,则弹出错误提示
          chMB(sz);
       }
    
       return(lDisposition);
    }

    (2)DelayLoadInfo结构体

    字段

    描述

    DWORD    cb

    结构体大小

    PCImgDelayDescr pidd

    原始数据,指向嵌入在模块中的延迟载入段,其中包含了延迟载入DLL和延迟载入函数的列表

    FARPROC* ppfn

    若函数查找成功,其地址保存在这个成员中

    PTCSTR szDll

    试图载入的DLL的名字

    DelayLoadProc dlp

    试图查打的函数的名字(可能通过序号或名称来)。DelayLoadProc是个联合体,用来表示序号或名称。

    HMODULE hmodCur

    DLL被载入的内存地址

    FARPROC pfnCur

    想要查找的函数的地址,为__delayLoadHelper2函数内部使用

    DWORD dwLastError

    错误码,为__delayLoadHelper2函数内部使用

    备注:①该结构体由__delayLoadHelper2函数分配和初始化,在函数动态载入DLL并取得被调用函数的地址过程中,它会填写结构中的成员。

    ②在我们的SHE过滤器内部,可以获得该结构中体(见上面的代码)

    20.3.5 延迟载入的钩子函数

    (1)作用:用于接收__delayLoadHelper2的进度通知和错误通知。

    (2)使用方法

      ①编写钩子函数:FARPROC WINAPI DliHook(unsigned dliNotify, PDelayLoadInfo pdli) {…};

      ②将钩子函数的地址传给DelayImp.lib库中的两个全局变量(类型为PfnDliHook)。这两个变量被初始化为NULL,即默认__delayLoadHelper2不调用任何钩子函数。为了执行我们的钩子函数,要将钩子函数的地址赋值给这两个变量(分别为__pfnDliNotifyHook2和__pfnDliFailureHook2)。(如PfnDliHook __pfnDliNotifyHook2 = DliHook);

      ③__delayLoadHelper2实际上用到了两个回调函数,一个用来报告通知,另一个用来报告失败。两个函数原型完全相同。

    【DelayLoadApp程序】演示延迟载入DLL、异常处理和钩子函数的使用

     //Dll端文件

    /************************************************************************
    Module: DelayLoadLib.h
    Notices:Copyright(c) 2008 Jeffrey Richter & Christophe Nasarre
    ************************************************************************/
    
    #ifndef DELAYLOADLIBAPI
    #define DELAYLOADLIBAPI  extern "C" __declspec(dllimport)
    #endif // !DELAYLOADLIBAPI
    
    //////////////////////////////////////////////////////////////////////////
    DELAYLOADLIBAPI  int fnLib();
    DELAYLOADLIBAPI  int fnLib2();
    
    ///////////////////////////////////文件结束////////////////////////////////////

    //DelayLoadLib.cpp

    /************************************************************************
    Module: DelayLoadLib.cpp
    Notices:Copyright(c) 2008 Jeffrey Richter & Christophe Nasarre
    ************************************************************************/
    
    #include <tchar.h>
    
    #define  DELAYLOADLIBAPI extern "C" __declspec(dllexport)
    #include  "DelayLoadLib.h"
    
    //////////////////////////////////////////////////////////////////////////
    int fnLib(){
        return (321);
    }
    
    //////////////////////////////////////////////////////////////////////////
    int fnLib2(){
        return (123);
    }
    
    //////////////////////////////文件结束/////////////////////////////////////

    //exe端文件

    /************************************************************************
    Module: DelayLoadApp.cpp
    Notices:Copyright(c) 2008 Jeffrey Richter & Christophe Nasarre
    ************************************************************************/
    
    #include "../../CommonFiles/CmnHdr.h"
    #include <tchar.h>
    #include <strsafe.h>
    
    //////////////////////////////////////////////////////////////////////////
    #include <delayimp.h>  //For error handling & advanced features
    #include "../20_DelayLoadLib/DelayLoadLib.h"
    #pragma comment(lib,"../../Debug/20_DelayLoadLib.lib")
    
    //////////////////////////////////////////////////////////////////////////
    ////要延迟延载入的模块名称(20_DelayLoadLib.dll)
    TCHAR g_szDelayLoadModuleName[] = TEXT("20_DelayLoadLib");
    
    //////////////////////////////////////////////////////////////////////////
    //延迟载入异常过滤函数前向声明
    LONG WINAPI DelayLoadDllExceptionFilter(PEXCEPTION_POINTERS pep);
    
    ////////////////////////////////////////////////////////////////////////
    //判断指定的DLL模块是否被加载到进载的地址空间
    void IsModuleLoaded(PCTSTR pszModuleName){
        HMODULE hmod = NULL;// GetModuleHandle(pszModuleName);
        char sz[100];
    #ifdef UNICODE
        StringCchPrintfA(sz, _countof(sz), "模块 "%S" %s加载.",
                         pszModuleName, (hmod == NULL) ? "" : "");
    #else
        StringCchPrintfA(sz, _countof(sz), "模块 "%s" %s加载.",
                         pszModuleName, (hmod == NULL) ? "" : "");
    #endif
    
        //chMB(sz);
    }
    
    //////////////////////////////////////////////////////////////////////////
    int APIENTRY _tWinMain(HINSTANCE hInstExe, HINSTANCE, PTSTR, int)
    {
        //捕获延迟加载时的异常
        __try{
            int x = 0;
            
            //调试模式下,可以通过菜单"调试"->"窗口"->"模块"来查看指定的DLL是否被加载
            IsModuleLoaded(g_szDelayLoadModuleName);
            x = fnLib(); //尝试调用延迟加载函数
            
            //调试下的模块窗口可查看到DLL是否被加载
            IsModuleLoaded(g_szDelayLoadModuleName);
            x = fnLib2();//尝试调用延迟加载函数
    
            //卸载延迟加载DLL
            //注意:动态库的名称必须正好匹配 /DelayLoad:(DllName)
            PCSTR pszDll = "20_DelayLoadLib.dll";
    
            //要以下函数生效,必须在“链接器”->"高级"->"卸载延迟加载的DLL"中选择
            //"/Delay:Unload"
            __FUnloadDelayLoadedDLL2(pszDll);
    
            //调试下的模块窗口可查看到DLL是否被加载
            IsModuleLoaded(g_szDelayLoadModuleName);
            x = fnLib();
    
            //调试下的模块窗口可查看到DLL是否被加载
            IsModuleLoaded(g_szDelayLoadModuleName);
    
        }
        __except (DelayLoadDllExceptionFilter(GetExceptionInformation())){
            //这里什么也不做,线程将正常的运行
        }
    
        //这里可以做些其他事...
    
        return 0;
    }
    
    LONG WINAPI DelayLoadDllExceptionFilter(PEXCEPTION_POINTERS pep){
        //假设异常为己知的
        LONG lDisposition = EXCEPTION_EXECUTE_HANDLER;
        
        //如果出现延时加载错误,ExceptionInformation[0]指向一个DelayLoadInfo结构体
        //其中包含出错的信息
        PDelayLoadInfo pdli = (PDelayLoadInfo)pep->ExceptionRecord->ExceptionInformation[0];
    
        //创建一个错误信息的缓冲区
        char sz[100] = { 0 };
    
        switch (pep->ExceptionRecord->ExceptionCode)
        {
            //#define VcppException(sev,err)  ((sev) | (FACILITY_VISUALCPP<<16) | err)
        case VcppException(ERROR_SEVERITY_ERROR,ERROR_MOD_NOT_FOUND):
            //运行时无法找到Dll模块
            StringCchPrintfA(sz, _countof(sz), "找不到Dll:%s", pdli->szDll);
            break;
    
        case VcppException(ERROR_SEVERITY_ERROR, ERROR_PROC_NOT_FOUND):
            //找到了Dll模块,但没有包含指定的函数
            if (pdli->dlp.fImportByName){
                StringCchPrintfA(sz, _countof(sz), "Dll(%s)中找不到指定的函数%s", pdli->szDll,
                                 pdli->dlp.szProcName);
            } else{
                StringCchPrintfA(sz, _countof(sz), "Dll(%s)中找不到指定序号(%d)的函数", pdli->szDll,
                                 pdli->dlp.dwOrdinal);
            }
            break;
    
        default:
            //不认识的异常
            lDisposition = EXCEPTION_CONTINUE_SEARCH;
            break;
        }
        
        if (lDisposition == EXCEPTION_EXECUTE_HANDLER){
            chMB(sz);
        }
        return (lDisposition);
    }
    
    //////////////////////////////////////////////////////////////////////////
    //DliHook函数处理的框架
    FARPROC WINAPI DliHook(unsigned dliNotify, PDelayLoadInfo pdli){
        FARPROC  fp = NULL; //默认的返回值
    
        //注意:pdli指向到目前为止处理的结果
        switch (dliNotify)
        {
        case dliFailLoadLib: //调用LoadLibrary函数失败
            //这里可以自己再调用LoadLibrary并返回HMODULE
            //假如返回NULL,__delayLoadHelper2会抛出一个ERROR_MOD_NOT_FOUND异常
            fp = (FARPROC)(HMODULE)NULL;
            break;
    
        case dliNotePreGetProcAddress: 
            //会在GetProcAddress函数前被调用
            //返回NULL可以使__delayLoadHelper2调用GetProcAddress或可以
            //自己调用GetProcAddress并返回其地址
            fp = (FARPROC)NULL;
            break;
    
        case dliFailGetProc:
            //当GetProcAddress调用失败时,被执行
            //这里可以自己调用GetProcAddress并返回地址,如果返回NULL
            //__delayLoadHelper2将抛出ERROR_PROC_NOT_FOUND异常
            fp = (FARPROC)NULL;
            break;
    
        case dliNoteEndProcessing:
            //一个简单的__delayLoadHelper2处理己完成
            //这里可以检查DelayLoadInfo结构体,如果有需要,也可以抛出一个异常
            break;
        }
        return (fp);
    }
    
    //告诉__delayLoadHelper2来调用我们的DliHook函数
    PfnDliHook __pfnDliNotifyHook2 = DliHook;
    PfnDliHook __pfnDliFailureHook2 = DliHook;

    //resource.h

    //{{NO_DEPENDENCIES}}
    // Microsoft Visual C++ 生成的包含文件。
    // 供 20_DelayLoadApp.rc 使用
    //
    #define IDI_DELAYLOADAPP                      101
    
    // Next default values for new objects
    // 
    #ifdef APSTUDIO_INVOKED
    #ifndef APSTUDIO_READONLY_SYMBOLS
    #define _APS_NEXT_RESOURCE_VALUE        102
    #define _APS_NEXT_COMMAND_VALUE         40001
    #define _APS_NEXT_CONTROL_VALUE         1001
    #define _APS_NEXT_SYMED_VALUE           101
    #endif
    #endif

    //20_DelayLoadApp.rc

    // Microsoft Visual C++ generated resource script.
    //
    #include "resource.h"
    
    #define APSTUDIO_READONLY_SYMBOLS
    /////////////////////////////////////////////////////////////////////////////
    //
    // Generated from the TEXTINCLUDE 2 resource.
    //
    #include "winres.h"
    
    /////////////////////////////////////////////////////////////////////////////
    #undef APSTUDIO_READONLY_SYMBOLS
    
    /////////////////////////////////////////////////////////////////////////////
    // 中文(简体,中国) resources
    
    #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
    LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
    
    #ifdef APSTUDIO_INVOKED
    /////////////////////////////////////////////////////////////////////////////
    //
    // TEXTINCLUDE
    //
    
    1 TEXTINCLUDE 
    BEGIN
        "resource.h"
    END
    
    2 TEXTINCLUDE 
    BEGIN
        "#include ""winres.h""
    "
        ""
    END
    
    3 TEXTINCLUDE 
    BEGIN
        "
    "
        ""
    END
    
    #endif    // APSTUDIO_INVOKED
    
    
    /////////////////////////////////////////////////////////////////////////////
    //
    // Icon
    //
    
    // Icon with lowest ID value placed first to ensure application icon
    // remains consistent on all systems.
    IDI_DELAYLOADAPP               ICON                    "DelayLoadApp.ico"
    #endif    // 中文(简体,中国) resources
    /////////////////////////////////////////////////////////////////////////////
    
    
    
    #ifndef APSTUDIO_INVOKED
    /////////////////////////////////////////////////////////////////////////////
    //
    // Generated from the TEXTINCLUDE 3 resource.
    //
    
    
    /////////////////////////////////////////////////////////////////////////////
    #endif    // not APSTUDIO_INVOKED
  • 相关阅读:
    面向对象高级
    面向对象基础总结
    面向对象基础剩余
    组合和封装
    继承与派生
    面向对象
    4.10
    4.9
    常用模块
    【转】CentOS: 开放80、22、3306端口操作
  • 原文地址:https://www.cnblogs.com/5iedu/p/5014798.html
Copyright © 2011-2022 走看看