一、問題。
這段時間考慮實現一個純C++的分布式服務包裝庫,簡要描述如下:
有如下類和函數:
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);
想把它們作為服務發布出去,以SOAP或其它方式。發布為一個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++有許多工具、庫來做到這點,但是,都需要生成一堆代碼,很是不爽。
其它語言,比如python, java, c#等,都可以通過自省機制,拋開IDL在語言內實現。
C++并非不能做這個,它只是缺少足夠的類型信息。比如上面的例子,如果要發布為服務,那么至少應該把它的參數、返回值搞得明確些,否則要么會造成不必要的參數傳遞,要么會產生錯誤(把OUT參數取值可不是安全的)。
比如上面出現的int, int&, int*,在作為in參數時,我們是想傳遞它的值,類型為int。而int*和string*作為out參數時,我們想讓它傳遞指針或引用,當調用返回時,我們給它賦值。
C++語言的類型極為豐富,卻沒有描述一個參數到底是in還是out。java也沒有,但它可以正常序列化一個null值,在C++中,這可能存在一些麻煩。
再考慮一下char*類型,假如它是in參數,那么它是要傳遞一個字符還是一個字符串?C++語言沒有對它進行描述。
所以要實現一個分布式服務包裝(或代理)庫,必須讓發布者提供這些信息。
我們知道,要查詢一個遠程服務,必須查詢相應主機端口,獲取服務信息。最簡單的服務信息包括:服務列表,每個服務中的方法列表,方法的類型(包括參數和返回值類型,in/out信息等)。
實際上,我們是要為C++增加一些簡單的自省能力。上面那個服務發布接口,實際上離這個要求還有很遠,再來看一下:
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);
};
可以想見,它是沒有一點自省能力的,我們如何向它查詢,它的名字?它的方法列表?方法的類型?它如何與Test類的成員函數以及test_func函數關聯?
二、方向。
要讓上面那個服務具有自省能力,要做的擴充其實并不多??紤]下面的代碼:
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;
};
這幾個Method可以用自己寫的委托類來做。
1、假如我們在TestService的構造函數里給它分配一個“TestService”名字,并且Service類實現了查詢名字的接口,那么它就知道它自己的名字了。
2、假如在TestService的構造函數里為各個Method分配名字,并且注冊到TestService,那么它就能夠查詢方法列表。
3、方法的類型?通過模板方式,把各個參數類型收集起來,給個字符串名稱就可以了。
使用宏來實現,大概可以寫成這樣:
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?()
通過上面這幾個宏,我們能夠生成TestService聲明。
不過,有幾個問題,羅列如下,并一一解決它:
1、如何把函數指針傳給它?如何把方法名稱傳給它?
這個只是C++語言為我們增加了一些麻煩,我們無法在定義成員的地方調用它的構造函數,不過這并不會造成多大障礙。
上面的METHOD宏如果只是生成類的聲明,那么函數指針可以省略。我把它加上的原因是,它可以被我用Ctrl+C, Ctrl+V這種世界上最先進的技術原樣拷貝下來,并且通過簡單修改的方法實現這種世界上最先進的重用。
上面的代碼經過修改,結果就成這樣:
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?()
看上去對應得非常整齊,修改起來也比較簡單。上面那部分被擴充為如下代碼:
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、客戶端的問題。
上面這種映射,直接拿到客戶端會有問題,Test類和test_func函數我們并不打算交給客戶端,所以使用函數指針會出現鏈接錯誤。
實際上客戶端不需要這個,我們想辦法把它拿掉就行了。客戶端實際需要生成的代碼如下:
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++給我們帶來的麻煩。這次需要另一組宏來完成它:
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*宏的參數都有一些多余的信息,沒有去掉是因為放在一起容易看到寫錯的地方。(這個技巧來源于前幾天看的一篇BLOG,很報歉沒有記下地址)
3、使用的問題。
如何才能比較方便地使用?我考慮了下面這種方式:
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 ()的各個參數都將可以接受相容的類型,像上面一樣,因為在TestService中我們已經定義了它要傳輸的值的類型。
a.NONE是什么?其實是為異步調用考慮的。假如指定某個OUT參數為NONE,則這個參數的值并不真正的OUT,而是保存在Method中。實際上Method中保存每個參數的值。
b.Method與Service如何發生關系?
從TestService的定義中我們知道,Method向Service注冊自己以實現自省,但它同時也會保存Service的指向。
我們的Proxy實際上是一個繼承模板,上面并沒有把它指出來。它的定義是:
template?<class?T>
class?XProxy?:?public?T
{
????//
};
所以我們的TestService其實也是模板類,它將使用XProxy中定義的序列化類。XProxy將實現Service基類中序列化虛函數以及調用虛函數。
當一個Method調用時,它會調用Service的序列化,由于被重寫了,所以調用的是XProxy中的序列化方法。這個方法會把這個Method的各in/inout參數序列化,然后執行遠程調用,再把調用結果反序列化給inout/out參數。
4、其它想法。
在考慮上面的定義方式時,我也考慮了其它方式,主要是返回值處理的方法,簡述如下。
前面我們假設了一段將被開放為遠程服務的代碼:
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);
在前面的做法中,我們的服務描述是放在那一組宏里面,好處是不用改這段代碼,壞處就是代碼定義的地方和描述不在一起,協調可能會有一些不便。
我也考慮了另一種做法:
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);
對于實現代碼,只需要修改返回值為void的函數,把return;修改為return VOID;,并且為沒有寫此語句的分支加上此句。
VOID是一個特殊類型的靜態變量,專為void返回值的函數設定。
這種做法修改了原有的代碼,不過在定義服務時可以節省一些工作:
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?()
它所需要的函數類型,將由函數指針推導。
在G++編譯器下,可以使用typeof來獲得函數指針的類型而不需要真得獲得函數指針值,不過目前僅僅在G++下可用。(順便說一下,typeof已經列入c++0x)
最終我放棄了這個想法,畢竟它要修改現有的代碼,某些情況下這是不可能的,而且typeof目前也不能跨編譯器。
三、實現。
老實說我現在還沒有一份完整的或半完整的實現,大部分想法還在頭腦中,測試代碼倒是寫了不少,主要是用來測試上述想法能否實現,我想大部分情況都已經測試了,只需要有時間來把它實現出來。
這是我近期要做的事之一,爭取月內把它做完罷。