如何在C语言中正确引用DLL文件?

99ANYc3cd6
预计阅读时长 21 分钟
位置: 首页 C语言 正文

场景设定

  1. DLL 名称: myutils.dll
  2. DLL 提供的功能:
    • 一个函数,接收两个整数,返回它们的和。
    • 一个函数,接收一个字符串,将其打印到控制台。
  3. 调用程序: 一个控制台应用程序,它将加载 myutils.dll 并调用其中的函数。

第一步:创建 DLL (使用 Visual Studio)

这里我们使用 Visual Studio 来创建 DLL 项目,如果你使用 MinGW (GCC) 或其他工具链,语法会略有不同,但核心概念是一样的。

c 引用c语言dll
(图片来源网络,侵删)

1. 创建 DLL 项目

  1. 打开 Visual Studio。
  2. 选择 "创建新项目"。
  3. 搜索并选择 "动态链接库 (DLL)" 项目模板。
  4. 给项目命名,MyUtils,并点击 "创建"。

2. 编写 DLL 代码

Visual Studio 会为你生成一个 dllmain.c 文件,我们将其重命名为 myutils.c 并修改其内容,添加我们需要的函数。

myutils.h (头文件,可选但推荐)

这个头文件定义了 DLL 将要导出的函数,方便其他程序包含和使用。

// myutils.h
#ifndef MYUTILS_H
#define MYUTILS_H
// 使用 __declspec(dllexport) 来告诉编译器这个函数要从 DLL 中导出
#ifdef MYUTILS_EXPORTS // 在 DLL 项目中定义这个宏
#define MYUTILS_API __declspec(dllexport)
#else // 在调用 DLL 的程序中不定义这个宏
#define MYUTILS_API __declspec(dllimport)
#endif
// 导出函数声明
MYUTILS_API int Add(int a, int b);
MYUTILS_API void PrintMessage(const char* message);
#endif // MYUTILS_H

myutils.c (DLL 源文件)

