首先在VS2019中新建一个解决方案,并选择新建一个静态库项目,这里我把解决方案名称命名为"DllTest" 静态库项目名称为StaticLib。随后VS2019为我们创建了StaticLib项目,并自动往里面添加了两个头文件和两个源文件。
为了简化,我们把framework.h和pch.cpp都删了,把pch.h作为我们的头文件,把StaticLib.cpp作为我们的源文件。
这里有一点需要注意,删除后需要在StaticLib.cpp上右键->属性,把预编译头由“使用”改成“创建”,否则会报错。
然后我们在StaticLib.cpp中写代码
#include "pch.h"
int Plus(int x, int y) {
return x + y;
}
在pch.h中写代码
#ifndef PCH_H
#define PCH_H
int Plus(int x, int y);
#endif
然后我们直接ctrl+B即可编译完成静态链接库。此时在解决方案的debug目录下会有StaticLib.lib静态库文件。
随后我们在同一个解决方案下再新建一个控制台项目“ConsoleApplication1”。把StaticLib.lib和pch.h都放到该项目目录下。然后编写下面代码,即可实现对静态库中函数的调用
#include
#include"pch.h"
#pragma comment(lib,"StaticLib.lib")
int main()
{
std::cout << Plus(2, 2) <<std::endl;
return 0;
}
当然,对于#pragma comment(lib,“StaticLib.lib”)这句话也可以不加,这需要在项目属性设置中添加附加依赖项,如下图所示。
上面就是添加静态链接库的方法,我们用Ollydbg打开ConsoleApplication1项目生成的exe,发现里面PE模块里面并没有发现我们添加的静态链接库,推断出静态链接库的源程序在编译的时候已经被编译器融入到了源程序中。所以对于引用静态链接库的程序,一旦其静态链接库有更新,需要重新编译哪些引用了该链接库的程序。
同样在VS2019中新建一个动态链接库的项目“DllTest01“,这时候会给我们默认添加一些文件,我这里把pch.h作为我的头文件、dllmain.cpp是系统加的用于启动DLL的入口函数,pch.cpp是我存放具体函数的源文件,其他的文件都删了(当然也要向上面静态链接库一样,预编译头选择为创建)。
下面是三个文件内容
//pch.h文件
#ifndef PCH_H
#define PCH_H
#include
extern"C" __declspec(dllexport) int Plus(int a, int b);
extern"C" __declspec(dllexport) int Sub(int a, int b);
//extern 表示该函数为全局函数,可以在其他地方调用
//“C”表示按照C语言方式进行编译和链接
//__declspec(dllexport)告诉编译器这是一个导出函数
#endif //PCH_H
//pch.cpp文件
#include "pch.h"
int Plus(int a, int b) {
return a + b;
}
int Sub(int a, int b) {
return a - b;
}
//dllmain.cpp文件
#include "pch.h"
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;
}
对于pch.h中的extern"C"表示按照C的方式导出函数,因为C++中有重载,为了确保导出函数名字重复时参数不同,如果去掉extern"C",那么编译器会修改我们的导出函数名字确保不会出现函数重名、参数相同的问题。我这里用dependencies工具看了一下不加extern"C"导出的DLL文件(如下图),可以看出编译器把函数名进行了修改
如果我们加上extern"C",再观察一下,就是下面的
下面我们在控制台项目中用这个DLL,首先把动态链接库项目生成的dll和lib文件放到控制台项目目录下。
#include
#pragma comment(lib,"DllTest01.lib")//DllTest01.lib中存放的是该DLL的位置信息
extern"C" __declspec(dllimport) int Plus(int x, int y);
extern"C" __declspec(dllimport) int Sub(int x, int y);
int main()
{
std::cout << Plus(2, 2) <<std::endl;
std::cout << Sub(2, 3) << std::endl;
return 0;
}
此时,我们通过Ollydbg看到动态链接库并不像静态链接库一样在控制台程序内部,而是作为一个单独的模块出现。
当然,我们还可以通过loadlibrary显示调用某个外部的DLL,程序如下所示:(此时我们仅需要把dll放到当前项目目录下,而无需lib文件了)
#include
#include
typedef int (*lpPlus)(int, int);//定义函数指针
int main()
{
lpPlus myPlus;
HINSTANCE hModule=LoadLibrary(L"DllTest01.dll");//显示链接,把DLL加载
myPlus = (lpPlus)GetProcAddress(hModule, "Plus");
std::cout << myPlus(2, 2) <<std::endl;
return 0;
}
此外,还有一些小的知识点,譬如__stdcall 、def文件等内容,我就举简单的例子吧
我们想自己给定导出函数的导出序号等其让信息,可以新建一个…def文件,然后在里面编写要求
EXPORTS
Plus @12 //告诉编译器Plus函数导出序号为12
Sub @15 NONAME //告诉编译器Sub函数导出序号为15,且不能按照函数名字调用函数
其余的.h文件里只保留最简单的函数声明即可
#ifndef PCH_H
#define PCH_H
#include
int Plus(int a, int b);
int Sub(int a, int b);
#endif //PCH_H
我们用Dependencies看一下生成的DLL,可以看出导出序号(Ordinal)确实如我们定义的,并且Sub函数名字并没有出现。
如果不加__stdcall,则默认采用_cdecl方式(外平栈方式),一般导出函数建议采用内平栈,即__stdcall方式。我们在函数名前面加上__stdcall即可
//头文件
#ifndef PCH_H
#define PCH_H
#include
extern"C" __declspec(dllexport) int __stdcall Plus(int a, int b);
extern"C" __declspec(dllexport) int __stdcall Sub(int a, int b);
#endif //PCH_H
//原文件
#include "pch.h"
int __stdcall Plus(int a, int b) {
return a + b;
}
int __stdcall Sub(int a, int b) {
return a - b;
}
此时,我们再用dependencies看一下,发现导出函数名字也变化了,在我们原来函数名字基础上前面加了一个_,后面加了一个@8 (8是参数占用字节数)。