A tutorial on how to build a shared library on Linux with EiffelStudio

In this tutorial you will be shown how to build a shared library "libfoo.so" and how to set up a build environment for it that also automatically generates a Python wrapping of the library. This tutorial is written for Ubuntu 8.10 but should help you get started on other Ubuntu releases and Linux distributions.

You should read or at least browse through Building Eiffel Shared Libraries on Linux first. It covers general aspects of designing shared library API:s in Eiffel and the known problems with (and solutions for) building shared libraries with EiffelStudio.

You should also read or browse through the  TLDP How To on Shared Libraries.

Setting up the environment

First you need to install (version, Ubuntu debian package version):

  1. EiffelStudio (6.3.7.6070). Make sure you apply the change to "egc_dynlib.h" which is neccesary to build working shared libraries. See Installing and setting up EiffelStudio for more information.
  1. SCons (v0.98.5.r3057, 0.98.5-1ubuntu1). This is the build tool.
  1. The SeiboEiffel version of the SCons EiffelStudio tool "Eiffel.py" (svn !r122). You need this to build your shared libraries with that tool since it generates a linker (ld) script .map file and modifies the "Makefile.SH" file of your projects in order to stop all symbols in the shared libraries from being exported.

  1. SWIG (1.3.35, 1.3.35-4ubuntu1). This is the tool used to generate Python wrappers.
  1. Python (2.5.2, 2.5.2-1ubuntu1). You need this to generated the Python wrappers and of course to access the shared library from Python!

Setting up the framework for a shared library

There are a number of files needed to build a shared library. We assume that you have created a project directory where all the project files will reside, eg:

~/proj/foo/trunk/bld/libfoo

The files to create are:

  1. An EiffelStudio .ecf file. This is the standard project/system configuration file for EiffelStudio. Specify the target name as "libfoo.so" and for that target specify a .def file. Typically you create and edit the .ecf file from within EiffelStudio, but here we show the actual contents of the "libfoo.ecf" file.
    ...
        <target name="libfoo.so">
            ...
            <setting name="shared_library_definition" value="libfoo.def"/>
            ...
        </target>
    ...
    

  1. A EiffelStudio .def file. This file defines the symbols in your Eiffel system that are to be exported. A recommended practice is to put all the features you want to export into a single class FOO_SHARED_LIBRARY. Your "libfoo.def" file could then look like this:
    -- EXPORTED FEATURE(s) OF THE SHARED LIBRARY 
    -- SYSTEM : libfoo.so
    
    -- CLASS [FOO_SHARED_LIBRARY]
    FOO_SHARED_LIBRARY (make) : foo_do_something
    FOO_SHARED_LIBRARY (make) : foo_get_something
    ...
    
    Note that you do not specify the formal parameter types or return type of the symbols.

When you have a .ecf file and a .def file you can compile your shared library with EiffelStudio. However in order to make it useable you need to add some more files.

  1. You need a C header (.h) file that declares the symbols available in your shared library. It is needed by clients of your shared library and it is also needed by SWIG in order to generate a Python wrapper for your shared library. The C header file must match the .def file above. Look in the "eif_types.h" and "eif_portable.h" files for how the basic Eiffel types man to C types. Unfortunately Eiffel Studio does not generate the C header file so you will have to create and edit it by hand. A C header file "foo.h" that matches the above .def file would look something like:
    #ifndef __FOO_API__
    #define __FOO_API__
    
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    #ifdef WIN32
    #define APICALL 
    #else
    #define APICALL
    #endif
    
    void foo_do_something (long, const char *);
    char * foo_get_something (long);
    
    #endif
    
    Note that in the C Header file you must specify the formal parameter types or return type of the symbols and they must match the Eiffel types used in the definition of the Eiffel features.

In order for your shared library to work you need to build it with SCons and the "Eiffel.py" SCons tool, which makes sure that only the symbols declared in the .def file are actually made global symbols in the resulting foo.so shared object file.

  1. Create a SConstruct file (or a SConscript file if you want to manage a larger recursive SCons build setup for many binaries):
    import os
    import sys
    
    # Create a SCons environment with the SCons Eiffel Tool
    env = Environment(ENV = os.environ, tools = ['default', 'Eiffel'])
    env.Append (arguments = ARGUMENTS)
    
    module_name = 'foo'
    lib_name = 'lib' + module_name
    py_name = 'py + module_name
    if sys.platform == "win32":
        ecf_file_name = lib_name + '-win32.ecf'
        rc_file = lib_name + '.rc'
        res_file = lib_name + '.res'
        target_name = module_name + '.dll'
    else:
        ecf_file_name = lib_name + '.ecf'
        target_name = module_name + '.so'
    swig_file = lib_name + '.i'
    
    eif_targets = env.Eiffel (target = target_name, source = ecf_file_name)
    
    # The Python wrapper
    env.Append (SWIGFLAGS = '-python')
    if sys.platform == "win32":
        env.RES (target = res_file, source = rc_file)
        env.Append (SWIGFLAGS = '-I.')
        env.Append (CPPPATH = ['.'])
    else:
        pass
    if sys.platform == "win32":
        py_targets = env.SharedLibrary (target = py_name, source = [swig_file, res_file, bin_name])
    else:
        env['LIBPATH'] = ["."]
        env['SHLIBPREFIX'] = ["_"]
        py_targets = env.SharedLibrary (target = py_name, source = [swig_file],
                                        CPPPATH = ['/usr/include/python2.5', '.'],
                                        LIBS = [module_name],
                                        SWIGPATH = ['.'])
    
    Note that we included the instructions for building the system on Windows too!

Finally, you need to create a SWIG interface .i file so that SWIG can generate a Python wrapper for your shared library.

  1. Create the SWIG interface file "libfoo.i":
    %module pyfoo
    %{
    #include "foo.h"
    %}
    
    %include "foo.h"
    

Building your shared library and taking it for a run

Now you should be able to build the shared library by typing:

~/proj/foo/trunk/bld/libfoo$ scons
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
libfoo.so
  ec -batch -config libfoo.ecf -target libfoo.so -finalize
  Eiffel.py: Creating .map file from .def file and modifying Makefile.SH
  finish_freezing
swig -o libfoo_wrap.c -I. -python libfoo.i
gcc -o libfoo_wrap.os -c -fPIC -I/usr/include/python2.5 -I. libfoo_wrap.c
gcc -o _pyfoo.so -shared libfoo_wrap.os -L. -lfoo
scons: done building targets.

The interesting files created are:

  • libfoo.so. This is your shared library!
  • _pyfoo.so. This is the Python wrapper shared library generated by SWIG.
  • pyfoo.py This is the Python module generated by SWIG. It simply relays calls to _pyfoo.so.

You can take the shared library for a run from Python. First you need update LD_LIBRARY_PATH with '.' so that the program loader can find your libapi.so file. Of course if you install libapi.so under one of the standard locations like '/usr/lib' you won't need to do that.

~/proj/foo/trunk/bld/libfoo$ export LD_LIBRARY_PATH=.
~/proj/foo/trunk/bld/libfoo$ python
Python 2.5.2 (r252:60911, Oct  5 2008, 19:24:49) 
[GCC 4.3.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pyfoo
>>> help (pyfoo)
>>> pyfoo.do_something (42, "foobar")
"I'm doing something to foobar 42 times!"

Eiffel code

TBD.

Using API_RESULT and JSON

TBD.

Installing and creating an installation package for your shared library

TBD. Including debian package.