静态链接库

静态链接库编译之后有两种使用方法。

  1. 将.h和.lib文件复制到项目根目录,然后在代码中引用

    1
    2
    #include "xxxx.h"
    #pragma comment(lib,"xxxx.lib")
  2. 将xxxx.h与xxxx.lib文件复制到VS安装目录,与库文件放在一起,然后在项目->设置->Linker->input下的Additional Dependencies添加lib文件名,之后就像使用std一样直接引用头文件就行了。

静态链接库的缺点

  1. 使用静态链接库生成的可执行文件体积较大,编译器将代码直接编译到模块里了

  2. 包含相同的公共代码,造成浪费


动态链接库

入口函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
extern "C" BOOL WINAPI DllMain (
//调用当前dll的模块
HINSTANCE const instance,
//调用原因
DWORD const reason,
LPVOID const reserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
} // reserved

如下,在头文件中声明要导出的函数

1
2
3
4
#pragma once

extern "C" _declspec(dllexport) int __stdcall add(int a, int b);
extern "C" _declspec(dllexport) int __stdcall sub(int a, int b);

extern “C”的意义是指示编译器这部分代码按照C语言进行编译而不是C++

另一种导出方式是使用.def文件,那么声明函数时就不需要extern关键字

1
2
3
4
LIBRARY "xxxx"
EXPORTS
add @12
sub @13 NONAME

使用序号导出的好处:可以隐藏函数名


使用动态链接库

运行时动态链接

通过函数指针的方法调用dll,则不需要使用头文件

将dll文件移动到项目目录,或者在项目属性>调试>环境选项中配置PATH=dll路径;$PATH$,这个路径是dll文件所在的路径,如果在Debug文件夹下那就要包含Debug文件夹。一般开发时不需要这么做,直接把dll文件放到项目目录就行了

  1. 定义函数指针

    1
    2
    typedef int (__stdcall *lpAdd)(int,int);
    typedef int (__stdcall *lpSub)(int,int);
  2. 声明函数指针变量

    1
    2
    lpAdd myAdd;
    lpSub mySub;
  3. 动态加载dll到内存中

    1
    HINSTANCE hModule = LoadLibrary("A.dll");
  4. 获取函数地址

    1
    2
    3
    4
    5
    myAdd = (lpAdd)GetProcAddress(hModule,"add");
    myAdd = (lpAdd)GetProcAddress(hModule,"sub");

    /*通过序号获取函数地址*/
    add = (fcAdd)GetProcAddress(hMod, (LPCTSTR)12);
  5. 调用函数

  6. 释放动态链接库

    1
    FreeLibrary(hModule);

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

#include<stdio.h>
#include<Windows.h>
#include<stdlib.h>

typedef int(__stdcall *lpAdd)(int, int);
typedef int(__stdcall *lpSub)(int, int);

lpAdd myAdd;
lpSub mySub;

int main() {
HINSTANCE hModule = LoadLibrary("myDll.dll");
myAdd = (lpAdd)GetProcAddress(hModule, "add");


//动态库中的sub没有函数名,使用序号获取地址
mySub = (lpSub)GetProcAddress(hModule, (CHAR*)0xD);

printf("%d\n",myAdd(1, 2));
printf("%d\n", mySub(2, 1));

FreeLibrary(hModule);

getchar();

return 0;

}

注意在DLL项目中要将.def文件添加进模块编译目录,否则.def文件是不会被编译,而且.def文件开头使用LIBRARY指定模块名称
在Project->Property pages->Linker->input->Module Definition File下指定.def文件,比如说A.def,要写全。


用pe工具查看输出表,没有名字的就是sub函数


加载时动态链接

加载时动态链接可以像静态库一样调用dll,将头文件、.lib文件和dll放入项目目录。

或者将头文件路径加入包含目录,将.lib文件路径加入到库目录中,然后就可以直接调用dll的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<Windows.h>
#include<stdio.h>
#include<stdlib.h>
#include<PETool.h>
#pragma comment(lib,"PEToolDll.lib")

int main() {


printf("%d", PEAlign(1000,3500));

getchar();

}



隐式链接

隐式链接的lib文件里只储存了描述性的信息,如果没有lib文件的话只能通过GetProcAddress显式获取函数地址。

  1. 将编译后的lib文件和dll一起复制到项目根目录

  2. #pragma comment(lib,"xxx.lib") 添加到调用文件中

  3. 加入函数声明

    1
    2
    __declspec(dllimport) int __stdcall add(int a, int b);
    __declspec(dllimport) int __stdcall sub(int a, int b);

隐式链接时编译器会在exe文件里生成一张输出表,详细记录了用到哪些dll,和dll里的哪些函数。

在pe工具中查看输出表: