原文路径:https://chromium.googlesource.com/chromium/src.git/+/master/docs/mojo_ipc_conversion.md
仍然使用Chrome的旧IPC系统在// ipc中定义了许多发送的IPC消息(主要是在浏览器和渲染器进程之间)。 该系统使用base :: Pickle作为消息序列化的基础,并且如果在// ipc中定义并在源树周围使用的IPC_ *预处理程序宏得到了数字的支持,则该系统将得到支持。
为了将这些消息转换为Mojo接口消息,正在进行持续的分布式工作。 在两个电子表格中跟踪仍需要转换的邮件:
本文档主要涉及将旧IPC消息死记硬地转换为Mojo接口消息。 如果您正在考虑对浏览器的整个子系统进行更全面的重构和更好的隔离,则可以考虑对功能进行服务而不是仅仅转换其IPC。
请参阅其他Mojo&Services文档以获取入门指南,API参考等。
每个Content子进程在其与浏览器进程之间都有一个IPC :: Channel实现,它用作在进程之间发送旧IPC消息的唯一双向FIFO。
遗留IPC消息有两种基本类型:通过IPC_MESSAGE_CONTROLn宏(其中n是一些小整数)定义的控制消息和通过IPC_MESSAGE_ROUTEDn宏定义的路由消息。
控制消息通常在浏览器端进程主机(例如RenderProcessHost或GpuProcessHost)和子端ChildThreadImpl子类之间传递。 所有这些类均实现IPC :: Sender并因此具有用于向其远程对等方发送控制消息的发送方法,并且它们均实现IPC :: Listener以通过OnMessageReceived接收传入的控制消息。
路由消息被降级为具有任意含义的路由,这些路由由它们在给定进程中的使用决定。 例如,渲染器使用路由来隔离范围为单个渲染帧的消息,因此此类路由的消息将在RenderFrameHostImpl及其对应的RenderFrameImpl之间传播,这两个消息也都实现了IPC :: Sender和IPC :: Listener。
旧IPC系统中的路由消息始终带有一个路由ID,以向接收端点标识消息所针对的路由对象(例如哪个RenderFrameImpl或RenderViewImpl或其他对象)。 因此,要求每个端点进行一些额外的簿记,以跟踪每个路由ID的含义。
Mojo接口消除了对路由ID的需求,因为可以通过简单地创建新的接口管道并将一个端点传递到知道如何绑定它的端点来建立新的“路由”。
在考虑将IPC消息转换为Mojo时,重要的是要考虑该消息是控制消息还是路由消息,因为这确定了您可能在哪里找到现有的Mojo接口来承载您的消息,或者您想在哪里添加消息。 为此目的使用了新的端到端Mojo接口。 这可能意味着每个RenderProcessHostImpl及其对应的RenderThreadImpl之间的单个进程接口与每个RenderFrameHostImpl及其对应的RenderFrameImpl之间的每个帧接口之间的差异。
进行IPC转换时,一个非常重要的考虑因素是IPC驱动的操作的相对顺序。 使用旧的IPC系统,因为两个进程之间的每个消息都是全局排序的,所以系统的各个部分(有意或无意地)依赖严格的排序保证非常容易。
例如,假设浏览器进程中的WebContentsObserver观察到框架导航并立即向框架发送IPC消息以配置一些新行为。 该实现可能会无意中依赖于此消息的到达,该消息是在同一导航事件之后不久将一些其他与切向相关的消息发送到同一帧之前到达的。
Mojo不会(实际上也不能)在单独的消息管道之间做出任何严格的顺序保证,因为消息管道可以在进程边界之间自由移动,因此不一定总是共享公共FIFO。
如果将上述两个消息移到单独的消息管道上的单独Mojo接口,则渲染器行为可能会中断,因为第一个消息可能在第二个消息之后到达。
解决此问题的最佳方法是重新考虑IPC表面和/或任一侧的实现,以消除两个接口之间在逻辑上似乎是不同的顺序依赖性。 失败的是,Mojo对这个问题的解决方案是支持关联的接口。 简而言之,它们允许在共享消息管道上多路复用多个不同的接口。
上一节将关联接口作为通用解决方案,通过在多个逻辑Mojo接口之间共享单个消息管道来建立相互FIFO。
在Chrome中,在两个进程之间传送所有旧IPC消息的IPC :: Channel本身就是Mojo消息管道。 我们提供了一种将任意Mojo接口与此管道关联的机制,这意味着可以将消息转换为Mojo,同时相对于其他传统IPC消息保留严格的FIFO。 此类接口在Chrome术语中被称为与通道相关的接口。
注意:与通道相关的接口获取不受Service Manager的任何限制,因此安全检查人员需要注意检查此类接口的新增功能和使用情况。
与通道相关的接口的使用应该很少,但是被认为是增量IPC转换的合理的中间解决方案,在这种情况下,一次转换一个较大的IPC表面太冒险或嘈杂,但是也无法在两个IPC表面之间进行拆分 传统IPC和专用的Mojo接口管道,而不会引入计时错误。
在Chrome开发的这一点上,与通道相关的接口的实际用法仅限于浏览器进程与渲染器进程之间的IPC :: Channel,因为这是最复杂的IPC表面,具有最隐式的排序依赖性。 存在一些简单的API来支持此功能。
RenderProcessHostImpl在渲染过程中为其对应的RenderThreadImpl拥有一个IPC :: Channel。 此对象具有GetRemoteAssociatedInterfaces方法,可用于传递任意关联的接口请求:
mojo::PendingAssociatedRemote<magic::mojom::GoatTeleporter> teleporter; channel_->GetRemoteAssociatedInterfaces()->GetInterface(teleporter.BindNewEndpointAndPassReceiver()); // These messages are all guaranteed to arrive in the same order they were sent. channel_->Send(new FooMsg_SomeLegacyIPC); teleporter->TeleportAllGoats(); channel_->Send(new FooMsg_AnotherLegacyIPC);
同样,ChildThreadImpl具有IPC :: Channel,可以以相同的方式将此类消息发送回浏览器。
为了接收和绑定传入的与通道相关的接口请求,上述对象还实现了IPC :: Listener :: OnAssociatedInterfaceRequest。
为了补充路由消息,RenderFrameHostImpl和RenderFrameImpl都定义了一种GetRemoteAssociatedInterfaces方法,该方法的工作原理类似于IPC :: Channel上的方法,并且这两个对象还实现了IPC :: Listener :: OnAssociatedInterfaceRequest来处理特定于其自身帧的传入关联接口请求。
这里有一些使用通道关联接口的示例转换CL
在开始任何IPC消息转换之旅之前,您应该问几个问题,并且有许多潜在的方法需要考虑。 正确的选择取决于上下文。
请注意,本节假定消息在浏览器进程和渲染器进程之间传播。 其他情况很少见,开发人员可能希望在继续之前先咨询chromium-mojo@chromium.org。 否则,请应用以下基本算法来决定如何进行:
注意:如果要转换同步IPC,请参阅Mojo文档中有关``同步呼叫''的部分
如果消息是答复,则意味着它具有一个“请求ID”,可以将其与相反方向上的先前消息相关联,请考虑按照上述算法转换请求消息。 与传统IPC不同,Mojo消息将答复作为一流的概念来支持。 因此,例如,如果您有:
IPC_CONTROL_MESSAGE2(FooHostMsg_DoTheThing, int /* request_id */, std::string /* name */); IPC_CONTROL_MESSAGE2(FooMsg_DidTheThing, int /* request_id */, bool /* success */);
您应该考虑使用以下mojom定义定义一个接口Foo,该接口绑定在RenderProcessHostImpl中并从RenderThreadImpl获取。
interface Foo { DoTheThing(string name) => (bool success); };
有关更多信息,请参见接收响应。
有时,进行部分IPC转换会很有用,在这种情况下,您要将消息转换为Mojo接口方法,但又不想转换消息传递的每个结构。 在这种情况下,您可以利用Mojo的类型映射系统重新利用现有的IPC :: ParamTraits
注意:尽管在某些情况下IPC :: ParamTraits <T>专业化是在库代码中手动定义的,但是IPC_STRUCT *宏助手也可以在幕后定义IPC :: ParamTraits <T>专业化。 本节中的所有建议都与两种定义有关。
如果声明的mojom结构没有结构体,并用[Native]标记,并且为该结构提供了相应的typemap,则发出的C ++绑定(就像魔术一样)将mojom类型替换为typemapped C ++类型 并将内部使用该类型的现有IPC :: ParamTraits <T>专业化功能来序列化和反序列化该结构。
例如,给定了resource_messages.h标头,该标头为content :: ResourceRequest定义了IPC映射:
IPC_STRUCT_TRAITS_BEGIN(content::ResourceRequest) IPC_STRUCT_TRAITS_MEMBER(method) IPC_STRUCT_TRAITS_MEMBER(url) // ... IPC_STRUCT_TRAITS_END()
resource_request.h标头实际上定义了content :: ResourceRequest类型:
namespace content { struct CONTENT_EXPORT ResourceRequest { // ... }; } // namespace content
我们可以声明一个相应的“本机” mojom结构:
module content.mojom; [Native] struct URLRequest;
添加一个类似url_request.typemap的类型图以定义如何在它们之间进行映射:
mojom = "//content/public/common/url_loader.mojom" public_headers = [ "//content/common/resource_request.h" ] traits_headers = [ "//content/common/resource_messages.h" ] ... type_mappings = [ "content.mojom.URLRequest=content::ResourceRequest" ]
特别注意,public_headers包括本机C ++类型的定义,而traits_headers包括旧版IPC特征的定义。
因此,其他mojom文件现在可以将content.mojom.URLRequest引用为方法参数和其他struct字段的类型,并且生成的C ++绑定将这些值专门表示为content :: ResourceRequest对象。
可以使用相同的基本方法来利用现有的IPC_ENUM_TRAITS来调用[Native] mojom枚举别名。
注意:[Native] mojom定义的使用严格限于C ++绑定。 如果mojom消息依赖于此类定义,则其他语言绑定无法发送或接收该消息。 此功能还取决于对旧IPC序列化的持续支持,因此应将其所有使用视为技术债务。
假设我们有一个像这样的mojom文件:
module example.mojom; interface Foo { SendData(string param1, array<int32> param2); };
以下GN代码段将生成两个具体目标:example和example_blink:
mojom("example") { sources = [ "example.mojom" ] }
目标示例将使用STL类型生成Chromium样式的C ++绑定:
// example.mojom.h namespace example { namespace mojom { class Example { virtual void SendArray(const std::string& param1, const std::vector<int32_t>& param2) = 0; } } // namespace mojom } // namespace example
目标example_blink将使用WTF类型生成Blink样式的C ++绑定:
// example.mojom-blink.h namespace example { namespace mojom { namespace blink { class Example { virtual void SendArray(const WTF::String& param1, const WTF::Vector<int32_t>& param2) = 0; } } // namespace blink } // namespace mojom } // namespace example
由于这些单独的绑定集,因此无需执行任何操作即可在Blink样式代码和Chromium样式代码之间转换类型。 它在消息序列化和反序列化过程中自动处理。
有关变体的更多信息,请参阅C ++绑定文档的本节。
返回值的Mojo方法采用base :: OnceCallback的实例。 根据对象的类型和回调使用WTF :: Bind()和适当的包装器函数。
对于拥有mojo :: Remote的垃圾收集(Oilpan)类,建议对连接错误处理程序使用WrapWeakPersistent(this),因为不能保证在有限的时间段内调用它们(在这种情况下用WrapPersistent包装对象) 会导致内存泄漏)。
如果在接收到响应时对象还没有存活的情况下可以丢弃响应,请使用WrapWeakPersistent(this)绑定响应回调:
// src/third_party/blink/renderer/modules/device_orientation/device_sensor_entry.cc sensor_.set_connection_error_handler(WTF::Bind( &DeviceSensorEntry::HandleSensorError, WrapWeakPersistent(this))); sensor_->ConfigureReadingChangeNotifications(/*enabled=*/false); sensor_->AddConfiguration( std::move(config), WTF::Bind(&DeviceSensorEntry::OnSensorAddConfiguration, WrapWeakPersistent(this)));
否则(例如,如果响应回调用于解决Promise),请使用WrapPersistent(this)使对象保持活动状态:
// src/third_party/blink/renderer/modules/nfc/nfc.cc ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state); ... nfc_->CancelAllWatches(WTF::Bind(&NFC::OnRequestCompleted, WrapPersistent(this), WrapPersistent(resolver)));
当mojo :: Remote由绑定到回调的对象拥有或由于其他原因保证该对象的寿命超过Mojo连接时,非垃圾收集的对象可以对响应和错误处理程序回调使用WTF :: Unretained(this) 。否则,应使用弱指针但是,这不是常见的模式,因为建议所有Blink代码都使用Oilpan。
在Oilpan管理的对象中实现Mojo接口时,仅应使用mojo :: Receiver或mojo :: ReceiverSet。 然后,该对象必须具有预终结器才能在将要清除该对象时关闭所有打开的管道,这是因为延迟清扫意味着在调用析构函数之前它可能是无效的。 这需要在对象标题和实现中进行设置。
// MyObject.h class MyObject : public GarbageCollected, public example::mojom::blink::Example { USING_PRE_FINALIZER(MyObject, Dispose); public: MyObject(); void Dispose(); // Implementation of example::mojom::blink::Example. private: mojo::Receiver<example::mojom::blink::Example> m_receiver{this}; }; // MyObject.cpp void MyObject::Dispose() { m_receiver.Close(); }
有关Blink的垃圾收集器的更多信息,请参见《 Blink GC API参考》。
由于依赖周期或对某些定义存在的正确位置的混淆,对在Blink和内容浏览器代码之间传递的消息使用类型映射有时可能很棘手。 这里提供了一些示例CL,但是如果您遇到麻烦,也可以随时与chromium-mojo@chromium.org联系以获取详细信息。
此CL为ui :: WindowOpenDisposition引入了Mojom定义和类型映射,作为以下IPC转换的前身。
后续CL使用该定义以及其他几个新的类型图(包括如上所述的本机类型图)将相对较大的ViewHostMsg_CreateWindow消息转换为Mojo。
如果本文档在某种程度上没有帮助,请在您友好的chromium-mojo@chromium.org邮件列表中发布一条消息。