Featured image of post 静态/动态链接库

静态/动态链接库

在C++开发中,库(Library)是代码复用的重要方式,库的存在形式本质上来说库是一种可执行代码的二进制,分为静态库(.lib、.a)和动态库(.dll、.so)

静态链接库

静态库是一种在编译时将代码直接嵌入到可执行文件中的库文件,它具有独立性、性能优势和版本控制等特点,适用于许多不同类型的项目和开发环境。如果你想要别人使用你的代码,但又不想将源代码公开的时候,也可以使用

优:静态写入封装,代码重用

缺:直接编译到exe(如若修改.lib,需重新编译exe)

创建静态链接库

这里以Visual Studio 2022为例

方法一:

  • 首先创建新项目模板为**“静态库”**

也可以在项目属性页修改配置类型

配置类型

若要自定义头文件,C/C++ → 预编译头 → 设置为"不使用预编译头"

pch.h中添加函数或类预定义

1
2
3
4
5
6
7
#pragma once

void PPTe();

namespace test { 
	void castue();
}

静态库头文件

pch.cpp中写出完整的类与方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <iostream>
#include "pch.h"
using namespace std;

void PPTe(){
	cout << "Hello World " << endl;
}

namespace test {
    void castue() {
        cout << "Ualeo Teui" << endl;
    }
}

静态库源文件

选择“生成” -> “生成解决方案”(Ctrl+Shift+B)

“x64” -> “Debug”中生成lib文件如下

生成的静态库

  • 然后创建一个新项目**“控制台应用”**

将刚刚生成的lib文件和头文件打包复制到新项目文件夹

导入文件

在源文件中预定义头文件,并且在代码中指定需要链接的库文件,添加代码为

1
2
#include "pch.h"
#pragma comment(lib, "TestLib.lib")

这样就成功导入静态链接库啦,可以使用静态库中的类与方法

程序示例

方法二:

刚刚的指定需要链接的库文件代码不添加

将头文件和库文件打包在同一个文件夹,将该文件夹路径添加到项目属性 → VC++目录 → 包含目录和库目录,注意前面要加; (如果是方法一中直接复制文件到新项目就跳过此步

项目属性中设置:打开项目属性 → 链接器 → 输入,在"附加依赖项"中添加 TestLib.lib

添加静态库

或者打开项目属性 → 链接器 → 命令行,在“其他选项”中添加 TestLib.lib

设置继承

点击“应用” → “确定”,和上述功能相同

程序示例2

动态链接库

动态库指的是在程序运行过程中动态加载库的方式使用的库,也就是动态库的链接是发生在程序运行时期的,它和可执行文件是分开的,只是可执行文件在运行的某个时期调用了它。

优:程序自身的体积不会因为动态函数库变大

缺:就是程序运行过程中使用到了这些函数库内的功能时,若系统特定的位置没有对应的动态库,就会造成程序崩溃或者各种奇怪的问题

创建动态链接库

方法一:隐式调用

创建新项目模板为**“动态链接库”**

配置属性 → 常规 → 配置类型:动态库(.dll)

函数声明与生成静态库不一样

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#pragma once

// DLL导出宏
#ifdef STRINGDYNAMICLIB_EXPORTS	//若使用该语句,C/C++ → 预处理器 → 预处理器定义:添加STRINGDYNAMICLIB_EXPORTS
#define STRING_API __declspec(dllexport)	//导出声明(__declspec(dllexport))告诉编译器哪些函数应该对外可见,没有导出的函数无法从外部调用
#else
#define STRING_API __declspec(dllimport)
#endif

namespace StringDynamic {
    STRING_API void reverse(char* str, int length);		// 反转字符串
    STRING_API int countChar(const char* str, char c);	// 统计字符出现次数
    STRING_API char* concatenate(const char* str1, const char* str2);	// 连接两个字符串
}

动态链接库头文件

.cpp代码

 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
30
31
32
33
#include "pch.h"
#include <cstring>
#include <cstdlib>

namespace StringDynamic {
    STRING_API void reverse(char* str, int length) {
        int start = 0;
        int end = length - 1;
        while (start < end) {
            std::swap(str[start], str[end]);
            start++;
            end--;
        }
    }

    STRING_API int countChar(const char* str, char c) {
        int count = 0;
        while (*str) {
            if (*str == c) count++;
            str++;
        }
        return count;
    }

    STRING_API char* concatenate(const char* str1, const char* str2) {
        size_t len1 = strlen(str1);
        size_t len2 = strlen(str2);
        char* result = (char*)malloc(len1 + len2 + 1);
        strcpy(result, str1);
        strcat(result, str2);
        return result;
    }
}

选择“生成” -> “生成解决方案”(Ctrl+Shift+B),会生成dll文件和lib

生成的动态库

同样创建一个新项目,将dll和lib打包复制到新项目文件夹中

1
2
3
#pragma comment(lib, "TestDll.lib")

extern "C" _declspec(dllimport) int __stdcall mySub(int x, int y);	//导入函数时要跟声明类型的一样

程序示例3

方法二:显示调用

dll文件为方法一中生成的,将dll与exe放于同一目录

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#include <windows.h>    //使用LoadLibrary函数必须包含此头文件
#include <iostream>
using namespace std;
typedef int(__stdcall* lpSub)(int, int);    //定义函数指针
int main() {
    lpSub mySub;		//声明函数指针变量				   //动态加载dll到内存
    HINSTANCE hModule = LoadLibrary(L"TestDll.dll");    //L -> 宽字符版本 -> 现代Windows兼容
    mySub = (lpSub)GetProcAddress(hModule, "mySub");    //获取函数地址
    cout << "2-1=" << mySub(2, 1) << endl;              //若是32位程序,__stdcall类型则为_mySub@8
    FreeLibrary(hModule);	//释放dll
}

程序示例4

静态库与动态库对比

静态库与动态库对比

使用.def导出库

同样使用动态链接库项目,def导出可以隐藏函数名

pch.h

1
int myMul(int x, int y);

pch.cpp

1
2
3
int myMul(int x, int y) {
	return x * y;
}

test.def

1
2
3
EXPORTS

Mul @13	NONAME	//13为该函数序号 NONAME表示没有名字,即隐藏函数名

def文件

配置属性 → 链接器 → 输入模块定义文件添加刚刚的test.def

def模块定义

选择“生成” -> “生成解决方案”(Ctrl+Shift+B),显示调用代码为

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <windows.h>    //使用LoadLibrary函数必须包含此头文件
#include <iostream>
using namespace std;
typedef int(*lpMul)(int, int);    //定义函数指针
int main() {
    lpMul myMul;    //声明函数指针变量                     //动态加载dll到内存
    HINSTANCE hModule = LoadLibrary(L"TestDll2.dll");   //L -> 宽字符版本 -> 现代Windows兼容 
    myMul = (lpMul)GetProcAddress(hModule, MAKEINTRESOURCEA(13));	//通过序号获取函数地址
    cout << "2*1=" << myMul(2, 1) << endl;
}

通过序号调用可隐藏函数名~

参考

Visual Studio 2022静态库与动态库创建及使用完全指南

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus
人生若只如初见
使用 Hugo 构建
主题 StackJimmy 设计