加载方式
隐式链接(静态加载)
定义:隐式链接,又称为静态加载,是指 DLL 在应用程序启动时被自动加载。在这种情况下,所有依赖的 DLL 必须在应用程序启动前被加载到内存中。
实现:应用程序的可执行文件(EXE)在编译时与 DLL 的接口(通常是一个导入库文件,.lib)链接。这个导入库告诉操作系统在哪里找到并加载 DLL。
优点:开发者不需要在代码中显式调用加载库的函数,操作系统负责在程序启动时加载所有需要的 DLL。
缺点:若 DLL 缺失或版本错误,应用程序将无法启动。
显式链接(动态加载)
定义:显式链接,又称为动态加载,允许应用程序在运行时根据需要加载和卸载 DLL。
实现:通过调用如 LoadLibrary(Windows)或 dlopen(UNIX/Linux)等操作系统提供的函数手动加载 DLL。同样,使用 GetProcAddress(Windows)或 dlsym(UNIX/Linux)等函数从 DLL 中获取函数或变量的地址。
优点:提供了更大的灵活性,只有在真正需要时才加载库,可以节省内存,也允许处理不同版本的库或在没有库的情况下仍然运行。
缺点:需要更多的代码来管理库加载和卸载,出错可能性增加。
DLL 的共享和内存管理
内存共享:当多个应用程序使用同一个 DLL 时,操作系统不会为每个应用程序分别加载 DLL 的多个副本。相反,DLL 会被加载到内存中的一个位置,多个应用程序可以共享这个位置的代码和数据。
代码共享:DLL 中的代码段是共享的,这意味着多个进程可以使用相同的物理内存来执行相同的代码,减少了内存的使用。
数据共享:尽管代码可以被共享,但数据通常是每个进程独立的,除非明确声明为共享数据。这意味着每个进程都有自己的数据副本或者其数据存储在进程私有的内存空间内。
资源共享
资源:DLL 不仅包含代码,也可以包含其他资源,如图像、声音文件和其他二进制数据。这些资源可以被不同的应用程序共享,从而避免资源的重复。
总结
DLL 的使用提供了模块化和资源共享的强大能力,使得软件开发更加灵活和内存效率更高。通过隐式或显式链接,开发者可以选择最适合他们需求的加载方式,利用操作系统的能力来优化应用程序的性能和资源使用。同时,共享代码和资源的能力意味着应用程序可以在运行时共享和协调它们的行为,而不是冗余地加载相同的功能。
示例
为了更好地理解隐式链接和显式链接在实际中的应用,以下提供了两个示例,涉及如何在Windows系统上使用这些方法加载和使用动态链接库(DLL)。
隐式链接(静态加载)
在隐式链接中,DLL在应用程序启动时被自动加载。开发者在源代码中包含DLL的头文件,并链接到导入库(通常是.lib
文件)。以下是一个简单的示例,说明如何使用隐式链接加载和调用DLL中的函数。
假设 DLL 提供如下函数:
// MathLibrary.h - 头文件
#pragma once
#ifdef MATHLIBRARY_EXPORTS
#define MATHLIBRARY_API __declspec(dllexport)
#else
#define MATHLIBRARY_API __declspec(dllimport)
#endif
extern "C" MATHLIBRARY_API int add(int a, int b);
这里,MATHLIBRARY_EXPORTS
宏用于控制符号是被导出还是被导入。当编译DLL时定义此宏,当使用DLL时不定义。
编写应用程序使用 DLL:
// Main.cpp
#include <iostream>
#include "MathLibrary.h"
int main() {
int result = add(10, 20);
std::cout << "The result is: " << result << std::endl;
return 0;
}
在这个例子中,你需要将应用程序链接到MathLibrary
的导入库(MathLibrary.lib)。这通常在项目的链接器设置中指定。
显式链接(动态加载)
在显式链接中,应用程序在运行时决定是否加载 DLL,并且使用特定的 API 调用来加载 DLL 和获取函数地址。
注:DLL 侧代码与隐式链接相同。
编写应用程序动态加载 DLL:
// Main.cpp
#include <Windows.h>
#include <iostream>
typedef int (*AddFunc)(int, int); // 定义函数指针类型
int main() {
HMODULE hDLL = LoadLibrary(TEXT("MathLibrary.dll")); // 加载 DLL
if (hDLL == NULL) {
std::cerr << "Unable to load DLL!" << std::endl;
return 1;
}
// 获取函数指针
AddFunc add = (AddFunc)GetProcAddress(hDLL, "add");
if (add == NULL) {
std::cerr << "Unable to find function!" << std::endl;
FreeLibrary(hDLL);
return 1;
}
// 调用函数
int result = add(10, 20);
std::cout << "The result is: " << result << std::endl;
// 卸载DLL
FreeLibrary(hDLL);
return 0;
}
在这个例子中,LoadLibrary
用于加载 DLL,GetProcAddress
获取函数指针,FreeLibrary
卸载DLL。这种方法提供了更高的灵活性,允许在运行时决定是否加载特定的 DLL,以及处理错误和不同版本的兼容性。
总结比较
隐式链接的优势在于简单和自动化,适用于依赖关系固定且稳定的场景。
显式链接的优势在于灵活性和控制力,适用于需要运行时决策、减少初始化负担或处理可选功能的场景。