暑假来了,但还不能停止学习,前段时间的知识点全部忘记了。幸好互联网是有记忆的,我们这就开始接着说动态链接库。
或许大家已经忘记这个系列了,其实我也忘得差不多了,这里丢两个链接跳转复习(顺便增加点击率):
学渣版白话 dll 介绍 1ming,公众号:花里胡哨不要搞动态链接库 第0章
奶奶煮蛋级 lib 搭建 1ming,公众号:花里胡哨不要搞动态链接库 第1章
这不是完成了一个学习任务,突然想起拖延了很久的??,为了能够及时上手,进入学习状态,现在就复习起来!好了好了,话不多说,立马操作!
Windows10、x64
VS2019、C++ 的桌面开发
从0开始 - 创建和使用 DLL
与静态链接库不同,Windows 是在加载时或在运行时连接应用(导入)和 DLL (导出),而不是在链接时连接它们。
接下来会介绍 DLL 的原始搭建,包括隐式调用和显式调用,知识点很多,可以不用看懂,干就完事!
创建 DLL 项目
新建动态库、空项目(操作可参考 创建静态库项目 )
向 DLL 中添加文件
然后添加头文件、源文件:
在 testdll.h
中添加声明:(需要通过预定义和 if 语句,完成灵活情况的导入导出)
#ifndef TESTDLL_H
#define TESTDLL_H
// 条件编译指令
// 在预处理器里事先定义好_DLLAPI,保证dll项目有预定义;
// 而新程序项目里没有,从而区分导入和导出。
#ifdef _DLLAPI
#define DLLAPI __declspec(dllexport) // 导出
#else
#define DLLAPI __declspec(dllimport) // 导入
#endif
// 声明导出函数
DLLAPI int add(int a, int b); // 导出add接口
#endif
并且在预处理器里进行定义:
在 testdll.cpp
中实现功能:
#include "testdll.h"
int add(int a, int b) // 函数实现
{
return a + b;
}
编译动态库
在菜单栏上依次选择“生成” > “生成解决方案” ,将创建 .dll
和相关编译器输出。
查看输出:
至此,动态链接库已经创造完成!
创造使用 DLL 的客户端应用
创建新项目:控制台、.exe
(操作参考 创建引用静态库的C++控制台应用 )
添加源文件
使用 DLL 需要:查找声明 DLL 导出的标头、链接器的导入库和 DLL 本身。
方法:
建议在客户端项目中设置包含路径,使其直接包括 DLL 项目中的 DLL 头文件。
在客户端项目中设置库路径以包括 DLL 项目中的 DLL 导入库。
将生成的 DLL 从 DLL 项目复制到客户端生成输出目录中。
可执行文件可以通过以下两种方法链接到DLL:
隐式链接
操作系统会与使用 DLL 的可执行文件同时加载它。
客户端可执行文件调用 DLL 的导出函数的方式与函数进行静态链接并包含在可执行文件中时的方式相同。
隐式链接有时称为静态加载 或 加载时动态链接 。
显式链接
操作系统会在运行时按需加载 DLL。
通过显式链接使用 DLL 的可执行文件必须显式加载和卸载 DLL。它还必须设置函数指针,用于访问它从 DLL 使用的每个函数。
显式链接有时称为动态加载 或 运行时动态链接 。
隐式调用
跟静态库调用类似。
右键 test
项目,属性 - “链接器” - “常规” - 附加库目录,编辑添加 .lib
文件存放路径。
在 test.cpp
文件里写入:
#include
#include "../testDll/testdll.h"
#pragma comment(lib, "testDll.lib") // 隐式调用,类似静态库但不同
using namespace std;
int main()
{
int a, b;
cout << "input 2 integer:";
cin >> a >> b;
// 调用
printf("%d + %d = %d", a, b, add(a, b));
return 0;
}
最后运行一下就行!
至此,隐式调用完成!
显式调用
应用程序必须在运行时进行函数调用以显式加载 DLL。
在 test.cpp
文件中手动加载动态库。在需要使用的地方加载,使用完后释放。
#include
#include "../testDll/testdll.h"
#include // LoadLibrary函数使用
using namespace std;
typedef int (*PADD)(int a, int b); // 定义所调用的导出函数的调用指针
int main()
{
// 显式调用
// 加载 DLL 文件,获取模块句柄。
HMODULE hDLL = LoadLibrary(L"testDll.dll");
if (hDLL == NULL)
{
cout << "加载 DLL 失败!\n";
return 0;
}
int a, b;
cout << "input 2 integer:";
cin >> a >> b;
PADD pAdd = (PADD)GetProcAddress(hDLL, "add"); // 获取函数指针
// 显性调用,在需要的时候使用。
printf("%d + %d = %d", a, b, pAdd(a, b));
FreeLibrary(hDLL); // 使用完后释放。
return 0;
}
注意:调用 GetProcAddress
以获取指向名为“DLLFunc1”的函数的指针,调用该函数并保存结果。
此处如果按照之前的 testdll.h
内的声明,则会改变引用的函数名,导致 PADD pAdd = (PADD)GetProcAddress(hDLL, "add");
返回函数值时候引用函数名 “add”
出错。
所以需要修改头文件的声明方式,让编译器使用C语言编译,保留函数名。
导出 C++ 函数以用于 C 语言可执行文件
如果要从 C 语言模块访问用 C++ 编写的 DLL 中的函数,则应使用 C 链接(而不是 C++ 链接)声明这些函数。增加 extern "C"
。
修改 testdll.h
当中的声明:
#ifndef TESTDLL_H
#define TESTDLL_H
// 条件编译指令
// 在预处理器里事先定义好_DLLAPI,保证dll项目有预定义;
// 而新程序项目里没有,从而区分导入和导出。
#ifdef _DLLAPI
#define DLLAPI __declspec(dllexport) // 导出
#else
#define DLLAPI __declspec(dllimport) // 导入
#endif
// 声明导出函数
extern "C" DLLAPI int add(int a, int b); // 导出add接口
// 使用C链接声明函数,导出时不会改变函数名。
// 使用C语言的方式进行编译。
#endif
运行一下
至此,显性调用完成!
使用模块定义文件调用
如果不想使用 [导出 C++ 函数以用于 C 语言可执行文件](#导出 C++ 函数以用于 C 语言可执行文件) 的方式还原函数名,还可以另外添加一个 .def
(模块定义)文件。
将头文件 testdll.h
修改为:
#ifndef TESTDLL_H
#define TESTDLL_H
int add(int a, int b);
#endif
添加一个模块定义文件:
在 Source.def
里写入:
LIBRARY testDll
EXPORT
add
注意:这里编译出错了,似乎是指针找不到函数名。上网查询之后,可能是函数名已经被使用过了,所以换一个名字,并且加上 __stdcall
调用约定。
提示:就是把所有的 add
改名并且加上约定。
将头文件 testdll.h
修改为:
#ifndef TESTDLL_H
#define TESTDLL_H
int __stdcall Add(int a, int b);
#endif
将 testdll.cpp
修改为:
#include "testdll.h"
int __stdcall Add(int a, int b) // 函数实现
{
return a + b;
}
将 test.cpp
修改为:
#include
#include "../testDll/testdll.h"
#include // LoadLibrary函数使用
using namespace std;
typedef int (__stdcall *PADD)(int a, int b); // 定义所调用的导出函数的调用签名
int main()
{
// 显式调用
// 加载 DLL 文件,获取模块句柄。
HMODULE hDLL = LoadLibrary(L"testDll.dll");
if (hDLL == NULL)
{
cout << "加载 DLL 失败!\n";
return 0;
}
int a, b;
cout << "input 2 integer:";
cin >> a >> b;
PADD pAdd = (PADD)GetProcAddress(hDLL, "Add"); // 获取函数指针
// 显性调用,在需要的时候使用。
printf("%d + %d = %d", a, b, pAdd(a, b));
FreeLibrary(hDLL); // 使用完后释放。
return 0;
}
在 Source.def
里写入:
LIBRARY testdll
EXPORTS
Add
运行一下:
至此,.def
文件调用完成!
太心酸了,咱们新新时代的人怎么会每次都从零搭建!
下期预告:
实战演练级动态链接库使用!待我复习完C++语法就更新,绝对不?!