IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
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.