PreviousNext

Pointer Examples

The following sample code demonstrates the basic properties of pointers. The first example demonstrates pointer arithmetic and how changes in the server address space can be reflected back to the client using full pointers. In the \&.idl file we declare a type that is an array of three integers, and a type that is a pointer to an integer. The operation takes the array as an [in] parameter and the pointer as an [in,out] parameter.

const unsigned32 ARRAY_SIZE = 3;

typedef unsigned32 num_array[ARRAY_SIZE];
typedef [ptr] unsigned32 *num_ptr;

void ptr_test1(
[in] handle_t handle,
[in, out] num_ptr *client_ptr,
[in ] num_array client_array,
[out] error_status_t *status
);

The server manager code to implement this points the client pointer to the beginning of the array and then increments it once:

void
ptr_test1(
handle_t h,
num_ptr *client_ptr,
num_array client_array,
error_status_t *status
)
{
*status = 0;
*client_ptr = client_array;
++(*client_ptr);
}

On return, the client's version of the pointer will point to memory that holds the second element of the array. It will not point to the array itself, however. The client code demonstrates this:

num_ptr client_ptr = NULL;
num_array client_array = {25, 50, 75};

ptr_test1(binding_h, &client_ptr, client_array, &status);

/*
* The test function pointed the client pointer to the
* second array element. On return, this points to memory
* that holds this value.
*/

printf("Client pointer points to %i0, *client_ptr);

/* However, if we now increment the pointer, it
* points to unintialized memory. This shows the
* limits of mirroring.
* *** WARNING: You may dump core here !! ***
*/

client_ptr++;
printf("Client pointer now points to %i0, *client_ptr);

What happens here is that the client stub allocates space for the new referent of client_ptr when the call returns. This space now holds the value in the second element of the array. The pointer no longer points to the original array but to this newly allocated space. You can see this clearly when the client attempts to increment the pointer. Instead of pointing to the third element of the array, it points to some undetermined place in memory, and the client may fail when it tries to dereference the pointer.

As an exercise, you could change the code to declare a pointer to the num_array defined type rather than to an integer. Then you could have the server manager point this to the input array and return it without incrementing the pointer. The returned pointer will now reference a copy of the original client array with all its elements. It will not, however reference the original array itself.

The second pointer example illustrates passing a linked list. The .idl declaration is as follows:

typedef struct link {
unsigned32 value;
[ptr] struct link *next;
} link_t;

void ptr_test2(
[in] handle_t handle,
[in, out, ref] link_t *head,
[out] error_status_t *status
);

The server manager code is as follows:

void
ptr_test2(
handle_t handle,
link_t *head,
error_status_t *status
)
{
link_t *element;

if (head)
{
element = head;
while (element->next)
element = element->next;

/* Add another element to the list... */
element->next = (link_t*) rpc_sm_allocate(sizeof(link_t), status);
element->next->value = element->value * 2;
element->next->next = NULL;
}
*status = error_status_ok;
};

The manager operation adds a new element to the end of the linked list. Note that the head parameter has [in, out] semantics here: we must pass in a pointer to a valid element. (The next example shows how to implement an [out] parameter that is allocated by the operation.)

In this and the following example, we use rpc_sm_allocate( ) to allocate data on the server side. This gives the semantics you probably want for a dynamically allocated referent for a pointer parameter: on return, the data is automatically deallocated on the server, and further manager operations that access this data do so via a pointer parameter passed by the client. Memory leaks on the server are thus avoided.

An application must be very cautious if it attempts to use pointer parameters in a way that contradicts such semantics: for example, by returning a pointer to static global storage on the server. In such a case, the server and client versions of such storage can easily become inconsistent. A context handle, which the client must not modify, is typically what you want in such a case.

The client code for the linked list test is as follows:

link_t first, *element;
int I;
first.value = 2;
first.next = NULL;

for (i = 0; i < 8; I++)
ptr_test2(binding_h, &first, &status);

element = &first;
while (element->next)
{
printf("%i, ", element->value);
element = element->next;
}
printf("%i0, element->value);

The client passes in the head element, and then calls the server several times to add more elements to the list. Finally, the client prints out the list.

The next pointer example illustrates how the stubs automatically allocate memory for an [out] parameter. The client application allocates a NULL pointer to the data structure of interest and passes the address of this pointer as the [out] parameter. The server manager allocates a structure, and on return the client stub allocates it too, automatically.

The .idl declaration is as follows:

typedef struct {
[ref] unsigned32 *value;
} number;

typedef [ptr] number *number_ptr;

void ptr_test3(
[in] handle_t handle,
[out, ref] number_ptr *client_ptr,
[out] error_status_t *status
);

The server manager operation is then as follows:

void
ptr_test3(
handle_t handle,
number_ptr *client_ptr,
error_status_t *status
)
{
number_ptr nptr;
unsigned32 *nval;
nptr = (number_ptr) rpc_sm_allocate(sizeof(number), status);
nval = (unsigned32 *) rpc_sm_allocate(sizeof(unsigned32), status);
*nval = 256;
nptr->value = nval;
*client_ptr = nptr;
*status = error_status_ok;
};

The client test code looks like this:

number_ptr client_ptr = NULL;

ptr_test3(binding_h, &client_ptr, &status);
printf("Value = %i0, (unsigned32)* (client_ptr->value));

Note the use of [ref] pointers here. The top-level [ref] pointer (the one passed as a parameter to the call) must point to valid storage when the call is made even though the pointer is not marshalled when the call is made. This follows the rules for [ref] pointers: they may not be NULL and may not change value during a call. The returned structure also contains a [ref] pointer, and the client stub does automatically allocate space for its referent when the call returns. This is an exception to the rule that an [out] [ref] pointer must point to valid storage when the call is made. In this case, the pointer is embedded in a structure which is created by the server. As long as the top-level pointer points to valid storage (to hold the returned structure), the client stub will allocate space for the referents of any newly-created [ref] pointers that it contains.

The final example illustrates node deletion. The .idl declaration is as follows:

[reflect_deletions] void ptr_test4(
[in] handle_t handle,
[in, out, ptr] unsigned32 *number,
[out] error_status_t *status);

The server code to implement this operation frees the memory pointed to by the input pointer and returns the pointer:

void
ptr_test4(
handle_t h,
unsigned32 *number,
error_status_t *status
)
{
*number = 32;
rpc_sm_free(number, status);
*status = error_status_ok;
};

The client code is as follows:

unsigned32 *num;

rpc_sm_enable_allocate(&status);
num = (unsigned32*) rpc_sm_allocate(sizeof(unsigned32), &status);
*num = 64;
ptr_test4(binding_h, num, &status);

There are so many ways to use (and misuse) IDL pointers that it would be impossible to give a complete set of examples. The topic on Arrays contains more pointer examples.