Interfacage de librairies C/C++ avec Kylix

    4 juin 2001

    Par Jean-Philippe Bempel

    Kylix comme Delphi fait figure de pavé dans une marre où règne en maitre les langages C et C++. Linux comme Windows est un système d'exploitation écrit en C et la plupart des librairies et programmes tournant pour cet OS le sont aussi. Il n'est heureusement pas impossible de pouvoir programmer et faire des appels sytèmes dans d'autres langages sans perdre en efficacité. La preuve en a déjà été faite avec Delphi sous windows, et maintenant avec Kylix sous l'OS du manchot !

    Interface avec librairies C

    C'est la partie la plus simple, cela va nous permettre d'introduire en même temps quelques outils très pratique de Linux.

    Présentation des librairies

    Je vais parler dans cet article exclusivement des librairies partagés (Shared Library .so) équivalente aux DLL windows. Comme ces consoeurs, ces librairies regroupent plusieurs fonctions. Attention on parle ici uniquement de fonctions et non pas de méthodes de classes ou d'objets (cf section C++).

    L'utilitaire nm permet de lister les symboles d'un fichier .o ou .so. Ces symboles sont utilisés par le lieur (linker) pour lier un appel de fonction au code associé. En C le nom de la fonction suffit pour faire le lien. (sans le underscore de début)

    > nm libz.so
    0000b000 d __DYNAMIC
    0000b060 D __GLOBAL_OFFSET_TABLE_
    0000b080 D __PROCEDURE_LINKAGE_TABLE_
    00005094 T __dist_code
    00005294 T __length_code
    00006798 T __tr_align
    00006a40 T __tr_flush_block
    00005488 T __tr_init
    000066f0 T __tr_stored_block
    00006c80 T __tr_tally
    000047e8 T _adler32
    00001698 T _compress
    000015f8 T _compress2
    000014fc T _crc32
    00003560 T _deflate
    ...

    Du coté de Kylix

    Dans cet exmple on va effectuer un chargement dynamique de la librairie. Créer un nouvelle unitée. Dans la clause uses rajouter les unités Libc et SysUtils. On déclare maintenant des types pointeurs sur fonctions qui vont correspondre aux signatures des fonctions de la librairies en C. Prener le .h du .so sous la main pour faire la conversion. par exemple dans le .h vous avez:

    int my_sum(int, int);
    int my_length(const char*);

    on va traduire en kylix de la façon suivante:

    interface

    type
      Tmy_sum = function (A, B: Integer): Integer; cdecl;
      Tmy_length = function (const S: PChar): Integer; cdecl;

    var
      my_sum: Tmy_sum;
      my_length: Tmy_length;

    implementation
    uses Libc, SysUtils;

    var
      Handle: HMODULE;

    procedure LoadLib;
    begin
      Handle := LoadLibrary('libtest.so'); // ce fichier doit se trouver soit dans /usr/lib soit dans /lib
      if Handle = 0 then
        raise Exception.Create('Error: ' + dlerror);
      my_sum := GetProcAddress(Handle, 'my_sum');
      my_length := GetProcAddress(Handle, 'my_length');
    end;

    procedure FreeLib;
    begin
      FreeLibrary(Handle);
    end;

    initilization
      LoadLib;
    finalization
      FreeLib;
    end.


    Interface avec librairies C++

    L'intefacage avec des librairies C++ est un peu plus compliquée. Pourquoi ? Tout simplement parce que les classes C++ ne sont pas compatibles avec les classes Delphi (orgranisation de la table des méthodes virtuelles, etc...)

    La solution pour contourner ce problème est de créer une librairie d'interface en C++ qui ne publie que des fonctions. Par exemple: soit la class C++ suivante:

    class Human
    {
    public:
      Human(const char* name)
      {
        cout << "+ constructor class Human: " << name << endl;
        _name = new char[strlen(name) + 1];
        strncpy(_name, name, strlen(name));
      }
      ~Human()
      {
        cout << "- destructor class Human" << endl;
      }
      void Speak(const char* msg, Human* another)
      {
        if (another == NULL)
        {
          cerr << "Speak - another == NULL" << endl;
          return;
        }
        cout << "Method Speak: " << msg << " to " << another->getName() << endl;
      }
      const char* getName() const
      {
        return _name;
      }

    private:
      char* _name;
    };

    Création de librairie d'interface C++

    On va créer une lib d'interface de la manière suivante:

    # include <iostream>
    // vous pouvez plutot que de redéfinir la classe inclure le .hpp
    class Human
    {
    public:
      Human(const char*);
      ~Human();
      void speak(const char*, Human*);
      const char* getName();
    };

    Human* createHuman(const char* name)
    {
      return new Human(name);
    }

    void destroyHuman(Human* self)
    {
      delete self;
    }

    void speak(Human* self, const char* msg, Human* another)
    {
      if (self == NULL)
      {
        cerr << "speak - self == NULL" << endl;
        return;
      }
      self->speak(msg, another);
    }

    const char* getName(Human* self)
    {
      if (self == NULL)
      {
        cerr << "speak - self == NULL" << endl;
        return NULL;
      }
      return self->getName();
    }

    Compiler le .so en linkant statiquement avec votre librairie C++:

    > gcc -shared -fPIC -o libtestintf.so testintf.cpp /usr/lib/libtest.so

    Le principe ici est de passer en paramètre pour chaque fonction l'objet, en faite de simuler ce que fait le compilateur avec des appels de méthodes ! Regarder ensuite les symboles défini dans cette lib d'interface, vous en aurez besoin pour la correspondance dans kylix:

    00000000 A GCC.INTERNAL
             U _._5Human
    00001ee8 A _DYNAMIC
    00001e84 A _GLOBAL_OFFSET_TABLE_
             U __5HumanPCc
    00001e78 ? __CTOR_END__
    00001e74 ? __CTOR_LIST__
    00001e80 ? __DTOR_END__
    00001e7c ? __DTOR_LIST__
    00001dc0 ? __EH_FRAME_BEGIN__
    00001da0 ? __EXCEPTION_TABLE__
    00001dc0 ? __FRAME_BEGIN__
    00001e70 ? __FRAME_END__
             U ___brk_addr@@GLIBC_2.0
    00001fa8 A __bss_start
             U __builtin_delete
             U __builtin_new
             U __curbrk@@GLIBC_2.0
             U __deregister_frame_info
    00000d08 t __do_global_ctors_aux
    00000ad0 t __do_global_dtors_aux
             U __environ@@GLIBC_2.0
             U __gmon_start__
             U __ls__7ostreamPCc
             U __ls__7ostreamPFR7ostream_R7ostream
             U __register_frame_info
             U __throw
    00001fa8 A _edata
    00001fc0 A _end
    00000d68 A _etext
    00000d68 ? _fini
    000009a0 ? _init
             U atexit@@GLIBC_2.0
             U cerr
    00001d9c d completed.3
    00000b8c T createHuman__FPCc
    00000c10 T destroyHuman__FP5Human
             U endl__FR7ostream
    00000b28 t fini_dummy
    00001da0 d force_to_data
    00001da0 d force_to_data
    00000b40 t frame_dummy
    00000ad0 t gcc2_compiled.
    00000ad0 t gcc2_compiled.
    00000d08 t gcc2_compiled.
    00000d68 t gcc2_compiled.
    00000b8c t gcc2_compiled.
             U getName__5Human
    00000ca4 T getName__FP5Human
    00000b74 t init_dummy
    00000d3c t init_dummy
    00001fa8 b object.8
    00001d98 d p.2
             U speak__5HumanPCcP5Human
    00000c40 T speak__FP5HumanPCcT0
             U terminate__Fv

    maintenant dans kylix créer l'unité suivante:

    unit MyIntfLib;
    interface

    type
      THuman = pointer;
      PHuman = ^THuman;
      TcreateHuman = function (const name: PChar): PHuman; cdecl;
      TdestroyHuman = procedure (Human: PHuman); cdecl;
      Tspeak = procedure (Human: PHuman; const msg: PChar; another: PHuman); cdecl;
      TgetName = function (Human: PHuman): PChar; cdecl;

    var
      createHuman: TcreateHuman;
      destroyHuman: TdestroyHuman;
      speak: Tspeak;
      getName: TgetName;

    implementation

    uses Libc, SysUtils;

    var
      Handle: HMODULE;

    procedure LoadMyIntfLib;
    begin
      Handle := LoadLibrary('libmyintflib.so'); // attention elle soit se trouver /lib ou /usr/lib
      if Handle = 0 then
        raise Exception.Create('Error: ', dlerror);
    // il faut mettre ici le symbole se trouvant dans le .so cf ci-dessus
      createHuman := GetProcAddress(Handle, 'createHuman__FPCc');
      destroyHuman := GetProcAddress(Handle, 'destroyHuman__FP5Human');
      speak := GetProcAddress(Handle, 'speak__FP5HumanPCcT0');
      getName := GetProcAddress(Handle, 'getName__FP5Human');
    end;

    procedure FreeMyIntfLib;
    begin
      FreeLibrary(Handle);
    end;

    initialization
      LoadMyIntfLib;

    finalization
      FreeMyIntfLib;

    end.

     
     
     
     
    Partenaires

    Hébergement Web