IPDL/Getting started: Difference between revisions

Changed document to reflect naming scheme change of XProtocol* to PX*
(Changed document to reflect naming scheme change of XProtocol* to PX*)
Line 33: Line 33:
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.
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.
The above specification will generate three headers: PPlugin.h, PPluginParent.h, and PPluginChild.h (We'll ignore the first completely; it's full of uninteresting implementation details.)  As you might guess, PPluginParent.h defines a C++ class called PPluginParent.  (The 'P' prefix is for 'protocol'.) This class is used in the parent actor - the browser, in this example.  Similarly, code running in the plugin actor will use the PPluginChild class.


These Parent and Child classes are abstract.  They will contain a "Send" function implementation for each message type in the protocol (which inheriting classes can call to initiate communication), and a pure virtual "Recv" function for the message receiver (which needs to be implemented by the inheriting class).  For example, the PluginProtocolParent class will look something like the following in C++
These Parent and Child classes are abstract.  They will contain a "Send" function implementation for each message type in the protocol (which derived classes can call to initiate communication), and a pure virtual "Recv" function for the message receiver.  "Recv" must be implemented by the derived class.  For example, the PPluginParent class will look something like the following in C++


   '''class''' PluginProtocolParent {
   '''class''' PPluginParent {
   '''public''':
   '''public''':
       '''void''' SendInit()
       '''void''' SendInit()
Line 49: Line 49:
   };
   };


