【转载】一个简洁通用的调用 DLL 函数的帮助类

335 阅读4分钟

参考原文地址: (原创)一个简洁通用的调用 DLL 函数的帮助类

我对文章的格式和错别字进行了调整,并补充并标注出了重要的部分。以下是正文。

正文

本次介绍一种调用 DLL 函数的通用简洁的方法,消除了原来调用方式的重复与繁琐,使得我们调用 DLL 函数的方式更加方便简洁。

用过 DLL 的人会发现 C++ 中调用 DLL 中的函数有点繁琐,调用过程是这样的:

  1. 在加载 DLL 后还要定义一个对应的函数指针类型
  2. 再调用 GetProcAddress 获取函数地址
  3. 再转成函数指针,最后调用该函数。

下面是调用 DLL 中 Max 和 Get 函数的例子。

void TestDll()
{
    typedef int (*pMax)(int a, int b);
    typedef int (*pGet)(int a);
    HINSTANCE hMode = LoadLibrary("MyDll.dll");
    if (hMode == nullptr)
        return;

    PMax Max = (PMax)GetProcAddress(hDLL, "Max");
    if (Max == nullptr)
        return;

    int ret = Max(5, 8); //8

    PMin Get = (PMin)GetProcAddress(hDLL, "Get");
    if (Get == nullptr)
        return;

    int ret = Get(5); //5

    FreeLibrary(hDLL);
}

这段代码看起来很繁琐,因为我们每用一个函数就需要先定义一个函数指针,然后再根据名称获取函数地址,最后调用。如果一个 DLL 中有上百个函数的话,这种重复而繁琐的定义会让人吐的。其实获取函数地址和调用函数的过程是重复逻辑,应该消除,我不想每次都定义一个函数指针和调用GetProcAddress,我觉得可以用一种简洁通用的方式去调用 DLL 中的函数。我希望调用 DLL 中的函数就像调用普通的函数一样,即传入一个函数名称和函数的参数就可以实现函数的调用了。就类似于:

Ret CallDllFunc(const string&funName, T arg)

如果以这种方式调用的话,我就能避免繁琐的函数指针定义以及反复的调用 GetProcAddress 了。

一种可行的解决方案

如果要按照

Ret CallDllFunc(const string& funName, T arg)

这种方式调用的话,首先我要把函数指针转换成一种函数对象或者泛型函数,这里我们可以用 std::function 去做这个事情,即通过一个函数封装 GetProcAddress ,这样通过函数名称我就能获取一个泛型函数 std::function ,我希望这个 function 是通用的,不论 DLL 中是什么函数都可以转换成这个 function , 最后调用这个通用 的 function 就可以了。但是调用这个通用的 function 还有两个问题需要解决:

  1. 不同函数的不同类型返回值怎么处理,因为函数的返回值可能是某些类型,如何以一种通用的返回值来消除这种不同返回值导致的差异呢?

  2. 函数的入参数目可能任意个数,且类型也不尽相同,如何来消除入参个数和类型的差异呢?

我们一个个解决问题吧,首先看看如何封装 GetProcAddress ,将函数指针转换成 std::function 。通过如下代码就可以了。

template <typename T>
std::function<T> GetFunction(const string &funcName)
{
    FARPROC funAddress = GetProcAddress(m_hMod, funcName.c_str());
    return std::function<T>((T *)(funAddress));
}

其中 T 是 std::function 的模板参数,即函数类型的签名。如果我们要获取上面例子中, Max 和 Get 函数,则可以这样获取:

auto fmax = GetFunction<int(int, int)>("Max");
auto fget = GetFunction<int(int)>("Get");

这种方式比之之前先定义函数指针再调用 GetProcAddress 的方式更简洁通用。

再看看如何解决函数返回值和入参不统一的问题,关于这个问题,其实在前面的博文中就讲到了,不知道的童鞋看这里

是的,还是通过 result_of可变参数模板来搞定。最终的调用函数是这样的:

template <typename T, typename... Args>
typename std::result_of<std::function<T>(Args...)>::type ExcecuteFunc(const string &funcName, Args &&...args)
{
    return GetFunction<T>(funcName)(args...);
}

上面的例子中要调用 Max 和 Get 函数,这样就行了:

auto max = ExcecuteFunc<int(int, int)>("Max", 5, 8);
auto ret = ExcecuteFunc<int(int)>("Get", 5);

怎么样,比之前的调用方式是不是简洁直观多了,没有了繁琐的函数指针的定义,没有了反复的调用 GetProcAddress 及其转换和调用。

如果要限定调用方式就在参数前面加以下代码,如

ExcecuteFunc<int __stdcall(int, int)>

最后看看完整的代码吧

#include <Windows.h>
#include <string>
#include <map>
#include <functional>
using namespace std;

class DllParser
{
public:
    DllParser()
    {
    }

    ~DllParser()
    {
        UnLoad();
    }

    bool Load(const string &dllPath)
    {
        m_hMod = LoadLibrary(dllPath.data());
        if (nullptr == m_hMod)
        {
            printf("LoadLibrary failed\n");
            return false;
        }

        return true;
    }

    bool UnLoad()
    {
        if (m_hMod == nullptr)
            return true;

        auto b = FreeLibrary(m_hMod);
        if (!b)
            return false;

        m_hMod = nullptr;
        return true;
    }

    /// @note
    template <typename T>
    T *GetFunction(const string &funcName)
    {
        auto addr = GetProcAddress(m_hMod, funcName.c_str());
        return (T *)(addr);
    }

    /// @note
    template <typename T, typename... Args>
    typename std::result_of<std::function<T>(Args...)>::type ExcecuteFunc(const string &funcName, Args &&...args)
    {
        auto f = GetFunction<T>(funcName);
        if (f == nullptr)
        {
            string s = "can not find this function " + funcName;
            throw std::exception(s.c_str());
        }

        return f(std::forward<Args>(args)...);
    }

private:
    HMODULE m_hMod;
    std::map<string, FARPROC> m_map;
};

附:std::result_of 的用法