动态链接库加载器

  1. 动态链接库加载器
    1. 完整代码
      1. DynamicLibraryLoader.h
      2. DynamicLibraryLoader.cpp
    2. 协议

动态链接库加载器

2020/11/28 六 阴天 月末周六刚下班,女朋友出去逛街了。

这次的话题是动态库加载器。

动态链接库通常分为隐式加载和显式加载。 隐式加载会在程序运行的开始自动加载动态库,不需要额外编写代码,但是当我们希望将动态库作为插件,动态地在程序运行时载入时,隐式加载的方法就不能达到目的了。

为了达到动态加载的目的,我们需要使用动态库的显式加载能力,动态库显式加载的“三兄弟”: Windows:LoadLibrary、GetProcAddress、FreeLibrary Linux:dlopen、dlsym、dlclose

调用动态库显式加载“三兄弟”,我们能够顺利完成:运行时加载动态库、根据函数名称获得函数地址、运行时卸载动态库。我们获取到函数地址后,就能够对函数进行调用。

但是,裸用API函数的做法看起来十分的低效,代码不够简洁,而且不便于维护。因此,我们期望对它进行一次封装,形成动态库加载器,把能力暴露出来,把难看的逻辑封闭在肚子里面。

首先,动态库是操作系统的概念,Windows和Linux的实现方式不同,我们需要对操作系统进行适配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#ifdef _WIN32
#include <Windows.h>
#define LIB_LIBRARY HMODULE
#define LIB_PROCESS FARPROC
#define LIB_INVALID_LIBRARY NULL
#define LIB_INVALID_PROCESS NULL
#define LIB_LOAD(file) LoadLibraryA(file)
#define LIB_UNLD(inst) !FreeLibrary(inst)
#define LIB_SYMB(inst, func) GetProcAddress(inst, func)
#define LIB_NAME "dll"
#else
#include <dlfcn.h>
#define LIB_LIBRARY void*
#define LIB_PROCESS void*
#define LIB_INVALID_LIBRARY nullptr
#define LIB_INVALID_PROCESS nullptr
#define LIB_LOAD(file) dlopen(file, RTLD_LAZY)
#define LIB_UNLD(inst) dlclose(inst)
#define LIB_SYMB(inst, func) dlsym(inst, func)
#define LIB_NAME "so"
#endif

接下来,我们看看加载器的数据结构。加载器不会涉及很复杂的数据结构,如果只做一般的加载和调用,只需要记录加载了的链接库LIB_LIBRARY就足够了。但是,每次的函数调用都去调用LIB_SYMB去找动态库里的函数地址,对于频繁的动态库内的函数调用,资源消耗会增大,因此我们做一个cache,将取过的函数地址缓存住,下次访问时直接从cache中取出来使用。

1
2
LIB_LIBRARY library;
std::unordered_map<std::string, LIB_PROCESS> cache;

接下来,动态库的加载和卸载,直调API函数就能完成。

1
2
3
// 实现略,可参见文末完整代码
bool Load(const std::string& file);
bool UnLoad();

最后,是最关键的函数调用。我们使用了C++的高级功能(模板函数、可变参数模板列表、自动类型推导),直接上代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
template <typename FuncAddr>
FuncAddr* GetFunction(const std::string& function)
{
LIB_PROCESS addr = LIB_INVALID_PROCESS;
auto it = cache.find(function);
if (it == cache.end()) {
LIB_PROCESS proc = LIB_SYMB(library, function.c_str());
it = cache.emplace(function, proc).first;
}
addr = it->second;
return reinterpret_cast<FuncAddr*>(addr);
}

template <typename Func, typename ...Args>
typename std::result_of<std::function<Func>(Args...)>::type
ExecuteFunction(const std::string& func, Args&& ...args)
{
auto fn = GetFunction<Func>(func);
if (fn == nullptr) {
std::string except = "Cannot find function: " + func;
throw std::exception(except.c_str());
}
return fn(std::forward<Args>(args)...);
}

我们在使用时,只需要调用ExecuteFunction函数,即可调用动态库中的函数。

具体可以看一个例子: Windows平台下DbgHelp.dll库中有一个MakeSureDirectoryPathExists函数,能够在指定的路径不存在时尝试创建这个路径,该函数能够快速创建多级目录,该函数的声明为 BOOL WINAPI MakeSureDirectoryPathExists(PCSTR path);。 假设我们想要动态调用这个函数,创建一个目录D:\test,那么只需要调用如下代码即可。

