C++的链接库分为静态链接库和动态链接库两种情况。
所谓静态、动态是指链接。回顾一下,将一个程序编译成可执行程序的步骤:
静态库(.lib)
静态链接库在链接时,编译器会将 .obj 文件和 .LIB 文件组织成一个 .exe 文件,程序运行时,将全部数据加载到内存。
如果程序体积较大,功能较为复杂,那么加载到内存中的时间就会比较长,最直接的一个例子就是双击打开一个软件,要很久才能看到界面。这是静态链接库的一个弊端。
总体静态链接库的缺点:
- 静态库对函数库的链接是放在编译时期完成的。
- 程序在运行时与函数库再无瓜葛,移植方便。
- 浪费空间和资源,因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件。
动态库(.dll)
动态链接库 DLL:(Dynamic Link Library)是一个被其他应用程序调用的程序模块,其中封装了可以被调用的资源或函数。DLL文件属于可执行文件,它符合Windows系统的PE文件格式,不过它是依附于EXE文件创建的的进程来执行的,不能单独运行。
动态链接库有两种加载方式:隐式加载和显示加载。
- 隐式加载又叫载入时加载,指在主程序载入内存时搜索DLL,并将DLL载入内存。隐式加载也会有静态链接库的问题,如果程序稍大,加载时间就会过长,用户不能接受。
- 显式加载又叫运行时加载,指主程序在运行过程中需要DLL中的函数时再加载。显式加载是将较大的程序分开加载的,程序运行时只需要将主程序载入内存,软件打开速度快,用户体验好。
静态(隐式)加载
项目DLLTest
- 选择.exe生成
- 编译DLLTest.dll,DLLTest.lib,与DLLTest.exe同路径
//DLLTest.h
#pragma once
__declspec(dllexport) int add(int a, int b);
//DLLTest.cpp
#include "DLL_test.h"
int add(int a, int b) {
return a + b;
}
//main.cpp
#include<iostream>
#include "DLL_test.h"
using namespace std;
int main() {
cout << add(10, 3) << endl; //13
system("pause");
return 0;
}
创建新项目:getDLL
- 将DLLTest.h放入项目下
- 导入项目属性->链接器->输入->附加依赖项(导入DLLTest.lib路径)
- 将DLLTest.dll放入release目录下,与getDLL.exe同路径
//main.cpp
#include <iostream>
#include "DLL_test.h"
using namespace std;
int main() {
cout << add(10, 2) << endl; //12
system("pause");
return 0;
}
动态(显式)加载
应用程序必须进行函数调用,以在运行时显示加载DLL。为显示链接到DLL,应用程序必须:
● 调用 LoadLibrary(或相似的函数)以加载 DLL 和获取模块句柄。
● 调用 GetProcAddress,以获取指向应用程序要调用的每个导出函数的函数指针。由于应用程序是通过指针调用 DLL 的函数,编译器不生成外部引用,故无需与导入库链接。
● 使用完 DLL 后调用 FreeLibrary。
#include <stdio.h>
#include <stdlib.h>
#include <windows.h> //必须包含 window.h
typedef int (*FUNADDR)(); //指向函数的指针
int main()
{
int a = 5,b = 10;
HINSTANCE dllDemo = LoadLibrary("dllDemo.dll");
FUNADDR add,sub;
if(dllDemo)
{
add = (FUNADDR)GetProcAddress(dllDemo,"add");
sub = (FUNADDR)GetProcAddress(dllDemo,"sub");
}
else
{
printf("Fail to load dll");
system("pause");
exit(1);
}
printf("a+b = %d\n",add(a,b));
printf("a - b = %d\n",sub(a,b));
system("pause");
return 0;
}
显示调用C++动态库注意点
对C++来说,情况稍微复杂。显式加载一个C++动态库的困难一部分是因为C++的name mangling;另一部分是因为没有提供一个合适的API来装载类,在C++中,您可能要用到库中的一个类,而这需要创建该类的一个实例,这不容易做到。
name mangling可以通过extern "C"解决。C++有个特定的关键字用来声明采用C binding的函数:extern “C” 。用 extern "C"声明的函数将使用函数名作符号名,就像C函数一样。因此,只有非成员函数才能被声明为extern “C”,并且不能被重载。尽管限制多多,extern "C"函数还是非常有用,因为它们可以象C函数一样被dlopen动态加载。冠以extern "C"限定符后,并不意味着函数中无法使用C++代码了,相反,它仍然是一个完全的C++函数,可以使用任何C++特性和各种类型的参数。
“显式”使用C++动态库中的Class是非常繁琐和危险的事情,因此能用“隐式”就不要用“显式”,能静态就不要用动态。
总结
- 隐式加载和显式加载这两种加载DLL的方式各有优点,如果采用动态加载方式,那么可以在需要时才加载DLL,而隐式链接方式实现起来比较简单,在编写程序代码时就可以把链接工作做好,在程序中可以随时调用DLL导出的函数。但是,如果程序需要访问十多个DLL,如果都采用隐式链接方式加载它们的话, 那么在该程序启动时,这些DLL都需要被加载到内存中,并映射到调用进程的地址空间, 这样将加大程序的启动时间。
- 但是这时所有的DLL都已经被加载到内存中,资源浪费是比较严重的。在这种情况下,就可以采用显式加载的方式访问DLL,在需要时才加载所需的DLL,也就是说,在需要时DLL才会被加载到内存中,并被映射到调用进程的地址空间中。有一点需要说明的是,实际上, 采用隐式链接方式访问DLL时,在程序启动时也是通过调用LoadLibrary() 函数加载该进程需要的动态链接库的。