[原创]chromium源码阅读-进程间通信IPC.消息的接收与应答
chromium源码阅读-进程间通信IPC.消息的接收与应答
chromium源码阅读-进程间通信IPC.消息的接收与应答
介绍
chromium进程间通信在win32下是通过命名管道的方式实现的,最后的数据都是以二进制流的方式进行传播,pickle类就是负责消息的封包与解包功能,它将各种数据已二进制的形式写到内存缓冲区中,在实际通信的时候通过与其他一些辅助类与模板函数来实现具体数据结构的写与读。本文主要介绍的是chromium在将消息发送与接收过程中,以及chromium如何通过各种消息宏与C++模板机制实现消息的分派与应答,限于篇幅此处忽略命名管道部分消息的发送与接收。
demo
以下代码是截取自chromium源代码里面关于ipc同步消息(SYN_MSG)的测试用例,在chromium中所谓的ipc_syn_message就是说: 当@A发送消息a给@B,@B接收到a之后,需要返回一个消息b给@A确认 通过下面代码分析消息的接收与应答过程。 在TestMessageReceiver中定义了多个消息形式为On_$(IN)_$(OUT)格式的函数,它的作用是对接收的消息进行处理以及返回相应的结果给远端,其中的IN表示远端发送过来的参数个数,OUT表示应答给远端的参数个数。
#define MESSAGES_INTERNAL_FILE "chrome/common/ipc_sync_message_unittest.h"
#include "chrome/common/ipc_message_macros.h"
static IPC::Message* g_reply; class TestMessageReceiver {
public: void On_0_1(bool* out1) {
*out1 = false;
} void On_0_2(bool* out1, int* out2) {
*out1 = true;
*out2 = 2;
} void On_0_3(bool* out1, int* out2, std::string* out3) {
*out1 = false;
*out2 = 3;
*out3 = "0_3";
} void On_1_1(int in1, bool* out1) {
DCHECK(in1 == 1);
*out1 = true;
}
//..... bool Send(IPC::Message* message) {
// gets the reply message, stash in global
DCHECK(g_reply == NULL);
g_reply = message;
return true;
} void OnMessageReceived(const IPC::Message& msg) {
IPC_BEGIN_MESSAGE_MAP(TestMessageReceiver, msg)
IPC_MESSAGE_HANDLER(Msg_C_0_1, On_0_1)//在ipc_enum中定义
IPC_MESSAGE_HANDLER(Msg_C_0_2, On_0_2)
IPC_MESSAGE_HANDLER(Msg_C_0_3, On_0_3)
IPC_MESSAGE_HANDLER(Msg_C_1_1, On_1_1)
//.....
IPC_END_MESSAGE_MAP()
//经过宏展开变为
/*
{
typedef TestMessageReceiver _IpcMessageHandlerClass;
const IPC::Message& ipc_message__ = msg;
bool msg_is_ok__ = true;
switch (ipc_message__.type()) {
case Msg_C_0_1::ID:
msg_is_ok__ = Msg_C_0_1::Dispatch(&ipc_message__, this, &On_0_1);
break;
....
DCHECK(msg_is_ok__);
}
}
*/
} };
TEST(IPCSyncMessageTest, Main) {
bool bool1 = true;
int int1 = 0;
std::string string1; Send(new Msg_C_0_1(&bool1));
DCHECK(!bool1); Send(new Msg_C_0_2(&bool1, &int1));
DCHECK(bool1 && int1 == 2); Send(new Msg_C_0_3(&bool1, &int1, &string1));
DCHECK(!bool1 && int1 == 3 && string1 == "0_3");
// ....
}
其中OnMessageReceived是消息的接收函数,里面定义的宏格式跟MFC定义的消息处理宏的功能是一样的,都是响应消息函数。如果接收到对应的消息比如消息Msg_C_0_1那么就会调用成员函数On_0_1,成员函数中设置了参数out1值为false,设置的这个值经过处理会转换为Message,然后将其传递给成员函数Send发送给远端,这里只是测试用例,因此没有实际的发送出去只是将这个消息暂时赋值给全局变量g_reply。
IPC消息宏
chromium消息机制通过头文件chrome/common/ipc_message_macros.h定义了许多有用的消息宏以及模板函数、模板类。这个头文件编写的非常巧妙,通过多次嵌套头文件本身,最后经过编译器预处理宏展开,方便的定义了用户定义的消息枚举类型以及消息类。
在上面的代码中首先定义了一个宏文件MESSAGES_INTERNAL_FILE,这是用户自己定义的消息类型文件,里面也include了文件chrome/common/ipc_message_macros.h,用户需要在这个文件定义自己需要的消息类型,这里定义了Msg_C_0_1,Msg_C_0_2等消息,在头文件chrome/common/ipc_message_macros.h中有如下的预处理操作:
//头文件没有ifndef define endif,因此能重复的嵌套包含
#include "chrome/common/ipc_message_utils.h"
#ifndef MESSAGES_INTERNAL_FILE
#error This file should only be included by X_messages.h, which needs to define MESSAGES_INTERNAL_FILE first.
#endif
// Trick scons and xcode into seeing the possible real dependencies since they
// don't understand #include MESSAGES_INTERNAL_FILE. See http://crbug.com/7828
/@1
//注意这里用了header guard,只会包含一次
#ifndef IPC_MESSAGE_MACROS_INCLUDE_BLOCK
#define IPC_MESSAGE_MACROS_INCLUDE_BLOCK
// Multi-pass include of X_messages_internal.h. Preprocessor magic allows
// us to use 1 header to define the enums and classes for our render messages.
#define IPC_MESSAGE_MACROS_ENUMS
//嵌套包含:@2
#include MESSAGES_INTERNAL_FILE #define IPC_MESSAGE_MACROS_CLASSES
//嵌套包含:@2
#include MESSAGES_INTERNAL_FILE #ifdef IPC_MESSAGE_MACROS_LOG_ENABLED
#define IPC_MESSAGE_MACROS_LOG
#include MESSAGES_INTERNAL_FILE
#endif
#undef MESSAGES_INTERNAL_FILE
#undef IPC_MESSAGE_MACROS_INCLUDE_BLOCK
#endif // IPC_MESSAGE_MACROS_INCLUDE_BLOCK @A
#undef IPC_BEGIN_MESSAGES
//..... //下面是定义消息枚举类型,消息类,以及日志类(日志类可选)
#if defined(IPC_MESSAGE_MACROS_ENUMS) //消息枚举类型定义
//这里undef很重要,因为在@2嵌套包含之后下面的代码通过@2展开在了@1里面
//如果不undef的话,在@1中就会重复的包含下面的代码
#undef IPC_MESSAGE_MACROS_ENUMS
//....
//定义枚举消息类型
#define IPC_BEGIN_MESSAGES(label) \
enum label##MsgType { \
label##Start = label##MsgStart << 12, \
label##PreStart = (label##MsgStart << 12) - 1,
///......
#define IPC_END_MESSAGES(label) \
label##End \
};
//....
#elif defined(IPC_MESSAGE_MACROS_LOG) //消息日志类定义
#undef IPC_MESSAGE_MACROS_LOG
//.... //定义日志消息类型
#define IPC_BEGIN_MESSAGES(label) \
void label##MsgLog(uint16 type, std::wstring* name, const IPC::Message* msg, std::wstring* params) { \
switch (type) { #define IPC_END_MESSAGES(label) \
default: \
if (name) \
*name = L"[UNKNOWN " L ## #label L" MSG"; \
} \
} \
class LoggerRegisterHelper##label { \
public: \
LoggerRegisterHelper##label() { \
g_log_function_mapping[label##MsgStart] = label##MsgLog; \
} \
}; \
LoggerRegisterHelper##label g_LoggerRegisterHelper##label; //....
#elif defined(IPC_MESSAGE_MACROS_CLASSES) //消息类定义
#undef IPC_MESSAGE_MACROS_CLASSES
// ....
//定义消息类
#define IPC_BEGIN_MESSAGES(label)
#define IPC_END_MESSAGES(label)
// 定义消息类,枚举msg_class__ID在IPC_MESSAGE_MACROS_ENUMS 中的IPC_MESSAGE_CONTROL0定义
// 定义的这个类继承自IPC::Message,初始化构造的时候传入该消息的ID
#define IPC_MESSAGE_CONTROL0(msg_class) \
class msg_class : public IPC::Message { \
public: \
enum { ID = msg_class##__ID }; \
msg_class() \
: IPC::Message(MSG_ROUTING_CONTROL, \
ID, \
PRIORITY_NORMAL) {} \
};
//....
#endif
上面代码IPC_MESSAGE_MACROS_INCLUDE_BLOCK中我们可以看到也多次include了用户自定义的文件MESSAGES_ITERNAL_FILE,上面的作用实际上就是*方便用户定义自己的消息类型,通过编写一个头文件就能一次性同时定义消息的枚举类型,实际的消息类*
参看自定义的消息头文件MESSAGES_INTERNAL_FILE以及添加的注释可以大致了解如何通过多次嵌套包含头文件同时实现消息枚举类型与类:
#include "chrome/common/ipc_message_macros.h"
// 下面两次(或者3次)宏召开中,定义enum时在IPC_BEGIN_MESSAGES用到一个枚举
//变量TestMsgStart 在ipc_message_utils.h中定义 //ipc_enum 宏展开
/*
enum TestMsgType {
TestStart = TestMsgStart << 12,
TestPreStart = (TestMsgStart << 12) - 1,
SyncChannelTestMsg_NoArgs__ID,
SyncChannelTestMsg_AnswerToLife__ID,
SyncChannelTestMsg_Double__ID,
Msg_C_0_1__ID,
....,
Msg_R_3_2__ID,
Msg_R_3_3__ID,
TestEnd
};
*/
//ipc_classes宏展开
/*
class SyncChannelTestMsg_NoArgs{...};
class SyncChannelTestMsg_AnswerToLife{...};
...
class Msg_C_0_1 : public IPC::MessageWithReply<Tuple0, Tuple1<bool&> > {
public:
enum { ID = Msg_C_0_1__ID }; //看TestMsgType
Msg_C_0_1(bool* arg1)
: IPC::MessageWithReply<Tuple0, Tuple1<bool&> >(
MSG_ROUTING_CONTROL,
ID,
MakeTuple(), MakeRefTuple(*arg1)) {}
};
....
class Msg_R_3_2{...};
class Msg_R_3_3{...};
*/
/*
SYNC消息机制
消息映射宏的形式为IPC_SYNC_MESSAGE_CONTROL$(IN)_$(OUT),表示的是同步消息,
意思是:当@A发送消息a给@B,@B接收到a之后,需要返回一个消息b给@A。
其中宏中的$IN和$OUT分别表示输入参数的个数
以及输出参数的个数,通过宏展开我们可以知道
*/
IPC_BEGIN_MESSAGES(Test)
IPC_SYNC_MESSAGE_CONTROL0_0(SyncChannelTestMsg_NoArgs) IPC_SYNC_MESSAGE_CONTROL0_1(SyncChannelTestMsg_AnswerToLife,
int /* answer */) IPC_SYNC_MESSAGE_CONTROL1_1(SyncChannelTestMsg_Double,
int /* in */,
int /* out */) // out1 is false
IPC_SYNC_MESSAGE_CONTROL0_1(Msg_C_0_1, bool)
//经过ipc_enum宏展开变为
//Msg_C_0_1__ID
//经过ipc_class宏展开变为
/*******************************************************************
class Msg_C_0_1 : public IPC::MessageWithReply<Tuple0, Tuple1<bool&> > {
public:
enum { ID = Msg_C_0_1__ID };
Msg_C_0_1(bool* arg1)
: IPC::MessageWithReply<Tuple0, Tuple1<bool&> >(
MSG_ROUTING_CONTROL,
ID,
MakeTuple(), MakeRefTuple(*arg1)) {}
};
*******************************************************************/
// out1 is true, out2 is 2
IPC_SYNC_MESSAGE_CONTROL0_2(Msg_C_0_2, bool, int)
这里定义的消息类有连个模板参数,一个输入参数tuple,与输出参数tuple,通过这样区分就可以实现消息分派处理函数中输入参数与输出参数,在测试用例中非指针参数就是输入参数,指针参数就是输出参数
消息的接收与应答
TestMessageReceiver::OnMessageReceived在接收到一个消息之后,通过IPC_BEGIN_MESSAGE_MAP与IPC_END_MESSAGE_MAP定义的消息处理宏会将消息分派到相依的消息处理函数中,第一个消息宏
IPC_MESSAGE_HANDLER(Msg_C_0_1, On_0_1)//在ipc_enum中定义
我们进一步了解如何处理接收到的消息,上面定义的宏经过展开变为如下的代码:
{
typedef TestMessageReceiver _IpcMessageHandlerClass;
const IPC::Message& ipc_message__ = msg;
bool msg_is_ok__ = true;
switch (ipc_message__.type()) {
case Msg_C_0_1::ID:
msg_is_ok__ = Msg_C_0_1::Dispatch(&ipc_message__, this, &On_0_1);
break;
....
DCHECK(msg_is_ok__);
}
}
可以看到在接收到消息Msg_C_0_1::ID之后调用了Msg_C_0_1::Dispatch函数, Msg_C_0_1继承自头文件chrome/common/ipc_message_utils.h中定义的消息类
template <class SendParamType/*输入参数*/, class ReplyParamType/*输出参数*/>
class MessageWithReply : public SyncMessage {
public:
typedef SendParamType SendParam;
typedef typename SendParam::ParamTuple RefSendParam;
typedef ReplyParamType ReplyParam;
//.....
static bool Dispatch(const Message* msg, T* obj, Method func) {
SendParam send_params;
void* iter = GetDataIterator(msg);
Message* reply = GenerateReply(msg);//
bool error;
if (ReadParam(msg, &iter, &send_params)) {//读取输入参数tuple
typename ReplyParam::ValueTuple reply_params;
DispatchToMethod(obj, func, send_params, &reply_params);//函数重载与模板特化
WriteParam(reply, reply_params);//将tuple写入msg
error = false;
#ifdef IPC_MESSAGE_LOG_ENABLED
if (msg->received_time() != 0) {
std::wstring output_params;
LogParam(reply_params, &output_params);
msg->set_output_params(output_params);
}
#endif
} else {
NOTREACHED() << "Error deserializing message " << msg->type();
reply->set_reply_error();
error = true;
} obj->Send(reply);//调用TestMessageReceiver::Send
return !error;
}
}
在上面的过程大致是:
- 读入输入参数存放到tuple send_params中,比如测试用例中On_1_1的输入参数Tuple<int>
- 调用模板特化函数DispatchToMethod
该函数有一系列的模板特化类型,如果接受到的消息类型为Msg_C_0_1,那么该特化函数版本为
template<class ObjT, class Method,
class OutA>
inline void DispatchToMethod(ObjT* obj, Method method,
const Tuple0& in,
Tuple1<OutA>* out) {
(obj->*method)(&out->a);
}
这里的代码 (obj->*method)(&out->a)调用的就是TestMessageReceiver::On_0_1, 如果接受的消息是On_1_1,那么该函数的特化版本为:
template<class ObjT, class Method, class InA,
class OutA>
inline void DispatchToMethod(ObjT* obj, Method method,
const Tuple1<InA>& in,
Tuple1<OutA>* out) {
(obj->*method)(in.a, &out->a);
}
这里的代码 (obj->*method)(in.a, &out->a)调用的就是TestMessageReceiver::On_1_1,
- 得到结果,调用obj->Send将结果返回给远端
通过上面代码可看到,调用完用户自定义消息处理函数后,得到输出参数tuple reply_param,用户自定义的消息处理函数如On_1_1中的输出参数(指针参数)写入的指针变量指向的内存地址实际就是reply_param, 得到出参数之后通过模板特化函数WriteParam将该replay_param写入Message(继承自Pickle),然后调用OnMessageReceived::Send将消息处理结果返回给远端。
结束
阅读chromium中消息的接收、处理以及将结果返回给远端的代码,可以返现里面大量使用到了宏预处理、函数重载、模板特化。
- 用户自定义消息,然后chromium通过一个头文件chrome/common/ipc_message_macros.h嵌套包含自己以及用户自定义消息头文件,这样就同时了消息的枚举类型与类。
- 在自定义消息中将输出参数与输出参数分离成两个tuple,在接收到远端消息的时候读取输入参数tuple,构造一个临时变量输出参数tuple
- 将输入tuple与输出tuple讲过函数DispatchToMethod传递给用户的消息处理函数
- 用户消息处理函数结束后,用户返回的输出参数写入到了前面构造的临时变量输出参数tuple中,将tupel序列化写入Message然后调用用户的Send函数将输出参数发送给远端
[原创]chromium源码阅读-进程间通信IPC.消息的接收与应答的更多相关文章
- chromium源码阅读--进程间通信(IPC)
第一篇就有提到Chromium是目前默认是采用多进程架构,当然,chromium有singe-process的版本. 多进程与多线程的区别,确实有很多可以讲的,我的另一篇博客也讲了一些,这里是从浏览器 ...
- chromium源码阅读--Browser进程初始化
最近在研读chromium源码,经过一段懵懂期,查阅了官网和网上的技术文章,是时候自己总结一下了,首先IPC message loop开始吧,这是每个主线程必须有的一个IPC消息轮训主体,类似之前的q ...
- chromium源码阅读--进程的Message Loop
上一篇总结了chromium进程的启动,接下来就看线程的消息处理,这里的线程包含进程的主进程. 消息处理是由base::MessageLoop中实现,消息中的任务和定时器都是异步事件的. 主要如下几点 ...
- chromium源码阅读--HTTP Cache
最近积累了一些关于HTTP缓存的知识,因此结合Chromium的实现总结一下,主要从如下2个分面: 1.HTTP缓存的基础知识 2.Chromium关于HTTP缓存的实现分析 一.HTTP缓存的基础知 ...
- chromium源码阅读--V8 Embbeding
V8是google提供高性能JavaScript解释器,嵌入在chromium里执行JavaScript代码. V8本身是C++实现的,所有嵌入本身毫无压力,一起编译即可,不过作为一个动态语言解释器, ...
- chromium源码阅读--图片处理
JavaScript 图像替换 JavaScript 图像替换技术检查设备能力,然后“做正确的事”. 您可以通过 window.devicePixelRatio 确定设备像素比,获取屏幕的宽度和高度, ...
- chromium源码阅读
linux下chromium的入口函数在文件:src/chrome/app/chrome_exe_main_aura.cc 中 int main(int argc, const char** argv ...
- JDK1.8源码阅读系列之四:HashMap (原创)
本篇随笔主要描述的是我阅读 HashMap 源码期间的对于 HashMap 的一些实现上的个人理解,用于个人备忘,有不对的地方,请指出- 接下来会从以下几个方面介绍 HashMap 源码相关知识: 1 ...
- Android源码阅读 – Zygote
@Dlive 本文档: 使用的Android源码版本为:Android-4.4.3_r1 kitkat (源码下载: http://source.android.com/source/index.ht ...
随机推荐
- chrome调试技巧--持续更新
1.开始调试:右键审查元素 2.按钮功能: 调出控制台: 切换开发环境全屏还是嵌入: 清空当前显示: 将压缩 js 文件格式化缩进规整的文件: 3.常用页面功能: 查看.编辑(双击)HTML: 查看选 ...
- 使用GitHub和Eclipse进行javaEE开发步骤
下载Git客户端:链接:http://pan.baidu.com/s/1jIueUEy 密码:7gef; 下载Eclipse javaee客户端:http://www.eclipse.org/down ...
- 安装配置好openstack环境的虚拟机,须要改动ip时,在数据库中同步改动ip的方法
感谢朋友支持本博客,欢迎共同探讨交流,因为能力和时间有限,错误之处在所难免,欢迎指正. 假设转载,请保留作者信息. 博客地址:http://blog.csdn.net/qq_21398167 原博文地 ...
- Unity UI大小动态设置(Resize Unity UI RectTransform)
我们在开发过程中发现,要调整Unity UI元素的大小,RectTransform提供了sizeDelta属性可以用来动态修改RectTransform的大小,但同时我们也google到另外一个修改R ...
- POSIX是什么?
1.什么是POSIX? POSIX是可移植操作系统接口(Portable Operating System Interface for UNIX)的缩写,是IEEE为了在各种UNIX操作系统上运行软件 ...
- POJ 1243 One Person
题意: 猜数字, 给定 G, L, G 表示可以猜的次数, 每猜一次, G减一, 假如猜的 number 大于 target, L 还需减一, 当 L == -1 或者 G==0 时, 若还没猜中, ...
- 关于直播学习笔记-004-nginx-rtmp、srs、vlc、obs
1.采集端:OBS RTMP推流地址:rtmp://192.168.198.21:1935/live 流密钥:livestream(任意-但播放地址与此一致) 2.播放端:nginx-rtmp-win ...
- ionic ui框架及creator使用帮助
UI框架使用方法:http://ionicframework.com/docs/api/ PS:路由之类的其他js代码示例建议用 官方的app 生成器弄一个简单的页面,然后下载回来看 https:// ...
- Dubbo(三) -- 多协议支持与多注册中心
一.Dubbo支持的协议 Dubbo协议.Hessian协议.HTTP协议.RMI协议.WebService协议.Thrift协议.Memcached协议.Redis协议 二.协议简介 详细参考:ht ...
- .net 防盗链
Global.asax 文件中 protected void Application_BeginRequest(object sender, EventArgs e) { //判断当前请求是否是访问 ...