C++ Plug-in 技术的一些深度思考(2)

C++ Plug-in 技术的一些深度思考(1)

 

我们可以使用动态连接库的技术实现上诉的功能。大方向说,将CalcAdd和CalcMul编译连接成动态连接库,CalcFactory.cpp动态的加载它们。这样的话,可以实现两个功能:

[1] 如果更新libCalcAdd.so或者增加一个libCalcMin.so,不需要重新编译,链接,发布主程序calc,只需要重新发布你更新或者增加的so文件

[2] 如果我们可以横向的定义出主程序的测试规范,我们甚至不需要回归测试主程序calc。

我们重新设计了文件目录的组织结构,如下图所示:



 

有修改的文件有CalcFactory.cpp / CalcFactory.h。主要是增加了load 动态连接库的内容。 CalcAdd.cpp和CalcMul.cpp分别增加了一个C函数,返回一个动态分配的CalcAdd或者CalcMul。

// core/src/CalcFactory.cpp
#include <dlfcn.h> //for dlopen and dlsym
#include <iostream>

#include "../include/CalcFactory.h"

using std::cerr;
using std::endl;
using std::string;
using std::map;


map<string, CalcFactory::GetNewInstanceFun> CalcFactory::calcHandlers;

ICalc* CalcFactory::getCalc(string calcType)
{
    if (calcHandlers.end() == calcHandlers.find(calcType)) {
        string libName = "../lib/libCalc" + calcType + ".so";
        void *pdlHandle = dlopen(libName.c_str(), RTLD_NOW);
        if(!pdlHandle)
        {
            cerr << "Fail to load " << libName << endl;
            return 0;
        }
        char *err = dlerror();
        if(err)
        {
            cerr << "Fail to load " << libName << " due to " << err << endl;
            return 0;
        }
        void *rst = dlsym(pdlHandle, "getNewInstance");
        if (!rst) {
            cerr << "There is no \"getNewInstance\" function in " << libName << endl;
            return 0;
        }
        GetNewInstanceFun newFun = (GetNewInstanceFun)rst;
        if (!newFun) {
            cerr << "Fail to cast to (ICalc *)(*)(). " << endl;
            return 0;
        }
        err = dlerror();
        if(err)
        {
            cerr << "There is no \"getNewInstance\" function in " << libName
                 << " due to " << err <<endl;
            return 0;
        }
        calcHandlers[calcType] = newFun;
    }
    GetNewInstanceFun nf = calcHandlers[calcType];
    return nf();
}

 

注意这里的修改。

[1] 取消了这个cpp对CalcAdd.h和CalcMul.h的依赖。本质上,在Core这边里对Plug-in的使用都是完全基于接口ICalc的。

[2] 增加了一个全局的map,用来避免反复的load动态连接库。

[3] 在Unix动态连接的思路就是dlopen和dlsym,具体的技术大家可以google一下。如果有Windows的经验,可以发现这些过程Windows和Unix几乎是一致的。

 

// plugin/src/CalcAdd.cpp

#include "../include/CalcAdd.h"

void CalcAdd::calc(int *p, size_t size, int *result)
{
    for (size_t i = 0 ; i < size; ++i)
    {
        (*result) += p[i];
    }
}

CalcAdd::~CalcAdd() { }

extern "C"
{
    ICalc *getNewInstance()
    {
        return new CalcAdd;
    }
}

 

注意了,其实整个类CalcAdd.cpp的实现没有任何的改变。改变的是增加了一个函数ICalc *getNewInstance()。这个函数要注意几点:

[1] extern “C” 在这里不能省略。也许你运气好,在你的平台上省略这个东西能顺利运行通过。但是这种代码绝对不能移植,至少在我的Mac上面,不能省略这个extern "C"。本质原因是因为这个函数要被动态加载,而dlsym几乎要返回的那个函数指针是C链接的;

[2] getNewInstance()这个函数的签名,包括返回类型ICalc*, 参数个数和类型,函数名getNewInstance是不能乱改的。首先,函数名getNewInstance被CalcFactory.cpp的dlsym用作找到函数指针的索引,void *rst = dlsym(pdlHandle, "getNewInstance"); 如果函数名错了,那么这个返回的rst是NULL。如果函数签名不对,那么GetNewInstanceFun newFun = (GetNewInstanceFun)rst; 会出错。而且这种错误不容易检查,也许newFun并不是NULL。所以一旦这个Core定下来了,所有的Plug-in里面都必须是这样的函数:

extern "C"
{
    ICalc *getNewInstance()
    {
        // do something

    }
}
当然不用太担心,如果你写错了这个函数,你一测试就能发现。

其实,还有的修改就是Makefile。这个不同的平台,不同的编译器选项差别很大。我大致介绍一下:

[1] Solaris 的 CC:生成libCalcAdd.so的时候,需要使用-G参数。生成使用so的主程序需要使用-ldl

[2] Mac的g++     : 生成libCalcAdd.so的时候,需要使用-dynamiclib -flat_namespace。生成 使用so的主程序不需要使用另外的选项
[3] Linux的g++   : 生成libCalcAdd.so的时候,需要使用-fPIC -shared。生成使用so的主程序需要使用-ldl.

大家可以下载v3.tar.bz2查看源代码和Makefile。需要先进入Core 中 make -f Makefile.(mac|sun|lnx) core

然后进入plugin中 make -f Makefile.(mac|sun|lnx) plugin

然后在主目录的bin下面运行calc就可以了。

我们还可以试一下,在plugin里面加入一个CalcMax的类,找出数组中的最大值。当我们把这个libAddMax.so放到lib之后,我们主程序就可以运行成calc Max了。

 

这样返回类的指针的设计非常的不错,首先,如果你原有的系统,很可能CalcAdd和CalcMul就是类。这样你几乎不需要修改什么代码;其次,如果你的类CalcAdd也许没有实现那个calc接口,没关系,用Adaptor模式适配一下。当然了,也许你会觉得这里的算法类CalcAdd应该是单件啊,不应该每次都去new。这是因为有的系统,现存的CalcAdd里面有很多pirvate的变量和函数,而且这些函数都不是线程安全的。如果你改成单件类,那么使用过程中你的程序也会线程不安全。至少我们现在的系统就是这样的。所以我选择每次返回一个对象。

 

使用动态库不错,可以完全做到plug-in系统。而且未来完全可以做到热启动,热加载。但是我们先停一停,在下一节,我将给出一种完全不一样的实现plug-in的机制。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值