动态链接库加载器

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

动态链接库加载器

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

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

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

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

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

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

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

#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中取出来使用。

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

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

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

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

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,那么只需要调用如下代码即可。

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

完整代码

DynamicLibraryLoader.h

////////////////////////////////////////////////////////////////////////////////
//
// 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

////////////////////////////////////////////////////////////////////////////////
//
// 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://tis.ac.cn/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" 署名-不可商用-禁止演绎 转载请注明原文链接及作者信息,侵权必究。

评论区 · 欢迎大家友好交流 · 若未正常显示请刷新网页

×

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