🔝 Retour au Sommaire
Les bindings (liaisons) permettent d'utiliser des bibliothèques écrites en C ou C++ directement depuis FreePascal. C'est une compétence essentielle pour accéder à l'écosystème riche des bibliothèques existantes et pour interagir avec le système d'exploitation.
- Réutiliser des bibliothèques C/C++ éprouvées sans les réécrire
- Accéder aux API système (Windows API, POSIX, etc.)
- Intégrer des bibliothèques tierces (SDL, OpenGL, OpenCV, etc.)
- Optimiser certaines parties critiques en C/C++ tout en gardant l'application en Pascal
Lorsque vous appelez une fonction externe, le compilateur doit savoir comment organiser les paramètres en mémoire. C'est ce qu'on appelle la convention d'appel.
cdecl (C declaration)
- Convention standard du langage C
- L'appelant nettoie la pile
- Compatible avec les fonctions variadiques (nombre variable de paramètres)
- Utilisée sur Linux et pour la plupart des bibliothèques C
function ma_fonction(x: Integer): Integer; cdecl; external 'malib';stdcall (Standard call)
- Convention Windows standard
- La fonction appelée nettoie la pile
- Utilisée par l'API Windows (Win32 API)
- Plus efficace que cdecl mais incompatible avec les fonctions variadiques
function MessageBoxA(hWnd: HWND; lpText: PChar; lpCaption: PChar;
uType: UINT): Integer; stdcall; external 'user32.dll';Choisir la bonne convention
- Sur Windows :
stdcallpour l'API Windows,cdeclpour les bibliothèques C standard - Sur Linux/Ubuntu :
cdecldans la grande majorité des cas
// Liaison simple - le nom Pascal et C sont identiques
function strlen(s: PChar): NativeInt; cdecl; external 'c';
// Liaison avec nom différent
function longueur_chaine(s: PChar): NativeInt; cdecl;
external 'c' name 'strlen';Sous Windows (.dll)
function sqlite3_open(filename: PChar; var db: Pointer): Integer; cdecl;
external 'sqlite3.dll';Sous Linux (.so)
function sqlite3_open(filename: PChar; var db: Pointer): Integer; cdecl;
external 'libsqlite3.so';Multi-plateforme
{$IFDEF WINDOWS}
const SQLITE_LIB = 'sqlite3.dll';
{$ELSE}
const SQLITE_LIB = 'libsqlite3.so';
{$ENDIF}
function sqlite3_open(filename: PChar; var db: Pointer): Integer; cdecl;
external SQLITE_LIB;La correspondance entre les types C et Pascal est cruciale pour éviter les erreurs.
| Type C | Type Pascal (32-bit) | Type Pascal (64-bit) | Notes |
|---|---|---|---|
| char | Byte ou AnsiChar | Byte ou AnsiChar | Signé ou non selon le compilateur C |
| unsigned char | Byte | Byte | |
| short | SmallInt | SmallInt | 16 bits signé |
| unsigned short | Word | Word | 16 bits non signé |
| int | Integer ou LongInt | Integer ou LongInt | 32 bits signé |
| unsigned int | LongWord ou Cardinal | LongWord ou Cardinal | 32 bits non signé |
| long | LongInt | Int64 | Dépend de la plateforme ! |
| unsigned long | LongWord | QWord | Dépend de la plateforme ! |
| long long | Int64 | Int64 | 64 bits signé |
| size_t | NativeUInt | NativeUInt | Taille dépendant de l'architecture |
| ptrdiff_t | NativeInt | NativeInt | |
| float | Single | Single | 32 bits |
| double | Double | Double | 64 bits |
| void* | Pointer | Pointer | Pointeur générique |
En C, les chaînes sont des tableaux de caractères terminés par zéro (\0).
// En C : char* ou const char*
// En Pascal : PChar ou PAnsiChar
function get_name(): PChar; cdecl; external 'mylib';
var
nom: PChar;
nomPascal: string;
begin
nom := get_name();
nomPascal := StrPas(nom); // Conversion en string Pascal
end;Attention aux encodages
PChar= pointeur vers caractère dans l'encodage par défautPAnsiChar= pointeur vers caractère ANSI (1 octet)PWideChar= pointeur vers caractère Unicode (UTF-16, 2 octets)PUTF8String= pointeur vers chaîne UTF-8
// Fonction C : void process_string(const char* str)
procedure process_string(str: PChar); cdecl; external 'mylib';
var
texte: string;
begin
texte := 'Bonjour le monde';
process_string(PChar(texte)); // Conversion temporaire
end;Important : Si la fonction C modifie la chaîne, utilisez un buffer fixe :
var
buffer: array[0..255] of AnsiChar;
begin
StrPCopy(buffer, 'Texte initial');
process_string(buffer);
end;En C, les tableaux et pointeurs sont très liés.
// En C
void process_array(int* arr, int size);// En Pascal
procedure process_array(arr: PInteger; size: Integer); cdecl;
external 'mylib';
var
tableau: array[0..9] of Integer;
begin
// @ donne l'adresse du premier élément
process_array(@tableau[0], Length(tableau));
end;Tableau dynamique
var
tableau: array of Integer;
begin
SetLength(tableau, 100);
// Les tableaux dynamiques sont déjà des pointeurs
process_array(PInteger(tableau), Length(tableau));
end;// Structure C
typedef struct {
int x;
int y;
char name[50];
} Point;// Record Pascal équivalent
type
TPoint = record
x: Integer;
y: Integer;
name: array[0..49] of AnsiChar;
end;
PPoint = ^TPoint;Problème : Le compilateur peut ajouter du padding (remplissage) pour aligner les champs en mémoire.
// Structure C
struct MyStruct {
char a; // 1 octet
int b; // 4 octets (peut être aligné sur 4 octets)
char c; // 1 octet
};
// Taille réelle : peut être 12 octets au lieu de 6 !Solution : Utiliser packed pour désactiver l'alignement
type
TMyStruct = packed record
a: AnsiChar; // 1 octet
b: Integer; // 4 octets
c: AnsiChar; // 1 octet
end;
// Taille garantie : 6 octetsAttention : Vérifiez toujours la taille avec SizeOf et comparez avec la structure C.
Si la bibliothèque C utilise un alignement spécifique :
{$PACKRECORDS C} // Utilise l'alignement C standard
type
TMyStruct = record
a: AnsiChar;
b: Integer;
c: AnsiChar;
end;
{$PACKRECORDS DEFAULT} // Retour à l'alignement par défautAutres options :
{$PACKRECORDS 1}: Aucun alignement (comme packed){$PACKRECORDS 2}: Alignement sur 2 octets{$PACKRECORDS 4}: Alignement sur 4 octets{$PACKRECORDS 8}: Alignement sur 8 octets
Les callbacks sont des fonctions Pascal que vous passez à une bibliothèque C pour qu'elle les appelle.
// En C
typedef void (*callback_t)(int value);
void register_callback(callback_t cb);// En Pascal
type
TCallback = procedure(value: Integer); cdecl;
procedure register_callback(cb: TCallback); cdecl; external 'mylib';
// Implémentation du callback
procedure mon_callback(value: Integer); cdecl;
begin
WriteLn('Valeur reçue : ', value);
end;
begin
register_callback(@mon_callback);
end;Important : Le callback doit utiliser la même convention d'appel que ce qu'attend la bibliothèque C (généralement cdecl).
Certaines bibliothèques permettent de passer un pointeur de contexte :
// En C
typedef void (*callback_with_context_t)(int value, void* user_data);
void register_callback_ex(callback_with_context_t cb, void* user_data);type
TCallbackWithContext = procedure(value: Integer; user_data: Pointer); cdecl;
procedure register_callback_ex(cb: TCallbackWithContext;
user_data: Pointer); cdecl; external 'mylib';
type
TMonContexte = record
compteur: Integer;
nom: string;
end;
PMonContexte = ^TMonContexte;
procedure callback_avec_contexte(value: Integer; user_data: Pointer); cdecl;
var
ctx: PMonContexte;
begin
ctx := PMonContexte(user_data);
Inc(ctx^.compteur);
WriteLn(ctx^.nom, ' a reçu : ', value);
end;
var
contexte: TMonContexte;
begin
contexte.compteur := 0;
contexte.nom := 'MonHandler';
register_callback_ex(@callback_avec_contexte, @contexte);
end;// En C
enum Color {
COLOR_RED = 0,
COLOR_GREEN = 1,
COLOR_BLUE = 2
};// En Pascal - Option 1 : constantes
const
COLOR_RED = 0;
COLOR_GREEN = 1;
COLOR_BLUE = 2;
// Option 2 : énumération typée (plus sûr)
type
TColor = (COLOR_RED = 0, COLOR_GREEN = 1, COLOR_BLUE = 2);// En C
#define FLAG_READ 0x01
#define FLAG_WRITE 0x02
#define FLAG_EXECUTE 0x04// En Pascal
const
FLAG_READ = $01;
FLAG_WRITE = $02;
FLAG_EXECUTE = $04;
// Utilisation
var
flags: Integer;
begin
flags := FLAG_READ or FLAG_WRITE; // Combinaison
if (flags and FLAG_READ) <> 0 then // Test
WriteLn('Lecture autorisée');
end;function open_file(filename: PChar): Integer; cdecl; external 'mylib';
var
result: Integer;
begin
result := open_file('data.txt');
if result < 0 then
WriteLn('Erreur lors de l''ouverture du fichier : ', result)
else
WriteLn('Fichier ouvert avec succès');
end;{$IFDEF UNIX}
function __errno_location(): PInteger; cdecl; external 'c' name '__errno_location';
function GetErrno: Integer; inline;
begin
Result := __errno_location()^;
end;
{$ENDIF}
procedure test_erreur;
var
f: Integer;
begin
f := FpOpen('fichier_inexistant.txt', O_RDONLY);
if f < 0 then
begin
WriteLn('Erreur : ', GetErrno);
WriteLn('Message : ', StrError(GetErrno));
end;
end;cURL est une bibliothèque populaire pour effectuer des requêtes HTTP.
unit curl_binding;
{$mode objfpc}{$H+}
interface
const
{$IFDEF WINDOWS}
CURL_LIB = 'libcurl.dll';
{$ELSE}
CURL_LIB = 'libcurl.so';
{$ENDIF}
type
TCURL = Pointer;
TCURLcode = Integer;
TCURLoption = Integer;
const
// Codes de retour
CURLE_OK = 0;
CURLE_FAILED_INIT = 2;
// Options
CURLOPT_URL = 10002;
CURLOPT_WRITEFUNCTION = 20011;
CURLOPT_WRITEDATA = 10001;
// Fonctions principales
function curl_easy_init(): TCURL; cdecl; external CURL_LIB;
function curl_easy_setopt(curl: TCURL; option: TCURLoption): TCURLcode;
cdecl; varargs; external CURL_LIB;
function curl_easy_perform(curl: TCURL): TCURLcode; cdecl; external CURL_LIB;
procedure curl_easy_cleanup(curl: TCURL); cdecl; external CURL_LIB;
function curl_easy_strerror(code: TCURLcode): PChar; cdecl; external CURL_LIB;
// Type callback pour recevoir les données
type
TWriteCallback = function(buffer: PChar; size: NativeInt;
nitems: NativeInt; userdata: Pointer): NativeInt; cdecl;
implementation
end.program test_curl;
uses
curl_binding, SysUtils;
// Callback pour recevoir les données
function write_callback(buffer: PChar; size: NativeInt;
nitems: NativeInt; userdata: Pointer): NativeInt; cdecl;
var
realsize: NativeInt;
output: ^string;
begin
realsize := size * nitems;
output := userdata;
SetLength(output^, Length(output^) + realsize);
Move(buffer^, output^[Length(output^) - realsize + 1], realsize);
Result := realsize;
end;
var
curl: TCURL;
res: TCURLcode;
response: string;
begin
response := '';
curl := curl_easy_init();
if curl <> nil then
begin
try
// Configuration
curl_easy_setopt(curl, CURLOPT_URL, PChar('https://api.github.com'));
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, @write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, @response);
// Exécution
res := curl_easy_perform(curl);
if res = CURLE_OK then
WriteLn('Réponse reçue : ', Length(response), ' octets')
else
WriteLn('Erreur : ', StrPas(curl_easy_strerror(res)));
finally
curl_easy_cleanup(curl);
end;
end
else
WriteLn('Impossible d''initialiser cURL');
end.Certaines fonctions C acceptent un nombre variable d'arguments (comme printf).
// Déclaration avec varargs
function printf(format: PChar): Integer; cdecl; varargs; external 'c';
// Utilisation
begin
printf('Nombre : %d, Texte : %s', 42, PChar('Hello'));
end;Attention : Les types doivent correspondre exactement (pas de conversion automatique).
Les macros C ne peuvent pas être importées directement. Il faut les réécrire en Pascal.
// En C - Macro
#define MAX(a, b) ((a) > (b) ? (a) : (b))// En Pascal - Fonction inline
function MAX(a, b: Integer): Integer; inline;
begin
if a > b then
Result := a
else
Result := b;
end;Si une fonction C alloue de la mémoire, vous devez la libérer avec la fonction appropriée.
// La bibliothèque alloue la mémoire
function create_object(): Pointer; cdecl; external 'mylib';
// Vous devez la libérer avec cette fonction
procedure destroy_object(obj: Pointer); cdecl; external 'mylib';
var
obj: Pointer;
begin
obj := create_object();
try
// Utilisation de obj
finally
destroy_object(obj); // Important !
end;
end;Ne jamais utiliser FreeMem ou Dispose sur de la mémoire allouée par du code C !
procedure fill_buffer(buffer: PByte; size: Integer); cdecl; external 'mylib';
var
buffer: PByte;
begin
GetMem(buffer, 1024);
try
fill_buffer(buffer, 1024);
// Traitement du buffer
finally
FreeMem(buffer);
end;
end;-
Crash au lancement
- Vérifier que la bibliothèque est présente et accessible
- Vérifier l'architecture (32/64 bits)
-
Crash lors de l'appel
- Convention d'appel incorrecte (cdecl vs stdcall)
- Types de paramètres incorrects
- Problème d'alignement des structures
-
Résultats incorrects
- Erreur dans la correspondance des types
- Problème d'endianness (rare)
- Buffer trop petit
Vérifier le chargement de la bibliothèque
function LoadLibrary(libname: PChar): TLibHandle;
var
lib: TLibHandle;
begin
lib := LoadLibrary('mylib.so');
if lib = 0 then
WriteLn('Impossible de charger la bibliothèque')
else
begin
WriteLn('Bibliothèque chargée avec succès');
FreeLibrary(lib);
end;
end;Charger dynamiquement les fonctions
uses
dynlibs;
type
TMyFunction = function(x: Integer): Integer; cdecl;
var
lib: TLibHandle;
my_func: TMyFunction;
begin
lib := LoadLibrary('mylib.so');
if lib <> 0 then
begin
my_func := TMyFunction(GetProcAddress(lib, 'my_function'));
if Assigned(my_func) then
WriteLn('Résultat : ', my_func(42))
else
WriteLn('Fonction non trouvée');
FreeLibrary(lib);
end;
end;- Un fichier par bibliothèque : Créez une unit dédiée pour chaque bibliothèque C
- Séparation interface/implémentation : Définissez les types et fonctions dans l'interface
- Documentation : Commentez les particularités et les gotchas
unit mylib_binding;
{$mode objfpc}{$H+}
interface
// Définition de la bibliothèque selon l'OS
const
{$IFDEF WINDOWS}
MYLIB = 'mylib.dll';
{$ELSE}
{$IFDEF DARWIN}
MYLIB = 'libmylib.dylib';
{$ELSE}
MYLIB = 'libmylib.so';
{$ENDIF}
{$ENDIF}
// Types
type
TMyHandle = Pointer;
// Constantes
const
MY_SUCCESS = 0;
MY_ERROR = -1;
// Fonctions
function my_init(): TMyHandle; cdecl; external MYLIB;
procedure my_cleanup(handle: TMyHandle); cdecl; external MYLIB;
implementation
// Code d'initialisation si nécessaire
end.- Testez chaque fonction binding individuellement
- Comparez les résultats avec un programme C équivalent
- Testez sur toutes les plateformes cibles
- Vérifiez les cas limites (valeurs nulles, buffers vides, etc.)
Les bindings C/C++ sont un outil puissant pour étendre les capacités de FreePascal. En maîtrisant :
- La correspondance des types
- Les conventions d'appel
- L'alignement mémoire
- La gestion des callbacks
Vous pouvez intégrer pratiquement n'importe quelle bibliothèque C dans vos projets FreePascal, tout en conservant la sécurité et l'élégance du langage Pascal.
N'oubliez pas de toujours :
- Vérifier la documentation de la bibliothèque C
- Tester exhaustivement vos bindings
- Gérer correctement la mémoire
- Prévoir la portabilité multi-plateforme dès le début