4. 定義IPC消息
如果你寫過MFC程序,對MFC那里面一大堆宏有所忌憚的話,那么很不幸,在Chrome中的IPC消息定義中,你需要再吃一點苦頭了,甚至,更苦大仇深一些;如果你曾經(jīng)領(lǐng)教過用模板的特化偏特化做Traits、用模板做函數(shù)重載、用編譯期的Tuple做變參數(shù)支持,之類機制的種種麻煩的話,那么,同樣很遺憾,在Chrome中,你需要再感受一次。。。
不過,先讓我們忘記宏和模板,看人肉一個消息,到底需要哪些操作。一個標(biāo)準(zhǔn)的IPC消息定義應(yīng)該是類似于這樣的:
class SomeMessage
: public IPC::Message { public: enum { ID = ...; } SomeMessage(SomeType & data) : IPC::Message(MSG_ROUTING_CONTROL, ID, ToString(data)) {...} ... };
大概意思是這樣的,你需要從Message(或者其他子類)派生出一個子類,該子類有一個獨一無二的ID值,該子類接受一個參數(shù),你需要對這個參數(shù)進(jìn)行序列化。兩個麻煩的地方看的很清楚,如果生成獨一無二的ID值?如何更方便的對任何參數(shù)可以自動的序列化?。。。
在Chrome中,解決這兩個問題的答案,就是宏 + 模板。Chrome為每個消息安排了一種ID規(guī)格,用一個16bits的值來表示,高4位標(biāo)識一個Channel,低12位標(biāo)識一個消息的子id,也就是說,最多可以有16種Channel存在不同的進(jìn)程之間,每一種Channel上可以定義4k的消息。目前,Chrome已經(jīng)用掉了8種Channel(如果A、B進(jìn)程需要雙向通信,在Chrome中,這是兩種不同的Channel,需要定義不同的消息,也就是說,一種雙向的進(jìn)程通信關(guān)系,需要耗費兩個Channel種類...),他們已經(jīng)覺得,16bits的ID格式不夠用了,在將來的某一天,估計就被擴展成了32bits的。書歸正傳,Chrome是這么來定義消息ID的,用一個枚舉類,讓它從高到低往下走,就像這樣:
enum SomeChannel_MsgType
{ SomeChannelStart = 5 << 12, SomeChannelPreStart = (5 << 12) - 1, Msg1, Msg2, Msg3, ... MsgN, SomeChannelEnd };
這是一個類型為5的Channel的消息ID聲明,由于指明了最開始的兩個值,所以后續(xù)枚舉的值會依次遞減,如此,只要維護(hù)Channel類型的唯一性,就可以維護(hù)所有消息ID的唯一性了(當(dāng)然,前提是不能超過消息上限...)。但是,定義一個ID還不夠,你還需要定義一個使用該消息ID的Message子類。這個步驟不但繁瑣,最重要的,是違反了DIY原則,為了添加一個消息,你需要在兩個地方開工干活,是可忍孰不可忍,于是Google祭出了宏這顆原子彈,需要定義消息,格式如下:
IPC_BEGIN_MESSAGES(PluginProcess, 3) IPC_MESSAGE_CONTROL2(PluginProcessMsg_CreateChannel, int /* process_id */, HANDLE /* renderer handle */)
IPC_MESSAGE_CONTROL1(PluginProcessMsg_ShutdownResponse, bool /* ok to shutdown */)
IPC_MESSAGE_CONTROL1(PluginProcessMsg_PluginMessage, std::vector<uint8> /* opaque data */)
IPC_MESSAGE_CONTROL0(PluginProcessMsg_BrowserShutdown)
IPC_END_MESSAGES(PluginProcess)
這是Chrome中,定義PluginProcess消息的宏,我挖過來放在這了,如果你想添加一條消息,只需要添加一條類似與IPC_MESSAGE_CONTROL0東東即可,這說明它是一個控制消息,參數(shù)為0個。你基本上可以這樣理解,IPC_BEGIN_MESSAGES就相當(dāng)于完成了一個枚舉開始的聲明,然后中間的每一條,都會在枚舉里面增加一個ID,并聲明一個子類。這個一宏兩吃,直逼北京烤鴨兩吃的高超做法,可以參看ipc_message_macros.h,或者看下面一宏兩吃的一個舉例。。。
多次展開宏的技巧
這是Chrome中用到的一個技巧,定義一次宏,展開多段代碼,我孤陋寡聞,第一次見,一個類似的例子,如下:
首先,定義一個macro.h,里面放置宏的定義:
#undef SUPER_MACRO
#if defined(FIRST_TIME) #undef FIRST_TIME
#define SUPER_MACRO(label, type) \ enum IDs { \ label##__ID = 10 \ };
#elif defined(SECOND_TIME) #undef SECOND_TIME
#define SUPER_MACRO(label, type) \ class TestClass \ { \ public: \ enum {ID = label##__ID}; \ TestClass(type value) : _value(value) {} \ type _value; \ };
#endif
可以看到,這個頭文件是可重入的,每一次先undef掉之前的定義,然后判斷進(jìn)行新的定義。然后,你可以創(chuàng)建一個use_macro.h文件,利用這個宏,定義具體內(nèi)容:
#include "macros.h"
SUPER_MACRO(Test, int)
這個頭文件在利用宏的部分不需要放到ifundef...define...這樣的頭文件保護(hù)中,目的就是為了可重入。在主函數(shù)中,你可以多次define + include,實現(xiàn)多次展開的目的:
#define FIRST_TIME #include "use_macro.h"
#define SECOND_TIME #include "use_macro.h"
#include <iostream>
int _tmain(int argc, _TCHAR* argv[]) { TestClass t(5); std::cout << TestClass::ID << std::endl; std::cout << t._value << std::endl;
return 0; }
這樣,你就成功的實現(xiàn),一次定義,生成多段代碼了。。。
此外,當(dāng)接收到消息后,你還需要處理消息。接收消息的函數(shù),是IPC::Channel::Listener子類的OnMessageReceived函數(shù)。在這個函數(shù)中,會放置一坨的宏,這一套宏,一定能讓你想起MFC的Message Map機制:
IPC_BEGIN_MESSAGE_MAP_EX(RenderProcessHost, msg, msg_is_ok)
IPC_MESSAGE_HANDLER(ViewHostMsg_PageContents, OnPageContents) IPC_MESSAGE_HANDLER(ViewHostMsg_UpdatedCacheStats, OnUpdatedCacheStats) IPC_MESSAGE_UNHANDLED_ERROR() IPC_END_MESSAGE_MAP_EX()
這個東西很簡單,展開后基本可以視為一個Switch循環(huán),判斷消息ID,然后將消息,傳遞給對應(yīng)的函數(shù)。與MFC的Message Map比起來,做的事情少多了。。。
通過宏的手段,可以解決消息類聲明和消息的分發(fā)問題,但是自動的序列化還不能支持(所謂自動的序列化,就是不論你是什么類型的參數(shù),幾個參數(shù),都可以直接序列化,不需要另寫代碼...)。在C++這種語言中,所謂自動的序列化,自動的類型識別,自動的XXX,往往都是通過模板來實現(xiàn)的。這些所謂的自動化,其實就是通過事前的大量人肉勞作,和模板自動遞推來實現(xiàn)的,如果說.Net或Java中的自動序列化是過山軌道,這就是那挑夫的驕子,雖然最后都是兩腿不動到了山頂,這底下費得力氣真是天壤之別啊。具體實現(xiàn)技巧,有興趣的看看《STL源碼剖析》,或者是《C++新思維》,或者Chrome中的ipc_message_utils.h,這要說清楚實在不是一兩句的事情。。。
總之通過宏和模板,你可以很簡單的聲明一個消息,這個消息可以傳入各式各樣的參數(shù)(這里用到了夸張的修辭手法,其實,只要是模板實現(xiàn)的自動化,永遠(yuǎn)都是有限制的,在Chrome的模板實現(xiàn)中,參數(shù)數(shù)量不要超過5個,類型需要是基本類型、STL容器等,在不BT的場合,應(yīng)該夠用了...),你可以調(diào)用Channel、ChannelProxy、SyncChannel之類的Send方法,將消息發(fā)送給其他進(jìn)程,并且,實現(xiàn)一個Listener類,用Message Map來分發(fā)消息給對應(yīng)的處理函數(shù)。如此,整個IPC體系搭建完成。。。
苦力的宏和模板
不論是宏還是模板,為了實現(xiàn)這套機制,都需要寫大量的類似代碼,比如為了支持0~N個參數(shù)的Control消息,你就需要寫N+1個類似的宏;為了支持各種基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)的序列化,你就需要寫上十來個類似的Write函數(shù)和Traits。。。
之所以做如此苦力的活,都是為了用這些東西的人能夠盡可能的簡單方便,符合DIY原則。規(guī)約到之前說的設(shè)計者的職責(zé)上來,這是一個典型的苦了我一個幸福千萬人的負(fù)責(zé)任的行為。在Chrome中,如此的代碼隨處可見,光Tuple那一套拳法,我現(xiàn)在就看到了使了不下三次(我曾經(jīng)做過一套,直接吐血...),如此兢兢業(yè)業(yè),真是可歌可泣啊。。。
出處:Venus神廟
責(zé)任編輯:bluehearts
上一頁 Chrome的多線程模型 中 下一頁 Chrome的進(jìn)程模型
◎進(jìn)入論壇網(wǎng)絡(luò)編程版塊參加討論
|