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表示应答给远端的参数个数。

  1. #define MESSAGES_INTERNAL_FILE "chrome/common/ipc_sync_message_unittest.h"
  2. #include "chrome/common/ipc_message_macros.h"
  3. static IPC::Message* g_reply;
  4.  
  5. class TestMessageReceiver {
  6. public:
  7.  
  8. void On_0_1(bool* out1) {
  9. *out1 = false;
  10. }
  11.  
  12. void On_0_2(bool* out1, int* out2) {
  13. *out1 = true;
  14. *out2 = 2;
  15. }
  16.  
  17. void On_0_3(bool* out1, int* out2, std::string* out3) {
  18. *out1 = false;
  19. *out2 = 3;
  20. *out3 = "0_3";
  21. }
  22.  
  23. void On_1_1(int in1, bool* out1) {
  24. DCHECK(in1 == 1);
  25. *out1 = true;
  26. }
  27. //.....
  28.  
  29. bool Send(IPC::Message* message) {
  30. // gets the reply message, stash in global
  31. DCHECK(g_reply == NULL);
  32. g_reply = message;
  33. return true;
  34. }
  35.  
  36. void OnMessageReceived(const IPC::Message& msg) {
  37. IPC_BEGIN_MESSAGE_MAP(TestMessageReceiver, msg)
  38. IPC_MESSAGE_HANDLER(Msg_C_0_1, On_0_1)//在ipc_enum中定义
  39. IPC_MESSAGE_HANDLER(Msg_C_0_2, On_0_2)
  40. IPC_MESSAGE_HANDLER(Msg_C_0_3, On_0_3)
  41. IPC_MESSAGE_HANDLER(Msg_C_1_1, On_1_1)
  42. //.....
  43. IPC_END_MESSAGE_MAP()
  44. //经过宏展开变为
  45. /*
  46. {
  47. typedef TestMessageReceiver _IpcMessageHandlerClass;
  48. const IPC::Message& ipc_message__ = msg;
  49. bool msg_is_ok__ = true;
  50. switch (ipc_message__.type()) {
  51. case Msg_C_0_1::ID:
  52. msg_is_ok__ = Msg_C_0_1::Dispatch(&ipc_message__, this, &On_0_1);
  53. break;
  54. ....
  55. DCHECK(msg_is_ok__);
  56. }
  57. }
  58. */
  59. }
  60.  
  61. };
  62. TEST(IPCSyncMessageTest, Main) {
  63. bool bool1 = true;
  64. int int1 = 0;
  65. std::string string1;
  66.  
  67. Send(new Msg_C_0_1(&bool1));
  68. DCHECK(!bool1);
  69.  
  70. Send(new Msg_C_0_2(&bool1, &int1));
  71. DCHECK(bool1 && int1 == 2);
  72.  
  73. Send(new Msg_C_0_3(&bool1, &int1, &string1));
  74. DCHECK(!bool1 && int1 == 3 && string1 == "0_3");
  75. // ....
  76. }

