1. DLL显式链接知识学习
在上一篇介绍了DLL封装并隐式链接调用,这一篇介绍DLL显式链接调用
首先学习一下预备知识(Win32API):
一、LoadLibrary是Windows API中的一个函数,用于动态加载一个库(通常是DLL文件)到调用进程的地址空间中。当你需要在运行时动态地使用某个库中的函数或资源时,可以使用LoadLibrary来加载该库,然后使用GetProcAddress来获取库中函数的地址。
HMODULE LoadLibrary(
LPCSTR lpLibFileName
);
lpLibFileName:要加载的库的名称。这可以是库的文件名,也可以是包含路径的文件名。如果只指定文件名,系统会按照特定的搜索顺序查找库。
返回值是加载的模块的句柄(HMODULE),如果函数调用失败,返回NULL。成功调用LoadLibrary后,你可以使用GetProcAddress来获取库中导出函数的地址。
使用LoadLibrary时需要注意以下几点:
1. 确保指定的库文件存在于搜索路径中,或者提供完整的路径。
2. 加载库后,记得在不再需要时使用FreeLibrary来卸载库,避免资源泄露。
3. 处理好错误情况,例如加载失败时,可以使用GetLastError获取错误代码。
二、GetProcAddress 是 Windows API 中的一个函数,用于动态获取某个已加载的动态链接库(DLL)中的函数或变量的地址。这允许程序在运行时决定调用哪些函数,而不是在编译时静态链接这些函数,从而提供了更大的灵活性。
在 Windows 的头文件 Winbase.h(包含于 Windows.h)中,GetProcAddress 的原型定义如下:
FARPROC GetProcAddress(
HMODULE hModule,
LPCSTR lpProcName
);
参数:
hModule:指向包含函数或变量的 DLL 模块的句柄。这个句柄通常是通过调用 LoadLibrary 或 LoadLibraryEx 函数获得的。
lpProcName:指向以 null 结尾的字符串的指针,该字符串指定函数或变量的名称。或者,如果函数的导出序号已知,可以通过将序号的低位字传递给 lpProcName 参数(高位字为0)来指定函数。Dependency Walker (DLL依赖查看工具)可以查看DLL中的函数。
返回值:
如果函数成功,返回值是指定函数或变量的地址。如果函数失败,返回值为 NULL。要获取扩展的错误信息,可以调用 GetLastError。
三、FreeLibrary 函数用于从调用进程的地址空间中卸载指定的动态链接库(DLL)。这意味着当一个 DLL 不再被使用时,可以通过调用 FreeLibrary 来释放占用的资源和内存。
BOOL FreeLibrary(
HMODULE hModule
);
参数
hModule: 这是要卸载的 DLL 模块的句柄。这个句柄是通过之前对 LoadLibrary 或 LoadLibraryEx 函数的调用获得的。
返回值
如果函数成功,返回值为非零。
如果函数失败,返回值为零。要获取扩展的错误信息,可以调用 GetLastError 函数。
2. DLL显式链接操作
参考资料:https://blog.csdn.net/xiamentingtao/article/details/51052925?spm=1001.2014.3001.5506
- 创建一个空的DLL
- 添加Interface接口类头文件
#pragma once
// 下列 ifdef 块是创建使从 DLL 导出更简单的
// 宏的标准方法。此 DLL 中的所有文件都是用命令行上定义的 INTERFACE_EXPORTS
// 符号编译的。在使用此 DLL 的
// 任何其他项目上不应定义此符号。这样,源文件中包含此文件的任何其他项目都会将
// INTERFACE_API 函数视为是从 DLL 导入的,而此 DLL 则将用此宏定义的
// 符号视为是被导出的。
#ifdef INTERFACE_EXPORTS
#define INTERFACE_API __declspec(dllexport)
#else
#define INTERFACE_API __declspec(dllimport)
#endif
class Interface
{
public:
virtual void ShowMsg() = 0; // 将调用方需要调用的成员函数声明成纯虚函数
virtual ~Interface() {};// 抽象类的虚析构函数
};
extern "C" INTERFACE_API Interface* Export(void);
- 添加test.h头文件,实现对Test类的声明。
#pragma once
#include "Interface.h"
#include <string>
class Test :public Interface
{
public:
Test();
virtual ~Test();
virtual void ShowMsg(void);
private:
std::string s;
};
- 添加test.cpp源文件,Test类的实现。
#include "pch.h"
#include "test.h"
#include <iostream>
Test::Test()
{
s = "hello form dll";
}
Test::~Test()
{
std::cout << "destroy";
}
void Test::ShowMsg()
{
std::cout << s << std::endl;
}
- 新建Interface.cpp,通过导出函数形式向调用方提供指向派生类对象的基类指针
// Interface.cpp : 定义 DLL 应用程序的导出函数。
//
#include "pch.h"
#include "Interface.h"
#include <iostream>
#include "test.h"
// 通过导出函数形式向调用方提供指向派生类对象的基类指针
Interface* Export(void)
{
return (Interface*)new Test();
}
- 新建一个调用DLL的项目,DLL_Explicit_Call.cpp。
- 将Dll_Explicit中的Interface.h头文件复制到DLL_Explicit_Call文件夹下,并通过现有项添加。
- 添加调用文件Dll_Explicit_Call.cpp。
#include <Windows.h>
#include <iostream>
#include "Interface.h" // 包含抽象类从而使用接口
// 在调用处添加如下代码
using pExport = Interface * (*)(void); // 定义指向导出函数的指针类型
int main()
{
HINSTANCE hDll = LoadLibrary("Dll_Explicit.dll");// 加载DLL库文件,DLL名称和路径用自己的
if (hDll == NULL)
{
std::cout << "load dll fail \n";
return -1;
}
pExport Get = (pExport)GetProcAddress(hDll, "Export");// 将指针指向函数首地址
if (Get == NULL)
{
std::cout << "load address fail \n";
return -1;
}
Interface *t = Get();// 调用导出函数获得抽象类指针
t->ShowMsg();// 通过该指针调用类成员函数
delete t; // 释放DLL中生成的对象
FreeLibrary(hDll); //释放库句柄
system("pause");
return 0;
}
- 设置启动项为DLL_Explicit_Call。
- 运行结果