[ANU] [DCS] [COMP2100/2500] [Description] [Schedule] [Lectures] [Labs] [Homework] [Assignments] [COMP2500] [Assessment] [PSP] [Java] [Reading] [Help]
COMP2100/2500
Lecture 26: The Eiffel/C InterfaceSummary
How to incorporate C code into Eiffel projects.
Outline
Introduction.
Some nuts and bolts of how to do it.
Examples.
Introduction
In the real world, we can't restrict ourselves to using nothing but Eiffel (or whatever your favourite language is), no matter how much we'd like to.
We may need to use packages or libraries written in languages other than Eiffel.
Some part of the system may have been written in some language other than Eiffel.
Some parts of the system may need to have direct access to low-level system details.
One option is just to forget Eiffel and write the system in another language, and this is often the right answer. Language choice should not just be a matter of personal preference, but should be carefully thought out for each project. By the time you finish your degree you should be familiar with several languages and capable of learning new ones in a few weeks.
But what if no language has all the libraries, capabilities and features that we need? We need to be able to make software components written in different languages work together. In practice, large systems often incorporate components written in several different languages.
Today we'll just look at one example of multi-language systems, making Eiffel and C work together. In fact we'll only look at one direction: calling C code from Eiffel.
Syntax of external calls
Routine body ::= Effective | Deferred Effective ::= Internal | External External ::= external Language_name [External_name] Language_name ::= Manifest_string External_name ::= alias Manifest_string
A simple example
Suppose you wanted to access the system clock so as to measure how long your program takes to perform different operations. This is easy to do in C, using a library routine clock() and a macro CLOCKS_PER_SEC, both declared in the standard header file time.h.
Here's the Eiffel code:
ticks: INTEGER is -- Number of CPU ticks since power on. external "C" ensure Result > 0 end ticks_per_second: INTEGER is -- Number of CPU ticks in one second. external "C" ensure Result > 0 endAnd here's the C code it calls.
#include <time.h> int ticks() { return (int) clock(); } int ticks_per_second() { return (int) CLOCKS_PER_SEC; }Eiffel routines like ticks and ticks_per_second are known as wrappers for the corresponding C routines.
The only reason we need the little bit of C code there is to perform the casts of the C type clock_t to int. Otherwise the Eiffel code could have called clock() and CLOCKS_PER_SEC directly. More on type compatibility later.
In the Eiffel class CPU_TIMER (which you will use in this week's lab) there are also attributes for storing the number of 'ticks' when the clock was started and stopped, queries for returning the number of ticks and seconds between starting and stopping it, and commands to start and stop the clock. The excerpt above is just the bit that made the external calls -- those routines aren't even visible to the client.
Another example
Assume that there is a C library routine f_close that takes an integer 'file descriptor' and closes the associated file. We want to call it from Eiffel.
f_close (fd: INTEGER): INTEGER is -- Close the file associated with 'fd' -- and return status in 'Result'. require exists (associated_file (fd)) external "C" ensure (Result = 0) implies closed (associated_file (fd)) endNote that this is a function which is really a procedure returning a status report through its return value. This is typical for C, but is not good Eiffel style.
A better way?
close_file (fd: INTEGER) is -- Close the file associated with 'fd' -- and record status in 'status'. do status := f_close (fd) end status: INTEGER -- Status of last file operation.
We'd probably want to declare the possible meaningful values of status as named constants, for example something like this:
File_close_success: INTEGER is 0 File_close_failure: INTEGER is 1so that client code can do something like this:
close_file (n) if status = File_close_failure then std_error.put_string ("Couldn't close file descriptor%N") endIf we hide the version on the previous slide in a feature {NONE} section, this hides that nasty C side-effect stuff and presents a uniform Eiffel interface to clients.
Aliases for external routines
Why might we need to refer to an external routine by a different name?
Because the foreign routine's name is not a legal Eiffel identifier.
Because the foreign routine's name does not conform to our naming conventions (nouns for queries, verbs for commands etc.).
For example:
file_status (fd: INTEGER): INTEGER is external "C" alias "_fstat" end
Issues for external calls
What if you want to pass parameters to the external routine? How do you know that the data representations are consistent?
Same for the return value of a function.
What if the external code has the only reference to an object? How can the garbage collector know not to clean it up?
Further thoughts
The Eiffel short form doesn't tell you anything about a feature's implementation -- not even what language it's written in.
We have a big example of this Eiffel-to-C stuff to look at: the EXG/eiffel-gtk Eiffel interface to the GTK+ graphics library.
Implementation with SmartEiffel
Note that some of the details will be different for other compilers like ISE EiffelBench.
SmartEiffel compiles to C and implements the basic types INTEGER, DOUBLE and CHARACTER using the corresponding C types. This means the question about compatibility of representations doesn't apply for these types.
SmartEiffel also implements class STRING using a C string, but not simply as a C string. (The Eiffel class stores some extra data.) So you have to pass your external routine a pointer to the C part.
You do this by using the feature to_external of class STRING.
This returns a result of class POINTER which is basically a C 'void *'.
SmartEiffel allows you the option of passing the external routine a pointer to (i.e. the address of) the Current object.
You also have the choice of whether the compiler should put a prototype of the C routine in the .h file it writes as part of its C output. This is useful for C routines you write yourself. Not including a prototype is better for calls to library routines.
Example of external calls with SmartEiffel
class EXAMPLE creation make feature make is -- Test some external calls. local s: STRING do c_write_integer (6) change_integer ($integer_attribute) io.put_integer (integer_attribute) io.put_new_line s := "Hello C" c_write_string (s.to_external) change_integer (s.to_external) c_write_string (s.to_external) end integer_attribute: INTEGER c_write_integer (i: INTEGER) is -- Ask C to print 'i'. external "C" alias "print_int" end c_write_string (sp: POINTER) is -- Ask C to print the string pointed to by 'sp'. external "C" alias "print_string" end change_integer (ip: POINTER) is -- Ask C to change the value of the integer at 'ip' external "C" alias "set_val" end end -- class EXAMPLENote: Notice the '$' operator. This is just like the '&' operator in C. It returns a POINTER to its argument -- in other words, its address. This operator is only used when passing a pointer to an object to external code.
external.c
#include <stdio.h> void print_int(int i){ printf("%d\n",i); } void print_string(char *s){ printf("%s\n",s); } void set_val(int *attribute){ *attribute = -1; }
How to compile systems with external calls
To compile the system above you need to say
compile -o example example make external.cYou could also do
gcc -c -Wall external.c compile -o example example make external.oOr you could write a Makefile containing
example: example.e external.o compile -o example example make external.o external.o: external.c gcc -c -Wall external.cand then just say make.
Or you could add an 'external' section to your Ace file, like this:
external external_c_files: "external.c"
Running the example
[u8304368@partch]$ compile -o example example make external.c [u8304368@partch]$ example 6 -1 Hello C ÿÿÿÿo CNote: No type checking is done on pointers passed from Eiffel to C. Look at the last two lines of code in the make routine. We pass a 'char *' (the address of the start of the string) to the C function 'set_val' which expects an 'int *'. The program compiles and runs happily, but writes the integer representation for -1 into the first four bytes of the string. (Think about two's complement representation for integers, and ISO-8859-1 character codes...)
How does the SmartEiffel compiler work?
It reads all the Eiffel classes in the system and translates them into a C program in the files example1.c, example2.c, ...
It also writes a header file example.h.
Then it calls gcc -c to compile each of those C files to a corresponding .o file.
Then it calls
gcc -o example example1.o example2.o...to compile the .o files into an executable.
Any extra file names or options on the command line that the Eiffel compiler doesn't recognise, are just passed on to the C compiler.
Now you're in a position to understand the Ace files we've been using for compiling EXG applications. The only extra thing you need to understand is how to specify which precompiled C libraries to include and where to look for them.
You do this in the 'external' section also, like this:
external external_lib_path: "-L/usr/local -L/foo/bar" external_lib: "-lm -lX11"This has exactly the same effect as putting those '-l' and '-L' options on the command line or in a Makefile.
Cecil
So far we have limited ourselves to calling C routines from Eiffel. What if we want the C part of a system to be able to call Eiffel routines?
Why would we want to do this? One reason is if we were trying to write an Eiffel interface to something like GTK+, where the main loop is running on the C side, and it needs to call Eiffel routines when various events take place.
How do we do it? Not at all in this unit. But the answer is CECIL (the C-Eiffel Call-In Library).
In the SmartEiffel implementation this requires you to supply a table of C names and corresponding Eiffel classes and feature names. You then call the C function with (a pointer to) an Eiffel object as the first argument.
[ANU] [DCS] [COMP2100/2500] [Description] [Schedule] [Lectures] [Labs] [Homework] [Assignments] [COMP2500] [Assessment] [PSP] [Java] [Reading] [Help]
Copyright © 2004, Ian Barnes, The Australian National University
Version 2004.1, Friday, 14 May 2004, 14:30:48 +1000
Feedback & Queries to
comp2100@cs.anu.edu.au