PreviousNext

Pointer Types

For reasons of efficiency, IDL distinguishes between reference [ref], full [ptr], and unique [unique] pointers. As we saw above, even though pointers are used by applications to pass data by reference, the lack of shared address space means that the stubs have to pass the data by value and provide the receiver with a reference to the passed data.

In the simplest case, a pointer always points to the same memory: that is, its value does not change. In such a case the stubs always marshall the passed value from and to the same memory location on the sender and receiver respectively. This style of marshalling is provided by [ref] pointers.

When the value of a pointer changes during a call, the stubs have a more complex task. Suppose, for example, an [in, out] pointer is NULL before an RPC and is set by the server application to point to some data structure allocated by the server. As in the [ref] pointer case, the server stub needs to marshall the (new) referent and the client to unmarshall it, but the client stub also needs to do two more things: it needs to allocate space for the unmarshalled referent, and it needs to point the previously NULL pointer to it. Similarly, for a pointer that initially points to one memory location and is changed during an RPC to point to another, the client stub needs to allocate new memory to hold the unmarshalled value of the new referent and to change the pointer value accordingly. Not all of the extra work is confined to the client stub either. Obviously, the client stub needs to find out that the value of the pointer has changed, so the server has to marshall, and the RPC protocol to transmit, extra data to indicate this. This style of marshalling is provided by full ([ptr]) pointers, and it obviously requires more overhead than reference pointer marshalling.

Unique pointers provide for an intermediate case: a pointer that always points either to a single memory location or is NULL. Such a pointer may change from NULL to a nonnull value or from a nonnull value to NULL, but never has more than one nonnull value. Such a pointer is marshalled more efficiently than a full pointer, but not as efficiently as a reference pointer.

Applications should consider the [ref] and [unique] pointer types as optimizations. A full [ptr] pointer can always be used. The [ref] and [unique] pointer types may be used whenever the application is guaranteed to meet the restrictive conditions under which these types work.

As a guide to using the pointer types, there are a few general rules and a number of special cases, having mainly to do with embedded pointers and data of variable size. The rules are as follows:

· In passing parameters, you need to distinguish carefully between top-level and lower-level pointers. A top-level pointer is a pointer passed as an argument to a call. A lower-level pointer is one contained in the referent of a top-level pointer. The directional semantics [out] and [in, out] both require parameters to be passed by reference and hence always require a top-level pointer.

The model is, essentially, that the client provides a container into which the returned value is written. In the [out] parameter case, the contents of the container are assumed to be unimportant on input and are not marshaled by the client stub. In the [in, out] case, the contents are assumed to be meaningful and are passed to the server.

The top-level pointer is thus the address of the parameter container, and obviously, this value should not change during the course of the call. If it did, the return value would be written to some undetermined place in the client address space. Hence, the top level of [out] and [in,out] parameters have reference pointer semantics. The IDL compiler enforces this for [out] parameters by permitting only the [ref] attribute. It does not force this for [in, out] parameters, but the behavior is exactly the same. Remember, the actual parameters of an RPC call are always passed by value: hence a call cannot change the value of a top-level pointer. It can only change the value of something passed by reference.

· To pass an [in] parameter by reference, you can pass its address as a pointer of either style. The server stub will allocate and deallocate the required memory for the pointer referent. Since an [in] pointer has no reason to change its value, it is at least slightly more efficient to use a reference pointer in this case.

· Since [out] semantics do not consider the contents of such storage to be meaningful, an [out] parameter is not marshalled on the call. The server stub will allocate memory to hold the referent as long as the size of the referent is known at compile time. The stub obviously cannot allocate memory for referents whose size is determined arbitrarily by the server application. For such parameters (such as linked lists) the server application must allocate space.

One tricky case to consider is a linked list. The server stub allocates space for the head element, since it knows the size of such an element. The server manager then allocates space for the remaining elements and marshalls them back to the client. The client stub will allocate all necessary space for the server-created receive parameters.
A server-created structure may contain reference pointers which the server may then set to point to objects it also allocates. All of this will be mirrored by the client stub. Note that this does not violate the rules for reference pointers, since the contained pointers do not change value during the call; they are created by the server application and passed back to the client exactly the same way that top-level reference pointers are created by clients and passed to servers.

When an application wishes to have the callee allocate space for an [out] parameter, it needs to use two levels of indirection: a reference pointer to a full pointer to the data structure to be allocated. The client allocates the full pointer, setting it to NULL, and passes its address to the call. The server application then allocates the data structure and sets the full pointer to point to it. The client stub will then allocate space for the data structure on the return.

· An [in,out] reference pointer behaves exactly like the [out] reference pointer case, except that the server stub may be able to allocate space for a referent even if its size is not known at compile time. This will be the case when the client application creates an instance of a variable sized referent, such as a linked list. In such a case, the server stub will allocate sufficient space for the referent supplied by the client.

· You must take care when a server deallocates a [ptr] pointer referent. For an [in,out] parameter, the client-side stub does not deallocate the client-side referent, but the application should treat the referent as undefined, as if, in effect, the deallocated pointer referent had been unmarshalled by the client stub. By default, in the case of an [in] parameter, the value of the pointer referent remains unchanged on the client side. However, this default behavior can be modified by applying the [reflect_deletions] attribute to the operation. In this case, the client-side stub will deallocate the pointer referent. The client and server must use the rpc_sm_* routines to allocate and free memory for this reflection of deletions to work.

· For an [in,out] parameter which is a [ptr] pointer, if the server sets the parameter value to NULL, the client will no longer be able to dereference the pointer on return. If the client has no other means to reference the original pointed-to node, the node is said to be orphaned: the client will be unable to free it.