第24课 可变参数模板(5)_DllHelper和lambda链式调用

1. dll帮助类

(1)dll的动态链接

  ①传统的调用方式:先调用LoadLibrary来加载dll,再定义函数指针类型,接着调用GetProcAddress获取函数地址。然后通过函数指针调用函数,最后通过FreeLibrary卸载dll

  ②问题:使用dll的过程存在重复逻辑。此外,如果dll中的函数较多,就需要频繁的定义函数指针和反复调用GetProcAddress。

(2)解决方案

  ①封装GetProcAddress函数,FARPROC类型的函数指针转换成std::function

  ②由于函数的入参数目、类型及返回值各不相同,可以通过result_of和可变参数模板来解决(见ExecuteFunc函数)。

【编程实验】简化dll的调用

//Dll.h

第24课 可变参数模板(5)_DllHelper和lambda链式调用

#ifndef _DLL_H_
#define _DLL_H_

#ifdef BUILD_DLL /* DLL export */ 
#define EXPORT __declspec(dllexport) 
#else /* EXE import */ 
#define EXPORT __declspec(dllimport) 
#endif 

#ifdef __cplusplus
extern "C"{
#endif

//求最大值
EXPORT int Max(int a, int b);

//求和
EXPORT int Sum(int a, int b);

#ifdef __cplusplus
}
#endif

#endif

View Code

//Dll.c

第24课 可变参数模板(5)_DllHelper和lambda链式调用

#include "dll.h"

//编译动态dll库:
//gcc -Wall -shared dll.c -o Mydll.dll
//或者
//gcc --share dll.c -o Mydll.dll
//调用dll库生成exe文件:
//gcc test.c Mydll.dll -o test
#define BUILD_DLL

//求最大值
EXPORT int Max(int a, int b)
{
    return a > b ? a : b;
}

//求和
EXPORT int Sum(int a, int b)
{
    return a + b;
}

View Code

//DllHelper.hpp

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

class DllHelper
{
private:
    HMODULE m_hMod;

    //FARPROC宏: typedef int (FAR WINAPI *FARPROC)();
    std::map<string, FARPROC> m_map; //将己加载的函数指针保存起来
public:
    DllHelper(): m_hMod(nullptr){}

    //封装GetProcAddress,将函数指针转为std::function对象
    template<typename T>  //T为要查找的函数类型
    std::function<T> GetFunction(const string& funcName)
    {
        auto it = m_map.find(funcName); //函数是否己被载入

        if(it == m_map.end()){
            auto addr = GetProcAddress(m_hMod, funcName.c_str()); //addr为FARPROC类型

            if(!addr)
                return nullptr;

            m_map.insert(std::make_pair(funcName, addr)); //保存addr地址(FARPROC类型)
            
            it = m_map.find(funcName); //取出addr(FARPROC类型)
        }

        return std::function<T>((T*)(it->second)); //将FARPROC类型的addr强转为T类型。
    }

    //调用函数
    template<typename T, typename...Args> //T为函数类型,Args为参数列表
    typename std::result_of<std::function<T>(Args...)>::type //原函数的返回类型
    ExecuteFunc(const string& funcName, Args&&...args)
    {
        auto f = GetFunction<T>(funcName);
        if(f == nullptr){
            string s = "Can not find this function " + funcName;
            throw std::exception(runtime_error(s.c_str()));
        }

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

    bool Load(const string& path)
    {
        m_hMod = LoadLibraryA(path.data());
        if(nullptr == m_hMod)
        {
            cout <<"LoadLibrary failed!" << endl;
        }

        return (m_hMod != nullptr);
    }

    bool UnLoad()
    {
        bool ret = true;
        if(m_hMod != nullptr){
            ret = FreeLibrary(m_hMod); //返回非零表示成功,零表示失败

            if(ret)
               m_hMod = nullptr;
        }

        return ret;
    }

    ~DllHelper(){UnLoad();}
};

//testDll.cpp

#include <iostream>
#include <windows.h>
#include "DllHelper.hpp"
using namespace std;

//传统方法
void testDll_1()
{
    typedef int(*PMax)(int, int);
    typedef int(*PSum)(int, int);
    HINSTANCE hDll=LoadLibrary("MyDll.dll");

    if(hDll == nullptr)
        return;

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

    int ret = Max(5, 8);

    cout << ret << endl;

    PSum Sum = (PSum)GetProcAddress(hDll,"Sum");
    if(Sum == nullptr)
        return;

    ret = Sum(5, 8);

    cout << ret << endl;

    FreeLibrary(hDll);        
}

//2. 利用DLLHelper类来简化调用
void testDll_2()
{
    DllHelper dh;
    dh.Load("MyDll.dll");

    cout << dh.ExecuteFunc<int(int,int)>("Max", 5, 9) << endl; //9
    cout << dh.ExecuteFunc<int(int,int)>("Sum", 4, 8) << endl; //12
}

int main()
{
    testDll_1();
    testDll_2();
    return 0;
}

2. Lambda链式调用

(1)链式调用使用场合:在一些延迟计算的场景下较为常见,目的是将多个函数按照前一个的输出作为下一个输入串起来然后再推迟到某个时刻计算

(2)实现细节

