返回

巧用初始化表,三招化解DLL加载失败问题

见解分享

前言

在项目开发过程中,DLL加载失败是一个令人头疼的问题。本文将通过一个经典示例,详细剖析DLL加载失败的根源,并提供三种化解方案,助力您轻松攻克DLL加载难题。

案例分析

以下是一个简化的示例,包含一个EXE工程和两个DLL工程:

  • EXE工程:main.cpp
  • DLL工程1:dll1.cpp
  • DLL工程2:dll2.cpp

main.cpp中包含以下代码:

#include <windows.h>
#include "dll1.h"
#include "dll2.h"

int main()
{
    // 加载dll1.dll
    HMODULE hDll1 = LoadLibrary("dll1.dll");
    if (hDll1 == NULL)
    {
        // 加载失败
        return -1;
    }

    // 获取dll1.dll中的导出函数GetCallCount
    FARPROC pGetCallCount = GetProcAddress(hDll1, "GetCallCount");
    if (pGetCallCount == NULL)
    {
        // 获取导出函数失败
        FreeLibrary(hDll1);
        return -1;
    }

    // 调用dll1.dll中的导出函数GetCallCount
    int callCount = ((int(__stdcall*)(void))pGetCallCount)();

    // 加载dll2.dll
    HMODULE hDll2 = LoadLibrary("dll2.dll");
    if (hDll2 == NULL)
    {
        // 加载失败
        FreeLibrary(hDll1);
        return -1;
    }

    // 获取dll2.dll中的导出函数GetCallCount
    FARPROC pGetCallCount2 = GetProcAddress(hDll2, "GetCallCount");
    if (pGetCallCount2 == NULL)
    {
        // 获取导出函数失败
        FreeLibrary(hDll1);
        FreeLibrary(hDll2);
        return -1;
    }

    // 调用dll2.dll中的导出函数GetCallCount
    int callCount2 = ((int(__stdcall*)(void))pGetCallCount2)();

    // 释放dll1.dll和dll2.dll
    FreeLibrary(hDll1);
    FreeLibrary(hDll2);

    return 0;
}

dll1.cpp中包含以下代码:

#include <windows.h>

// 全局变量
int g_callCount = 0;

// 导出函数
__declspec(dllexport) int GetCallCount()
{
    return g_callCount++;
}

dll2.cpp中包含以下代码:

#include <windows.h>

// 导出函数
__declspec(dllexport) int GetCallCount()
{
    return 0;
}

运行main.cpp后,可能会遇到DLL加载失败的问题。原因在于,dll1.dll中的全局变量g_callCount在dll2.dll中被引用,但dll2.dll没有加载dll1.dll。因此,当dll2.dll中的导出函数GetCallCount被调用时,会因为找不到dll1.dll中的g_callCount而导致DLL加载失败。

解决方法

为了解决DLL加载失败的问题,有以下三种方法:

  1. 在dll2.dll中显式加载dll1.dll
// dll2.cpp
#include <windows.h>

// 显式加载dll1.dll
HMODULE hDll1 = LoadLibrary("dll1.dll");

// 导出函数
__declspec(dllexport) int GetCallCount()
{
    // 如果dll1.dll加载失败,返回0
    if (hDll1 == NULL)
    {
        return 0;
    }

    // 获取dll1.dll中的导出函数GetCallCount
    FARPROC pGetCallCount = GetProcAddress(hDll1, "GetCallCount");

    // 如果获取导出函数失败,返回0
    if (pGetCallCount == NULL)
    {
        return 0;
    }

    // 调用dll1.dll中的导出函数GetCallCount
    int callCount = ((int(__stdcall*)(void))pGetCallCount)();

    // 释放dll1.dll
    FreeLibrary(hDll1);

    return callCount;
}
  1. 在dll1.dll中使用延时加载
// dll1.cpp
#include <windows.h>

// 全局变量
__declspec(dllexport) int g_callCount = 0;

// 导出函数
__declspec(dllexport) int GetCallCount()
{
    return g_callCount++;
}

// dllmain函数
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved)
{
    switch (dwReason)
    {
    case DLL_PROCESS_ATTACH:
        // 加载dll2.dll
        LoadLibrary("dll2.dll");
        break;
    case DLL_PROCESS_DETACH:
        // 释放dll2.dll
        FreeLibrary(GetModuleHandle("dll2.dll"));
        break;
    }

    return TRUE;
}
  1. 在main.cpp中使用初始化表
// main.cpp
#include <windows.h>
#include "dll1.h"
#include "dll2.h"

#pragma data_seg(".CRT$XIU")
__declspec(allocate(".CRT$XIU")) extern const PIMAGE_TLS_CALLBACK __xi_a[];
#pragma data_seg()
#pragma comment(linker, "/merge:.CRT=.rdata")

BOOL WINAPI XiCallback(PVOID hDllHandle, DWORD dwReason, PVOID lpReserved)
{
    switch (dwReason)
    {
    case DLL_PROCESS_ATTACH:
        // 加载dll2.dll
        LoadLibrary("dll2.dll");
        break;
    case DLL_PROCESS_DETACH:
        // 释放dll2.dll
        FreeLibrary(GetModuleHandle("dll2.dll"));
        break;
    }

    return TRUE;
}

int main()
{
    // 加载dll1.dll
    HMODULE hDll1 = LoadLibrary("dll1.dll");
    if (hDll1 == NULL)
    {
        // 加载失败
        return -1;
    }

    // 获取dll1.dll中的导出函数GetCallCount
    FARPROC pGetCallCount = GetProcAddress(hDll1, "GetCallCount");
    if (pGetCallCount == NULL)
    {
        // 获取导出函数失败
        FreeLibrary(hDll1);
        return -1;
    }

    // 调用dll1.dll中的导出函数GetCallCount
    int callCount = ((int(__stdcall*)(void))pGetCallCount)();

    // 释放dll1.dll
    FreeLibrary(hDll1);

    return 0;
}

总结

本文通过一个经典示例,详细剖析了DLL加载失败的根源,并提供了三种化解方案。希望对您解决DLL加载失败问题有所帮助。