Dll基础

发布时间 2023-06-24 20:34:25作者: hejianglin

DLL-基础

Windows 存在 3 个最重要的 dll, 分别如下

  1. kernel32.dll 用来管理内存,进程、线程
  2. user32.dll 用于处理用户界面相关的东西
  3. 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;

延迟加载

延迟加载有一些限制,具体可以参考这边
那什么时候会用到这个?

  1. 模块要加载的 dll 数量多, 很影响启动速度
  2. 有一些版本上的接口需要处理(当然这个通常直接采用 version 的方式处理, 在此处需要通过捕获异常处理)

使用

使用延迟加载有几个事项

  1. 项目->链接器->输入->延迟加载 dll 添加需要加载的 dll 完整信息即 cl 选项的 /DELAYLOAD:"delay_lib.dll"
  2. 其他的使用同照常的静态链接使用即可;
    具体可以参考 app/delayload.cpp 的实现

卸载

若需要动态的卸载延迟加载的模块,可以以下步骤实现

  1. 项目->链接器->高级->卸载延迟加载的 dll选择/DELAY:UNLOAD选项
  2. 在代码中动态的调用 __FUnloadDelayLoadedDLL2 实现卸载
    具体可以参考 app/delayload.cpp 的实现

DllMain

可以参考这个
TODO: 在了解 TLS 来完善这个部分

参考