一、問(wèn)題。
這段時(shí)間考慮實(shí)現(xiàn)一個(gè)純C++的分布式服務(wù)包裝庫(kù),簡(jiǎn)要描述如下:
有如下類(lèi)和函數(shù):
struct?Test
{
????void?test1?(/*in*/?int?v1,?/*in*/?int*?v2);
????int?test2?(/*in*/?int&?v1,?/*out*/?int*?v2);
};
int?test_func?(/*in*/?int*?v1,?/*inout*/?string*?v2);
想把它們作為服務(wù)發(fā)布出去,以SOAP或其它方式。發(fā)布為一個(gè)TestService,并讓它攜帶多一些信息:
struct?TestService
{
????void?test1?(in<int>?v1,?in<int>?v2);
????int?test2?(in<int>?v1,?out<int>?v2);
????int?test_func?(in<int>?v1,?inout<string>?v2);
};
C++有許多工具、庫(kù)來(lái)做到這點(diǎn),但是,都需要生成一堆代碼,很是不爽。
其它語(yǔ)言,比如python, java, c#等,都可以通過(guò)自省機(jī)制,拋開(kāi)IDL在語(yǔ)言?xún)?nèi)實(shí)現(xiàn)。
C++并非不能做這個(gè),它只是缺少足夠的類(lèi)型信息。比如上面的例子,如果要發(fā)布為服務(wù),那么至少應(yīng)該把它的參數(shù)、返回值搞得明確些,否則要么會(huì)造成不必要的參數(shù)傳遞,要么會(huì)產(chǎn)生錯(cuò)誤(把OUT參數(shù)取值可不是安全的)。
比如上面出現(xiàn)的int, int&, int*,在作為in參數(shù)時(shí),我們是想傳遞它的值,類(lèi)型為int。而int*和string*作為out參數(shù)時(shí),我們想讓它傳遞指針或引用,當(dāng)調(diào)用返回時(shí),我們給它賦值。
C++語(yǔ)言的類(lèi)型極為豐富,卻沒(méi)有描述一個(gè)參數(shù)到底是in還是out。java也沒(méi)有,但它可以正常序列化一個(gè)null值,在C++中,這可能存在一些麻煩。
再考慮一下char*類(lèi)型,假如它是in參數(shù),那么它是要傳遞一個(gè)字符還是一個(gè)字符串?C++語(yǔ)言沒(méi)有對(duì)它進(jìn)行描述。
所以要實(shí)現(xiàn)一個(gè)分布式服務(wù)包裝(或代理)庫(kù),必須讓發(fā)布者提供這些信息。
我們知道,要查詢(xún)一個(gè)遠(yuǎn)程服務(wù),必須查詢(xún)相應(yīng)主機(jī)端口,獲取服務(wù)信息。最簡(jiǎn)單的服務(wù)信息包括:服務(wù)列表,每個(gè)服務(wù)中的方法列表,方法的類(lèi)型(包括參數(shù)和返回值類(lèi)型,in/out信息等)。
實(shí)際上,我們是要為C++增加一些簡(jiǎn)單的自省能力。上面那個(gè)服務(wù)發(fā)布接口,實(shí)際上離這個(gè)要求還有很遠(yuǎn),再來(lái)看一下:
struct?TestService
{
????void?test1?(in<int>?v1,?in<int>?v2);
????int?test2?(in<int>?v1,?out<int>?v2);
????int?test_func?(in<int>?v1,?inout<string>?v2);
};
可以想見(jiàn),它是沒(méi)有一點(diǎn)自省能力的,我們?nèi)绾蜗蛩樵?xún),它的名字?它的方法列表?方法的類(lèi)型?它如何與Test類(lèi)的成員函數(shù)以及test_func函數(shù)關(guān)聯(lián)?
二、方向。
要讓上面那個(gè)服務(wù)具有自省能力,要做的擴(kuò)充其實(shí)并不多。考慮下面的代碼:
struct?TestService?:?public?Service
{
????TestService?();
????Method?<void(in<int>,?in<int>)>?test1;
????Method?<int(in<int>,?out<int>)>?test2;
????Method?<int(in<int>,?inout<string>)?test_func;
};
這幾個(gè)Method可以用自己寫(xiě)的委托類(lèi)來(lái)做。
1、假如我們?cè)赥estService的構(gòu)造函數(shù)里給它分配一個(gè)“TestService”名字,并且Service類(lèi)實(shí)現(xiàn)了查詢(xún)名字的接口,那么它就知道它自己的名字了。
2、假如在TestService的構(gòu)造函數(shù)里為各個(gè)Method分配名字,并且注冊(cè)到TestService,那么它就能夠查詢(xún)方法列表。
3、方法的類(lèi)型?通過(guò)模板方式,把各個(gè)參數(shù)類(lèi)型收集起來(lái),給個(gè)字符串名稱(chēng)就可以了。
使用宏來(lái)實(shí)現(xiàn),大概可以寫(xiě)成這樣:
BEGIN_SERVICE?(TestService)
????METHOD?(void(in<int>,?in<int>),?test1,?&Test::test1)
????METHOD?(int(in<int>,?out<int>),?test2,?&Test::test2)
????METHOD?(int<in<int>,?inout<string>),?test_func,?test_func)
END_SERVICE?()
通過(guò)上面這幾個(gè)宏,我們能夠生成TestService聲明。
不過(guò),有幾個(gè)問(wèn)題,羅列如下,并一一解決它:
1、如何把函數(shù)指針傳給它?如何把方法名稱(chēng)傳給它?
這個(gè)只是C++語(yǔ)言為我們?cè)黾恿艘恍┞闊覀儫o(wú)法在定義成員的地方調(diào)用它的構(gòu)造函數(shù),不過(guò)這并不會(huì)造成多大障礙。
上面的METHOD宏如果只是生成類(lèi)的聲明,那么函數(shù)指針可以省略。我把它加上的原因是,它可以被我用Ctrl+C, Ctrl+V這種世界上最先進(jìn)的技術(shù)原樣拷貝下來(lái),并且通過(guò)簡(jiǎn)單修改的方法實(shí)現(xiàn)這種世界上最先進(jìn)的重用。
上面的代碼經(jīng)過(guò)修改,結(jié)果就成這樣:
BEGIN_SERVICE?(TestService)
????METHOD?(void(in<int>,?in<int>),?test1,?&Test::test1)
????METHOD?(int(in<int>,?out<int>),?test2,?&Test::test2)
????METHOD?(int<in<int>,?inout<string>),?test_func,?test_func)
????BEGIN_DEFINE?(TestService)
????????METHOD_DEFINE?(void(in<int>,?in<int>),?test1,?&Test::test1)
????????METHOD_DEFINE(int(in<int>,?out<int>),?test2,?&Test::test2)
????????METHOD_DEFINE(int(in<int>,?inout<string>),?test_func,?test_func)
????END_DEFINE?()
END_SERVICE?()
看上去對(duì)應(yīng)得非常整齊,修改起來(lái)也比較簡(jiǎn)單。上面那部分被擴(kuò)充為如下代碼:
struct?TestService?:?public?Service
{
????Method?<void(in<int>,?in<int>)>?test1;
????Method?<int(in<int>,?out<int>)>?test2;
????Method?<int(in<int>,?inout<string>)?test_func;
????TestService?()
????:?Service?("TestService")
????{
????????test1.setName?("test1");
????????test1.setMethod?(&Test::test1);
????????this->registerMethod?(&test1);
????????test2.setName?("test2");
????????test2.setMethod?(&Test::test2);
????????this->registerMethod?(&test2);
????????test_func.setName?("test_func");
????????test_func.setMethod?(test_func);
????????this->registerMethod?(&test3);
????}
};
基本上需要的東西都在這里了。
2、客戶(hù)端的問(wèn)題。
上面這種映射,直接拿到客戶(hù)端會(huì)有問(wèn)題,Test類(lèi)和test_func函數(shù)我們并不打算交給客戶(hù)端,所以使用函數(shù)指針會(huì)出現(xiàn)鏈接錯(cuò)誤。
實(shí)際上客戶(hù)端不需要這個(gè),我們想辦法把它拿掉就行了。客戶(hù)端實(shí)際需要生成的代碼如下:
struct?TestService?:?public?Service
{
????Method?<void(in<int>,?in<int>)>?test1;
????Method?<int(in<int>,?out<int>)>?test2;
????Method?<int(in<int>,?inout<string>)?test_func;
????TestService?()
????:?Service?("TestService")
????{
????????test1.setName?("test1");
????????this->registerMethod?(&test1);
????????test2.setName?("test2");
????????this->registerMethod?(&test2);
????????test_func.setName?("test_func");
????????this->registerMethod?(&test3);
????}
};
還是上面提到的,C++給我們帶來(lái)的麻煩。這次需要另一組宏來(lái)完成它:
BEGIN_SERVICE_D?(TestService)
????METHOD_D?(void(in<int>,?in<int>),?test1)
????METHOD_D?(int(in<int>,?out<int>),?test2)
????METHOD_D?(int(in<int>,?inout<string>),?test_func)
????BEGIN_DEFINE_D?(TestService)
????????METHOD_DEFINE_D?(void(in<int>,?in<int>),?test1)
????????METHOD_DEFINE_D(int(in<int>,?out<int>),?test2)
????????METHOD_DEFINE_D(int(in<int>,?inout<string>),?test_func)
????END_DEFINE_D?()
END_SERVICE_D?()
METHOD*和METHOD_DEFINE*宏的參數(shù)都有一些多余的信息,沒(méi)有去掉是因?yàn)榉旁谝黄鹑菀卓吹綄?xiě)錯(cuò)的地方。(這個(gè)技巧來(lái)源于前幾天看的一篇BLOG,很報(bào)歉沒(méi)有記下地址)
3、使用的問(wèn)題。
如何才能比較方便地使用?我考慮了下面這種方式:
template?<class?T>
struct?IProxy;
template?<class?T>
struct?SOAPProxy;
SOAPProxy?<TestService>?service;
service.connect?(5000,?"localhost");
int?a=0;
int?*n?=?&a;
service.test1?(3,?n);
service.test1?(3,?*n);
service.test2?(3,?n);
service.test2?(3,?*n);
service.test2?(3,?NONE);
//
Method::operator ()的各個(gè)參數(shù)都將可以接受相容的類(lèi)型,像上面一樣,因?yàn)樵赥estService中我們已經(jīng)定義了它要傳輸?shù)闹档念?lèi)型。
a.NONE是什么?其實(shí)是為異步調(diào)用考慮的。假如指定某個(gè)OUT參數(shù)為NONE,則這個(gè)參數(shù)的值并不真正的OUT,而是保存在Method中。實(shí)際上Method中保存每個(gè)參數(shù)的值。
b.Method與Service如何發(fā)生關(guān)系?
從TestService的定義中我們知道,Method向Service注冊(cè)自己以實(shí)現(xiàn)自省,但它同時(shí)也會(huì)保存Service的指向。
我們的Proxy實(shí)際上是一個(gè)繼承模板,上面并沒(méi)有把它指出來(lái)。它的定義是:
template?<class?T>
class?XProxy?:?public?T
{
????//
};
所以我們的TestService其實(shí)也是模板類(lèi),它將使用XProxy中定義的序列化類(lèi)。XProxy將實(shí)現(xiàn)Service基類(lèi)中序列化虛函數(shù)以及調(diào)用虛函數(shù)。
當(dāng)一個(gè)Method調(diào)用時(shí),它會(huì)調(diào)用Service的序列化,由于被重寫(xiě)了,所以調(diào)用的是XProxy中的序列化方法。這個(gè)方法會(huì)把這個(gè)Method的各in/inout參數(shù)序列化,然后執(zhí)行遠(yuǎn)程調(diào)用,再把調(diào)用結(jié)果反序列化給inout/out參數(shù)。
4、其它想法。
在考慮上面的定義方式時(shí),我也考慮了其它方式,主要是返回值處理的方法,簡(jiǎn)述如下。
前面我們假設(shè)了一段將被開(kāi)放為遠(yuǎn)程服務(wù)的代碼:
struct?Test
{
????void?test1?(/*in*/?int?v1,?/*in*/?int*?v2);
????int?test2?(/*in*/?int&?v1,?/*out*/?int*?v2);
};
int?test_func?(/*in*/?int*?v1,?/*inout*/?string*?v2);
在前面的做法中,我們的服務(wù)描述是放在那一組宏里面,好處是不用改這段代碼,壞處就是代碼定義的地方和描述不在一起,協(xié)調(diào)可能會(huì)有一些不便。
我也考慮了另一種做法:
struct?Test
{
????idl?<void(in<int>,?in<int>)>?test1?(int?v1,?int*?v2);
????idl?<int(in<int>,?out<int>)>?test2?(int&?v1,?int*?v2);
};
idl?<int(in<int>,?inout<string>)?test_func?int*?v1,?string*?v2);
對(duì)于實(shí)現(xiàn)代碼,只需要修改返回值為void的函數(shù),把return;修改為return VOID;,并且為沒(méi)有寫(xiě)此語(yǔ)句的分支加上此句。
VOID是一個(gè)特殊類(lèi)型的靜態(tài)變量,專(zhuān)為void返回值的函數(shù)設(shè)定。
這種做法修改了原有的代碼,不過(guò)在定義服務(wù)時(shí)可以節(jié)省一些工作:
BEGIN_SERVICE?(TestService)
????METHOD?(test1,?&Test::test1)
????METHOD?(test2,?&Test::test2)
????METHOD?(test_func,?test_func)
????BEGIN_DEFINE?(TestService)
????????METHOD_DEFINE?(test1,?&Test::test1)
????????METHOD_DEFINE?(test2,?&Test::test2)
????????METHOD_DEFINE?(test_func,?test_func)
????END_DEFINE?()
END_SERVICE?()
它所需要的函數(shù)類(lèi)型,將由函數(shù)指針推導(dǎo)。
在G++編譯器下,可以使用typeof來(lái)獲得函數(shù)指針的類(lèi)型而不需要真得獲得函數(shù)指針值,不過(guò)目前僅僅在G++下可用。(順便說(shuō)一下,typeof已經(jīng)列入c++0x)
最終我放棄了這個(gè)想法,畢竟它要修改現(xiàn)有的代碼,某些情況下這是不可能的,而且typeof目前也不能跨編譯器。
三、實(shí)現(xiàn)。
老實(shí)說(shuō)我現(xiàn)在還沒(méi)有一份完整的或半完整的實(shí)現(xiàn),大部分想法還在頭腦中,測(cè)試代碼倒是寫(xiě)了不少,主要是用來(lái)測(cè)試上述想法能否實(shí)現(xiàn),我想大部分情況都已經(jīng)測(cè)試了,只需要有時(shí)間來(lái)把它實(shí)現(xiàn)出來(lái)。
這是我近期要做的事之一,爭(zhēng)取月內(nèi)把它做完罷。