其中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中有如下的预处理操作:

  1. //头文件没有ifndef define endif,因此能重复的嵌套包含
  2. #include "chrome/common/ipc_message_utils.h"
  3. #ifndef MESSAGES_INTERNAL_FILE
  4. #error This file should only be included by X_messages.h, which needs to define MESSAGES_INTERNAL_FILE first.
  5. #endif
  6. // Trick scons and xcode into seeing the possible real dependencies since they
  7. // don't understand #include MESSAGES_INTERNAL_FILE. See http://crbug.com/7828
  8. /@1
  9. //注意这里用了header guard,只会包含一次
  10. #ifndef IPC_MESSAGE_MACROS_INCLUDE_BLOCK
  11. #define IPC_MESSAGE_MACROS_INCLUDE_BLOCK
  12. // Multi-pass include of X_messages_internal.h. Preprocessor magic allows
  13. // us to use 1 header to define the enums and classes for our render messages.
  14. #define IPC_MESSAGE_MACROS_ENUMS
  15. //嵌套包含:@2
  16. #include MESSAGES_INTERNAL_FILE
  17.  
  18. #define IPC_MESSAGE_MACROS_CLASSES
  19. //嵌套包含:@2
  20. #include MESSAGES_INTERNAL_FILE
  21.  
  22. #ifdef IPC_MESSAGE_MACROS_LOG_ENABLED
  23. #define IPC_MESSAGE_MACROS_LOG
  24. #include MESSAGES_INTERNAL_FILE
  25. #endif
  26. #undef MESSAGES_INTERNAL_FILE
  27. #undef IPC_MESSAGE_MACROS_INCLUDE_BLOCK
  28. #endif // IPC_MESSAGE_MACROS_INCLUDE_BLOCK
  29.  
  30. @A
  31. #undef IPC_BEGIN_MESSAGES
  32. //.....
  33.  
  34. //下面是定义消息枚举类型,消息类,以及日志类(日志类可选)
  35. #if defined(IPC_MESSAGE_MACROS_ENUMS) //消息枚举类型定义
  36. //这里undef很重要,因为在@2嵌套包含之后下面的代码通过@2展开在了@1里面
  37. //如果不undef的话,在@1中就会重复的包含下面的代码
  38. #undef IPC_MESSAGE_MACROS_ENUMS
  39. //....
  40. //定义枚举消息类型
  41. #define IPC_BEGIN_MESSAGES(label) \
  42. enum label##MsgType { \
  43. label##Start = label##MsgStart << 12, \
  44. label##PreStart = (label##MsgStart << 12) - 1,
  45. ///......
  46. #define IPC_END_MESSAGES(label) \
  47. label##End \
  48. };
  49. //....
  50. #elif defined(IPC_MESSAGE_MACROS_LOG) //消息日志类定义
  51. #undef IPC_MESSAGE_MACROS_LOG
  52. //....
  53.  
  54. //定义日志消息类型
  55. #define IPC_BEGIN_MESSAGES(label) \
  56. void label##MsgLog(uint16 type, std::wstring* name, const IPC::Message* msg, std::wstring* params) { \
  57. switch (type) {
  58.  
  59. #define IPC_END_MESSAGES(label) \
  60. default: \
  61. if (name) \
  62. *name = L"[UNKNOWN " L ## #label L" MSG"; \
  63. } \
  64. } \
  65. class LoggerRegisterHelper##label { \
  66. public: \
  67. LoggerRegisterHelper##label() { \
  68. g_log_function_mapping[label##MsgStart] = label##MsgLog; \
  69. } \
  70. }; \
  71. LoggerRegisterHelper##label g_LoggerRegisterHelper##label;
  72.  
  73. //....
  74. #elif defined(IPC_MESSAGE_MACROS_CLASSES) //消息类定义
  75. #undef IPC_MESSAGE_MACROS_CLASSES
  76. // ....
  77. //定义消息类
  78. #define IPC_BEGIN_MESSAGES(label)
  79. #define IPC_END_MESSAGES(label)
  80. // 定义消息类,枚举msg_class__ID在IPC_MESSAGE_MACROS_ENUMS 中的IPC_MESSAGE_CONTROL0定义
  81. // 定义的这个类继承自IPC::Message,初始化构造的时候传入该消息的ID
  82. #define IPC_MESSAGE_CONTROL0(msg_class) \
  83. class msg_class : public IPC::Message { \
  84. public: \
  85. enum { ID = msg_class##__ID }; \
  86. msg_class() \
  87. : IPC::Message(MSG_ROUTING_CONTROL, \
  88. ID, \
  89. PRIORITY_NORMAL) {} \
  90. };
  91. //....
  92. #endif

上面代码IPC_MESSAGE_MACROS_INCLUDE_BLOCK中我们可以看到也多次include了用户自定义的文件MESSAGES_ITERNAL_FILE,上面的作用实际上就是*方便用户定义自己的消息类型,通过编写一个头文件就能一次性同时定义消息的枚举类型,实际的消息类*

参看自定义的消息头文件MESSAGES_INTERNAL_FILE以及添加的注释可以大致了解如何通过多次嵌套包含头文件同时实现消息枚举类型与类:

  1. #include "chrome/common/ipc_message_macros.h"
  2. // 下面两次(或者3次)宏召开中,定义enum时在IPC_BEGIN_MESSAGES用到一个枚举
  3. //变量TestMsgStart 在ipc_message_utils.h中定义
  4.  
  5. //ipc_enum 宏展开
  6. /*
  7. enum TestMsgType {
  8. TestStart = TestMsgStart << 12,
  9. TestPreStart = (TestMsgStart << 12) - 1,
  10. SyncChannelTestMsg_NoArgs__ID,
  11. SyncChannelTestMsg_AnswerToLife__ID,
  12. SyncChannelTestMsg_Double__ID,
  13. Msg_C_0_1__ID,
  14. ....,
  15. Msg_R_3_2__ID,
  16. Msg_R_3_3__ID,
  17. TestEnd
  18. };
  19. */
  20. //ipc_classes宏展开
  21. /*
  22. class SyncChannelTestMsg_NoArgs{...};
  23. class SyncChannelTestMsg_AnswerToLife{...};
  24. ...
  25. class Msg_C_0_1 : public IPC::MessageWithReply<Tuple0, Tuple1<bool&> > {
  26. public:
  27. enum { ID = Msg_C_0_1__ID }; //看TestMsgType
  28. Msg_C_0_1(bool* arg1)
  29. : IPC::MessageWithReply<Tuple0, Tuple1<bool&> >(
  30. MSG_ROUTING_CONTROL,
  31. ID,
  32. MakeTuple(), MakeRefTuple(*arg1)) {}
  33. };
  34. ....
  35. class Msg_R_3_2{...};
  36. class Msg_R_3_3{...};
  37. */
  38. /*
  39. SYNC消息机制
  40. 消息映射宏的形式为IPC_SYNC_MESSAGE_CONTROL$(IN)_$(OUT),表示的是同步消息,
  41. 意思是:当@A发送消息a给@B,@B接收到a之后,需要返回一个消息b给@A。
  42. 其中宏中的$IN和$OUT分别表示输入参数的个数
  43. 以及输出参数的个数,通过宏展开我们可以知道
  44. */
  45. IPC_BEGIN_MESSAGES(Test)
  46. IPC_SYNC_MESSAGE_CONTROL0_0(SyncChannelTestMsg_NoArgs)
  47.  
  48. IPC_SYNC_MESSAGE_CONTROL0_1(SyncChannelTestMsg_AnswerToLife,
  49. int /* answer */)
  50.  
  51. IPC_SYNC_MESSAGE_CONTROL1_1(SyncChannelTestMsg_Double,
  52. int /* in */,
  53. int /* out */)
  54.  
  55. // out1 is false
  56. IPC_SYNC_MESSAGE_CONTROL0_1(Msg_C_0_1, bool)
  57. //经过ipc_enum宏展开变为
  58. //Msg_C_0_1__ID
  59. //经过ipc_class宏展开变为
  60. /*******************************************************************
  61. class Msg_C_0_1 : public IPC::MessageWithReply<Tuple0, Tuple1<bool&> > {
  62. public:
  63. enum { ID = Msg_C_0_1__ID };
  64. Msg_C_0_1(bool* arg1)
  65. : IPC::MessageWithReply<Tuple0, Tuple1<bool&> >(
  66. MSG_ROUTING_CONTROL,
  67. ID,
  68. MakeTuple(), MakeRefTuple(*arg1)) {}
  69. };
  70. *******************************************************************/
  71. // out1 is true, out2 is 2
  72. IPC_SYNC_MESSAGE_CONTROL0_2(Msg_C_0_2, bool, int)

这里定义的消息类有连个模板参数,一个输入参数tuple,与输出参数tuple,通过这样区分就可以实现消息分派处理函数中输入参数与输出参数,在测试用例中非指针参数就是输入参数,指针参数就是输出参数

消息的接收与应答

TestMessageReceiver::OnMessageReceived在接收到一个消息之后,通过IPC_BEGIN_MESSAGE_MAP与IPC_END_MESSAGE_MAP定义的消息处理宏会将消息分派到相依的消息处理函数中,第一个消息宏

  1. IPC_MESSAGE_HANDLER(Msg_C_0_1, On_0_1)//在ipc_enum中定义

我们进一步了解如何处理接收到的消息,上面定义的宏经过展开变为如下的代码:

  1. {
  2. typedef TestMessageReceiver _IpcMessageHandlerClass;
  3. const IPC::Message& ipc_message__ = msg;
  4. bool msg_is_ok__ = true;
  5. switch (ipc_message__.type()) {
  6. case Msg_C_0_1::ID:
  7. msg_is_ok__ = Msg_C_0_1::Dispatch(&ipc_message__, this, &On_0_1);
  8. break;
  9. ....
  10. DCHECK(msg_is_ok__);
  11. }
  12. }

可以看到在接收到消息Msg_C_0_1::ID之后调用了Msg_C_0_1::Dispatch函数, Msg_C_0_1继承自头文件chrome/common/ipc_message_utils.h中定义的消息类

  1. template <class SendParamType/*输入参数*/, class ReplyParamType/*输出参数*/>
  2. class MessageWithReply : public SyncMessage {
  3. public:
  4. typedef SendParamType SendParam;
  5. typedef typename SendParam::ParamTuple RefSendParam;
  6. typedef ReplyParamType ReplyParam;
  7. //.....
  8. static bool Dispatch(const Message* msg, T* obj, Method func) {
  9. SendParam send_params;
  10. void* iter = GetDataIterator(msg);
  11. Message* reply = GenerateReply(msg);//
  12. bool error;
  13. if (ReadParam(msg, &iter, &send_params)) {//读取输入参数tuple
  14. typename ReplyParam::ValueTuple reply_params;
  15. DispatchToMethod(obj, func, send_params, &reply_params);//函数重载与模板特化
  16. WriteParam(reply, reply_params);//将tuple写入msg
  17. error = false;
  18. #ifdef IPC_MESSAGE_LOG_ENABLED
  19. if (msg->received_time() != 0) {
  20. std::wstring output_params;
  21. LogParam(reply_params, &output_params);
  22. msg->set_output_params(output_params);
  23. }
  24. #endif
  25. } else {
  26. NOTREACHED() << "Error deserializing message " << msg->type();
  27. reply->set_reply_error();
  28. error = true;
  29. }
  30.  
  31. obj->Send(reply);//调用TestMessageReceiver::Send
  32. return !error;
  33. }
  34. }

在上面的过程大致是:

  1. 读入输入参数存放到tuple send_params中,比如测试用例中On_1_1的输入参数Tuple<int>
  2. 调用模板特化函数DispatchToMethod

该函数有一系列的模板特化类型,如果接受到的消息类型为Msg_C_0_1,那么该特化函数版本为

  1. template<class ObjT, class Method,
  2. class OutA>
  3. inline void DispatchToMethod(ObjT* obj, Method method,
  4. const Tuple0& in,
  5. Tuple1<OutA>* out) {
  6. (obj->*method)(&out->a);
  7. }

这里的代码 (obj->*method)(&out->a)调用的就是TestMessageReceiver::On_0_1, 如果接受的消息是On_1_1,那么该函数的特化版本为:

  1. template<class ObjT, class Method, class InA,
  2. class OutA>
  3. inline void DispatchToMethod(ObjT* obj, Method method,
  4. const Tuple1<InA>& in,
  5. Tuple1<OutA>* out) {
  6. (obj->*method)(in.a, &out->a);
  7. }

这里的代码 (obj->*method)(in.a, &out->a)调用的就是TestMessageReceiver::On_1_1,

  1. 得到结果,调用obj->Send将结果返回给远端

通过上面代码可看到,调用完用户自定义消息处理函数后,得到输出参数tuple reply_param,用户自定义的消息处理函数如On_1_1中的输出参数(指针参数)写入的指针变量指向的内存地址实际就是reply_param, 得到出参数之后通过模板特化函数WriteParam将该replay_param写入Message(继承自Pickle),然后调用OnMessageReceived::Send将消息处理结果返回给远端。

结束

阅读chromium中消息的接收、处理以及将结果返回给远端的代码,可以返现里面大量使用到了宏预处理、函数重载、模板特化。

  1. 用户自定义消息,然后chromium通过一个头文件chrome/common/ipc_message_macros.h嵌套包含自己以及用户自定义消息头文件,这样就同时了消息的枚举类型与类。
  2. 在自定义消息中将输出参数与输出参数分离成两个tuple,在接收到远端消息的时候读取输入参数tuple,构造一个临时变量输出参数tuple
  3. 将输入tuple与输出tuple讲过函数DispatchToMethod传递给用户的消息处理函数
  4. 用户消息处理函数结束后,用户返回的输出参数写入到了前面构造的临时变量输出参数tuple中,将tupel序列化写入Message然后调用用户的Send函数将输出参数发送给远端

Date: 2015-06-11T23:17+0800

Author: liangsijian

Org version 7.9.3f with Emacs version 24

Validate XHTML 1.0

[原创]chromium源码阅读-进程间通信IPC.消息的接收与应答的更多相关文章

  1. chromium源码阅读--进程间通信(IPC)

    第一篇就有提到Chromium是目前默认是采用多进程架构,当然,chromium有singe-process的版本. 多进程与多线程的区别,确实有很多可以讲的,我的另一篇博客也讲了一些,这里是从浏览器 ...

  2. chromium源码阅读--Browser进程初始化

    最近在研读chromium源码,经过一段懵懂期,查阅了官网和网上的技术文章,是时候自己总结一下了,首先IPC message loop开始吧,这是每个主线程必须有的一个IPC消息轮训主体,类似之前的q ...

  3. chromium源码阅读--进程的Message Loop

    上一篇总结了chromium进程的启动,接下来就看线程的消息处理,这里的线程包含进程的主进程. 消息处理是由base::MessageLoop中实现,消息中的任务和定时器都是异步事件的. 主要如下几点 ...

  4. chromium源码阅读--HTTP Cache

    最近积累了一些关于HTTP缓存的知识,因此结合Chromium的实现总结一下,主要从如下2个分面: 1.HTTP缓存的基础知识 2.Chromium关于HTTP缓存的实现分析 一.HTTP缓存的基础知 ...

  5. chromium源码阅读--V8 Embbeding

    V8是google提供高性能JavaScript解释器,嵌入在chromium里执行JavaScript代码. V8本身是C++实现的,所有嵌入本身毫无压力,一起编译即可,不过作为一个动态语言解释器, ...

  6. chromium源码阅读--图片处理

    JavaScript 图像替换 JavaScript 图像替换技术检查设备能力,然后“做正确的事”. 您可以通过 window.devicePixelRatio 确定设备像素比,获取屏幕的宽度和高度, ...

  7. chromium源码阅读

    linux下chromium的入口函数在文件:src/chrome/app/chrome_exe_main_aura.cc 中 int main(int argc, const char** argv ...

  8. JDK1.8源码阅读系列之四:HashMap (原创)

    本篇随笔主要描述的是我阅读 HashMap 源码期间的对于 HashMap 的一些实现上的个人理解,用于个人备忘,有不对的地方,请指出- 接下来会从以下几个方面介绍 HashMap 源码相关知识: 1 ...

  9. Android源码阅读 – Zygote

    @Dlive 本文档: 使用的Android源码版本为:Android-4.4.3_r1 kitkat (源码下载: http://source.android.com/source/index.ht ...

随机推荐

  1. PHP 去除iphone,ios,emoji表情

    public static function removeEmoji($text) { $clean_text = ""; // Match Emoticons $regexEmo ...

  2. Nginx配置PATHINFO隐藏index.php

    1.网络来源:http://www.shouce.ren/post/view/id/1529 server {      listen       80;     default_type text/ ...

  3. 第四章 Spring.Net 如何管理您的类___统一资源访问接口

    在前面章节有童鞋提到过 关于配置文件 Objects.xml 路径的相关问题,这些东西是 IResource 接口的一些内容,接下来就详细介绍一下 IResource 接口. IResource 接口 ...

  4. 高级类特性----接口(intertface)

    接 口 有时必须从几个类中派生出一个子类,继承它们所有的属性和方法.但是,Java不支持多重继承.有了接口,就可以得到多重继承的效果. 接口(interface)是抽象方法和常量值的定义的集合. 从本 ...

  5. Python 高斯坐标转经纬度算法

    # 高斯坐标转经纬度算法# B=大地坐标X# C=大地坐标Y# IsSix=6度带或3度带def GetLatLon2(B, C,IsSix): #带号 D = math.trunc(C / 1000 ...

  6. python2.0 s12 day7

    开发的第二阶段 网络编程阶段 之所以叫网络编程,是因为,这里面就不是你在一台机器中玩了.多台机器,CS架构.即客户端和服务器端通过网络进行通信的编程了. 首先想实现网络的通信,你得先学网络通信的一个基 ...

  7. django model 数据类型

    转自:http://www.cnblogs.com/lhj588/archive/2012/05/24/2516040.html Django 通过 models 实现数据库的创建.修改.删除等操作, ...

  8. Linux中的命令学习笔记

    Linux挂载Winodws共享文件夹 mount -t cifs -o username=xxx,password=xxxx //1.1.1.1/test /win 产生一个5位随机字符串 | md ...

  9. 关于android定位的坐标系问题

    按照正常的思路,我们通过GPS或者基站定位等方式获取到经纬度信息后,把它放到地图上,就能够完成定位.但实际上,我们很有可能会在实际操作中发现,我们的定位出现了较大的偏移.这是因为我国出于国家安全(或者 ...

  10. lua中类的实现原理和实践

    一.基础概念  Lua 本身是函数式的语言,但借助 metatable (元表)这个强大的工具,Lua 实现操作符重载易如反掌.. 下文将详细的解释在Lua中实现类的原理,涉及到的细节点将拆分出来讲, ...