动态链接库
前言
动态链接库(Dynamic Link Library 或者 Dynamic-link Library,缩写为 DLL),是微软公司在微软Windows操作系统中,实现共享函数库概念的一种方式。这些库函数的扩展名是 ”.dll”、”.ocx”(包含ActiveX控制的库)或者 “.drv”(旧式的系统驱动程序)。
——百度百科
正文
与之对应的在基础那会学过静态链接库.lib
,要使用特定的功能,就必须加载这个静态库,这样在编译的时候其实exe就包含了这个lib。
而现在的动态链接库,他只管调用,不去负责连接的过程,要么你写好了不用,他不发生连接,要么写了要用的时候才会去连接。
静态库编译完成后,已经和exe合并,所以这个exe会比较大。
动态库在内存中连接,并没有本质上的合并,相对而言exe比较小,但是计算机如果缺失这个动态库,那么这个exe基本就废了。
windows常见的动态库(.dll)
- gdi32.dll 绘图
- user32.dll 用户界面有关的函数
- kernel32.dll 内存、线程、进程
- d3d9x_11.dll 绘图
动态链接库的意义
- 模块化
- 方便更新迭代
- 提高共享率和利用率
- 节约内存
- 本地化支持
- 跨语言编程
- 解决版本问题
- 等等诸如此类
歪瓜的事说不得。能做到动态库的瓜也不是一般人了。
动态库的问题
- 因为动态链接,需要时间
- 找不到动态库,exe没法跑
- 因为更新而导致接口或者参数不一样了,那么以前的代码全废了
创建动态库
vs有模板,直接创建动态链接库的项目就行了。
当然是因为第一次,后面自己想怎么来也无所谓。
其中pch.h 和 pch.cpp是用来预编译的。核心文件自然就是dllmain.cpp。
1 | // dllmain.cpp : 定义 DLL 应用程序的入口点。 |
dllmain就是这个程序的主入口。
我们自定义一个函数
1 | //前缀试了让编译器导出这个函数 _d |
其实要考虑东西比较多。
如果定义这块写成这样很麻烦,可以新建一个头文件,把声明写好,这样定义写起来至少看着正常点。
1 |
|
不过实际用途上,这个头文件还是为了让用你的库的人用的。
还有一种解决办法
模块化文件
1 | LIBRARY |
这么写之后,就不用头文件了。
上述情况中,我们先忽视了c++的函数重载。
1 | int ave(int a, int b){ |
有一种解决办法就是提前给链接器做好准备#pragma comment(linker,"/export:ave=?ave@@YAHHH@Z")
只不过这种写法还不如模块化文件,而且不确定会不会有问题
还有一种比较麻烦的就是还带了函数调用约定
1 | //1为调用风格 2为导出 3为调用约定 |
_stdcall在windows api中倒是常见。不过函数在编译后就不会是单纯ave了。
要指定的话#pragma comment(linker,"/export:ave=_ave@4")
当然显得也有些奇怪
模块化文件之所以能够直接用,其实编译器做了处理的
简单回顾几种
1 |
|
1 | //模块化文件 |
因为懒得下拆解dll函数的软件,就纯yy了。
一种通过链接器提前导,一种在代码里导出,一种就是模块化文件导出
dll除了导出函数,还能导出变量
链接动态库
首先新建项目,这会空项目问题不大,然后可以放到一个解决方案下。
右击新建的项目,找到生成项目依赖项,选择
打上勾。
然后新建项目里自然要去调用了。
1 |
|
注:此处loaddll的时候,只写了文件名是因为两个项目在一个解决方案里面,所以生成的exe和dll也在一个文件夹,就不用这么麻烦写路径
1 |
|
typedef int (FAR WINAPI *FARPROC)();
类似于函数指针。
发现函数输出乱值,其实也不难猜到,因为没有输入参数,但是它又不报错。
这种情况不报错其实不太好,那么FARPROC是函数指针,我们也可以自己定义一个
1 | typedef int (*FAVE_1)(int a, int b); |
这样他就会提示要输入参数了。
然后随便输入俩
这样就成功了。
1 |
|
HMODULE hMod = LoadLibraryA("myDll.dll");
这一步就是程序跑的时候,把这个库加载到程序的内存中。
FAVE_1 func = (FAVE_1)GetProcAddress(hMod, "ave_1");
这一步是为了把函数的地址取出来,用了自定义类型是因为原本的类型不符合我们的需求。同时要注意,如果dll没有导出这个函数,那么根据这个函数名是找不到的
如果不想用这个dll了,就可以使用FreeLibrary(hMod);
再往后,如果我们想调用这个函数
1 | extern "C" int _stdcall ave(int a){ |
首先肯定要自定义类型了,那么关键在于extern "C"
和_stdcall
要不要加的问题extern "C"
其实是告诉编译器怎么编译它,那么编译完之后其实就不用管了。
但是_stdcall
不一样,函数调用约定比较麻烦。所以_stdcall
是必须的。
1 | typedef int(_stdcall *FAVE)(int); |
模板的形参名是可以省略的,有印象的话最好。
至于_stdcall
,其实在之前写的线程进程的时候,有用到一个宏WINAPI
,它本质上就是_stdcall
。
所以这么写也没问题typedef int(WINAPI *FAVE)(int);
效果也ok的。
1 |
|
结语
关于动态库,其实还有挺多可以优化的地方,但现在了解为主吧。