DLL 延时加载与生成导入库

版权声明:署名-非商业性使用-相同方式共享

@@ Tags: win32;dll;延时加载
@@ Date: 2021-8-22

导入库原理

  1. 导入库是由 dllexe 工程生成 (必须包含至少一个导出符号)。
  2. 导入库经过 连接器 连接生成二进制文件后, 会在目标二进制文件中添加 导入段
  3. 运行执行文件时 加载程序 根据 导入段 中的 DLL名称导入符号 来加载动态库,并载入导入符号。
    即:将该符号的 相对虚拟地址 + DLL基址(映射到进程地址空间的虚拟地址)计算出实际符号的虚拟地址,后存储至导入段。

通过 DLL 生成导入库

在很多时候,我们会在没有头文件导入库的情况下使用某些私有DLL,如果不想使用 LoadLibrary()GetProcAddress() 不妨看看这个办法。

它可以使我们从动态库中提取所有的导出符号来制作 .lib 的导入库,然后搭配自制的 .h 文件,解锁 隐式链接 DLL。

  1. 制作 .def 文件
# CoreFoundation.dll
dumpbin /exports CoreFoundation.dll > CoreFoundation.txt
echo LIBRARY COREFOUNDATION > CoreFoundation.def
echo EXPORTS >> CoreFoundation.def
for /f "skip=19 tokens=4" %A in (CoreFoundation.txt) do echo %A >> CoreFoundation.def

# MobileDevice.dll
dumpbin /exports MobileDevice.dll > MobileDevice.txt
echo LIBRARY MOBILEDEVICE > MobileDevice.def
echo EXPORTS >> MobileDevice.def
for /f "skip=19 tokens=4" %A in (MobileDevice.txt) do echo %A >> MobileDevice.def
  1. 通过 .def 生成 .lib 导入库
lib /def:CoreFoundation.def /out:CoreFoundation.lib /machine:x86
lib /def:MobileDevice.def /out:MobileDevice.lib /machine:x86

lib /def:CoreFoundation.def /out:CoreFoundation.lib /machine:x64
lib /def:MobileDevice.def /out:MobileDevice.lib /machine:x64

if your DLL is 64 bit, you may use /machine:x64 or /machine:x86-64 depending on your environment setup.

https://stackoverflow.com/questions/9946322/how-to-generate-an-import-library-lib-file-from-a-dll

延时加载

延时加载主要可以解决两个问题:

  1. 减少启动时间, 降低资源占用
    • 传统的DLL加载方式会在程序启动时立即加载所有依赖的DLL,这可能导致程序启动时间变长。
    • 不是所有DLL都在程序运行期间始终被需要。通过延时加载,可以在实际需要时再加载它,从而更有效地利用系统资源。
  2. 应用程序可以自行决定加载路径
    • 在程序运行前,并不知道所依赖的DLL是否可用,或者需要根据程序的运行环境来确定加载哪个版本的DLL。

在 MSVC 系列编译器中启用延时加载需要指定连接器选项: /DELAYLOAD:"CoreFoundation.dll"(连接器->输入)。

注意: 延迟加载 不支持导出字段(如 全局变量、常量等)。

下面是延时加载的异常处理示例:

int main()
{
    __try
    {
        CFArrayGetTypeID(); // 首次调用 DLL 中导出函数时将触发DLL加载
    }
    __except (DelayLoadDllExceptionFilter(GetExceptionInformation()))
    {
        // Nothing
    }
}

LONG WINAPI DelayLoadDllExceptionFilter(PEXCEPTION_POINTERS pep)
{
    PDelayLoadInfo pdli = PDelayLoadInfo(pep->ExceptionRecord->ExceptionInformation[0]);

    switch (pep->ExceptionRecord->ExceptionCode)
    {
    case VcppException(ERROR_SEVERITY_ERROR, ERROR_MOD_NOT_FOUND): // 找不到指定的DLL模块
        printf("Dll not found: %s", pdli->szDll);
        break;

    case VcppException(ERROR_SEVERITY_ERROR, ERROR_PROC_NOT_FOUND): // 在DLL模块中找不到指定的函数
        if (pdli->dlp.fImportByName) // 名称导入
        { 
            printf("Function %s was not found in %s",pdli->dlp.szProcName, pdli->szDll);
        } 
        else  //序号导入
        {   
            printf("Function ordinal %d was not found in %s",pdli->dlp.dwOrdinal, pdli->szDll);
        }
        break;
    }

    return EXCEPTION_EXECUTE_HANDLER;    // 已处理, 程序恢复执行
    return EXCEPTION_CONTINUE_SEARCH     // 抛到上一层处理
    return EXCEPTION_CONTINUE_EXECUTION; // 执行下一个异常
}

卸载延时加载的DLL

要启用卸载延时加载功能必须指定连接器选项: /DELAY:UNLOAD (连接器->高级)。

调用函数: __FUnloadDelayLoadedDLL2(dllname), 它位于头文件: delayimp.h

使用该函数时要注意, dllname 可能与目标dll的名称不一致, 该名称与 .def 中的 LIBRARY 后面的名称 一致。

这可以调试跟踪到函数内部, 由于优化的原因我们已经看不到了, 所以只能在返汇编中跟踪查看:

DLL生成导入库&延时加载.延时加载.png

改变DLL搜索路径

@@ Tags: 动态库;搜索顺序;加载顺序
@@ Refer: 《WINDOWS 核心编程》第19章: DLL基础.运行可执行模块

默认情况下 Windows的DLL搜索顺序如下:

  1. 包含可执行文件的目录。
  2. Windows的系统目录,该目录可以通过 GetSystemDirectory() 得到。
  3. 16位的系统目录,即 Windows 目录中的 System 子目录。
  4. Windows目录,该目录可以通过 GetWindowsDirectory() 得到。
  5. 进程的当前目录。
  6. PATH 环境变量中所列出的目录。

影响搜索顺序的因素

略,参考:

https://docs.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order

SetDllDirectory()

该函数会对影响 所有子进程LoadLibrary()LoadLibraryEx() 及延时加载DLL的操作, 并且会禁用 "安全DLL搜索模式", 当指定目录在搜索路径中时.

After calling SetDllDirectory, the standard DLL search path is:

  1. The directory from which the application loaded.
  2. The directory specified by the lpPathName parameter.
  3. The system directory. Use the GetSystemDirectory function to get the path of this directory. The name of this directory is System32.
  4. The 16-bit system directory. There is no function that obtains the path of this directory, but it is searched. The name of this directory is System.
  5. The Windows directory. Use the GetWindowsDirectory function to get the path of this directory.
  6. The directories that are listed in the PATH environment variable.

https://docs.microsoft.com/zh-cn/windows/win32/api/winbase/nf-winbase-setdlldirectorya?redirectedfrom=MSDN

但需要注意的是

注册表方式

Each application can now store it own path the registry under the following key:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths

The use the application path, set a key for your application, using ONE.EXE from the example above:
HKEY_LOCAL_MACHINE...\CurrentVersion\App Paths\ONE.exe

Set the (Default) value to the full path of your executable, for example:

C:\Program Files\ONE\ONE.exe

Add a sub-key named Path

http://www.codeguru.com/Cpp/W-P/dll/article.php/c99