Native Client‎ > ‎2: How Tos‎ > ‎

Using Simple RPC to Implement Native Client Services

Note: SRPC in the browser is not supported any more. Use the asynchronous PostMessage instead.

Native Client currently provides a very basic Remote Procedure Call (RPC) mechanism for implementing services that communicate using only C++ basic types (no structs, unions, etc.). It is called Simple RPC or SRPC.

Adding Simple RPC to Your Module

When the Native Client service runtime (sel_ldr) starts a module, it initiates a thread. To implement a blocking RPC service, a module needs
  • An RPC message processing loop (receive request, dispatch it, send response)
  • Method dispatching
  • One or more methods to dispatch to

The simple RPC library provides an easy method to get a simple single-threaded message processing and dispatch loop.

Implementing RPC Methods

The methods that implement the service are written by the user, but differ very little from ordinary C/C++ functions. An example method that uses simple RPC is given below.

   #include <nacl/nacl_srpc.h>

   NaClSrpcError MyMethod(NaClSrpcChannel *channel
                          NaClSrpcArg     **in_args,
                          NaClSrpcArg     **out_args)
   {
     int v0 = in_args[0]->u.ival;
     int v1 = in_args[1]->u.ival;
     int v2;
     int num = out_args[0]->u.iaval.count;
     int *dest = out_args[0]->u.iaval.iarr;
     int i;

     if (num < 2) return NACL_SRPC_RESULT_APP_ERROR;
     *dest++ = v0;
     *dest++ = v1;
     for (i = 2; i < num; ++i) {
       v2 = v0 + v1;
       *dest++ = v2;
       v0 = v1; v1 = v2;
     }
     return NACL_SRPC_RESULT_OK;
   }

   // Export MyMethod as fib, which takes two scalar integers and returns an array of integers.
   NACL_SRPC_METHOD("fib:ii:I", MyMethod);

Because the method uses simple RPC it needs to include nacl/nacl_srpc.h to define the types and functions needed. Every method is declared as in the example, taking two NaClSrpcArg** parameters and returning a NaClSrpcError. The first parameter is the input vector pointer; the second is the output vector pointer. Both vector pointers point to a NULL-terminated vector of pointers to NaClSrpcArg. That is, if you have two input arguments, as in the example, the input vector pointer points to a three-element vector, with the last element containing NULL.

Method Export Declarations and Types

Exporting an RPC method requires using the NACL_SRPC_METHOD macro. This macro declares that the second argument (MyMethod in the example) is exported, and will be reported in service discovery. The first argument contains three fields separated by colons. The first is the name that will be reported by service discovery, and is useful for name lookup when invoking RPC methods. The second field is the input type string, and describes the length and types of the input arguments that will be accepted by this method. Similarly, the third field is the output type string. The elements of these type strings, along with the C/C++ code to access those values is described in the following table:

C/C++ type type char field name length name notes
bool b u.bval N/A  
char c u.cval N/A  
char[] C u.caval.carr u.caval.count  
double d u.dval N/A  
double[] D u.daval.darr u.daval.count  
handle h u.hval N/A No corresponding C/C++ type
int i u.ival N/A  
int[] I u.iaval.iarr u.iaval.count  
char* s u.sval N/A when used as a return, client must free

Handles are further described below.

Adding the Library to Your Module

Adding simple RPC library support is as easy as adding -lsrpc to your link command.

Using Built-in Method Processing

Adding the library to your module provides you with two methods for simple testing of your modules. The first is by using sel_ldr directly, by invoking

   sel_ldr -f mymodule.nexe

you would see:

   RPC Name                 Input args Output args
     0 service_discovery               C
     1 fib                  ii         I
   0>

Typing "service" will return a string containing this information. The rpc command is the most interesting, with the command format being

   rpc name in_args * out_args

where name is the name of the method you want to invoke, and in_args and out_args are space-separated lists of

   type_char(value)                # for scalars
   type_char(length, v0, v1, ...)  # for array input types
   type_char(length)               # for array output types

So, for example, typing

  rpc fib i(1) i(1) * I(5)

yields

  using rpc fib no 1
  fib RESULTS:   I(5,1,1,2,3,5)

Using sel_universal and the plug_test.html Example

The same processing loop is used when invoking sel_universal. That is:

   sel_universal -f mymodule.nexe

would be the same.

There is also a web-based version in tests/plug_univ/plug_test.html in the source tree. It is currently not capable of handling array types or string arguments.

Using SRPC from the Browser

Note: SRPC in the browser is not supported any more. Use the asynchronous PostMessage instead.

SRPC may be used to communicate from the browser to Native Client module and to coordinate communication amongst several Native Client modules. To load a Native Client module one needs to use an embed tag such as

  <embed type="application/x-nacl-srpc" id="nacl" src="srpc_nrd_server.nexe">

The id "nacl" of course could be different, but it is necessary to have a handle to access a scriptable plugin instance. A plugin instance provides two useful basic functions: loading Native Client modules and creating shared memory regions. Loading a Native Client module is illustrated by the following example:

var postLoadInit = function() {
  if (1 == plugin.__moduleReady) {
    clearTimeout(startupTimeout);
    // do your scripting here or afterwards.
  } else {
    if (plugin.__moduleReady == undefined) {
      alert('The Native Client plugin was unable to load');
      return;
    }
    startupTimeout = setTimeout(postLoadInit, 100);
  }
}

var Init = function() {
  var plugin = document.getElementById('nacl');
  // Note that at this point the module may not have been loaded yet.
  // Scripting must wait for the __moduleReady property above to become 1 before starting.
  postLoadInit();
}

Once a module is loaded, any of the methods exported may be invoked using its method name and the proper set of argument types. For example,

var arr = service.fib(1, 1, 5);

invokes the the fib method as above, again requiring the 5 to be passed to specify the size of the output array. After invocation, arr = [1, 1, 2, 3, 5].

To see a working demo of loading and interacting with Native Client modules via SRPC, please take a look at any of the tests in tests/srpc/ or tests/srpc_hw.

Creating shared memory may also be done by invoking method on a plugin instance.

  var region_size = 65536;
  var plugin = document.getElementById('nacl');
  var shared_memory = plugin.__shmFactory(region_size);

The __shmFactory method takes one argument, the size of the shared memory region. The size on any platform must be a multiple of 64k due to (Windows) requirements and Native Client's platform neutrality aim. Once allocated, a shared memory region may be directly manipulated in JavaScript by two methods.

shared_memory.write(offset, length, string);

writes length characters of string at the specified offset into the region. Similarly,

var str = shared_memory.read(offset, length);

read reads a string of length length starting at offset within the region. If the length and offset would result in accesses outside the range of the shared memory object, the read or write fails.

Shared memory regions are passable to and from Native Client modules as handles.

Native Client Handles

Handles provide access to NaCl resource descriptors. Inside of Native Client modules handles are integers, just like Linux file descriptors, and they may be used as described in the intermodule communication module discussion.

Most handle types are treated opaquely in JavaScript. Shared memory handles were described above. The only other handle type that has a JavaScript-related action is a socket address handle. Native Client modules typically communicate by creating bound socket / socket address pairs and relying on JavaScript to pass the socket address handle from one module to another. The JavaScript may also open connections to socket address objects returned from Native Client modules. For example, the fragment

var connected_socket = socket_address.connect();

results in the creation of a connected socket object "connected_socket" which will implement an SRPC interface.

To see a full illustration of creating and passing various sorts of handles, please take a look at tests/srpc/srpc_nrd_xfer.html and tests/srpc/srpc_nrd_*.c in the source tree.

In trusted code they are pointer to NaClDescs. Further description required here.

Comments