DLL-基础
Windows 存在 3 个最重要的 dll, 分别如下
- kernel32.dll 用来管理内存,进程、线程
- user32.dll 用于处理用户界面相关的东西
- GDI32.dll 用来绘制和显示文字
使用 dll 有什么好处,可以参考官方说明
初步使用
创建动态的 dll 可以直接参考官方说明,或者 DynamicExport1
, 如下
#pragma once
#ifdef DYNAMICLIB1_EXPORTS
#define LIBRARY_API_1 __declspec(dllexport)
#else
#define LIBRARY_API_1 __declspec(dllimport)
#endif
namespace DLLTEST
{
class LIBRARY_API_1 DynamicExport1
{
public:
int add(int l, int r);
int sub(int l, int r);
};
}
extern "C" LIBRARY_API_1 int Add(int l, int r);
//以下函数不会被导出
void InnerFun(void);
以下描述几个要点
符号的导入和导出(dllexport/dllimport)
msvc 通过 export 和 import 来区分导出项和导入项,从结果来看 export(可以通过 msvc 的 dumpbin /exports 来查看导出项)
我们注意到 dll 的生成结果通常包含以下几个(以 dynamic_lib_1
为例)
-a---- 2023/6/17 22:40 10752 dynamic_lib_1.dll //包含完整符号信息和偏移量
-a---- 2023/6/17 22:40 1582 dynamic_lib_1.exp //导出文件, 一般用于相互依赖的情况
-a---- 2023/6/17 22:40 3212 dynamic_lib_1.lib //仅导出符号, 具体可见下文的查看符号
-a---- 2023/6/17 22:40 937984 dynamic_lib_1.pdb //调试符号
关于 exp 文件可以参考这个
我们可以通过dumpbin
查看 dll 和 lib 的符号, 如
:\Personal\learning-notes\05-platform\01-win\dll\x64\Release>dumpbin /exports dynamic_lib_1.lib
Microsoft (R) COFF/PE Dumper Version 14.35.32216.1
Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file dynamic_lib_1.lib
File Type: LIBRARY
Exports
ordinal name
??4DynamicExport1@DLLTEST@@QEAAAEAV01@$$QEAV01@@Z (public: class DLLTEST::DynamicExport1 & __cdecl DLLTEST::DynamicExport1::operator=(class DLLTEST::DynamicExport1 &&))
??4DynamicExport1@DLLTEST@@QEAAAEAV01@AEBV01@@Z (public: class DLLTEST::DynamicExport1 & __cdecl DLLTEST::DynamicExport1::operator=(class DLLTEST::DynamicExport1 const &))
?add@DynamicExport1@DLLTEST@@QEAAHHH@Z (public: int __cdecl DLLTEST::DynamicExport1::add(int,int))
?sub@DynamicExport1@DLLTEST@@QEAAHHH@Z (public: int __cdecl DLLTEST::DynamicExport1::sub(int,int))
Add
Summary
D5 .debug$S
14 .idata$2
14 .idata$3
8 .idata$4
8 .idata$5
12 .idata$6
GCC 采用
/MT 和 /MD
MT 和 MD主要用来控制C/C++运行库是静态引用(MT)还是动态引用(MD),可以参考这个
MD 的发布时一般需要携带 MSVCR(versionnumber).DLL, 这也是我们经常见到 msvc110~170 的原因.
extern "C"
extern "C" 用于处理名称修饰的问题(去掉namespace), 具体用法可以参考这个
ABI 的兼容性
Visual Studio 2013 及更早版本中的 Microsoft C++ (MSVC) 编译器工具集不保证主版本间的二进制兼容性。 无法链接由这些工具集的不同版本生成的对象文件、静态库、动态库和可执行文件。 ABI、对象格式和运行时库不兼容。我们在 Visual Studio 2015 及更高版本中改变了此行为。 由其中任一版本的编译器编译的运行时库和应用具有二进制兼容性。 这反映在 C++ 工具集主版本号中,对于自 Visual Studio 2015 以来的所有版本,该版本号都以 14 开头。 (对于 Visual Studio 2015、2017、2019 和 2022,工具集版本分别为 v140、v141、v142 和 v143)。 假设你具有 Visual Studio 2015 生成的第三方库。 你仍可在 Visual Studio 2017、2019 或 2022 生成的应用程序中使用它们。 无需使用匹配工具集重新编译。 最新版本的 Microsoft Visual C++ 可再发行程序包(可再发行程序包)适用于所有版本。
具体可以参考这个
静态载入、延迟载入、动态载入
我们更经常使用静态载入的方式,动态载入的方式更多使用在插件处理上, 而延迟加载更多用在有性能问题的时候
动态载入
动态载入我们经常会在插件化的情况使用, 以下是一般的使用过程
//1. 创建C++类接口和实现
//提供 virutal 接口,实现多态
namespace DLLTEST
{
class DynamicExport2Base
{
public:
virtual ~DynamicExport2Base() = 0; // 析构函数的需要具体实现
virtual int add(int left, int right) = 0;
virtual int sub(int l, int r) = 0;
};
class DynamicExport2Impl : public DynamicExport2Base
{
public:
DynamicExport2Impl();
virtual ~DynamicExport2Impl();
virtual int add(int l, int r) override;
virtual int sub(int l, int r) override;
};
}
//2.导出 C 风格,用于动态加载
extern "C"
{
LIBRARY_API_2 DLLTEST::DynamicExport2Base *createObject(void);
LIBRARY_API_2 void releaseObject(const DLLTEST::DynamicExport2Base*);
}
//3. 加载模块
HMODULE dynamicModule2 = ::LoadLibraryEx(L"dynamic_lib_2", 0, NULL);
if (dynamicModule2 == nullptr)
{
cout << "loadlibrary fail:" << ::GetLastError() << "\n";
return 0;
}
//4. 获取调用对象的函数指针
typedef void* (*pFunCreateObject)(void);
typedef void (*pFunReleaseObject)(void*);
pFunCreateObject funCreateObj = (pFunCreateObject)::GetProcAddress(dynamicModule2, "createObject");
if (funCreateObj == nullptr)
{
cout << "get create fun fail:" << ::GetLastError() << "\n";
::FreeLibrary(dynamicModule2);
return 0;
}
pFunReleaseObject funReleaseObj = (pFunReleaseObject)::GetProcAddress(dynamicModule2, "releaseObject");
if (funReleaseObj == nullptr)
{
cout << "get release fun fail:" << ::GetLastError() << "\n";
::FreeLibrary(dynamicModule2);
return 0;
}
//5. 创建相应的对象
DynamicExport2Base* dynamicObj = (DynamicExport2Base*)funCreateObj();
if (dynamicObj == nullptr)
{
cout << "create dynamic object 2 fail:" << ::GetLastError() << "\n";
::FreeLibrary(dynamicModule2);
return 0;
}
//6. 根据偏移量调用接口
cout << "add(1,100)=" << dynamicObj->add(1, 100) << "\n";
//7. 释放对象
funReleaseObj(dynamicObj);
//8. 释放动态加载
::FreeLibrary(dynamicModule2);
return 0;
延迟加载
延迟加载有一些限制,具体可以参考这边
那什么时候会用到这个?
- 模块要加载的 dll 数量多, 很影响启动速度
- 有一些版本上的接口需要处理(当然这个通常直接采用 version 的方式处理, 在此处需要通过捕获异常处理)
使用
使用延迟加载有几个事项
- 在
项目
->链接器
->输入
->延迟加载 dll
添加需要加载的 dll 完整信息即 cl 选项的/DELAYLOAD:"delay_lib.dll"
- 其他的使用同照常的静态链接使用即可;
具体可以参考app/delayload.cpp
的实现
卸载
若需要动态的卸载延迟加载的模块,可以以下步骤实现
- 在
项目
->链接器
->高级
->卸载延迟加载的 dll
选择/DELAY:UNLOAD
选项 - 在代码中动态的调用
__FUnloadDelayLoadedDLL2
实现卸载
具体可以参考app/delayload.cpp
的实现
DllMain
可以参考这个
TODO: 在了解 TLS 来完善这个部分