and the PluginProtocolChild will be something like:
and the PPluginChild will be something like:


   '''class''' PluginProtocolChild {
   '''class''' PPluginChild {
   '''protected''':
   '''protected''':
       '''virtual''' '''void''' RecvInit() = 0;
       '''virtual''' '''void''' RecvInit() = 0;
Line 57: Line 57:
   };
   };


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.
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
So these abstract Parent and Child classes are meant to be used as base classes by C++ ''implementors''.  Below is a dirt-simple example of how a browser implementor might utilize the PluginProtocolParent


   '''class''' PluginParent : '''public''' PluginProtocolParent {
   '''class''' PluginParent : '''public''' PPluginParent {
   '''public''':
   '''public''':
       PluginParent(String pluginDsoFile) {
       PluginParent(String pluginDsoFile) {
Line 68: Line 68:
   };
   };


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.
This is a boring class.  It simply launches the plugin child process.  Note that since PluginParent inherits from PPluginParent, the browser code can invoke the <code>SendInit()</code> and <code>SendDeinit()</code> methods on PluginParent objects.  We show an example of this below.


Here's how the PluginProtocolChild might be used by a C++ implementor in the plugin process:
Here's how the PPluginChild might be used by a C++ implementor in the plugin process:


   '''class''' PluginChild : '''public''' PluginProtocolChild {
   '''class''' PluginChild : '''public''' PPluginChild {
   '''protected''':
   '''protected''':
       // implement the PluginProtocolChild "interface"
       // implement the PPluginChild "interface"
       '''void''' RecvInit() {
       '''void''' RecvInit() {
           printf("Init() message received\n");
           printf("Init() message received\n");
Line 94: Line 94:
Launching the subprocess and hooking these protocol actors into our IPC "transport layer" is beyond the scope of this document.  See [[IPDL/Five minute example]] for more details.
Launching the subprocess and hooking these protocol actors into our IPC "transport layer" is beyond the scope of this document.  See [[IPDL/Five minute example]] for more details.


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.
Here is 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"
{| border="1"
Line 112: Line 112:
  | (idle)
  | (idle)
  |-
  |-
  | PluginProtocolParent.h: (construct Init() message, send it)
  | PPluginParent.h: (construct Init() message, send it)
  | ...
  | ...
  |-
  |-
  | (idle)
  | (idle)
  | PluginProtocolChild.h: (unpack Init() message, call RecvInit();)
  | PPluginChild.h: (unpack Init() message, call RecvInit();)
  |-
  |-
  | ...
  | ...
Line 142: Line 142:
  // ... generates these C++ abstract classes ...
  // ... generates these C++ abstract classes ...


  // ----- DirectionProtocolParent.h -----
  // ----- PDirectionParent.h -----
  '''class''' DirectionProtocolParent {
  '''class''' PDirectionParent {
  '''protected''':
  '''protected''':
     '''virtual''' '''void''' RecvBar() = 0;
     '''virtual''' '''void''' RecvBar() = 0;
Line 153: Line 153:
  };
  };


  // ----- DirectionProtocolChild.h -----
  // ----- PDirectionChild.h -----
  '''class''' DirectionProtocolChild {
  '''class''' PDirectionChild {
  '''protected''':
  '''protected''':
     '''virtual''' '''void''' RecvFoo() = 0;
     '''virtual''' '''void''' RecvFoo() = 0;
Line 168: Line 168:
=== Parameters ===
=== Parameters ===


Message declarations allow any number of ''parameters'', which are data serialized by the sender and deserialized by the receiver.  The following snippet of IPDL and generated code shows how parameters can be used in message declarations.
Message declarations allow any number of ''parameters''.  Parameters specify data that are sent with the message.  Their values are serialized by the sender and deserialized by the receiver.  The following snippet of IPDL and generated code shows how parameters can be used in message declarations.


   // protocol Blah { ...
   // protocol Blah { ...
Line 174: Line 174:
     Foo(int parameter);
     Foo(int parameter);
    
    
   // class BlahProtocolParent { ...
   // class PBlahParent { ...
     '''void''' SendFoo('''const''' '''int'''& parameter) { // boilerplate }
     '''void''' SendFoo('''const''' '''int'''& parameter) { // boilerplate }
    
    
   // class BlahProtocolChild { ...
   // class PBlahChild { ...
     '''virtual''' '''void''' RecvFoo('''const''' '''int'''& paramter) = 0;
     '''virtual''' '''void''' RecvFoo('''const''' '''int'''& paramter) = 0;


IPDL has two categories of built-in types.  The first is C++ types that IPDL "imports" (described in more detail below) automatically, on behalf of the IPDL author.  These builtin types include the C/C++ integer types (bool, char, int, ..., int8_t, uint16_t, ...) and string types (<code>nsString</code>, <code>nsCString</code>).  IPDL does this because these types are common, and library code already exists to serialize and deserialize parameters of those types.  These builtin types are in flux and will certainly change.  See <code>ipc/ipdl/ipdl/builtins.py</code> for the most up-to-date reference.
IPDL has two categories of built-in types.


The second category of built-ins are types that are native to IPDL.  These are IPDL unions and arrays and are described below.  These native IPDL types translate into C++ types.
The first category of types is C++ types that IPDL "imports" (described in more detail below) automatically.  These builtin types include the C++ integer types (bool, char, int, ..., int8_t, uint16_t, ...) and string types (<code>nsString</code>, <code>nsCString</code>).  IPDL imports these automatically because they are common.  Library code already exists to serialize and deserialize parameters of those types.  See <code>ipc/ipdl/ipdl/builtins.py</code> for the most up-to-date list of automatically imported types.


The builtin types are insufficient for all protocols.  When you need to send data of type other than one built into IPDL, you can add a <code>'''using'''</code> declaration of the type in an IPDL specification, and in C++ define your own data serializer and deserializer.  The details of this are beyond the scope of this document; see <code>dom/plugins/PPluginInstance.ipdl</code> and <code>dom/plugins/PluginMessageUtils.h</code> for examples of how this is done.
The second category of built-ins types are those that are native to IPDL.  These are IPDL unions and arrays.  They are described below.  These native IPDL types translate into C++ types.
 
==== <code>using</code> ====
 
The builtin types are not always sufficient for implementing a protocol.  When you need to send data of a type other than one built into IPDL, you can add a <code>'''using'''</code> declaration in an IPDL specification.  In that case, you must define your own data serializer and deserializer in the C++ code.  The details of this are beyond the scope of this document; see <code>dom/plugins/PPluginInstance.ipdl</code> and <code>dom/plugins/PluginMessageUtils.h</code> for examples of how this is done.


=== Semantics ===
=== Semantics ===
Line 190: Line 194:
Note that in all the IPDL message declarations above, the generated C++ methods corresponding to those messages always had the return type <code>void</code>.  What if we wanted to ''return'' values from message handlers, in addition to sending ''parameters'' in the messages?  If we thought of IPDL messages as C++ functions, this would be very natural.  However, "returning" values from message handlers is much different from returning values from function calls.  Please don't get them mixed up!
Note that in all the IPDL message declarations above, the generated C++ methods corresponding to those messages always had the return type <code>void</code>.  What if we wanted to ''return'' values from message handlers, in addition to sending ''parameters'' in the messages?  If we thought of IPDL messages as C++ functions, this would be very natural.  However, "returning" values from message handlers is much different from returning values from function calls.  Please don't get them mixed up!


In the Plugin protocol example above, we saw the parent actor send the Init() message to the child actor.  Under the covers, "sending the Init() message" encompasses code on the parent side creating some Init()-like message object in C++, serializing that message object into a sequence of bytes, and then sending those bytes over a socket to the child actor.  (All this is hidden from the C++ implementor, PluginParent above.  You don't have to worry about this except to better understand message semantics.)  What happens in the parent-side code once it has written those bytes to the socket's file descriptor?  It is this behavior that we broadly call ''message semantics''.
In the Plugin protocol example above, we saw the parent actor send the Init() message to the child actor.  Under the covers, "sending the Init() message" encompasses code on the parent side creating some Init()-like message object in C++, serializing that message object into a sequence of bytes, and then sending those bytes to the child actor.  (All this is hidden from the C++ implementor, PluginParent, above.  You don't have to worry about this except to better understand message semantics.)  What happens in the parent-side code once it has sent those bytes?  It is this behavior that we broadly call ''message semantics''.


In IPDL, the parent-side code has three options for what happens after those serialized bytes are written to the socket:
In IPDL, the parent-side code has three options for what happens after those serialized bytes are sent:
# Continue executing.  We call this '''asynchronous''' semantics; the parent is not blocked.
# Continue executing.  We call this '''asynchronous''' semantics; the parent is not blocked.
# Wait until the child acknowledges that it received the message.  We call this '''synchronous''' semantics, as the parent blocks until the child receives the message and sends back a reply.
# Wait until the child acknowledges that it received the message.  We call this '''synchronous''' semantics, as the parent blocks until the child receives the message and sends back a reply.
Line 207: Line 211:
We added two new keywords to the Plugin protocol, '''sync''' and '''returns'''.  '''sync''' marks a message as being sent synchronously; note that the Deinit() message has no specifier.  The default semantics is asynchronous.  The '''returns''' keyword marks the beginning of the list of values that are returned in the reply to the message.  (It is a type error to add a '''returns''' block to an asynchronous message.)
We added two new keywords to the Plugin protocol, '''sync''' and '''returns'''.  '''sync''' marks a message as being sent synchronously; note that the Deinit() message has no specifier.  The default semantics is asynchronous.  The '''returns''' keyword marks the beginning of the list of values that are returned in the reply to the message.  (It is a type error to add a '''returns''' block to an asynchronous message.)


'''Detour''': the above protocol will fail the IPDL type checker.  Why?  IPDL protocols also have "semantics specifiers", just like messages.  A protocol must be declared to have semantics at least as "strong" as its strongest message semantics.  Synchronous semantics is called "stronger than" asynchronous.  Like message declarations, the default protocol semantics is asynchronous; however, since the Plugin protocol declares a synchronous message, this type rule is violated.  The fixed up Plugin protocol is shown below.
==== Message Semantics Strength ====
 
The above protocol will fail the IPDL type checker.  Why?  IPDL protocols also have "semantics specifiers", just like messages.  A protocol must be declared to have semantics at least as "strong" as its strongest message semantics.  Synchronous semantics is called "stronger than" asynchronous.  Like message declarations, the default protocol semantics is asynchronous; however, since the Plugin protocol declares a synchronous message, this type rule is violated.  The fixed up Plugin protocol is shown below.


  '''sync''' '''protocol''' Plugin {
  '''sync''' '''protocol''' Plugin {
Line 215: Line 221:
  };
  };


This new '''sync''' message with '''returns''' values changes the generated PluginProtocolParent and PluginProtocolChild headers as follows
This new '''sync''' message with '''returns''' values changes the generated PPluginParent and PPluginChild headers, as follows.


  // class PluginProtocolParent { ...
  // class PPluginParent { ...
     '''int''' SendInit() { /* boilerplate */ }
     '''int''' SendInit() { /* boilerplate */ }
   
   
  // class PluginProtocolChild { ...
  // class PPluginChild { ...
     '''virtual''' '''int''' RecvInit() = 0;
     '''virtual''' '''int''' RecvInit() = 0;


To the parent implementor, the new SendInit() method signature means that it receives an int return code back from the child.  To the child implementor, the new RecvInit() method signature means that it must return an int back from the handler, signifying whether the plugin was initialized successfully.
To the parent implementor, the new SendInit() method signature means that it receives an int return code back from the child.  To the child implementor, the new RecvInit() method signature means that it must return an int back from the handler, signifying whether the plugin was initialized successfully.


'''Important''': To reiterate, after the parent code calls SendInit(), it will block the parent actor's thread until the response to this message is received from the child actor.  On the child side, the int returned by child implementor from RecvInit() is packed into the response to Init(), then this response is sent back to the parent.  Once the parent reads the bytes of response message from its socket, it deserializes the int return value and unblocks the parent actor's thread, returning that deserialized int to the caller of SendInit().  It is very important to grok this sequence of events.
'''Important''': To reiterate, after the parent actor's thread calls SendInit(), it will block until the response to this message is received from the child actor.  On the child side, the int returned by child implementor from RecvInit() is packed into the response to Init(), then this response is sent back to the parent.  Once the parent reads the bytes of the response, it deserializes the int return value and unblocks the parent actor's thread, returning that deserialized int to the caller of SendInit().  It is very important to understand this sequence of events.


'''Implementation detail''': IPDL supports multiple '''returns''' values, such as in the following example message declaration
'''Implementation detail''': IPDL supports multiple '''returns''' values, such as in the following example message declaration
Line 233: Line 239:
     '''sync''' Foo(int param1, char param2) '''returns''' (long ret1, int64_t ret2);
     '''sync''' Foo(int param1, char param2) '''returns''' (long ret1, int64_t ret2);


C++ does not have syntax that allows returning multiple values from functions/methods.  Additionally, IPDL needs to allow implementor code to signal error conditions, and IPDL itself needs to notify implementors of errors.  To those ends, IPDL generates interface methods with nsresult return types and "outparams" for the IPDL '''returns''' values.  So in reality, the C++ interface generated for the Blah example above would be
C++ does not have syntax that allows returning multiple values from functions/methods.  Additionally, IPDL needs to allow implementor code to signal error conditions, and IPDL itself needs to notify implementors of errors.  To those ends, IPDL generates interface methods with nsresult return types and "outparams" for the IPDL '''returns''' values.  So in reality, the C++ code generated for the Blah example above would be


  // class BlahProtocolParent { ...
  // class PBlahParent { ...
     nsresult SendFoo('''const''' '''int'''& param1, '''const''' '''int'''& param2, '''long'''* ret1, int64_t* ret2) { /* boilerplate */ }
     nsresult SendFoo('''const''' '''int'''& param1, '''const''' '''int'''& param2, '''long'''* ret1, int64_t* ret2) { /* boilerplate */ }
   
   
  // class BlahProtocolChild { ...
  // class PBlahChild { ...
     '''virtual''' nsresult RecvFoo('''const''' '''int'''& param1, '''const''' '''int'''& param2, '''long'''* ret1, int64_t* ret2) = 0;
     '''virtual''' nsresult RecvFoo('''const''' '''int'''& param1, '''const''' '''int'''& param2, '''long'''* ret1, int64_t* ret2) = 0;


And the actual interface generated for the Plugin protocol is
And the actual interface generated for the Plugin protocol is


  // class PluginProtocolParent { ...
  // class PPluginParent { ...
     nsresult SendInit('''int'''* rv) { /* boilerplate */ }
     nsresult SendInit('''int'''* rv) { /* boilerplate */ }
   
   
  // class PluginProtocolChild { ...
  // class PPluginChild { ...
     '''virtual''' nsresult RecvInit('''int'''* rv) = 0;
     '''virtual''' nsresult RecvInit('''int'''* rv) = 0;


31

edits