1
2
3
4
DynamicLibraryLoader loader;
loader.Load("DbgHelp.dll");
loader.ExecuteFunction<BOOL WINAPI (PCSTR)>("MakeSureDirectoryPathExists", "D:\\test\\");
loader.UnLoad();

完整代码

DynamicLibraryLoader.h

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
////////////////////////////////////////////////////////////////////////////////
//
// MIT License
//
// Copyright (c) 2020 kongdeyou(https://tis.ac.cn/blog/kongdeyou/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
////////////////////////////////////////////////////////////////////////////////

#ifndef _DYNAMIC_LIBRARY_LOADER_H_
#define _DYNAMIC_LIBRARY_LOADER_H_

#include <string>
#include <functional>
#include <unordered_map>

#ifdef _WIN32
#include <Windows.h>
#define LIB_LIBRARY HMODULE
#define LIB_PROCESS FARPROC
#define LIB_INVALID_LIBRARY NULL
#define LIB_INVALID_PROCESS NULL
#define LIB_LOAD(file) LoadLibraryA(file)
#define LIB_UNLD(inst) !FreeLibrary(inst)
#define LIB_SYMB(inst, func) GetProcAddress(inst, func)
#define LIB_NAME "dll"
#else
#include <dlfcn.h>
#define LIB_LIBRARY void*
#define LIB_PROCESS void*
#define LIB_INVALID_LIBRARY nullptr
#define LIB_INVALID_PROCESS nullptr
#define LIB_LOAD(file) dlopen(file, RTLD_LAZY)
#define LIB_UNLD(inst) dlclose(inst)
#define LIB_SYMB(inst, func) dlsym(inst, func)
#define LIB_NAME "so"
#endif

class DynamicLibraryLoader final {
public:
DynamicLibraryLoader();
~DynamicLibraryLoader();

bool Load(const std::string& file);
bool UnLoad();

template <typename FuncAddr>
FuncAddr* GetFunction(const std::string& function)
{
LIB_PROCESS addr = LIB_INVALID_PROCESS;
auto it = cache.find(function);
if (it == cache.end()) {
LIB_PROCESS proc = LIB_SYMB(library, function.c_str());
it = cache.emplace(function, proc).first;
}
addr = it->second;
return reinterpret_cast<FuncAddr*>(addr);
}

template <typename Func, typename ...Args>
typename std::result_of<std::function<Func>(Args...)>::type
ExecuteFunction(const std::string& func, Args&& ...args)
{
auto fn = GetFunction<Func>(func);
if (fn == nullptr) {
std::string except = "Cannot find function: " + func;
throw std::exception(except.c_str());
}
return fn(std::forward<Args>(args)...);
}

private:
LIB_LIBRARY library;
std::unordered_map<std::string, LIB_PROCESS> cache;
};

#endif

DynamicLibraryLoader.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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
////////////////////////////////////////////////////////////////////////////////
//
// MIT License
//
// Copyright (c) 2020 kongdeyou(https://tis.ac.cn/blog/kongdeyou/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
////////////////////////////////////////////////////////////////////////////////

#include "DynamicLibraryLoader.h"

DynamicLibraryLoader::DynamicLibraryLoader()
{
library = LIB_INVALID_LIBRARY;
}

DynamicLibraryLoader::~DynamicLibraryLoader()
{
UnLoad();
}

bool DynamicLibraryLoader::Load(const std::string& file)
{
if (library != LIB_INVALID_LIBRARY) {
return false;
}

library = LIB_LOAD(file.c_str());
if (library == LIB_INVALID_LIBRARY) {
return false;
}
return true;
}

bool DynamicLibraryLoader::UnLoad()
{
if (library == LIB_INVALID_LIBRARY) {
return true;
}

cache.clear();

if (LIB_UNLD(library)) {
return false;
}
library = LIB_INVALID_LIBRARY;
return true;
}

协议

本文遵循CC BY-ND 4.0协议,署名-禁止演绎。

本文中所提供的源代码遵循MIT开源协议。 代码托管于:https://github.com/KondeU/DynamicLibraryLoader

转载请注明出处:https://tis.ac.cn/blog/kongdeyou/动态链接库加载器/

原始链接:https://blog.kdyx.net/blog/kongdeyou/%E5%8A%A8%E6%80%81%E9%93%BE%E6%8E%A5%E5%BA%93%E5%8A%A0%E8%BD%BD%E5%99%A8/

版权声明: "CC BY-NC-ND 4.0" 署名-不可商用-禁止演绎 转载请注明原文链接及作者信息,侵权必究。

×

喜欢或有帮助?赞赏下作者呗!