IPDL/Getting started: Difference between revisions

Line 176: Line 176:


=== Semantics ===
=== Semantics ===
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 IPDL, the parent-side code has three options for what happens after those serialized bytes are written to the socket:
# 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.
# The third option is more complicated and will be introduced below, after another example.
In the Plugin protocol example above, we might want the Init() message to be synchronous.  It may not make sense for the browser to continue executing until it knows whether the plugin was successfully initialized.  (We will discuss which semantics, asynchronous or synchronous, is preferred in a later section.)  So for this particular case, let's extend the Plugin protocol's Init() message to be synchronous and return an error code: 0 if the plugin was initialized successfully, non-zero if not.  Here's a first attempt at that
  '''protocol''' Plugin {
    '''sync''' '''out''' Init() '''returns''' (int rv);
    '''out''' Deinit();
  };
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.
'''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.
  '''sync''' '''protocol''' Plugin {
    '''sync''' '''out''' Init() '''returns''' (int rv);
    '''out''' Deinit();
  };
This new '''sync''' message with '''returns''' values changes the PluginProtocolParent and PluginProtocolChild headers as follows
  // class PluginProtocolParent { ...
      int SendInit() { /* boilerplate */ }
 
  // class PluginProtocolChild { ...
      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.
'''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.
'''Implementation detail''': IPDL supports multiple '''returns''' values, such as in the following example message declaration
  // protocol Blah {
      out Foo(int param1, char param2) returns (long ret1, int64_t ret2);
C++ does not have syntax that allows returning multiple values from functions/methods.  Therefore, IPDL must use "outparams" to support multiple return values.  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.  So in reality, the C++ interface generated for the Blah example above would be
  // class BlahProtocolParent { ...
      nsresult SendFoo(const int& param1, const int& param2, long* ret1, int64_t* ret2) { /* boilerplate */ }
  // class BlahProtocolChild { ...
      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
  // class PluginProtocolParent { ...
      nsresult SendInit(int* rv) { /* boilerplate */ }
 
  // class PluginProtocolChild { ...
      virtual nsresult RecvInit(int* rv) = 0;
RPC semantics.


=== Return values ===
=== Return values ===
Confirmed users
699

edits