As shown in the next figure, EWG takes an already preprocessed C header file and generates Eiffel classes and a C glue code library. The generation of the C glue code library might seem a little counter productive at first, since we really want to use Eiffel not C. But first of all this generated C code makes accessing the C library from Eiffel possible (at least for some constructs) and second of all this generated C code is wrapped by the generated Eiffel classes as well, eliminating the need to deal with it directly.
The above figure omits one detail, EWG doesn't generate the C glue code library, but rather the C source code files that will have to be compiled and linked (via geant for example) into a library.
An application that uses the EWG wrapper uses the generated Eiffel Classes and links against the generated glue code library as well as to the C library.
How exactly the application has to be linked is topic of the next chapter.
EWG creates a whole directory structure containing generated source code.
You will see that some of the generated files have the file name extension ge. Those classes contain macros and need to be preprocessed by gepp. As you will see later out of one such a .ge file 4 Eiffel class files will be created, one for each Eiffel compiler.
The following shows the directory structure generated by EWG
c -- C source code for c glue library
function -- function glue code
struct -- struct glue code
enum -- enum glue code
ewg_c_library -- basic c library copied from ${EWG}/library/runtime/c
callback -- callback glue code
eiffel -- eiffel classes
external -- low level wrapper classes
callback -- low level wrapper for callbacks
enum -- low level wrapper for enums
struct -- low level wrapper for structs
union -- low level wrapper for unions
function -- low level wrapper for functions
abstraction -- higher level wrapper classes
callback -- higher level wrapper for callbacks
enum -- higher level wrapper for enums
struct -- higher level wrapper for structs
union -- higher level wrapper for unions
function -- higher level wrapper for functions
As you can see there are low-level and higher level wrappers. For a library to become usable for application developers you have to provide a third layer that uses the lower layers, but provides a consistent object oriented interface.
This section describes what code gets generated for a C function declaration and how to use that code to call the declared function.
Let us look at the following function declaration (taken from the simple-example)
int func2 (int a, int b);
func2 is a function that takes two parameters of type int and returns a value of type int. EWG generates an Eiffel wrapper class for it in the cluster eiffel\external\function:
class SIMPLE_HEADER_FUNCTIONS_EXTERNAL feature func2_external (a: INTEGER; b: INTEGER) : INTEGER get_func2_pointer_external: POINTER end
The name of the class is SIMPLE_HEADER_FUNCTIONS_EXTERNAL. This is because the function declaration comes from the C header file simple_header.h. EWG will put all function declarations from the same header in the same class.
func2_external is the routine you will want to call if you want to execute the c function func2. get_func2_pointer_external gives you the address of the C function. You need the address of an function if you want to pass it as a callback for example.
For functions only low-level wrappers are generated.
This section describes what code gets generated for a C struct declaration and how to use that code to create, free, read from and write to a struct.
Let us look at the following struct declaration:
struct foo
{
int a;
};
For structs EWG generates low-level and high-level wrappers. The low level wrappers are purely procedural but still provide full access to C structs. The high level wrappers use the low level wrappers to provide object oriented access. Lets have a look at the generated low level wrapper for struct foo first:
class FOO_STRUCT_EXTERNAL
feature {NONE} -- Implementation
sizeof_external: INTEGER
get_a_external (item: POINTER): INTEGER
set_a_external (a_item: POINTER; a_value: INTEGER)
end
To use this wrapper directly (although this is not recommended) inherit from FOO_STRUCT_EXTERNAL. To create or free a struct one can use the malloc and free functions from the EWG runtime library. But to create (malloc) a struct I need to know how big it is, well thats what FOO_STRUCT_EXTERNAL.sizeof_external is for. The return value of malloc will be a POINTER. This is the reference to my newly created struct. To set member a to 3 one can write
set_a_external (pointer, 3)
To read member a, write
i := get_a_external (pointer)
As you can see the first parameter here is always a pointer to the struct.
As hinted in the previous section, the low level wrappers are not meant to be used directly. They give you a complete, but purely imperative API to deal with structs. The high level struct wrappers on the other hand provide an object oriented way to handle structs. The idea is that every struct you use from within Eiffel has a counterpart, the wrapper object. For every struct that EWG parses it generates the corresponding wrapper class. Every struct wrapper class inherits from EWG_STRUCT which is a deferred class with the following interface:
deferred class interface EWG_STRUCT feature(s) from EWG_STRUCT -- Access item: POINTER require exists: exists ensure item_not_default_pointer: Result /= default_pointer sizeof: INTEGER -- Size of the struct in bytes -- This needs to be redefined by the -- descendant. is_shared: BOOLEAN -- Is the contents of `item' referenced by other C or Eiffel code? -- If `is_shared' is `True' then when the current object will be -- collected by the garbage collector, the wrapped struct will -- also be freed. -- This is a good idea, only if you can be sure that when the -- Eiffel object gets collected, the C side does not have a reference -- to the wrapped struct anymore. require exists: exists exists: BOOLEAN -- Does `item' point to a valid C struct ? invariant managed_data_not_void: managed_data /= Void; managed_capacity_equals_sizeof: exists implies managed_data.capacity = sizeof; end of deferred EWG_STRUCT
As you can see the interface offers you direct access to the wrapped struct through the item query. sizeof tells you how many bytes the struct value occupies. With exits you can check if this wrapper is attached to a valid (non NULL) struct value. The boolean query is_shared tells you whether the memory occupied by the wrapped struct value will be freed when the wrapper object gets collected or not. You decide whether a struct wrapper is shared or not by using a corresponding creation routine (Obviously the ones with shared in its name create shared struct wrapper and the ones with unshared create ones who are not) Every struct wrapper has the same creation routines. You can see them in the interface of FOO_STRUCT, the high level wrapper class for struct foo:
class interface FOO_STRUCT creation make_new_unshared -- Create a new pointer wrapper to a new struct. -- Allocates as much new memory as the struct needs. -- 'unshared' means if the Current object -- gets collected by the garbage collector, -- the memory allocated for the struct will -- be freed as well. ensure exists: exists; is_not_shared: not is_shared make_new_shared -- Create a new pointer wrapper to a new struct. -- Allocates as much new memory as the struct needs. -- 'shared' means if the Current object -- gets collected by the garbage collector, -- the memory allocated for the struct will -- not be freed as well. ensure exists: exists; is_shared: is_shared make_unshared (a_item: POINTER) -- Create a new pointer wrapper to a given struct. -- 'unshared' means if the Current object -- gets collected by the garbage collector, -- the memory allocated for the struct will -- be freed as well. require a_item_not_default_pointer: a_item /= default_pointer ensure exists: exists; item_set: item = a_item; is_not_shared: not is_shared make_shared (a_item: POINTER) -- Create a new pointer wrapper to a given struct. -- 'shared' means if the Current object -- gets collected by the garbage collector, -- the memory allocated for the struct will -- not be freed as well. require a_item_not_default_pointer: a_item /= default_pointer ensure exists: exists; item_set: item = a_item; is_shared: is_shared feature(s) from FOO_STRUCT -- Member Access a: INTEGER set_a (a_value: INTEGER) invariant managed_data_not_void: managed_data /= Void; managed_capacity_equals_sizeof: exists implies managed_data.capacity = sizeof; end of FOO_STRUCT
Every high level struct wrapper class offers four creation routines. Two to create shared wrapper object, and two to create unshared unshared ones. Additionally you can create a wrapper that will attach to an already existing C struct value, or you create a struct wrapper that automatically creates its own struct value to wrap. To create a struct wrapper that automatically creates a new struct value use a creation routine with new in it's name. To create a struct wrapper that attaches to an existing C struct value, use a creation routine that hasn't.
For every member in the C struct, the high level struct wrapper class offers a getter and a setter (Actually some members only have a getter, such as const members). The following code snippet demonstrates how to use the high level struct wrapper class:
test_foo is local foo: FOO_STRUCT do create foo.make_new_unshared foo.set_a (10) print (foo.get_a.out) end
This section describes what code gets generated for a C enum declaration and how to use that code.
Let us look at the following enum declaration (taken from the simple-example)
typedef enum
{
red,
blue,
green
} colors;
colors is an alias for an anonymous enum. EWG generates an Eiffel wrapper class for it in the cluster eiffel\external\enum:
class interface COLORS_ENUM_EXTERNAL feature(s) from COLORS_ENUM_EXTERNAL red: INTEGER blue: INTEGER green: INTEGER end of COLORS_ENUM_EXTERNAL
The name of the class is COLORS_ENUM_EXTERNAL. Since Eiffel does not have the enum concept, enums are mapped as INTEGER constants in Eiffel. Simply inherit from COLORS_ENUM_EXTERNAL wherever you want to use them.
This section describes what code gets generated for C callbacks and how to use that code. Now first of all its not as trivial to define what a callback is. EWG defines callbacks as pointers to functions. Wherever it sees a pointer to a function, it generates a callback wrapper. Of course chances are some pointers to functions are not meant to be callbacks, but in this case we can simply ignore the generated code.
It is important to understand that there is an inherent problem when wrapping callbacks in Eiffel. For example the following callback:
typedef void (*void_callback) (void);
Does not convey any state when invoked (other than it has been invoked). This is the reason for the following limitation: One can only register one Eiffel callback receiver per callback type. If more receivers were allowed how would one decide on the invocation of a callback which receiver is meant? In practice this limitation is usually of no concern. Most C libraries have adopted a pseudo OO technique of supplying a user definable parameter as the first parameter on every callback. This parameter can be used to do further dispatching. Have a look a the GTK or OpenGL examples to see how to do this.
A solution to overcome this limitation using dynamic function generation is being worked on.
Let us look at the following declarations (taken from the callback-example)
typedef void (*sample_callback_type) (void* pdata, int a_event_type); // make `sample_callback_type' receive events. void register_callback (sample_callback_type a_callback, void* pdata); // make all registered callbacks receive an event void trigger_event (int a_event_type);
sample_callback_type is the actual callback. register_callback is used to register a given callback receiver, so that it gets called whenever one calls trigger_event. The two function declarations are wrapped as usual. For sample_callback_type EWG generates two classes. Both are located in eiffel\abstraction\callback. Actually EWG generates lower level classes two, even special C code, but these are omitted here. The two classes of interest are SAMPLE_CALLBACK_TYPE_DISPATCHER and SAMPLE_CALLBACK_TYPE_CALLBACK. The first one is the singleton that you have to create in order to establish the C-Eiffel bridge. Use SAMPLE_CALLBACK_TYPE_DISPATCHER.c_dispatcher whenever you need to provide the C side with a pointer to the callback receiver. The creation routine of SAMPLE_CALLBACK_TYPE_DISPATCHER expects an object of type SAMPLE_CALLBACK_TYPE_CALLBACK. Create your own callback receiver class let it inherit from SAMPLE_CALLBACK_TYPE_DISPATCHER and use an object of your new class for the creation routine argument. Here are the interfaces of the generated classes:
class interface SAMPLE_CALLBACK_TYPE_DISPATCHER
creation
make (a_callback: SAMPLE_CALLBACK_TYPE_CALLBACK)
require
a_callback_not_void: a_callback /= Void
feature(s) from SAMPLE_CALLBACK_TYPE_DISPATCHER
callback: SAMPLE_CALLBACK_TYPE_CALLBACK
c_dispatcher: POINTER
invariant
callback_not_void: callback /= Void;
end of SAMPLE_CALLBACK_TYPE_DISPATCHER
deferred class interface SAMPLE_CALLBACK_TYPE_CALLBACK
feature(s) {SAMPLE_CALLBACK_TYPE_DISPATCHER} from SAMPLE_CALLBACK_TYPE_CALLBACK
on_callback (a_pdata: POINTER; a_a_event_type: INTEGER)
end of deferred SAMPLE_CALLBACK_TYPE_CALLBACK
Assuming that you have created a class MY_CALLBACK_RECEIVER which inherits from SAMPLE_CALLBACK_TYPE_CALLBACK, the following code demonstrates the use of callbacks
feature make is do -- Establish the C<->Eiffel bridge. -- From now on, whenever `trigger_event_external' gets called -- `MY_CALLBACK_RECEIVER.on_callback' gets called too. register_callback_external (dispatcher.c_dispatcher) end dispatcher: SAMPLE_CALLBACK_TYPE_DISPATCHER is local my_callback_receiver: MY_CALLBACK_RECEIVER once create my_callback_receiver.make create Result.make (my_callback_receiver) end trigger is do trigger_event_external -- `MY_CALLBACK_RECEIVER.on_callback' has now been called end