c 引用c语言dll
(图片来源网络,侵删)
// myutils.c
#include "myutils.h"
#include <stdio.h>
// Add 函数的实现
int Add(int a, int b) {
    return a + b;
}
// PrintMessage 函数的实现
void PrintMessage(const char* message) {
    printf("Message from DLL: %s\n", message);
}
// DLL 的入口点 (可选,但通常需要)
BOOL APIENTRY DllMain(HMODULE hModule,
    DWORD  ul_reason_for_call,
    LPVOID lpReserved
) {
    switch (ul_reason_for_call) {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

3. 编译 DLL

F7 或 "生成" -> "生成解决方案",编译成功后,你会在项目的 x64/Debug (或 x64/Release) 目录下找到 MyUtils.dllMyUtils.lib 文件。

  • .dll 文件是动态链接库本身,运行时需要。
  • .lib 文件是导入库链接器,用于编译阶段,帮助链接器找到函数地址。

第二步:创建调用 DLL 的 C 程序

现在我们创建一个新的 C 控制台台项目来调用我们刚刚生成的 DLL。

1. 创建新项目

  1. 在 Visual Studio 中,"创建新项目"。
  2. 选择 "控制台应用" 项目模板。
  3. 命名,CallMyUtils,并点击 "创建"。

2. 编写调用代码

main.c

// main.c
#include <stdio.h>
#include <windows.h> // 必须包含,用于 LoadLibrary, GetProcAddress, FreeLibrary 等
// 为了方便,我们可以复制 DLL 的头文件过来,或者设置项目包含目录
// 这里我们直接复制过来,但去掉 MYUTILS_EXPORTS 的定义
// 在调用方,MYUTILS_API 会被定义为 __declspec(dllimport)
// 复制 myutils.h 的内容,但移除 #ifdef MYUTILS_EXPORTS ... #endif 部分
// 简化版,只包含函数声明
#ifdef __cplusplus
extern "C" { // 确保使用 C 方式链接,避免 C++ 的名称修饰
#endif
// 在调用方,我们告诉编译器这些函数是从外部 DLL 导入的
__declspec(dllimport) int Add(int a, int b);
__declspec(dllimport) void PrintMessage(const char* message);
#ifdef __cplusplus
}
#endif
int main() {
    // 1. 加载 DLL
    // 使用 LoadLibrary 函数,需要传入 DLL 的文件名。
    // DLL 不在系统路径或当前工作目录下,需要提供完整路径。
    HMODULE hDll = LoadLibrary(L"MyUtils.dll"); // 注意:Windows API 通常使用宽字符 L"..."
    if (hDll == NULL) {
        printf("Failed to load DLL. Error: %lu\n", GetLastError());
        return 1;
    }
    printf("DLL loaded successfully.\n");
    // 2. 获取函数地址
    // 使用 GetProcAddress 函数,通过函数名获取其在内存中的地址。
    // 我们使用 typedef 来定义函数指针,使代码更清晰、更安全。
    typedef int (*AddFuncPtr)(int, int);
    typedef void (*PrintMessageFuncPtr)(const char*);
    AddFuncPtr pAdd = (AddFuncPtr)GetProcAddress(hDll, "Add");
    PrintMessageFuncPtr pPrintMessage = (PrintMessageFuncPtr)GetProcAddress(hDll, "PrintMessage");
    // 检查是否成功获取函数地址
    if (pAdd == NULL || pPrintMessage == NULL) {
        printf("Failed to get function address. Error: %lu\n", GetLastError());
        FreeLibrary(hDll); // 记得释放资源
        return 1;
    }
    printf("Function addresses obtained.\n");
    // 3. 调用函数
    int result = pAdd(5, 7);
    printf("Result from Add(5, 7): %d\n", result);
    pPrintMessage("Hello from the calling program!");
    // 4. 释放 DLL
    // 当不再需要使用 DLL 时,必须调用 FreeLibrary 来释放它。
    FreeLibrary(hDll);
    printf("DLL unloaded.\n");
    return 0;
}

3. 配置调用项目

这是最关键的一步,否则链接器会找不到函数。

c 引用c语言dll
(图片来源网络,侵删)
  1. 设置包含目录:

    • CallMyUtils 项目上右键 -> "属性"。
    • 配置选择 "所有配置"。
    • 左侧导航到 "C/C++" -> "常规" -> "附加包含目录"。
    • 点击下拉箭头,选择 "编辑..."。
    • 添加 MyUtils 项目的头文件所在目录的路径,如果 MyUtils 项目在同一解决方案下,路径可能是 ..\MyUtils
  2. 设置库目录:

    • 在属性页中,导航到 "链接器" -> "常规" -> "附加库目录"。
    • 添加 MyUtils 项目生成的 .lib 文件所在的目录路径。..\MyUtils\x64\Debug
  3. 添加依赖项:

    • 在属性页中,导航到 "链接器" -> "输入" -> "附加依赖项"。
    • 点击下拉箭头,选择 "编辑..."。
    • 添加 MyUtils.lib,链接器会使用这个 .lib 文件在编译时解析函数符号。

4. 编译和运行

  1. 确保 CallMyUtils 项目是启动项目(在解决方案资源管理器中右键点击 CallMyUtils -> "设为启动项目")。
  2. 重要: 将 MyUtils.dll 复制到 CallMyUtils 的输出目录(x64/Debug),因为 LoadLibrary 默认只在当前目录和系统路径中查找 DLL。
  3. F5 启动调试。

预期输出:

DLL loaded successfully.
Function addresses obtained.
Result from Add(5, 7): 12
Message from DLL: Hello from the calling program!
DLL unloaded.

  1. 导出函数 (DLL侧):

    • 使用 __declspec(dllexport) 关键字修饰需要被外部程序调用的函数。
    • 通常配合宏(如 #ifdef MYUTILS_EXPORTS)来区分 DLL 源文件和调用方源文件。
  2. 导入函数 (调用方侧):

    • 使用 __declspec(dllimport) 关键字声明来自 DLL 的函数。
    • 包含 DLL 的头文件。
    • 正确配置项目的 "附加包含目录" 和 "附加依赖项"。
  3. 运行时调用:

    • LoadLibrary(L"DllName.dll"): 加载 DLL 到内存,返回一个模块句柄 HMODULE
    • GetProcAddress(HMODULE, "FunctionName"): 根据函数名获取其在内存中的地址,返回一个 FARPROC 类型的指针,需要强制转换为正确的函数指针类型。
    • 通过函数指针调用函数。
    • FreeLibrary(HMODULE): 卸载 DLL,释放资源。

高级主题和注意事项

  • C++ 名称修饰 (Name Mangling): 如果用 C++ 编写 DLL,函数名会被编译器修改(名称修饰),导致 GetProcAddress 找不到,解决方案是在 C++ 函数声明上使用 extern "C",如 extern "C" __declspec(dllexport) int Add(...);
  • DLL_PROCESS_ATTACHDLL_PROCESS_DETACH: 在 DllMain 中,你可以响应 DLL 的加载和卸载事件,进行初始化和清理工作。
  • 错误处理: 始终检查 LoadLibraryGetProcAddress 的返回值,并使用 GetLastError() 获取详细的错误信息。
  • 静态链接 vs. 动态链接: 上面描述的是“隐式链接”,通过 .lib 文件在链接时解析地址,还有一种“显式链接”,就是我们上面代码中使用的 LoadLibrary/GetProcAddress 方式,这种方式更灵活,可以在运行时决定是否加载 DLL。
-- 展开阅读全文 --
头像
织梦content如何实现高效内容创作?
« 上一篇 04-10
dede官方微信插件怎么用?
下一篇 » 04-10

相关文章

取消
微信二维码
支付宝二维码

目录[+]