linux下C++ 插件(plugin)實現技術

linux下C++ 插件(plugin)實現技術

標籤: c++ 插件函數

應用程序中使用插件技術,有利於往後的版本更新、維護(好比打補丁)和功能擴展,是一種很實用的技術。其最大的特色是更新插件時無需從新編譯主程序,對於一個設計良好的應用系統而言,甚至能夠作到業務功能的在線升級。本文介紹了linux下用C++實現插件的一個簡單實例,但願能對你們有所啓發。spa

爲了能作到更新插件時無需從新編譯主程序,要求主程序中定義的接口是定死的,而接口的實現被放到了具體的插件中,這樣主程序在運行時刻將插件加載進來,就可使用這些接口所提供的功能了。在面向對象的系統中,各個功能模塊被封裝到類中,所以在C++中實現插件技術,就須要在主程序中提供基類,併爲這些基類定義明確的接口,而後在插件(動態庫或共享庫)中定義派生類,並實現基類中全部的接口。插件

咱們以計算多邊形面積爲例,首先定義一個基類CPolygon:設計

/*+********************************************************/
/*+********************************************************/
/*+********************************************************/指針

/* polygon.h */htm

#ifndef __POLYGON_H__
#define __POLYGON_H__對象

#include < iostream >

class CPolygon
{
public:

CPolygon(){}

virtual ~CPolygon(){}

virtual double area(void) const = 0;
};

#endif /* __POLYGON_H__ */

/*-********************************************************/
/*-********************************************************/
/*-********************************************************/

注意基類不必定是虛類(有純虛函數的類),可是接口必定要定義成虛函數,由於最終主程序是經過基類指針
來調派生類的接口函數,另外若是基類中有資源分配(new)的話,析構函數必定要定義成虛的,不然不會被
調用,形成內存泄漏。

接下來要定義派生類,並放到共享庫(triangle.so)中:

/*+********************************************************/
/*+********************************************************/
/*+********************************************************/

/* triangle.h */

#ifndef __TRIANGLE_H__
#define __TRIANGLE_H__

#include " polygon.h "
#include < iostream >

class CTriangle : public CPolygon
{
public:

virtual double area(void) const;

};

#endif /* __TRIANGLE_H__ */


/* triangle.cpp */

#include "triangle.h"

extern "C"
{
void * create()
{
return new CTriangle;
}

}

double CTriangle::area(void) const
{
std::cout << "area of triangle" << std::endl;
return 0;
}

/*-********************************************************/
/*-********************************************************/
/*-********************************************************/

其中定義了函數「create」用來建立CTriangle類對象,該函數可以讓主程序得到CTriangle對象指針,從而
能夠訪問CTriangle類對象。主程序經過調用dlsym獲取指向該函數的指針,須要指出的是,因爲dlsym被
設計成c-style方式,所以調用c++定義的函數時,須要加上extern "C"

那麼主程序是如何調用共享庫的呢,代碼片斷以下:

/*+********************************************************/
/*+********************************************************/
/*+********************************************************/

typedef CPolygon* create_t();

void * handle = dlopen("triangle.so", RTLD_LAZY);

if( !handle )
{
std::cerr << dlerror() << std::endl;
exit(1);
}

create_t * create_triangle = (create_t *)dlsym(handle, "create");

CPolygon * pObj = create_triangle();

if( 0 != pObj )
{
pObj->area();
}

delete pObj;

dlclose(handle);

/*-********************************************************/
/*-********************************************************/
/*-********************************************************/

主程序經過dlopen打開triangle.so,而後經過dlsym獲得庫中的函數create指針,調用create後返回了
指向CTriangle類對象的指針,類型是CPolygon的,因爲虛函數的多態性, pObj->area() 實際是調用
了CTriangle::area.

好了,插件技術就是這麼簡單,回顧一下實現過程:寫一個基類,定義接口函數,而後在共享庫中寫
派生類,最後在主程序運行時刻打開共享庫(dlopen),並經過create函數獲得指向新建立的派生類
對象的指針,而後利用虛函數的多態性,調用派生類的各類方法。


