|
|
(40 intermediate revisions by 6 users not shown) |
Line 1: |
Line 1: |
| IPDL is a domain-specific language that allows programmers to define a message-passing "protocol" between "actors." These "actors" are thread contexts, and they can execute both in separate address spaces or the same address space (share-nothing threads). "Protocols" consist of two elements: declarations of messages that can be exchanged between two actors, and the definition of a state machine that describes when each message is allowed to be sent.
| | #REDIRECT [[MDC:IPDL/Getting Started]] |
| | |
| From an IPDL specification, several C++ headers are generated. These headers are meant to be opaque to the author of an IPDL specification; internally, they manage the tedious details of setting up and tearing down the underlying communication layer (sockets and pipes), constructing and sending messages, ensuring that all actors adhere to their specifications, and "correctly" handling errors.
| |
| | |
| This guide intends to introduce the basic concepts of IPDL through an increasingly complicated example. By the end of the guide, you should be able to write IPDL specs and the C++ implementations of message handlers. This guide does not attempt to cover how IPDL works under the covers.
| |
| | |
| == Running example: Browser plugins ==
| |
| | |
| We will use the example of a web browser launching plugins in separate processes and then controlling them. A ''plugin'' here is a dynamically loaded code module, such as libflash.so. Once a plugin module has been loaded, the browser can ask the plugin module to create ''instances''. A plugin instance is what lives inside an object frame on a particular web page; a single youtube video is a Flash plugin instance, for example. There can be any number of plugin ''instances'' per plugin ''module''. (There can be any number of youtube videos open in a web browser.)
| |
| | |
| Plugin instances are scriptable, which means that they can access JavaScript objects in the browser, and the browser can access JavaScript objects created by the plugin instance. Using the youtube Flash video example again, the youtube instance can create a JavaScript object that represents the video player, and the browser can access that video player object and ask it to "Pause," "Play," etc. There can be any number of script objects per plugin instance.
| |
| | |
| == Protocols and actors ==
| |
| | |
| Protocols define how two actors communicate. We'll introduce protocols and actors using the ''plugin modules'' described above. Recall that in this example, we have two processes: the browser process and the process in which the plugin module's code executes. There are two actors: the thread context in which the browser's plugin management code executes (in the browser process), and the thread on which the plugin's code executes (in the plugin process).
| |
| | |
| We have chosen to codify the concept of ''parent'' and ''child'' actors in IPDL; we use "parent actor" to refer to the "more trusted" actor, and "child actor" to refer to the "less trusted" actor. In the case of plugins, the browser actor is the ''parent'', and the plugin actor is the ''child''.
| |
| | |
| The parent and child actors communicate by sending messages to each other. The messages that can be exchanged are explicitly declared in IPDL. The following IPDL code defines a very basic interaction of browser and plugin actors
| |
| | |
| '''protocol''' Plugin {
| |
| '''out''' Init();
| |
| '''out''' Deinit();
| |
| };
| |
| | |
| This code defines a <code>Plugin</code> protocol. On the next two lines, the code declares two messages, <code>Init()</code> and <code>Deinit()</code>. We will describe the messages in more detail in the next section. To finish the introduction of protocols and actors, note the <code>'''out'''</code> keywords used on the 2nd and 3rd lines. This keyword defines the ''direction'' of the message --- that is, whether the message is sent from the parent actor to the child, from the child to the parent, or both ways.
| |
| | |
| '''Important''': protocols are written from the perspective of the parent actor. An <code>'''out'''</code> specifier means that the message is only sent ''out'' from the parent, ''to'' the child. This choice was completely arbitrary.
| |
| | |
| == Messages ==
| |
| | |
| It is very important to understand message semantics. It's tempting to think of protocol messages as C++ function calls, but that's not very useful. We'll delve into gory details of the above example to illustrate these semantics. To keep the example as concrete as possible, we first need to take a detour into how the above example is translated into something that C++ code can utilize.
| |
| | |
| The above specification will generate three headers: PluginProtocolParent.h, PluginProtocolChild.h, and PluginProtocol.h (we'll ignore the third completely; it's full of uninteresting implementation details). As you might guess, PluginProtocolParent.h defines a C++ class (PluginProtocolParent) that code on the parent side utilizes, the browser in this example. And similarly, code running in the plugin process will use the PluginProtocolChild class.
| |
| | |
| These Parent and Child classes are "abstract" in the Java sense. The PluginProcotolParent class will look something like the following in C++
| |
| | |
| class PluginProtocolParent {
| |
| public:
| |
| void SendInit()
| |
| {
| |
| // boilerplate generated code ...
| |
| }
| |
| void SendDeinit()
| |
| {
| |
| // boilerplate generated code ...
| |
| }
| |
| };
| |
| | |
| and the PluginProtocolChild will be something like:
| |
| | |
| class PluginProtocolChild {
| |
| protected:
| |
| virtual void RecvInit() = 0;
| |
| virtual void RecvDeinit() = 0;
| |
| };
| |
| | |
| These Parent and Child abstract classes take care of all the "protocol layer" concerns; sending messages, checking protocol safety (we'll discuss that later), and so forth. However, these abstract classes can do nothing but send and receive messages; they don't actually ''do'' anything, like draw to the screen or write to files. It's up to ''implementing'' C++ code to actually do interesting things with the messages.
| |
| | |
| So these abstract Parent and Child classes are meant to be subclassed by C++ ''implementors''. Below is a dirt simple example of how a browser implementor might utilize the PluginProtocolParent
| |
| | |
| class PluginParent : public PluginProtocolParent {
| |
| public:
| |
| PluginParent(string pluginDsoFile) {
| |
| // launch child plugin process
| |
| }
| |
| };
| |
| | |
| This is a boring class. It simply launches the plugin child process. Note that since PluginParent inherits from PluginProtocolParent, the browser code can invoke the <code>SendInit()</code> and <code>SendDeinit()</code> methods on PluginParent objects. We'll show an example of this below.
| |
| | |
| Here's how the PluginProtocolChild might be used by a C++ implementor in the plugin process:
| |
| | |
| class PluginChild : public PluginProtocolChild {
| |
| protected:
| |
| // implement the PluginProtocolChild "interface"
| |
| void RecvInit() {
| |
| printf("Init() message received\n");
| |
| // initialize the plugin module
| |
| }
| |
| void RecvDeinit() {
| |
| printf("Deinit() message received\n");
| |
| // deinitialize the plugin module
| |
| }
| |
| | |
| public:
| |
| PluginChild(string pluginDsoFile) {
| |
| // load the plugin DSO
| |
| }
| |
| };
| |
| | |
| The PluginChild is more interesting: it implements the "message handlers" RecvInit() and RecvDeinit() that will be automatically invoked by the protocol code when the plugin process receives the Init() and Deinit() messages, respectively.
| |
| | |
| Let's run through an example of how this dirt simple protocol and its implementation could be used. In the table below, the first column shows C++ statements executing in the browser process, and the second shows C++ statements executing in the plugin process.
| |
| | |
| {| border="1"
| |
| |+ Example execution of PluginParent and PluginChild
| |
| ! Browser process !! Plugin process
| |
| |-
| |
| | BrowserMain.cc: PluginParent* pp = new PluginParent("libflash.so");
| |
| |
| |
| |-
| |
| | (do browser stuff)
| |
| | ''plugin process is created''
| |
| |-
| |
| | ...
| |
| | PluginMain.cc: PluginChild* pc = new PluginChild("libflash.so");
| |
| |-
| |
| | BrowserMain.cc: pp->SendInit();
| |
| | (spin event loop)
| |
| |-
| |
| | PluginProtocolParent.h: (construct Init() message, send it)
| |
| | ...
| |
| |-
| |
| | (spin event loop)
| |
| | PluginProtocolChild.h: (unpack Init() message, call RecvInit();)
| |
| |-
| |
| | ...
| |
| | PluginChild.cc: RecvInit() { // do stuff }
| |
| |-
| |
| | ...
| |
| | ...
| |
| |}
| |
| | |
| '''TODO: is it clear what is going on here?'''
| |
| | |
| === Direction ===
| |
| | |
| As introduced above, message direction specifiers simply denote which actor sends the message and which receives it. The following artificial example should make it clear what the '''out''', '''in''', and '''inout''' direction specifiers mean to the C++ implementors.
| |
| | |
| // this protocol ...
| |
| protocol Direction {
| |
| out Foo();
| |
| in Bar();
| |
| inout Baz();
| |
| };
| |
| // ... generates these C++ abstract classes ...
| |
| | |
| // ----- DirectionProtocolParent.h -----
| |
| class DirectionProtocolParent {
| |
| protected:
| |
| void RecvBar();
| |
| void RecvBaz();
| |
|
| |
| public:
| |
| void SendFoo() { /* boilerplate */ }
| |
| void SendBaz() { /* boilerplate */ }
| |
| };
| |
| | |
| // ----- DirectionProtocolChild.h -----
| |
| class DirectionProtocolParent {
| |
| protected:
| |
| void RecvFoo();
| |
| void RecvBaz();
| |
|
| |
| public:
| |
| void SendBar() { /* boilerplate */ }
| |
| void SendBaz() { /* boilerplate */ }
| |
| };
| |
| | |
| === Parameters ===
| |
| | |
| === Semantics ===
| |
| | |
| === Return values ===
| |