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

chromium源码阅读-进程间通信IPC.消息的接收与应答

chromium源码阅读-进程间通信IPC.消息的接收与应答

介绍

chromium进程间通信在win32下是通过命名管道的方式实现的,最后的数据都是以二进制流的方式进行传播,pickle类就是负责消息的封包与解包功能,它将各种数据已二进制的形式写到内存缓冲区中,在实际通信的时候通过与其他一些辅助类与模板函数来实现具体数据结构的写与读。本文主要介绍的是chromium在将消息发送与接收过程中,以及chromium如何通过各种消息宏与C++模板机制实现消息的分派与应答,限于篇幅此处忽略命名管道部分消息的发送与接收。

demo

以下代码是截取自chromium源代码里面关于ipc同步消息(SYNMSG)的测试用例,在chromium中所谓的ipcsynmessage就是说: 当@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定义的消息处理宏的功能是一样的,都是响应消息函数。如果接收到对应的消息比如消息MsgC01那么就会调用成员函数On01,成员函数中设置了参数out1值为false,设置的这个值经过处理会转换为Message,然后将其传递给成员函数Send发送给远端,这里只是测试用例,因此没有实际的发送出去只是将这个消息暂时赋值给全局变量greply。

IPC消息宏

chromium消息机制通过头文件chrome/common/ipcmessagemacros.h定义了许多有用的消息宏以及模板函数、模板类。这个头文件编写的非常巧妙,通过多次嵌套头文件本身,最后经过编译器预处理宏展开,方便的定义了用户定义的消息枚举类型以及消息类。

在上面的代码中首先定义了一个宏文件MESSAGESINTERNALFILE,这是用户自己定义的消息类型文件,里面也include了文件chrome/common/ipcmessagemacros.h,用户需要在这个文件定义自己需要的消息类型,这里定义了MsgC01,MsgC02等消息,在头文件chrome/common/ipcmessagemacros.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

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

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

#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在接收到一个消息之后,通过IPCBEGINMESSAGEMAP与IPCENDMESSAGEMAP定义的消息处理宏会将消息分派到相依的消息处理函数中,第一个消息宏

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__);
    }
}

可以看到在接收到消息MsgC01::ID之后调用了MsgC01::Dispatch函数, MsgC01继承自头文件chrome/common/ipcmessageutils.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;
  }
}

在上面的过程大致是:

  1. 读入输入参数存放到tuple sendparams中,比如测试用例中On11的输入参数Tuple&lt;int>
  2. 调用模板特化函数DispatchToMethod

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

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::On01, 如果接受的消息是On11,那么该函数的特化版本为:

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::On11,

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

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

结束

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

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

Date: 2015-06-01T00:38+0800

Author: liangsijian

Org version 7.9.3f with Emacs version 24

Validate XHTML 1.0

时间: 2024-10-13 11:03:20

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

chromium源码阅读--HTTP Cache

最近积累了一些关于HTTP缓存的知识,因此结合Chromium的实现总结一下,主要从如下2个分面: 1.HTTP缓存的基础知识 2.Chromium关于HTTP缓存的实现分析 一.HTTP缓存的基础知识 基本上每个浏览器都启用了HTTP缓存功能. 当服务器返回响应时,会响应一组HTTP头,用于描述响应的内容类型.长度.缓存指令.验证令牌等. 例如,在上图的交互中,服务器返回一个 1024 字节的响应,指示客户端将其缓存最多 120 秒,并提供一个验证令牌("x234dff"),可在响应

chromium源码阅读

linux下chromium的入口函数在文件:src/chrome/app/chrome_exe_main_aura.cc 中 int main(int argc, const char** argv) { return ChromeMain(argc, argv); } ChromeMain函数在 src/chrome/app/chrome_main.cc 中 base::CommandLine::Init(params.argc, params.argv); 具体函数如下: bool Com

chromium源码阅读--图片处理

JavaScript 图像替换 JavaScript 图像替换技术检查设备能力,然后"做正确的事". 您可以通过 window.devicePixelRatio 确定设备像素比,获取屏幕的宽度和高度,甚至可通过 navigator.connection 或发出假请求来执行某种网络连接嗅探.收集了所有这些信息后,您就可以决定加载哪个图像. 此方法的一大缺陷是,使用 JavaScript 意味着您将延迟加载图像,至少要等到先行解析器结束. 这意味着,图像要等到pageload 事件触发后才

Android系统源码阅读(13):Input消息的分发过程

Android系统源码阅读(13):Input消息的分发过程 请对照AOSP版本:6.0.1_r50.学校电脑好渣,看源码时卡半天 先回顾一下前两篇文章.在设备没有事件输入的时候,InputReader和InputDispatcher都处于睡眠状态.当输入事件发生,InputReader首先被激活,然后发送读取消息,激活Dispatcher.Dispatcher被激活以后,将消息发送给当前激活窗口的主线程,然后睡眠等待主线程处理完这个事件.主线程被激活后,会处理相应的消息,处理完毕后反馈给Dis

AsyncTask 源码阅读

1.参考资料 1. http://developer.android.com/reference/android/os/AsyncTask.html 2. http://blog.csdn.net/pi9nc/article/details/12622797 3. http://www.tuicool.com/articles/VnMf2i3 下面详细解析Asynctask的源码流程.我们在阅读源码的时候需要知道的重要点是:输入输出(参数).以及程序入口(什么地方进入调用). 2 传递的三个泛型

JDK部分源码阅读与理解

本文为博主原创,允许转载,但请声明原文地址:http://www.coselding.cn/article/2016/05/31/JDK部分源码阅读与理解/ 不喜欢重复造轮子,不喜欢贴各种东西.JDK代码什么的,让整篇文章很乱...JDK源码谁都有,没什么好贴的...如果你没看过JDK源码,建议打开Eclipse边看源码边看这篇文章,看过的可以把这篇文章当成是知识点备忘录... JDK容器类中有大量的空指针.数组越界.状态异常等异常处理,这些不是重点,我们关注的应该是它的一些底层的具体实现,这篇

chromium浏览器开发系列第二篇:如何编译最新chromium源码

说一下为什么这么晚才发第二篇,上周和这周department的工作太多了,晚上都是十点半从公司出发,回家以后实在没有多余的精力去摸键盘了.所以请大家包涵! 上期回顾: chromium源码下载: 1.找个靠谱的vpn(我试过了,网上说的不用vpn拿代码的都不靠谱): 2.获取depot_tools,解压,设置环境变量; 3.gclient获取python和git,svn,设置环境变量: 4.fetch–nohooks chromium –nosvn=true 获取源码: 5.gclientsyn

Flume-NG源码阅读之SourceRunner,及选择器selector和拦截器interceptor的执行

在AbstractConfigurationProvider类中loadSources方法会将所有的source进行封装成SourceRunner放到了Map<String, SourceRunner> sourceRunnerMap之中.相关代码如下: 1 Map<String, String> selectorConfig = context.getSubProperties( 2 BasicConfigurationConstants.CONFIG_SOURCE_CHANNE

【原】SDWebImage源码阅读(三)

[原]SDWebImage源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1.SDWebImageDownloader中的downloadImageWithURL 我们来到SDWebImageDownloader.m文件中,找到downloadImageWithURL函数.发现代码不是很长,那就一行行读.毕竟这个函数大概做什么我们是知道的.这个函数大概就是创建了一个SDWebImageSownloader的异步下载器,根据给定的URL下载image. 先映入眼帘的