linux下C++ 插件(plugin)實(shí)現(xiàn)技術(shù)
文章來(lái)源:http://masterdog.bokee.com/563395.html
?
?????應(yīng)用程序中使用插件技術(shù),有利于日后的版本更新、維護(hù)(比如打補(bǔ)丁)和功能擴(kuò)展,是一種很實(shí)用的技術(shù)。其最大的特點(diǎn)是更新插件時(shí)無(wú)需重新編譯主程序,對(duì)于一個(gè)設(shè)計(jì)良好的應(yīng)用系統(tǒng)而言,甚至可以做到業(yè)務(wù)功能的在線升級(jí)。本文介紹了linux下用C++實(shí)現(xiàn)插件的一個(gè)簡(jiǎn)單實(shí)例,希望能對(duì)大家有所啟發(fā)。
為了能做到更新插件時(shí)無(wú)需重新編譯主程序,要求主程序中定義的接口是定死的,而接口的實(shí)現(xiàn)被放到了具體的插件中,這樣主程序在運(yùn)行時(shí)刻將插件加載進(jìn)來(lái),就可以使用這些接口所提供的功能了。在面向?qū)ο蟮南到y(tǒng)中,各個(gè)功能模塊被封裝到類(lèi)中,因此在C++中實(shí)現(xiàn)插件技術(shù),就需要在主程序中提供基類(lèi),并為這些基類(lèi)定義明確的接口,然后在插件(動(dòng)態(tài)庫(kù)或共享庫(kù))中定義派生類(lèi),并實(shí)現(xiàn)基類(lèi)中所有的接口。
我們以計(jì)算多邊形面積為例,首先定義一個(gè)基類(lèi)CPolygon:
/**/
/*
+*******************************************************
*/
/**/
/*
+*******************************************************
*/
/**/
/*
+*******************************************************
*/
/**/
/*
?polygon.h?
*/
#ifndef?__POLYGON_H__
#define
?__POLYGON_H__
#include?
<
?iostream?
>
class
?CPolygon
{
public
:

????CPolygon()
{}
????
virtual
?
~
CPolygon()
{}
????
virtual
?
double
?area(
void
)?
const
?
=
?
0
;
}
;
#endif
?/*?__POLYGON_H__?*/
/**/
/*
-*******************************************************
*/
/**/
/*
-*******************************************************
*/
/**/
/*
-*******************************************************
*/
注意基類(lèi)不一定是虛類(lèi)(有純虛函數(shù)的類(lèi)),但是接口一定要定義成虛函數(shù),因?yàn)樽罱K主程序是通過(guò)基類(lèi)指針
來(lái)調(diào)派生類(lèi)的接口函數(shù),另外如果基類(lèi)中有資源分配(new)的話,析構(gòu)函數(shù)一定要定義成虛的,否則不會(huì)被
調(diào)用,造成內(nèi)存泄漏。
接下來(lái)要定義派生類(lèi),并放到共享庫(kù)(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
;
}
/**/
/*
-*******************************************************
*/
/**/
/*
-*******************************************************
*/
/**/
/*
-*******************************************************
*/
其中定義了函數(shù)“create”用來(lái)創(chuàng)建CTriangle類(lèi)對(duì)象,該函數(shù)可讓主程序獲得CTriangle對(duì)象指針,從而
可以訪問(wèn)CTriangle類(lèi)對(duì)象。主程序通過(guò)調(diào)用dlsym獲取指向該函數(shù)的指針,需要指出的是,由于dlsym被
設(shè)計(jì)成c-style方式,因此調(diào)用c++定義的函數(shù)時(shí),需要加上extern "C"
那么主程序是如何調(diào)用共享庫(kù)的呢,代碼片段如下:
/**/
/*
+*******************************************************
*/
/**/
/*
+*******************************************************
*/
/**/
/*
+*******************************************************
*/
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);

