一、基本概念
若要导出一个全局函数,就用关键字__declspec(dllexport)来声明
在Dll项目中建立一个全局函数
__declspec(dllexport)int Add(int a, int b)
{
return a + b;
}
DLL项目设置:
- 取消"预编译头文件”
- 改为"/MTd"编译
- 修改输出Dll名字
编译得到dll和lib。
.lib:包含一个列表,表明dll中含有哪些符号,每个符号对应在dll中的位置
简单调用dll:
#include <stdio.h>
//使用库
#pragma comment(lib,"TestDLL.lib")
//声明:此函数需要从dll导入
__declspec(dllimport) int Add(int a, int b);
int main()
{
int result = Add(10, 11);
return 0;
}
Dll放置位置:(其中第二条是指在调试的时候,项目的目录)
二、DLL加载和卸载
加载
在exe文件中有一些标识信息,表示exe依赖哪些dll文件,操作系统根据此去寻找,加载相应的dll文件。
当dll被加载时,代码段只被加载一次,是公有的;
数据段被每个程序各自拷贝一份,是私有的。
当动态库中定义了一个全局变量static int value =0;2个进程同时调用一个Dll,value的值互相不影响。
卸载
当所有调用进程都退出后,该Dll被卸载,这时候Dll才可以删除。
不同进程间不能比较变量的地址(因为是虚拟地址)
三、动态内存管理
在Dll中malloc申请的内存,必须在Dll中free,这是微软的规定,记住即可。
四、使用头文件
原则:
在Dll项目中,将函数声明为__declspec(dllexport)
在APP项目中,将函数声明为__declspec(dllimport)
使用条件编译,Dll部分:
//mydll.h
#ifndef _MYDLL_H
#define _MYDLL_H
#ifdef MYDLL_EXPORTS
#define MYDLL __declspec(dllexport)
#else
#define MYDLL __declspec(dllimport)
#endif
MYDLL int Add(int a, int b);
#endif
//mydll.cpp
#include <stdio.h>
#define MYDLL_EXPORTS
#include "mydll.h"
int Add(int a,int b)
{
return a+b;
}
调用部分:
#include <stdio.h>
#include "mydll.h"
#pragma comment(lib,"my.lib")
int main()
{
int result = Add(10,1);
return 0;
}
五、导出类
要将类导出,只要在类前面加个标识符,如:
class MYDLL CMyClass
{ };
六、静态库
仅一个.lib文件,静态库中直接就含有代码段和数据段,在链接过程中,是直接把里面的东西链接过来,形成完整的可执行程序。
exe运行的时候不再依赖lib文件。
lib例子
//mylib.h
#ifndef _MYLIB_H
#define _MYLIB_H
int Add(int a, int b);
#endif
//mylib.cpp
#include "mylib.h"
int Add(int a, int b)
{
return a + b;
}
测试lib例子:
#include <stdio.h>
#include "mylib.h"
#pragma comment(lib,"TestDLL_Static.lib")
int main()
{
int result = Add(10, 14);
printf("result: %d\n", result);
return 0;
}
七、静态库和动态库对比
静态库优点:最后可执行程序执行对这个库不再依赖(已经把符号链接过来了)
静态库缺点:很多,一般使用Dll
动态库优点:便于升级更新,只要保持接口不变,更换Dll进行升级
八、动态库自动加载与手动加载(也叫动态加载)
自动加载(隐式链接):在编译的时候指定dll,当exe程序启动运行时,首先加载相关的dll。
手动加载(显示链接):运行时候调用LoadLibrary来加载dll;使用FreeLibrary来卸载dll。动态加载增加了变成的灵活性。
手动加载对DLL的要求:
- 要求待调用的函数按"C"方式编译(符号名即为函数名)
- dll文件放在可被系统搜索到的文件夹
例如:
C编译形式Dll
//mydll.h
#ifndef _MYDLL_H
#define _MYDLL_H
#ifdef MYDLL_EXPORTS
#define MYDLL __declspec(dllexport)
#else
#define MYDLL __declspec(dllimport)
#endif
extern "C" MYDLL int Add(int a, int b);
#endif
//mydll.cpp
#include <stdio.h>
#define MYDLL_EXPORTS
#include "mydll.h"
int Add(int a, int b)
{
return a + b;
}
动态调用:
#include <stdio.h>
#include <WinSock2.h>
#include <Windows.h>
int main()
{
HINSTANCE handle = LoadLibrary(L"TestDLL_CDll.dll");
if(handle)
{
//定义要找的函数原型
typedef int(*DLL_FUNCTION_ADD) (int, int);
//找到目标函数的地址
DLL_FUNCTION_ADD dll_func = (DLL_FUNCTION_ADD)GetProcAddress(handle, "Add");
if(dll_func)
{
int result = dll_func(10, 20);
printf("result:%d\n", result);
}
//卸载
FreeLibrary(handle);
}
}
九、动态编译和静态编译/MT /MD
d:debug
M:多线程multi-threading
T:text代码
D:dynamic动态库
静态编译:/MT /MTd
是指使用libc和msvc相关的静态库(lib)
文件会比较大
动态编译:/MD /MDd
是指使用相应的DLL版本编译
会导致目标机器没有对应的动态库而运行不了
1、/MT /MD选择:
为什么选择/MD,不选/MT?
(1)程序就不需要静态链接运行时库,可以减小软件的大小;
(2)所有的模块都采用/MD,使用的是同一个堆,不存在A堆申请,B堆释放的问题。
为什么选择/MT,不选择/MD?
(1)有些系统可能没有程序所需要版本的运行时库,程序必须把运行时库静态链接上
多个模块,必须选择相同类型的运行时库,不要混合使用。
2.选择/MT需要解决的堆空间释放问题:
不同的模块各自有一份C运行时库代码,各个C运行库会有各自的堆,导致了各个模块会有各自的堆。如果在A堆中申请空间,到B堆中释放就会有崩溃,在模块A申请的空间,必须在模块A中释放。
附件(下载地址:http://files.cnblogs.com/cswuyg/Test_MD_and_MT.rar)的DLL以及DLLUser代码,以STL的string为例,通过修改编译选项验证了这个问题。(string在赋值的时候需要释放掉原来的空间,然后再申请新的空间存储新的内容。)
string在赋值的时候需要释放掉原来的内存空间,然后再申请新的内存空间存储新的内容,如果跨模块了,释放的时候就存在“A模块申请B模块释放”的问题,导致程序崩溃。
(跨模块释放内存导致崩溃的内容,在《windows核心编程》第五版Page511谈DLL和进程的地址空间时有谈到)
3.选择/MD需要注意多个模块使用不同版本运行时库的问题:
多个dll被一个exe LoadLibrary加载,如果这些dll使用的运行时库是不同的,那么可能出现加载失败,原因可能是旧版本的运行时库已经在了,而某个dll它需要的是新版本的运行时库,旧版本不符合要求。
如果工程里所有的模块都是自己写的或者可以完全控制的,那么这个问题不难解决,只需要在工程设置里都设置/MD,然后在相同的环境下编译一次就行。但是假如这个模块是外界提供的呢?
可能存在这种情况:A动态库使用了B静态库,B静态库使用了C动态库,B静态库是外界提供的,我们要使用它,但无法修改它,我们也无法接触到C动态库。如果C动态库使用的运行时库版本跟编译A动态库的本地使用的不一致,那么A动态库里的嵌入信息就会记录两个不同版本的运行时库,它被加载的时候,可能会选择版本新的。假设A动态库被一个exe LoadLibrary加载,而这个exe本身的运行时库是旧的,这样就会导致A动态库加载失败,即便把新的运行时库拷贝到目录下也不行,因为exe这个进程已经加载了那个旧的运行时库。这时候必须使用manifest文件指定嵌入到A动态库里的运行时库为某个版本,忽略掉C动态库使用的运行时库版本。
这个问题挺复杂的,我心思没去验证windows的PE文件加载会对运行时库做什么样的优先选择、运行时库在静态库里的记录…。只要记住,给外界使用的组件版本尽量避免使用/MD(这样会导致膨胀吗?据说,安装包可以做字节流式压缩)。
附上另一个问题:静态库的依赖关系:exe-->libA-->libB,现在不想让exe接触到libB,于是把libA的librarian选项-->General选项-->Link Library Dependencies设置为Yes,这样即可,libA会包含libB,exe只需要接触libA。另外需要特别注意,libA对libB的依赖只需要且只能在Solution的Project Dependencies里设置,如果在libA的代码里写了”#pragma comment(lib, "libB.lib")”,会导致exe在link libA的时候提示找不到libA。如果exe还出现link错误,那一定是VS抽筋了:)
vc++编译时运行库选择(/MT、/MTd、/MD、/MDd)https://blog.csdn.net/lwwl12/article/details/77045717
VS项目属性中C/C++运行库 、MT /MTd /MD /MDdhttps://blog.csdn.net/u010059658/article/details/51026662
十、dumpbin.exe查看导入导出函数
打开VS开发人员命令提示,进入到VC\Bin目录下
查看导出:dumpbin -exports D:\WorkSpace\DLLTutorial\Debug\DLLTutorial.dll
查看导入:dumpbin -imports D:\WorkSpace\DLLTutorial\Debug\DLLTutorial.exe