  ①先创建一个task对象,然后连续调用Then函数,其实该函数中的lambda形参可以是任意类型,只要保证前一个函数的输出为后一个函数的输入就行。

  ②每调用Then函数都生成一个task,并将这些task串起来,最后在需要的时候调用Run去计算结果。

  ③Then函数里,会生成一个新的Lambda表达式,该lambda的功能将参数传给上一个lambda(func)计算并将计算结果作为参数传回给f,以供新的lambda调用。最后将这个新的lambda表达式传入新Task并保存起来,以供后面的Run调用。

  ④Run函数的调用过程:会将参数传给最开始的函数计算出结果后传给第2个函数作为入参,这样一直计算到最后一个,从而得到最终结果。

//Lambda.cpp

#include <functional>
#include <iostream>
#include <type_traits>
using namespace std;

template <typename T> class Task; //前向声明

template<typename R, typename...Args>
class Task<R(Args...)> //R为返回值类型,Args为参数类型
{
private:
    std::function<R(Args...)> m_fn;
public:
    Task(std::function<R(Args...)>&& f) : m_fn(std::move(f)) {}
    Task(std::function<R(Args...)>& f) : m_fn(f) {}

    R Run(Args&& ...args)
    {
        return m_fn(std::forward<Args>(args)...); //完美转发
    }

    //连续调用该函数,将生成一个个新的Task。相当于将函数不断地串联起来
    //Then函数的返回值为Task<R(Args...)>类型,注意R表示返回值类型
    template<typename F>
    auto Then(F&& f)-> Task<typename std::result_of<F(R)>::type(Args...)>
    {
        //result_of<F(R)>:表示求参数为R类型的F函数的返回值类型,
        //其中的R表示上一个函数输出类型作为本F函数的输入参数类型
        using ret_type = typename std::result_of<F(R)>::type; //获取F的返回值类型

        auto func = std::move(m_fn);

        //调用Task<ret_type(Args...)>()构造函数,并将新产生的lambda保存在新Task的m_fn中。
        return Task<ret_type(Args...)>([func, f](Args...args)
        {
            //注意,这里不是将args直接传给f,即f(args...),而是先由前一个func(args...)计算结果作为f的入参。
            return f(func(std::forward<Args>(args)...));//将前一个函数的输出作为后一个函数的输入
        });
    }
};

void TestTask()
{
    Task<int(int)> task([](int i) {return i; });
    auto ret = task.Then([](int i) {return i + 1; })  
                   .Then([](int i) {return i + 2; })
                   .Then([](int i) {return i + 3; }).Run(1);  //Run的延迟调用
    cout << ret << endl; //7
}

int main()
{
    TestTask();
    return 0;
}

原文链接: https://www.cnblogs.com/5iedu/p/7823607.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    第24课 可变参数模板(5)_DllHelper和lambda链式调用

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/392837

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月3日 下午3:00
下一篇 2023年4月3日 下午3:00

相关推荐