Difference between revisions of "Wrapping C libraries"
Line 61: | Line 61: | ||
We usually place each wrapper library in its own directory following this organization: |
We usually place each wrapper library in its own directory following this organization: |
||
− | |||
− | <pre> |
||
− | zlib/ |
||
− | |-- examples -- Examples on how to use the Zlib wrappers |
||
− | |-- library -- Contains the high-level classes, those used directly in Eiffel programs |
||
− | | |-- externals -- Contains the low-level classes to interface with C (externals, macros, enumeration and so on) |
||
− | | | `- generated -- Contains the low-level classes automatically generated with a tool such as wrappers_generator |
||
− | | `-- loadpath.se -- Lists the clusters of the library itself, i.e. which directories contains its classes |
||
− | |-- loadpath.se -- Lists the clusters used by the library, i.e. its dependencies |
||
− | `-- tests -- Contains the tests to be used with eiffeltest tool |
||
− | </pre> |
||
* zlib/ |
* zlib/ |
Revision as of 16:56, 24 March 2016
Developer's guidelines
or how to write GNU-Eiffel wrappers for a C library
"longum iter est per praecepta, breve et efficax per exempla"
"The path of precept is long, that of example short and effectual."
Seneca the Elder
So instead of giving rules I guide you on my path of wrapping the ZLib library, one of the most widespread compression library.
I deliberately chose a "low-level" library with very precise and narrow aims which lets to show basic wrapping techniques: a wider, properly designed library has infrastructures and details that requires to know some tricks from the beginning of the work, otherwise the resulting wrapper should be heavily re-worked on many times.
I usually work with Debian and Ubuntu GNU/Linux, so all the system commands and the package names listed in this tutorial/how-to should apply without changes on Debian and Debian-derived machines. Other distributions, like Fedora, RedHat, Suse, Mandriva shall have similarly named packages. People using BSD OSes could have those libraries available in their package manager. As a last resort you can always download the sources, compile and install them manually. I hope you know how to do it, otherwise the rest of this document would be quite useless for you.
Prerequites
You should be proficient with:
- the C language,
- the Eiffel language,
- the C library you want to wrap; you shall known how to use it in a C program; take some time to read the documentation of the library and to study the provided example in order to make yourself familiar with the style required to use the library. You will need them to provide extensive, readable documentation of your classes.
Software prerequisites
Make sure you have access on your machine to:
- Liberty Eiffel, you may download the Debian packages from
- The C headers of the library to wrap; you shall be able to compile the examples of the library you're wrapping; so I issued
sudo apt-get install zlib1g-dev
- Documentation of the library. Even if it is not absolutely necessary to make working wrappers they are indeed necessary to provide properly documented, therefore usable classes; (GNU) Eiffel has standard rules on how to document code and we shall abide to that; adapting the original C documentation while we wraps various features is a good style to handle documentation. Please do not leave any part of your code undocumented. Spend some time reading the documentation. It is always worth the effort.
I would like to repeat the concept: even if source-code in general is intented to be read by a compiler to produce binary modules its readability and understandability for people is of the utmost importance. Code is not meant to be written-once and never re-read. It actually happens that good code will be read again and again by many people. The fact that programs are covered and protected by copyright laws is not incidental. It means that source code is the expression of an author, like a novel or poetry; I like to think that this fact includes in itself, in-nuce that source text will be read by people several time. And this reading does occour when the source-code text is well-written, well-documentated, useful for study and reuse if its license does allow it.
This approach does resemble Knuth's Literate Programming and indeed many design choices made by Bertrand Meyer during the conception of Eiffel regarding readability and understandability of source-code, expecially those exposed in Object-Oriented Software Construction are strikingly similar to Knut's ideas.
Let's return to our wrapping/binding work after a due phylosophical digression
Preliminares
Most data structure in C are usually handled by reference, i.e. using pointers.
Passing objects by value is currently requires "expanded external classes" and will no be covered in this guide. Therefore if your library extensively pass structures around by value and not by reference (with a pointer), for example (TODO: put links to online example of passing structure by value) in function calls or contained in other structures our life will be quite hard. Fortunately enought most libraries handles most of its data structures by reference and zlib is no exception: in fact its basic functions takes pointers as first argument.
Identifying types and classes
Now you will wonder how to identify the types used in Zlib. This is quite an extensive argument that people smarter than me already handled in great details here.
A simplicistic rule-of-thumb specifically fitted to wrap libraries written in C is that when we have several functions that takes a pointer to a struct this struct is a good candidate for a class and those functions are good candidates to be wrapped as features usable by an Eiffel program.
Zlib has many functions taking a z_streamp which is nothing more than a typedef-fed pointer to structure z_stream_s. We will wrap those facilities in the Eiffel's (non-expanded) class ZLIB_STREAM.
Code organization and class hierarchy.
Before diving into code writing let's spend some words on how to cleanly organize our work. We want to provide the user of our library a pretty object-oriented API; since he/she will look at the sources directly soon-or-later it is useful to separate the high-level features and classes, those that will be used directly in other Eiffel projects from the low-level details, like external features or plugins.
We usually place each wrapper library in its own directory following this organization:
- zlib/
- examples -- Examples on how to use the Zlib wrappers
- library -- Contains the high-level classes, those used directly in Eiffel programs
- externals -- Contains the low-level classes to interface with C (externals, macros, enumeration and so on)
- generated -- Contains the low-level classes automatically generated with a tool such as wrappers_generator
- loadpath.se -- Lists the clusters of the library itself, i.e. which directories contains its classes
- externals -- Contains the low-level classes to interface with C (externals, macros, enumeration and so on)
- loadpath.se -- Lists the clusters used by the library, i.e. its dependencies
- tests -- Contains the tests to be used with eiffeltest tool
If the library is conceptually separated into different areas, subsystems or if it is simple too wide to put cleanly every public class into a single directory we can split the content of the directory library into several sub-directory, like GLib wrapper. See its directory layout:
- eiffel-glib/library/
- core
- data_types
- externals
- generated
- fundamentals
- utilities
Let's fill zlib/library/loadpath.se
. It lists two directories: those containing the high-level classes, the same of the loadpath.se file itself, i.e. the current directory (.) and the ./externals directory which will contain deferred classes that provide access to external features.
./ ./externals/
zlib/loadpath.se will list the clusters used by the library itself, i.e. its dependencies. Like the previous it is a plain text file containing directories or other loadpaths file (Ok... I'm not assuming proficiency with GNU-Eiffel here). Zlib is quite a "plain" library and does not have dependencies - here we are speaking of dependencies of the Eiffel wrappers - so he only shall list only the base cluster of a wrapper library, ../common/loadpath.se and the soad-path of the library itself <./library/loadpath.se/code>:
../common/loadpath.se ./library/loadpath.se
Low-level access to functions
Writing the Eiffel source code to access external C code is an extremely long, tedious and verbose process so a tool named wrappers_generator has been created.
It was not included in binary form in the adler release since it was not ready for prime time. Now it isn't robust as I would like but at least it processes sources like Gtk3, Glib, &slasho;mq and other libraries without crashing.
It will be included into bell and later releases; before it's relase I would suggest to rebuild the compiler and work from the Git repository.
You can get the sources from https://savannah.gnu.org/projects/liberty-eiffel/ or from https://github.com/LibertyEiffel/Liberty and build it with install.sh script which also build wrappers_generator.
wrappers_generator does not parses C code directly but reads the output of gccxml or its successor, castxml. Those tools provides an XML description of the source code analyzed that greatly ease the task of generating glue code. In fact they were created for that purpose.
gccxml
is available in Debian stable while
castxml
is available in Debian testing. Both are also available for Ubuntu, Redhat et cetera.
Given an XML file and a list of C files (headers or sources) to wrap wrappers_generator:
- for each source file create a deferred class containing all the wrappable functions found in that file; functions in foo.h are wrapped into FOO_EXTERNALS
- for each struct creates a deferred class containing queries and setters feature to manipulate all members which have an Eiffel wrapper type;
- the same is made for unions
- creates a deferred Eiffel class named like the xml file containing wrappers for all typedefs, so when you have to refer to C "long" which is notorious for change size on various platform you can use "like long" in Eiffel
- each enumeration is wrapped into expanded classes with proper queries and setters. That way they are as efficient as plain integers values but they can't assume arbitrary values; enum foobar is wrapped into FOOBAR_ENUM
- when the values of an enumeration are actually flags, i.e. they all are diffent powers of 2, it's wrapped as a "flag" enumeration with commands to set and unset each flag of the enum.
- C++ classes, namespaces and all its fineries are currently ignored
- macros, #defines and all cannot be handled since they are "preprocessed away" and their text is not preseted to gccxml or castxml
- variadic functions can't be wrapped in Eiffel
- it transform CamelCase into CAMEL_CASE for classes, camel_case for features (queries, commands and so on)
- if it doesn't exist it creates a directory plugin/c that will contain all the C code that will be compiled by liberty eiffel when using the library
It does not try to give high-level, directly usable wrappers but it writes fairly verbose low-level wrappers that hopefully will save you quite a lot of typing.
Text to be still reviewd
Let's write some low-level classes. We will put the functions found in the include file /usr/include/zlib.h in the class ZLIB_EXTERNALS and the low-level getters and setters features for the structure z_stream structure in the class Z_STREAM_STRUCT (this scheme is also used by eiffel-gcc-xml tool; I know it is simplicistic and that it could produce name-clashes but it has worked fine until now. ). Here's some examples C function Eiffel feature const char * zlibVersion (void); zlib_version: POINTER int deflateInit (z_streamp strm, int level); deflate_init (a_stream: POINTER; a_level: INTEGER_32): INTEGER_32 int deflate (z_streamp strm, int flush); deflate (a_stream: POINTER;a_flush: INTEGER_32): INTEGER int deflateEnd (z_streamp strm); deflate_end (a_stream: POINTER): INTEGER int inflateInit (z_streamp strm); inflate_init (a_stream: POINTER): INTEGER int inflate (z_streamp strm, int flush); inflate (a_stream: POINTER; a_flush: INTEGER): INTEGER int inflateEnd (z_streamp strm); inflate_end (a_stream: POINTER): INTEGER
As you can see I have turned camelCaseFunction into camel_case_function which is far more in tune with Eiffel style. Int is actually an INTEGER_32 or only INTEGER (it still don't matter, even if I suspect that it will do soon: world is already filled with 64-bit machines and literally billion of devices with 4,8,16 bit CPU are in use today, not counting GPGPU)
Let's take a look at ZLIB_EXTERNALS. You'll see many entries like
inflate_init (a_stream: POINTER): INTEGER is -- int inflateInit (z_streamp strm);
external "plug_in" alias "{ location: "${eiffel_libraries}/plugins" module_name: "zlib" feature_name: "inflateInit" }" end
this entry tells us that the Eiffel feature "inflate_init" refers to the symbol "inflateInit" in the module "zlib" of the plugins found in the directory.
Entries like this are the only "esoteric" Eiffel code you will see. All the details required to interface to C, to link to the actualy library and how to compile the code written in other languages is handled by the plugin part.
Without using plugins every Eiffel program should know all the details required to compile a C application with zlib. It would require to always use ACE files - even for simple examples - because you need to provide proper "c_compiler_options" and "linker_options"; for a single, simple library like Zlib it looks a tame task, only requiring to add -lzlib to the "linker_options" but when you start building real-world application things will get complicated really fast.
See SmartEiffel documentation on plugins.
All those functions returns an integer with standardized values #defined in the C header. We will wrap them in the deferred class ZLIB_CONSTANTS.
Deferred? Why deferred? We want to avoid the Eiffel programmer the burden to deal with all the details that C requires - otherwise we would have used plain C - so we do not want him/her to directly use that libraries. Deferred classes cannot have creation clauses so we are sure that no objects of type ZLIB_EXTERNALS and ZLIB_CONSTANTS will be created. They are helper classes that will be either inherited from or - more properly - inserted into ZLIB_STREAM and its effective heirs, ZLIB_INPUT_STREAM and ZLIB_OUTPUT_STREAM.
Making sure that the Eiffel developer will not use low-level features directly is the main reason why all the external features are not exported to anyone else, using the syntax feature {} -- External calls. This way only heirs - conforming or not - of that class can use them; for people coming from C++ it's like having private functions members. C types
Here's a quick conversion table for various C types C type Eiffel type int INTEGER; short int INTEGER_16 long int INTEGER_32 or 64 long long int INTEGER_64 int8_t (defined at stdint.h, ISO C 99) INTEGER_8 int16_t INTEGER_16 int32_t INTEGER_32 int64_t INTEGER_64 unsigned int NATURAL unsigned short int NATURAL_16 unsigned long int NATURAL_32 on 32-bit, NATURAL_64 on 64.bit. See long int unsigned long long int NATURAL_64 signed/unsigned char CHARACTER float REAL_32 double REAL_64 long double REAL_EXTENDED char BOOLEAN void* or any other pointer POINTER
Some notes:
intXX_t are defined in GNU systems (Linux Glibc, CygWin and similar). Currently INTEGER is mere alias for INTEGER_32 but this is bound to change in future version of SmartEiffel. Personally I think that it should be "the integer type with the same bit-lenght of C int". A nice feature of long int is that is could be longer that int. So we can't really be sure whenever it is 32 or 64 bit. Usually it is an INTEGER_32 on 32-bit machines and INTEGER_64 on 64-bit machines. See this post on the SmartEiffel mailing list. The same problem applies for long unsigned int: NATURAL_32 on 32-bits and NATURAL_64 on 64-bit. This mismatch can be quite problematic and it will be discussed-addressed later. At the time of writing of this document SmartEiffel has some know issues regarding NATURALs types. If they actually do cause bugs there is an (unsatisfactory) workaround: use a bigger INTEGER. This implies a hidden conversion at C level and bargains correctness with a little waste of space and worse performance. Do not use an INTEGER of same size: it "usually" works nicely but this is a known source of nasty bugs since you will soon or later it will silently overflow.
An enlighting explanation about C variable types and declarations can be read on Wikipedia (I find particurarly confusing - expecially for the novice - that char is at the same time an 8-bit integer and an encoding for ASCII characters. Most programmers could always end up thinking about it only as a character. After more than 30 years they could have called it "byte". Yet this behaviour is fit the spirit of C very well. ).
The resulting ZLIB_CONSTANTS class is almost boring and undocumented. I maintained the documentation level found in the original documentation, since those are values that will be handled only by the implementation of the wrapper.
Have a look at WRAPPER_HANDLER. Eiffel has the ability to selectively export Any class that needs to access some of the Access to structure field
Let's wrap the z_stream structure (the structure name is actually struct z_stream_s typedeffed to z_stream during its definition); the wrapper will have a similar two layer design: the low-level Z_STREAM_STRUCT will be a deferred class, with feature {} -- Hidden, low-level getters and setters. This class will also have a feature struct_size: INTEGER that will be the value of sizeof(z_stream)
Field named FooBar in the struct my_struct will be read with the Eiffel query my_struct_get_foo_bar and set with the command my_struct_set_foo_bar. Remember that those are low-level features; references to other objects will be pointers and its signature will be one of an external function: the first argument will be always the address of - a POINTER to - the wrapped struct. Naming of placeholder arguments
Placeholder names should be chosen in a way that makes the documentation of the feature in English as smooth as possible for a proper argument placeholder for the Gnu-Eiffel language. "CamelCase" will be translated into "a_camel_case", "ENOO" is translated into "an_enoo". Eventual underscores in front of `a_name' are removed: "__foo" becomes "a_foo". The prefixed preposition is added to make clearer that the name refers to a generic value, not to a particular one. Always tries to use a placeholder that can be directly put in the feature's documentation without paraphrase. ZLIB_STREAM, at last.
So let's write something that will be used in an actual Eiffel program! We start writing ZLIB_STREAM from file common/skeleton that provides us the usual LGPL-2.1-or-later headers. It inserts ZLIB_EXTERNALS and Z_STREAM_STRUCT.
TODO: finish this The "common" cluster
The basic building blocks of a wrapper library have been put in the common cluster.
The WRAPPER class it the "fundamental" one even if embarassingly simple. Its only effective field (i.e.: stored, non-computed) is the handle POINTER. It shall handle the allocation and deallocation of the memory referred by handle so it is an heir of DISPOSABLE but it doesn't define dispose because there is no such a thing like a default memory handling in C.
C_STRUCT inherits from WRAPPER and provides default implementations for copy, is_equal and from_external_pointer features; it introduces the deferred "struct_size" query which should give the same result of C operator sizeof(MyStruct).
Note that there are - at least conceptually - other potentially heirs of WRAPPER, like pointers to functions
Many C libraries often return const char* strings. Those strings shall not be modified and their memory will be handled by the library; by all other means they actually are plain strings. A correct behaviour is to write foo: STRING is do create Result.from_external_copy(a_function_returning_a_const_char_pointer(handle)) end; a STRING is meant to be manipulated and changed and has control on the memory of its content. Here we are instead given a piece of memory holding some text that is "mostly read-only" whose control is held by the library. To provide correct behaviour STRING must copy the buffer, from_external can't be used. CONST_STRING is an effective heir of STRING that provides efficient wrapping of "const strings". It is usable like any other STRING but all the changes are made to a separe buffer preserving the original content - we are actually not allowed to change it. No further memory is allocated when a CONST_STRING is created: the buffer returned by the C library is used directly. For example, GTK+ has many calls like const gchar* gtk_entry_get_text (GtkEntry *entry); Memory efficiency is achieved making changing features slower. If you need to change its content consider using its feature string to get a new (non-const) STRING with the same content.
TODO: add proper description about: CACHING_FACTORY C_ARRAY COMPARABLE_C_STRUCT COMPARABLE_WRAPPER CONST_STRING C_OWNED C_STRUCT EIFFEL_OWNED ENUM EXPANDED_WRAPPER GLOBAL_CACHE GLOBALLY_CACHED HASHABLE_WRAPPER ITERATOR_ON_C_ARRAY LINKED_STRING MIXED_MEMORY_HANDLING NULL_TERMINATED_C_ARRAY NULL_TERMINATED_STRING_ARRAY POINTER_HANDLING REFERENCE_COUNTED SHARED STRING_ARRAY STRING_ARRAY_ITERATOR WRAPPER_COLLECTION WRAPPER_DICTIONARY WRAPPER WRAPPER_FACTORY WRAPPER_HANDLER WRAPPERS_CACHE Enumerations
There are two general tecnique to wrap an enum.
Put all the possible values of enumerations into a class as INTEGER constants accessibile to the user of your library. This is mostly similar to the actual usage of an enumeration. Each and every feature that accept an enum value as argument will need a precondition like is_valid_foo (a_foo_value) Create a new type. In fact an enumeration is an INTEGER that can assume only precise and prestabilited values, so that its value is correct and consistent all the times. Class ENUM provides some facilities to achieve this. It shall be used in classes like this:
-- This file have been created by eiffel-gcc-xml. -- Any change will be lost by the next execution of the tool.
expanded class PANGO_WEIGHT
insert ENUM
creation default_create feature -- Validity is_valid_value (a_value: INTEGER): BOOLEAN is do Result := ((a_value = pango_weight_ultralight) or else (a_value = pango_weight_light) or else (a_value = pango_weight_normal) or else (a_value = pango_weight_semibold) or else (a_value = pango_weight_bold) or else (a_value = pango_weight_ultrabold) or else (a_value = pango_weight_heavy)) end
feature -- Setters default_create, set_ultralight is do value := pango_weight_ultralight end
set_light is do value := pango_weight_light end
set_normal is do value := pango_weight_normal end
set_semibold is do value := pango_weight_semibold end
set_bold is do value := pango_weight_bold end
set_ultrabold is do value := pango_weight_ultrabold end
set_heavy is do value := pango_weight_heavy end
feature -- Queries is_ultralight: BOOLEAN is do Result := (value=pango_weight_ultralight) end
is_light: BOOLEAN is do Result := (value=pango_weight_light) end
is_normal: BOOLEAN is do Result := (value=pango_weight_normal) end
is_semibold: BOOLEAN is do Result := (value=pango_weight_semibold) end
is_bold: BOOLEAN is do Result := (value=pango_weight_bold) end
is_ultrabold: BOOLEAN is do Result := (value=pango_weight_ultrabold) end
is_heavy: BOOLEAN is do Result := (value=pango_weight_heavy) end
feature {WRAPPER, WRAPPER_HANDLER} -- Low level values pango_weight_ultralight: INTEGER is external "plug_in" alias "{ location: "foo" module: "bar" feature_name: "PANGO_WEIGHT_ULTRALIGHT" }" end
pango_weight_light: INTEGER is external "plug_in" alias "{ location: "foo" module: "bar" feature_name: "PANGO_WEIGHT_LIGHT" }" end
pango_weight_normal: INTEGER is external "plug_in" alias "{ location: "foo" module: "bar" feature_name: "PANGO_WEIGHT_NORMAL" }" end
pango_weight_semibold: INTEGER is external "plug_in" alias "{ location: "foo" module: "bar" feature_name: "PANGO_WEIGHT_SEMIBOLD" }" end
pango_weight_bold: INTEGER is external "plug_in" alias "{ location: "foo" module: "bar" feature_name: "PANGO_WEIGHT_BOLD" }" end
pango_weight_ultrabold: INTEGER is external "plug_in" alias "{ location: "foo" module: "bar" feature_name: "PANGO_WEIGHT_ULTRABOLD" }" end
pango_weight_heavy: INTEGER is external "plug_in" alias "{ location: "foo" module: "bar" feature_name: "PANGO_WEIGHT_HEAVY" }" end
end
As you can see each possible value has a setter command
(set_light, set_normal and so on) and a boolean
query (is_light, is_normal). As usual low-level values are accessible
only by a WRAPPER or a WRAPPER_HANDLER.
Using eiffel-gcc-xml
Have a look at the manually written ZLIB_EXTERNALS. Eiffel is a language that is meant to be easy to the reader at the cost of being verbose. All that text of source to access a bunch of C functions. Think about serious libraries that have literally thousands of functions, structures, and enumeration! Writing the low level side of those wrappers is a really long, tedious task, the kind of task that people usually would leave to an unfaticable, astonishgly fast yet quite dumb worker, a computer. Andreas Leitner - who is surely quite smarter than myself - wrote a C parser for his Eiffel Wrapper Generator. I do not have neither the time neither the abilities of Andreas but it seems that I'm more lucky than him. In fact by the time I got serious with this project, people with the same problem, requiring tons of wrappers for Python, produced gcc-xml. These days parsing XML is a considered a common task, a task to be left to standard libraries, so SmartEiffel have an XML parser. So I wrote a little tool name "eiffel-gcc-xml" that takes the output of gcc-xml as input to produce the low-level "external" classes that we need to access functions, structures and enumerations.
Well, it actually requires a little more work since it breaks very often. Memory handling C_STRUCT is a wrapper for a data structure implemented in C programming language using a structure. It does not make any assumption about memory handling; the developer of a SmartEiffel wrapper of a C library has to inherit to create a proper wrapper for it the developer shall inherit both from this class and from classes providing memory handling behavior, depending on how structure's memory shall be handled. This is decided case-by-case by C library. Currently available memory handling classes are:
C_OWNED: memory is always handled by the underlying C library. EIFFEL_OWNED: once created memory is fully handled by Eiffel runtime, usually with the Garbage Collector. GLOBALLY_CACHED: Until disposed by Eiffel the wrapper registered in wrappers dictionary will be the unique wrapper used on the Eiffel side. MIXED_MEMORY_HANDLING: whose memory can either by handled by the Eiffel library or by the underlying C code. Who handles memory is decided on a per-object based on the value of the flag `is_shared': handle will not be freed on dispose of the Eiffel wrapper object, when `is_shared' is true. REFERENCE_COUNTED: memory is handled thought reference counting, i.e.GObject
Add proper examples of the various memory handling classes. Implementing collections Quite often objects are stored into containers or collections like arrays, linked lists, dictionaries, hashed-tables and so on. Many C libraries provides their own implementation of the classic containers, for example the GLib library provides GList, GSList, GHashTable and many others. A WRAPPER_COLLECTION is a COLLECTION implemented wrapping some facilities offered by the wrapped C library, like GLib's linked list GList. When you wrap those data structures you will encounter two kinds of problems:
Pointer returned by C containers could be newly allocated, not already wrapped by the Eiffel wrapper or already wrapper by the Eiffel wrapper. You can't naively create a new WRAPPER every time. Beside the obvious waste of memory and the stress you put on the garbage collector you will break most postconditions and invariant of a COLLECTION, since things that seems obvious like
do_stuff (my_collection: WRAPPER_COLLECTION[ITEM]) is require
my_collection/=Void not my_collection.is_empty local a,b: ITEM do a := my_collection.first b := my_collection.first check will_fail: a = b end end
In fact a and b will be different wrappers referring to the same underlying C structure. A WRAPPER_COLLECTION shall avoid this repeated, unnecessary and wrong creation of WRAPPERs, that will breaks invariants and post-conditions of a COLLECTION. A solution is to inherit from WRAPPERS_CACHE, initializing cache at creation time and keeping it updated during usage. Cache's key is the address (pointer) to the wrapped C structure, value is the corresponding Eiffel wrapper. This way you can get back an already-created Eiffel wrapper. Classes can implement different scheme; for example G_OBJECT_LIST retrieve the reference to the eventual WRAPPER directly from underlying GObject.
The container has no means to know how it shall create the wrapper for it, since C does not have the notion of generic, strongly typed container. The effective type of the containee is either not know by WRAPPER_COLLECTION or worse its conteinees could actually belong to different classes, sharing a common ancestor. Since each library has its own idea on how the programmer shall handle �memory when dealing with containers an effective, non-deferred heir of WRAPPER_COLLECTION shall implement `wrapper' feature that given a pointer creates or retrieve a WRAPPER of the fittest class. Usually "plain" usage could return a fixed type; more elaborate type systems like GObject could provide the facilities necessary to pick a useful Eiffel type.
TODO: finish it. Comparability and hashability Some collections does not require anything in particular to their containee, like arrays or linked lists. Other collections needs either to compare their containee or to obtain an hash value from it. Therefore wrapper classes that are conceptually comparable or hashable shall inherit from COMPARABLE_WRAPPER and HASHABLE_WRAPPER respectively so they could be stored into collections that requires such abilities. How to adapt old wrappers (This is almost obsolete) Previous design was a black-or-white design: a class was either Eiffel-owned or not; C_STRUCT's are "owned" by the Eiffel code, and the Eiffel side should keep then longest lived reference to this struct; SHARED_C_STRUCT modelled the non-Eiffel-owned. This approach is overly simplicistic since real-life libraries implement a wide spectrum of memory handling policies. One solutions does not fit all needs. Study the documentation of the library and pick the right memory handling Eiffel class. The long int problem
Shortly it could be solved having two separate clusters one for 32bit, one for 64 bit where a deferred class defines a parameterless query for anchored declaration like
long_int: INTEGER_32 is do end
long_int: INTEGER_64 is do end
but this looks like an unfeasible hack to me, at least now. Currently it is the only available answer; a "proper" solution would require changes in both the compiler and the standard library. I would like to know how ISE solved this.
GObject-based libraries
Nowadays many libraries are based on the GLib Object System also known as Gobject. This library "provides a portable object system and transparent cross-language interoperability". It is used as the basic building block for literally hundeds of libraries and applications; in fact the command apt-cache rdepends libglib2.0-0 |wc -l told me that more than 2200 applications and libraries depend on either GLib or GObject.
GObject implement its own classed type system. With the exception of basic types (and some other subtleties I will write later) all objects are structures referred by pointer, i.e. non-expanded classes in Eiffellese; the type of each object is identified by a number, a GType which has an associated structure to describe the class - the type - itself. During startup of the Eiffel application the wrapper library will register for each type name (i.e. GtkWindow, GtkButton....) it wraps an agent that given a pointer creates an Eiffel wrapper object for the creation_agents dictionary, with a call similar to creation_agents.put (agent create_gtk_window, "GtkWindow"), given the feature create_gtk_window (p: POINTER): GTK_WINDOW is do create Result.from_external_pointer(p) end.
A Gobject can store arbitrary properties into itself. We use this to store a reference pointer to its Eiffel wrapper.
When we receive a pointer to a Gobject the G_OBJECT_FACTORY first looks if this Gobject already has an attacked wrapper (an effective heir of G_OBJECT). Otherwise it ask the Gobject its GType and the associated class-name. It this class-name has a creation agent the wrapper is created invoking the retrieved creation agent; otherwise we recursively look at its parent class until we find a type that has a corresponding creation agent.
When an effective G_OBJECT heir is actually created either explicitly because we know for sure its type or throught a creation agent the address of the wrapper is stored in the property "eiffel-wrapper" using the C function g_object_set_qdata (see store_eiffel_wrapper in G_OBJECT).
As usual this approach is both correct, memory efficient and collection-friendly (see "Implementing collections" earlier) at the cost of being computing intensive. There are times when the wrapper library has to create transient wrappers for shortly lived objects. Here enters secondary wrappers, or G_OBJECTs that are not referred-by the GOBject they refer to. Letting code speak: having a_gtk_window_ptr: POINTER; my_window: GTK_WINDOW; factory: G_OBJECT_EXPANDED_FACTORY check create my_window.secondary_wrapper_from(a_gtk_window_ptr) /= factory.wrapper(a_gtk_window_ptr) end will hold and also create my_window.main_wrapper_from(a_gtk_window_ptr); check my_window = factory.wrapper(a_gtk_window_ptr) end will be successfully passed. The difference is that the first is only an allocation of a smallish object (few bytes) while the latter could potentially require several looks-up into a dictionary plus an O(n) search into the GObject properties (during g_object_get_qdata used to see if a wrapper does actually exist)
TODO: show how to wrap a typical GObject-using library, like Gnome's GConf configuration system. Commit's policy
There is no strict rule... Ideally each commit should provide compilable code and working examples. Provided examples and classes can be unfinished and uncomplete, can contain warnings, empty features and so on. My motto is "something is better than nothing". it is nice to tell other developers what are you working on; anemail on eiffel-libraries-devel@gna.org suffice. I promise I won't track you with a 9-tail cat to bash you if you commit uncompilable code. Code that compiles is a good thing. Wisely designed code that compiles is better, since it has better performances; but I prefer published, working code even if has no state-of-the-art performances instead of
Various very old notes Those notes are left here to be worked on.
Suggested Start copying original documentation into an Eiffel class and comment it out entirely. Add proper indexing clauses, then progressively convert it into Eiffel from top to bottom. Sketch difficoult features into comments. This behaviour is not meant to pile rubbish, but to quickly know how much work we still have to do. This means that the resulting Eiffel classes will be a derivative work of the original documentation of the library. This is usally not a problem these days because documentation is often automatically extracted from sources, so the Eiffel wrapper will end up having the same license of the wrapper library; for example our GTK+ wrapper being a derivative work of the actual GTK+ C library must abide its LGPL license. All the infrastructure of GTK+, including GObject are - in my humble opinion - explicitly designed to make the life easy for people writing wrappers for loosely-typed languages such as Python, Perl and other scriptiong languages. This (sadly) means that life for heavily/strongly-typed languages such as Eiffel are make annoying. Objects like closures and other amenities like that won't be wrapped until the rest is done and working.
Incoming tools
Libraries using gobject-introspection and typelib provides much more informations to write wrappers and binding for other languages.
The metadata of a typelib file allows to directly produce high-level wrappers.
This will give us support for Gtk3, Glib, Cairo and many many other libraries in a much faster way that manually wrapping each one.
The tool that will generate such binding will be named leggow, acronym for Liberty Eiffel Generator of GObject Wrappers and it currently lies in the leggow branch in repository https://github.com/tybor/Liberty but it is currently only an idea.