ANU The Australian National University



____________________________________________________

[ANU] [DCS] [COMP2100/2500] [Description] [Schedule] [Lectures] [Labs] [Homework] [Assignments] [COMP2500] [Assessment] [PSP] [Java] [Reading] [Help]

____________________________________________________

COMP2100/2500
Lecture 26: The Eiffel/C Interface

Summary

How to incorporate C code into Eiffel projects.

Outline


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.

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
   end

And 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))
   end

Note 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.

Aliases for external routines

Why might we need to refer to an external routine by a different name?

For example:

file_status (fd: INTEGER): INTEGER is
   external
      "C"
   alias
      "_fstat"
   end

Issues for external calls


Further thoughts


Implementation with SmartEiffel

Note that some of the details will be different for other compilers like ISE EiffelBench.


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 EXAMPLE

Note: 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.c

You could also do

gcc -c -Wall external.c
compile -o example example make external.o

Or 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.c

and 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 C

Note: 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?

  1. It reads all the Eiffel classes in the system and translates them into a C program in the files example1.c, example2.c, ...

  2. It also writes a header file example.h.

  3. Then it calls gcc -c to compile each of those C files to a corresponding .o file.

  4. Then it calls

    gcc -o example example1.o example2.o...

    to compile the .o files into an executable.

  5. 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