/**/
/*
-*******************************************************
*/
/**/
/*
-*******************************************************
*/
/**/
/*
-*******************************************************
*/
主程序通過(guò)dlopen打開(kāi)triangle.so,然后通過(guò)dlsym得到庫(kù)中的函數(shù)create指針,調(diào)用create后返回了
指向CTriangle類(lèi)對(duì)象的指針,類(lèi)型是CPolygon的,由于虛函數(shù)的多態(tài)性, pObj->area() 實(shí)際是調(diào)用
了CTriangle::area.
好了,插件技術(shù)就是這么簡(jiǎn)單,回顧一下實(shí)現(xiàn)過(guò)程:寫(xiě)一個(gè)基類(lèi),定義接口函數(shù),然后在共享庫(kù)中寫(xiě)
派生類(lèi),最后在主程序運(yùn)行時(shí)刻打開(kāi)共享庫(kù)(dlopen),并通過(guò)create函數(shù)得到指向新創(chuàng)建的派生類(lèi)
對(duì)象的指針,然后利用虛函數(shù)的多態(tài)性,調(diào)用派生類(lèi)的各種方法。
不過(guò)進(jìn)一步使用后你可能會(huì)發(fā)現(xiàn),這樣實(shí)現(xiàn)會(huì)有些問(wèn)題:
1. 每寫(xiě)一個(gè)派生類(lèi)就需要重寫(xiě)一個(gè)create函數(shù)
注意到CTriangle類(lèi)實(shí)現(xiàn)時(shí)定義的create函數(shù)必須返回 new CTriangle:
?
extern
?
"
C
"
{
????
void
?
*
?create()
????
{
????????
return
?
new
?CTriangle;
????}
}
?
那么如果再建一個(gè)類(lèi)比如CRectangle, create函數(shù)必須重寫(xiě),返回 new CRectangle
這樣做一方面麻煩,另外CTriangle、CRectangle兩個(gè)類(lèi)不能放到同一個(gè)共享庫(kù)中,否則會(huì)編譯時(shí)刻
提示重復(fù)定義錯(cuò)誤。
2. 主程序無(wú)法判斷create函數(shù)返回的是哪個(gè)類(lèi)所創(chuàng)建的對(duì)象
當(dāng)只有一個(gè)基類(lèi)(CPolygon)時(shí)主程序當(dāng)然知道返回的是CPolygon派生類(lèi)的對(duì)象指針:
create_t * create_triangle = (create_t *)dlsym(handle, "create");
CPolygon * pObj = create_triangle();
假如有多個(gè)基類(lèi),根據(jù)這些基類(lèi)派生出不同類(lèi)型的類(lèi)時(shí),無(wú)法在主程序中判斷返回的是那個(gè)類(lèi)的對(duì)象。
3. 操作繁瑣
沒(méi)有一個(gè)統(tǒng)一的操作界面,實(shí)現(xiàn)共享庫(kù)的加載、卸載、派生類(lèi)對(duì)象的創(chuàng)建,特別是當(dāng)需要加載一個(gè)目錄
下所有的共享庫(kù)時(shí),感覺(jué)一個(gè)一個(gè)地加載太麻煩了,能不能批量加載呢。
通過(guò)動(dòng)態(tài)類(lèi)加載和建立Helper類(lèi)可以很好地解決上述問(wèn)題,其中dynclass.h/dynclass.cpp中實(shí)現(xiàn)了動(dòng)態(tài)
加載類(lèi)對(duì)象,pluginhelper.h/pluginhelper.cpp實(shí)現(xiàn)了Plugin Helper,具體細(xì)節(jié)見(jiàn)附件。
下面簡(jiǎn)單介紹一下使用步驟:
1. 首先定義基類(lèi)(CPolygon),方法同上
2. 在共享庫(kù)中實(shí)現(xiàn)派生類(lèi)
比如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
;
}
/**/
/*
-*******************************************************
*/
/**/
/*
-*******************************************************
*/
/**/
/*
-*******************************************************
*/
注意到此時(shí)派生類(lèi)的實(shí)現(xiàn)(triangle.cpp)中已沒(méi)有了那個(gè)討厭的create了,被我偷偷放到
dynclass.cpp中了:
extern
?
"
C
"
{
????
void
?
*
?createByClassName(
const
?
char
?
*
?strClassName)
????
{
????????
return
?DYN_CREATE(strClassName);
????}
}
由于對(duì)任何派生類(lèi)而言,該函數(shù)的實(shí)現(xiàn)都一樣,因此只需要實(shí)現(xiàn)一次,對(duì)使用者是不可見(jiàn)的,達(dá)到
了從派生類(lèi)中拿走的目的。
另外增加了一個(gè)宏:DYN_DECLARE(CTriangle); 參數(shù)是類(lèi)名(這里用到了RTTI),每個(gè)派生類(lèi)對(duì)應(yīng)
一個(gè)這樣的宏,該類(lèi)就可以支持類(lèi)對(duì)象的動(dòng)態(tài)加載了,需要包含頭文件dynclass.h
2. 在主程序中如何使用
使用起來(lái)也非常簡(jiǎn)單,在主程序(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對(duì)象,調(diào)用Load方法加載共享庫(kù),其中第一個(gè)參數(shù)是共享庫(kù)的路徑,第二
個(gè)參數(shù)是共享庫(kù)的名稱(chēng),共享庫(kù)名支持模式匹配,這里表示要加載./plugin目錄所有so共享庫(kù),
當(dāng)然也可以是某個(gè)具體的共享庫(kù)名。
隨后可以通過(guò)CPluginHelper::Create方法,根據(jù)類(lèi)名稱(chēng)創(chuàng)建該類(lèi)的對(duì)象,實(shí)現(xiàn)了參數(shù)化創(chuàng)建對(duì)象
的目的,然后就是對(duì)該對(duì)象的調(diào)用,當(dāng)不用該對(duì)象時(shí),需要調(diào)用delete來(lái)刪除。
最后,調(diào)用CPluginHelper::Unload將指定共享庫(kù)卸載。
http://masterdog.bokee.com/inc/20050116132524159116.zip
posted on 2006-08-26 04:37 楊粼波 閱讀(829) 評(píng)論(0) 編輯 收藏 引用 所屬分類(lèi): C++

