Using the C Binding

This guide uses "xxx" as the service name. Substitute with the service name from your etch file.

Generated code

The C Binding compiler is called using

etch -b c -d . -w INTF,MAIN,IMPL,CLIENT,SERVER xxxNSDL.etch

It generates code for both sides (client and server). The main files you need to change manually to add your implementation are:

File Function
xxx_client_main.c main method of client, starts runtime, connects to server, calls of server methods, disconnect, destroy runtime
xxx_listener_main.c main method of server, starts runtime, waits for calls
xxx_server_impl.c/.h implementations of server directed methods
xxx_client_impl.c/.h implementations of client directed methods

How to implement a method on the server / on the client

The following steps describe implementation of server-directed methods. Since Etch is symmetric, the client directed functions are implemented exactly the
same way in the client-related generated files.

Assume you have an IDL containing

	@Direction(server)
	int simpleFunction(int x)
  • open xxx_server_impl.c
  • declare an implementation of simpleFunction,e.g.:
    etch_int32* simpleFunctionImpl(void* thisx, etch_int32* x);
    
    You can get the desired function signatures from xxx_interface.h. This file contains typedefs for all functions from the NSDL.
  • implement simpleFunction, e.g:
    etch_int32* simpleFunctionImpl(void* thisx, etch_int32* x){
        etch_int32* result;
        result = new_int32(x->value + 1);
        etch_object_destroy(x);
        return result;
    }
    
    The first parameter is an instance of the server_impl object. It also contains a reference to the calling client. You can use this for callbacks (if the call is @AsyncReceiver or @Oneway, otherwise you will end up in a deadlock). To do a callback, do the following:
        ((tester_server_impl*)thisx)->client->callbackFunctionName(((tester_server_impl*)thisx)->client, ...);
    
    Parameters of functions have to be destroyed by the function implementation using etch_object_destroy.
  • find function new_xxx_server_impl, this function is called when a client connects (the client is given as parameter)
  • add the function pointer of simpleFunctionImpl to the pserver object in this function. This "registers" the function as the implementation of the call.
    xxx_server_impl* new_xxx_server_impl(xxx_remote_client* client) {
        .... // some other code
        pserver->simpleFunction = pserver_base->simpleFunction = simpleFunctionImpl;
    }
    
  • Thats it!
If you want to return a user defined exception (which was defined in the NSDL), then simply return it in your function implementation instead of the usual return value.

How to call a method on the server / on the client

As above, the instructions below are symmetrical for client and server. Server directed calls are called like this:

  • open xxx_client_main.c
  • find main method
  • add your call after the starting of the runtime, e.g.
        ...//some code
	etch_int32* result;
        ...//some code

        //start the runtime and connect
        etch_status = tester_helper_remote_server_start_wait(remote, waitupms);
        ...//some code
 
        //call the remote function
	result = remote->simpleFunction(remote,new_int32(42));
	if(is_etch_exception(result)){
                wchar_t* extext = NULL;
	 	ex = (etch_exception*)result;
                if(etch_exception_get_message(ex)){
                      extext = etch_exception_get_message(ex)->v.valw;
                }
                ..//do something with exception message...
        }else{
                ..//do something with result
        }
  
        ..//do something with result
        etch_object_destroy(result);
}
Result Objects have to be freed by the caller using etch_object_destroy. Parameters of calls will be freed by the runtime automatically.
You can get an built-in exception from any remote Etch call (e.g. in case of timeouts). This is why the code above contains is_etch_exception(result).
Please note that there are special is_blabla_exception test methods generated for used defined exception, which you can use to further refine the type of exception "thrown".

Memory Management

The C Binding for Etch has the following memory management rules:

  • Implementation side: Parameters of functions have to be destroyed by the function implementation using etch_object_destroy.
  • Caller side: Result Objects have to be freed by the caller using etch_object_destroy. Parameters of calls will be freed by the runtime automatically.

All Etch Objects (including the primitive type wrappers) are freed using

etch_object_destroy(void* val)

Etch primitive data types

All primitive types have constructurs of the form

new_int32(42)

User defined struct types

Etch generates structs and constructors for user defined types.
Assume you have the following in your IDL:

	struct simpleStruct (
		int x,
		int y
	)

The compiler will generate (in xxx_interface.c/.h)

typedef struct tester_simpleStruct
{
    etch_object object;
    int x;
    int y; 
 } tester_simpleStruct;


tester_simpleStruct* new_tester_simpleStruct();

You can pass those structs to Etch function calls just like primitive type wrappers. You can also use etch_object_destroy on them to free the memory.

Array types

Arrays in the IDL are implemented using the struct

etch_nativearray

You have to supply the correct CLASSID for the content for the C Binding to work properly. Here are samples for primitive and complex arrays:

Primitive Arrays

	int values[4] = {0,1,2,3};
	const int numdims = 1, itemsize = sizeof(int), itemcount = sizeof(values)/itemsize;
	param = new_etch_nativearray_from (values, CLASSID_ARRAY_INT32,
            itemsize, numdims, itemcount, 0, 0);

"numdims" is the number of dimensions of the array (e.g. int[] vs. int[][]). The last three parameters of new_etch_nativearray are the sizes of the (up to) three dimensions of the array. More dimensions are not supported.

Complex typed Arrays

Assume the NSDL with "simpleStruct" from above. Create an array of simplestruct using:

    int arraysize = 5;
    tester_simpleStruct** values = etch_malloc(arraysize * sizeof(tester_simpleStruct*),0);
    ... //fill values
    etch_nativearray* natarray = new_etch_nativearray_from(
values, CLASSID_ARRAY_STRUCT, sizeof(tester_simpleStruct*), 1, arraysize, 0, 0);
    natarray->content_class_id = CLASSID_XXX_SIMPLESTRUCT;
    natarray->content_obj_type = ETCHTYPEB_USER;
    natarray->is_content_owned = TRUE;
    ... // do something with natarray
For complex typed arrays you have to supply the CLASSID of the content of the object. Those CLASSIDs are generated by the compiler for your user defined types.
The field is_content_owned tells Etch whether it should destroy the array content on array destruction, too. This will take effect when calling etch_object_destroy on the natarray or if it is destroyed by the runtime itself (e.g. when it is used as a remote call parameter, see above).