An interface type in MSRPC2 is instantiated via a function of the form:
bool_t Aubergine_Interface_Create( Aubergine_Interface **intf, MSRPCInterfaceRef *ref, Aubergine_Binding_Constructor bind, Aubergine_Binding_Destructor destroy, PFC allocFunc, PFV freeFunc, caddr_t state, Aubergine_Op1 Op1, ... Aubergine_Opn Opn )The function returns true if it has succeeded. The parameters are:
For each interface type in MSRPC2 there is a destructor function:
void Aubergine_Interface_Destroy( Aubergine_Interface *intf )The sole parameter is a pointer to the interface to destroy. There is no way to destroy user-defined per-interface state (given as the state parameter when the interface was created, so this should be disposed of before this function is called.
When this function is called, all bindings are destroyed and then the interface is dismantled. Any invocations on the interface which are already in progress are allowed to complete, but no new bindings can be created. The function only returns when all the bindings have been disposed of cleanly.
When a client attempts to bind to the interface, the stub code will attempt to create a new binding. The user code will not be notified of the new binding being created unless it has registered a state constructor (a method called userBind) in the interface structure. If this exists, the thread handling the binding will call the constructor giving the interface, the new binding, and an address hint which will typically contain information about the client. The userBind routine should initialise any state it needs, set the userState field of the binding and return the boolean TRUE. If FALSE is returned, the binding is assumed to have been rejected and the client side bind call will return a failure code.
The constructor may also set the release method of the binding, which will be called after the result of an RPC has been dispatched, but before the server thread exits. This will typically be used to free any buffers the server routines has allocated for the RPC.
A Binding can be destroyed by calling its destroy method. This must be done by the thread handing the binding, i.e. it can only be called from within an invocation.
When a binding is destroyed, the user code implementing the interface is called via the userDestroy method, if present. The user code should clean up all per-client state to prevent memory leaks and return. The thread calling this method is guaranteed to be the thread handling the binding that is to be destroyed. After the call returns the binding will not exist.
When a client invokes an operation on a binding, the thread handling the binding calls the C function for the operation that was specified when the interface was created (i.e. relevant member of the ops structure in both the interface and server binding structures.
This function takes as parameters a pointer to the binding and the parameters and results listed in the interface specification. It should perform the service.
The function returns a bool_t. If this is TRUE, it implies that the operation was in some sense successful that the results can be dispatched. If FALSE is returned, it implies that the operation failed in some way and that an exception has been raised.
To raise an exception within the user routine implementing an operation, simply call the relevant member of the excs structure in the binding, and then return whatever boolean value that function returned.
MSRPC2 provides a call which allows bindings to tweak protocol-specific parameters such as timeouts. Each binding structure has a method which looks like:
(bool_t)(*control)( Aubergine_Binding *b, u_int32 op, caddr_t param )As bindings are inherently single-threaded, this call must only be invoked by the thread handling the binding. op is an operation code whose meaning depends on the type of protocol the interface is using and is determined by the runtime system. param is a generic parameter whose meaning is determined by the value of op.
To set the protocol parameters for a binding to values other than the runtime library defaults, register a user binding constructor with the interface which makes the appropriate calls to control (see userBind above). This is guaranteed to be invoked when the protocol session structure and binding have been created, and runs in the binding thread. It is also the only time that control can be invoked outside an actual operation.
A key aim of the local case version of MSRPC2 is that looks pretty much the same as the remote one. You create and destroy interfaces in the same way, and the same events occur when bindings are created and destroyed.
However, in the local case there are no threads waiting to service requests: invocations optimise right down to single function calls. This means that withdrawing a service is quite dangerous: no checks are made on existing bindings, indeed there is no information on which bindings exist. Withdrawing services should be done with caution. The same goes for destroying a binding from the server side. As the binding structure is shared between the client and server, problems will arise if both try and use the userState field for their own purposes.
There is currently a real problem involving local bindings and subtyping. Suppose that interface type Aubergine is a subtype of the Vegetable interface (Aubergines are technically fruit, but in practice they conform to vegetables). If a client does a
Vegetable_Binding_Createto an Aubergine interface, the client gets a binding of type Vegetable and the server gets one of type Aubergine. In the local case, the client gets an Aubergine_Binding cast to a Vegetable_Binding. This would be fine if the methods matched up. They don't. Beware. This should be fixed in a revision of MSRPC2. It's not a showstopper. Control functions are ignored by local bindings. Attempts to invoke the control function for a local interface will return FALSE.