XPCOM--LINUX下的組件開發技術的一些補充與說明
原文出自:《世界商業評論》ICXO.COM ( 日期:2004-07-14 13:56)
--------------------------------------------------------------------------------
boise bjgxjob@163.com
--------------------------------------------------------------------------------
COM技術作為微軟推行的一種組件技術,在WINDOWS平台站有重要地位,在模組重用,跨語言通訊等方面都能見到其身影。但今天給我要介紹的是LINUX下的COM實現----XPCOM,這是MOZILLA瀏覽器項目中所使用的基本技術,我們可以用C++製作XPCOM組件,在C++客戶程式或MOZILLA瀏覽器中通過JAVASCRIPT指令碼來調用組件,從而實現軟體模組的複用。
1、 配置XPCOM的開發環境。
首先到MOZILLA的FTP下載Gecko-sdk包,這是XPCOM的開發包,MOZILLA的源碼中也包括該SDK。解壓該tgz包,可以看到產生大約十多個目錄:
/sdk/gecko-sdk/
/sdk/gecko-sdk/xpcom/bin
/sdk/gecko-sdk/xpcom/idl
/sdk/gecko-sdk/xpcom/include
/sdk/gecko-sdk/nspr
這裡說明一下其中的一些基本部分。
/sdk/gecko-sdk/xpcom/bin下主要包含了一些檔案:
xpidl:這是idl編譯器,用以根據idl產生c++標頭檔或組件類型庫檔案.
Regxpcom:這是組件註冊工作,如果我們在MOZILLA瀏覽器中調用組件,其實不會用該工具。
Xpt-dump:類型庫查看程式,用來查看.xpt檔案中的組件資訊。
libxpcomglue.a:這是XPCOM的基本庫檔案,在產生組件時將會被串連到我們的組件庫中。
/sdk/gecko-sdk/xpcom/idl,該目錄中包含了idl資料類型定義檔案。
/sdk/gecko-sdk/xpcom/include,其中包含了製作XPCOM時所需要的基本的C++標頭檔。
/sdk/gecko-sdk中還包含了其它一引起目錄,如/sdk/sdk/gecko-sdk/string/include,其中包含了XPCOM中常字串類的C++頭
檔案,如果我們的組件中需要使用這些類,只需包含進必要的標頭檔及庫檔案即可。
2、 撰寫idl檔案。
這裡要先用到一個uuidgen(LINUX下類似MS GUIDGEN的一個命令列程式)用以產生組件的uuid, 我們將其輸出先重新導向到一個文本中,呆會兒即可使用,這裡我們舉一個簡單的例子,來示範組件的產生過程。
Idl檔案如下:
//filename: nsIMyCom.idl
//begin idl --------------------------------------
#include nsISupports.idl
[scriptable, uuid(5217115e-11fe-4d01-966d-9b27ffda6498)]
interface nsIMyCom:nsISupports//在這裡需要注意的是:nsIMyCom以"nsI"為首碼,
//可以方便在之後的C++檔案中產生相應的類名,類名為nsI之後的部份,如MyCom;
//若介面名前三個字母為其它的字元,一般產生的C++類的類名為_MYCLASS_
{
void Hello(in string in_str, [retval] out string out_str);
};
//end idl-----------------------------------------
好了,該組件很簡單,只有一個介面,並且也只有一個方法,該方法有一個字串輸入參數in_str,並且有一個字串傳回值out_str。
3、編譯該idl檔案,並完成該組件對應的C++實現。
/sdk/gecko-sdk/xpcom/bin/xpidl -I/sdk/gecko-sdk/xpcom/bin/idl -m header nsIMyCom.idl
如果沒有錯誤,這時在目前的目錄下將會產生一個nsIMyCom.h檔案,該檔案是idl編譯器對應上面的idl檔案所產生的c++標頭檔。
下面是編譯器產生的nsIMyCom.h檔案內容:
//--------------------------------------------------------
#ifndef __gen_nsIMyCom_h__
#define __gen_nsIMyCom_h__
#ifndef __gen_nsISupports_h__
#include nsISupports.h
#endif
/* For IDL files that dont want to include root IDL files. */
#ifndef NS_NO_VTABLE
#define NS_NO_VTABLE
#endif
/* starting interface: nsIMyCom */
#define NS_IMYCOM_IID_STR 5217115e-22fe-4d01-966d-9b27ffda6498
#define NS_IMYCOM_IID /
{0x5217115e, 0x22fe, 0x4d01, { 0x96, 0x6d, 0x9b, 0x27, 0xff, 0xda, 0x64, 0x98 }}
class NS_NO_VTABLE nsIMyCom : public nsISupports {
public:
NS_DEFINE_STATIC_IID_ACCESSOR(NS_IMYCOM_IID)
/* void Hello (in string in_str, [retval] out string out_str); */
NS_IMETHOD Hello(const char *in_str, char **out_str) = 0;
};
/* Use this macro when declaring classes that implement this interface. */
#define NS_DECL_NSIMYCOM /
NS_IMETHOD Hello(const char *in_str, char **out_str);
/* Use this macro to declare functions that forward the behavior of this interface to another object. */
#define NS_FORWARD_NSIMYCOM(_to) /
NS_IMETHOD Hello(const char *in_str, char **out_str) { return _to Hello(in_str, out_str); }
/* Use this macro to declare functions that forward the behavior of this interface to another object in a safe
way. */
#define NS_FORWARD_SAFE_NSIMYCOM(_to) /
NS_IMETHOD Hello(const char *in_str, char **out_str) { return !_to ? NS_ERROR_NULL_POINTER : _to->Hello
(in_str, out_str); }
#if 0
/* Use the code below as a template for the implementation class for this interface. */
/* Header file */
class nsMyCom : public nsIMyCom
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIMYCOM
nsMyCom();
virtual ~nsMyCom();
/* additional members */
};
/* Implementation file */
NS_IMPL_ISUPPORTS1(nsMyCom, nsIMyCom)
nsMyCom::nsMyCom()
{
/* member initializers and constructor code */
}
nsMyCom::~nsMyCom()
{
/* destructor code */
}
/* void Hello (in string in_str, [retval] out string out_str); */
NS_IMETHODIMP nsMyCom::Hello(const char *in_str, char **out_str)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
/* End of implementation class template. */
#endif
#endif /* __gen_nsIMyCom_h__ */
//---------------------------------------------------------
從上面可以看到, xpidl產生了對應該介面的標頭檔,同時還包括對該標頭檔實現的C++類模板.下一步的工作一樣很輕鬆,
我們將#if 0 至#endif 之間的代碼分別複製到建立的nsMyCom.h 和nsMyCom.cpp檔案中,
注意其中有新增的代碼,下面是產生的兩個檔案.
//filename: nsMyCom.h
#include nsImyCom.h
#define NS_MYCOM_CID /
{0x5217115e, 0x22fe, 0x4d01, { 0x96, 0x6d, 0x9b, 0x27, 0xff, 0xda, 0x64, 0x98 }}
//類似WINDOWS 中CLSID
#define NS_MYCOM_CONTRACTID @westsoft.org/mycom;1 //類似WINDOWS中的progid;
class nsMyCom : public nsIMyCom
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIMYCOM
nsMyCom();
virtual ~nsMyCom();
/* additional members */
};
//filename: nsMyCom.cpp
#include nsMyCom.h
#include nsMemory.h
#include <cstdio>
#include <cstdlib>
#include <string>
NS_IMPL_ISUPPORTS1_CI(nsMyCom, nsIMyCom) //此處的宏已修改:注意這裡與自動產生的C++代碼的不同之處:NS_IMPL_ISUPPORTS1
nsMyCom::nsMyCom()
{
}
nsMyCom::~nsMyCom()
{
}
/* void Hello (in string in_str, [retval] out string out_str); */
NS_IMETHODIMP nsMyCom::Hello(const char *in_str, char **out_str)
{
printf(n-----------------n);
printf(%sn, in_str);
std::string str_tmp = your input is: ;
str_tmp += in_str;
*out_str = (char*)malloc(str_tmp.length() + 1);
*out_str = (char*)str_tmp.c_str();
return NS_OK;
}
4、完成組件的Factory 方法及註冊模組。
組件本身的實現就上面兩個類即可以了. 但是我們僅把上面的類產生動態庫是不能作為組件工作的,我們還需要做一件事情.實現組件的註冊及建立相關的功能.這幾乎是一個固定的模式.
下面是該部分的代碼,跟MS中的實作類別似,用了許多的宏:
#include nsIGenericFactory.h
#include nsMyCom.h
NS_GENERIC_FACTORY_CONSTRUCTOR(nsMyCom)
static NS_METHOD nsMyComRegistrationProc(nsIComponentManager *aCompMgr,
nsIFile *aPath, const char *registryLocation, const char *componentType, const nsModuleComponentInfo *info)
{
return NS_OK;
}
static NS_METHOD nsMyComUnregistrationProc(nsIComponentManager *aCompMgr,
nsIFile *aPath, const char *registryLocation, const nsModuleComponentInfo *info)
{
return NS_OK;
}
NS_DECL_CLASSINFO(nsMyCom)
static const nsModuleComponentInfo components[] ={
{ "nsMyCom Component", NS_MYCOM_CID, NS_MYCOM_CONTRACTID,nsMyComConstructor,//這裡,"Constructor"是固定的,nsMyCom是類名
nsMyComRegistrationProc /* NULL if you dont need one */,
nsMyComUnregistrationProc /* NULL if you dont need one */,
NULL /* no factory destructor */,
NS_CI_INTERFACE_GETTER_NAME(nsMyCom),
NULL /* no language helper */,
&NS_CLASSINFO_NAME(nsMyCom)
}
};
NS_IMPL_NSGETMODULE(nsMyComModule, components)//nsMyComModule是模組源碼檔案編譯後的模組名:nsMyComModule
5、製作Makefile,產生,安裝組件
好了,我們可以編寫Makefile檔案,來編譯我們剛才編寫的組件了.
#filename:Makefile
#begine-------------------------------------
CPP = g++
CPPFLAGS += -fno-rtti -fno-exceptions -shared
GECKO_SDK_PATH = /sdk/gecko-sdk
XPIDL = $(GECKO_SDK_PATH)/xpcom/bin/xpidl
#產生C++標頭檔
CPPHEADER = -m header
#組建類型庫檔案
TYPELIB = -m typelib
REGDIR = /usr/local/lib/mozilla-1.6
OUTDIR = $(REGDIR)/components
GECKO_CONFIG_INCLUDE = -include mozilla-config.h
GECKO_DEFINES = -DXPCOM_GLUE
GECKO_INCLUDES = -I$(GECKO_SDK_PATH)
-I$(GECKO_SDK_PATH)/xpcom/include
-I$(GECKO_SDK_PATH)/nspr/include
GECKO_LDFLAGS = -L$(GECKO_SDK_PATH)/xpcom/bin -lxpcomglue
-L$(GECKO_SDK_PATH)/nspr/bin -lnspr4
GECKO_IDL = -I$(GECKO_SDK_PATH)/xpcom/idl
build: idl nsMyCom.o nsMyComModule.o
$(CPP) $(CPPFLAGS) -o libxpmycom.so $(GECKO_DEFINES)
$(GECKO_LDFLAGS) nsMyCom.o nsMyComModule.o
chmod +x libxpmycom.so
idl: nsIMyCom.idl
$(XPIDL) $(GECKO_IDL) $(CPPHEADER) nsIMyCom.idl
$(XPIDL) $(GECKO_IDL) $(TYPELIB) nsIMyCom.idl
nsMyCom.o: nsMyCom.cpp
$(CPP) $(GECKO_CONFIG_INCLUDE) $(GECKO_DEFINES)
$(GECKO_INCLUDES) -c nsMyCom.cpp -o nsMyCom.o
nsMyComModule.o: nsMyComModule.cpp
$(CPP) $(GECKO_CONFIG_INCLUDE) $(GECKO_DEFINES)
$(GECKO_INCLUDES) -c nsMyComModule.cpp -o nsMyComModule.o
install:
cp nsIMyCom.xpt $(OUTDIR)/
cp libxpmycom.so $(OUTDIR)/
clean:
rm *.o
rm *.so
rm *.*~
rm *~
#end-------------
如果一切無誤,我們make之後,g++就會在目前的目錄下產生libxpmycom.so庫檔案,nsIMyCom.xpt是相應的類型檔案。然後再將該組件安裝到mozilla的組件目錄中:
make install
該組件庫及對應的類型庫nsIMyCom.xpt將會被拷到/usr/local/lib/mozilla-1.6/components(要確認你的組件目錄,如mozilla1.4目錄一般為usr/local/lib/mozilla-1.4/components)目錄中。
這時我們可以從控制台啟動mozilla瀏覽器,在瀏覽器輸出的一系列資訊中,將會有該組件被註冊成功的資訊。
6、在html/javascript中測試該組件。
該html如下:
//------------------------------------
<html>
<head>
<title>
測試XPCOM組件
</title>
</head>
<body>
<script>
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var mycom = Components.classes[@westsoft.org/mycom;1].createInstance();
mycom = mycom.QueryInterface(Components.interfaces.nsIMyCom);
function testxpcom(f)
{
netscape.security.PrivilegeManager.enablePrivilege(UniversalXPConnect);
var ret_string;
ret_string = mycom.Hello(document.form_test.input_string.value);
alert(ret_string);
}
</script>
<form name=form_test>
輸入資訊:
<textarea name = input_string cols = 70 rows = 5></textarea>
<input type=button value=testxpcom onClick = testxpcom(this.form);>
</form>
</body>
</html>
//--------------------------------------------------
我們在mozilla中開啟該html,在輸入框中輸入一些文字,在點擊testxpcom後,怎麼樣,看到從組件返回的資訊了嗎?
另外,上面的輸入資訊如果是中文,則返回中會產生亂碼,xpcom idl中有nsAString, wstring等支援unicode的字串類型,可以利用這些類型來解決漢字編碼的問題,以下是我使用的方法。
假設XPCOM內部已經正常地取得了GB2312的漢字編碼的資料,要在測試頁面上由JavaScript來正常地解釋與顯示這些編碼,就必須要把GB2312轉換成為UNICODE編碼。在此之前,需要修改前面所定義的IDL檔案:nsIMyCom.idl,例如,修改如下:
//filename: nsIMyCom.idl
//begin idl --------------------------------------
#include nsISupports.idl
[scriptable, uuid(5217115e-11fe-4d01-966d-9b27ffda6498)]
interface nsIMyCom:nsISupports
{
void Hello(in string in_str, [retval] out wstring out_str);
};
//end idl-----------------------------------------
產生相應的HEADER檔案後,修改C++類檔案並編譯,安裝後運行就可以在Mozilla中正常顯示出漢字了。
GB2312轉換為UNICODE的代碼如下:
//begin-------------------------
#include <iconv.h>
#define MAX_PATH 400
char * GetUnicode(char * ucInGB2312, char *ucOutUnicode)
{
if(ucInGB2312 == NULL || ucOutUnicode == NULL)
{
return "/0";
}
char *strIn = ucInGB2312;
char *strRet = ucOutUnicode;
char tmp[MAX_PATH*10] = {0};
char *pTmp = tmp;
size_t nInLen = strlen(ucInGB2312);
size_t nOutLenLeft = nInLen * 3;
size_t nOrgLen = nInLen*3;
iconv_t cd = 0;
if((cd = iconv_open("UNICODE", "GB2312")) == (iconv_t)-1)
{
return "/0";
}
if(iconv(cd, &strIn, &nInLen, &pTmp, &nOutLenLeft) == (size_t)-1)
{
return "/0";
}
memcpy(ucOutUnicode, tmp, (nOrgLen - nOutLenLeft));
iconv_close(cd);
return strRet;
}
//end--------------------------
以上只是展示了XPCOM組件的基本技術,還有許多內容未提及,如果要將該技術應用於項目中,更多的資料請查閱mozilla的網上資源。
以上是根據《XPCOM--LINUX下的組件開發技術》做了補充說明,希望對這方面的開發人員有協助,不到之處請多多指教!
若有問題與我聯絡:bjgxjob@163.com