不過進一步使用後你可能會發現,這樣實現會有些問題:

1. 每寫一個派生類就須要重寫一個create函數

注意到CTriangle類實現時定義的create函數必須返回 new CTriangle:

extern "C"
{
void * create()
{
return new CTriangle;
}

}

那麼若是再建一個類好比CRectangle, create函數必須重寫,返回 new CRectangle

這樣作一方面麻煩,另外CTriangle、CRectangle兩個類不能放到同一個共享庫中,不然會編譯時刻
提示重複定義錯誤。


2. 主程序沒法判斷create函數返回的是哪一個類所建立的對象

當只有一個基類(CPolygon)時主程序固然知道返回的是CPolygon派生類的對象指針:
create_t * create_triangle = (create_t *)dlsym(handle, "create");
CPolygon * pObj = create_triangle();

假若有多個基類,根據這些基類派生出不一樣類型的類時,沒法在主程序中判斷返回的是那個類的對象。


3. 操做繁瑣

沒有一個統一的操做界面,實現共享庫的加載、卸載、派生類對象的建立,特別是當須要加載一個目錄
下全部的共庫時,感受一個一個地加載太麻煩了,能不能批量加載呢。


經過動態類加載和創建Helper類能夠很好地解決上述問題,其中dynclass.h/dynclass.cpp中實現了動態
加載類對象,pluginhelper.h/pluginhelper.cpp實現了Plugin Helper,具體細節見附件。


下面簡單介紹一下使用步驟:


1. 首先定義基類(CPolygon),方法同上

2. 在共享庫中實現派生類

好比CTriangle:

/*+********************************************************/
/*+********************************************************/
/*+********************************************************/

/* triangle.h */

#ifndef __TRIANGLE_H__
#define __TRIANGLE_H__

#include " polygon.h "
#include < iostream >

class CTriangle : public CPolygon
{
public:

virtual double area(void) const;

};

#endif /* __TRIANGLE_H__ */


/* triangle.cpp */

#include " triangle.h "
#include " dynclass.h "

DYN_DECLARE(CTriangle);

double CTriangle::area(void) const
{
std::cout << "area of triangle" << std::endl;
return 0;
}

/*-********************************************************/
/*-********************************************************/
/*-********************************************************/


注意到此時派生類的實現(triangle.cpp)中已沒有了那個討厭的create了,被我偷偷放到
dynclass.cpp中了:

extern "C"
{
void * createByClassName(const char * strClassName)
{
return DYN_CREATE(strClassName);
}
}

因爲對任何派生類而言,該函數的實現都同樣,所以只須要實現一次,對使用者是不可見的,達到
了從派生類中拿走的目的。

另外增長了一個宏:DYN_DECLARE(CTriangle); 參數是類名(這裏用到了RTTI),每一個派生類對應
一個這樣的宏,該類就能夠支持類對象的動態加載了,須要包含頭文件dynclass.h


2. 在主程序中如何使用

使用起來也很是簡單,在主程序(main.cpp)中:

/*+********************************************************/
/*+********************************************************/
/*+********************************************************/
...

#include " pluginhelper.h "
#include " polygon.h "

...

CPluginHelper pluginHelper;

pluginHelper.Load( "./plugin", "*.so" );

CPolygon * pbase = (CPolygon *)pluginHelper.Create("CTriangle");

if( 0 != pbase )
{
pbase->area();
}

delete pbase;

pluginHelper.Unload( "./plugin", "*.so" );

/*-********************************************************/
/*-********************************************************/
/*-********************************************************/

首先定義CPluginHelper對象,調用Load方法加載共享庫,其中第一個參數是共享庫的路徑,第二
個參數是共享庫的名稱,共享庫名支持模式匹配,這裏表示要加載./plugin目錄全部so共享庫,
固然也能夠是某個具體的共享庫名。

隨後能夠經過CPluginHelper::Create方法,根據類名稱建立該類的對象,實現了參數化建立對象
的目的,而後就是對該對象的調用,當不用該對象時,須要調用delete來刪除。

最後,調用CPluginHelper::Unload將指定共享庫卸載。

相關文章
相關標籤/搜索