Fonctions Utilisateur Interbase (UDF) - John COLIBRI. |
- résumé : Création et utilisation de fonctions utilisateur Interbase en Delphi
- mots clé : UDF - Interbase - User Defined Function - DLL
- logiciel utilisé : Windows XP personnel, Delphi 6.0, Interbase 6
- matériel utilisé : Pentium 2.800 Mhz, 512 Meg de mémoire, 250 Giga disque dur
- champ d'application : Delphi 1 à 2006 sur Windows, Interbase
- niveau : développeur Delphi / Interbase
- plan :
1 - Les UDF Interbase
Interbase fut le premier moteur à proposer l'ajout de fonctions externes. Les UDF (User Defined Functions sont des routines placées dans des DLL Windows qui peuvent être utilisées dans des requêtes SQL.
Nous nous sommes surtout intéressés aux UDF car la base de donnée du Portail Asp Net Delphi emploie de telles fonctions. Pour pouvoir intégralement recréer le Portail, il faut ajouter les UDF, d'où le présent article.
2 - Ajout d'une UDF 2.1 - Principe des EXTERNAL FUNCTIONS Voici la situation avant l'ajout d'une UDF: Pour ajouter une UDF, il faut
- écrire la DLL qui exporte la routine qui nous intéresse
- placer la DLL dans le répertoire des UDF Interbase
- ajouter la routine en tant que EXTERNAL FUNCTION aux objets des bases qui souhaitent l'utiliser
- utiliser la routine dans nos requêtes SQL
2.2 - La fonction utilisée Nous allons écrire des UDF qui ajouterons à Interbase quelques fonctions de traitement de chaînes de caractères:
- RTRIM(p_chaîne) qui supprime les espaces situés après une chaîne
- STRLEN(p_chaîne) qui retourne la taille d'une chaîne
- RPAD(p_chaîne, p_taille_max, p_caractère) qui complète une chaîne avec le caractère p_caractère pour arriver à la taille p_taille_max
- SUBSTR(p_chaîne, p_position, p_nombre) qui extrait de p_nombre caractères à partir de p_position
3 - Le Projet Delphi
3.1 - L'écriture de l'UDF Les fonctions externes doivent être placées dans une .DLL Windows. Cette .DLL peut être réalisée avec n'importe quel outil. En général, n'importe lequel n'est pas assez bon, et c'est pourquoi nous
utiliserons Delphi. L'unité qui calcule les chaînes est la suivante:
unit u_udf_string; interface
uses SysUtils, Windows, Classes;
function f_trim_right(p_pchar: PChar): PChar; cdecl; export;
function f_right_pad(p_pchar: PChar; var pv_target_length: Integer;
p_pad_pchar: PChar): PChar; cdecl; export;
function f_string_length(p_pchar: PChar): Integer; cdecl; export;
function f_sub_string(p_pchar: PChar;
VAR pv_position, pv_length: Integer): PChar; cdecl; export;
implementation uses u_udf_globals;
function f_trim_right(p_pchar: PChar): PChar;
begin
Result:= f_build_pchar(PChar(TrimRight(String(p_pchar))), nil, 0);
end; // f_trim_right
function f_right_pad(p_pchar: PChar; var pv_target_length: Integer;
p_pad_pchar: PChar): PChar; cdecl; export;
// -- complement the string to the right up to the desired length
var l_string, l_padded_string: String;
l_padded_length, l_length, l_index: Integer;
begin
l_string:= String(p_pchar);
l_length:= Length(l_string);
l_padded_string:= String(p_pad_pchar);
l_padded_length:= Length(l_padded_string);
if (l_length< pv_target_length) and (l_padded_length<= pv_target_length- l_length)
then begin
// -- if padding is a string, can return a string longer than pv_target_length
for l_index:= 1 to (pv_target_length- l_length) div l_padded_length do
l_string:= l_string+ l_padded_string;
end;
Result:= f_build_pchar(PChar(l_string), nil, 0);
end; // f_right_pad
function f_string_length(p_pchar: PChar): Integer;
begin Result:= StrLen(p_pchar);
end; // f_string_length
function f_sub_string(p_pchar: PChar;
VAR pv_position, pv_length: Integer): PChar; cdecl; export;
var l_string, l_substring: String;
begin
l_string:= String(p_pchar);
l_substring:= Copy(l_string, pv_position, pv_length);
Result:= f_build_pchar(PChar(l_substring), nil, 0);
end; // f_sub_string end. |
Cette unité utilise une unité d'allocation de pChar: unit u_udf_globals;
interface uses Windows, SysUtils;
function f_build_pchar(p_source_pchar, p_destination_pchar: PChar;
p_length: DWORD): PChar; implementation
function malloc(Size: Integer): Pointer; cdecl; external 'msvcrt.dll' ;
function f_build_pchar(p_source_pchar, p_destination_pchar: PChar;
p_length: DWORD): PChar;
begin result:= p_destination_pchar;
if (result= nil)
then begin
if (p_length= 0)
then p_length:= StrLen(p_source_pchar)+ 1;
result:= malloc(p_length);
end;
if (p_source_pchar<> result)
then begin
if (p_source_pchar= nil) or (p_length= 1)
then result[0]:= #0
else Move(p_source_pchar^, result^, p_length);
end; end; // f_build_pchar
end. | Et le texte de la DLL est le suivant:
library d_string_udf; uses SysUtils,
Classes, u_udf_string; exports
f_trim_right, f_right_pad, f_string_length,
f_sub_string ; end. |
Il faut donc
| compiler cette librairie | |
compiler le fichier d_string_udf.dll dans le répertoire d'Interbase qui contient toutes les .DLL qui seront intégrées au moteur. Dans notre cas, il s'agit de C:\Program Files\Borland\InterBase\UDF\ |
| rebooter le PC pour que le moteur Interbase prenne cette .DLL en compte |
3.2 - Ajout des EXTERNAL FUNCTIONS
3.2.1 - Création d'une base Nous allons créer une base spécialement pour tester nos UDF. Vous pouvez très bien utiliser une base préexitante de votre choix. Le détail de la création de la base, de la création et du remplissage d'une
table contenant quelques CHAR et VARCHAR se trouve dans le .ZIP téléchargeable. Quoi qu'il en soit, il s'agit d'une base de cours de formations contenant la table suivante:
CREATE TABLE training
( f_id INTEGER, f_name CHAR(30),
f_location VARCHAR(10), f_days INTEGER,
f_price FLOAT ) | remplie avec les éléments suivants:
100 Interbase Client Server Paris 3 1400
101 ADO.Net Sql Server London 3 1400
102 ASP.Net Oracle Hong Kong 3 1400
103 TCP / IP Internet Montreal 3 1400
104 Turbo Delphi Design Patterns Sao Paulo 3 1400
105 Delphi OO Programming Dallas 3 1400
| Voici d'ailleurs le projet avec la table de test:
3.2.2 - Ajout des UDF
Nous allons ajouter aux UDF connues du moteur Interbase la fonction qui calcule la taille d'une string: - le nom de la fonction est StrLen
- la fonction provient de la fonction Delphi f_string_length, qui est située dans la .DLL "d_string_udf.dll"
La requête SQL pour ajouter cette UDF est:
DECLARE EXTERNAL FUNCTION StrLen CSTRING(32767)
RETURNS INTEGER BY VALUE
ENTRY_POINT 'f_string_length' MODULE_NAME 'd_string_udf' |
Pour ajouter cette UDF, il suffit d'envoyer cette requête au moteur: const
k_create_strlen= 'DECLARE EXTERNAL FUNCTION STRLEN'
+ ' CSTRING(32767)'
+ ' RETURNS INTEGER BY VALUE'
+ ' ENTRY_POINT ''f_string_length'' MODULE_NAME ''d_string_udf''';
procedure TForm1.create_udf_Click(Sender: TObject);
begin
initialize_ib_database(k_database_name, k_user, k_password, IbDatabase1);
if f_open_ib_database('', IbDatabase1)
then begin
display('ok_open');
with IbSql1 do
begin Close;
Database:= IbDatabase1;
end;
if f_execute_ibsql(IbSql1, k_create_strlen)
then display('ok_strlen');
end; end; // create_udf_Click |
3.2.3 - Suppression d'une fonction externe Pour retirer cette fonction du moteur Interbase, nous appelons naturellement:
DROP EXTERNAL FUNCTION StrLen |
Le code se trouve dans le .ZIP téléchargeable
3.2.4 - Liste des fonctions Et pour vérifier si la fonction est bien enregistrer, nous utilisons un composant IbExtract en lui demandant d'afficher les UDF:
procedure TForm1.display_udf_Click(Sender: TObject);
// -- extract all the stored procedures
var l_item: Integer; begin
initialize_ib_database(k_database_name, k_user, k_password, IbDatabase1);
if f_open_ib_database('', IbDatabase1)
then begin
with IBExtract1 do
begin
ExtractObject(eoFunction, '');
with Items do
for l_item:= 0 to Count- 1 do
display(Strings[l_item]);
end; // with IBExtract1
end; end; // display_udf_Click |
Voici le projet à ce stade:
3.2.5 - Test des UDF Pour utiliser une fonction externe, nous l'appelons depuis une requête SQL, que
celle-ci soit lancée depuis le Client ou depuis une procédure cataloguée. Dans le cas de StrLen, nous pouvons appeler, par exemple:
SELECT StrLen(f_location) FROM training WHERE f_id= 105
| Depuis Delphi, nous utilisons un IbQuery et récupérons la valeur dans la première colonne de la réponse:
const k_select_strlen= 'SELECT F_LOCATION, STRLEN(F_LOCATION) '
+ ' FROM TRAINING WHERE f_id= 105';
procedure TForm1.select_strlen_Click(Sender: TObject);
var l_rtrim_name, l_name: String; begin
initialize_ib_database(k_database_name,
k_user, k_password, IbDatabase1);
if f_open_ib_database('', IbDatabase1)
then begin
display('ok_open');
open_ibquery(display_udf_ibquery_, k_select_strlen);
l_name:= display_udf_ibquery_.Fields[0].AsString;
l_rtrim_name:= display_udf_ibquery_.Fields[1].AsString;
display(IntToStr(Length(l_name))+ ' |'+ l_name+ '|');
display(l_rtrim_name); end;
end; // select_strlen_Click |
3.2.6 - Test préalable des fonctions
Pour certaines fonctions (RPad, par exemple), nous avons eu un peu de mal à faire fonctionner le tout. Nous avons alors testé la fonction de la .DLL séparément, pour nous assurer que le code était correct.
Pour cela nous avons utilisé une unité d'importation qui permettait d'appeler facilement les fonctions de la .DLL:
unit u_import_udf_string; interface
const k_udf_string= 'd_string_udf.dll';
function f_trim_right(p_pchar: PChar): PChar;
cdecl; external k_udf_string;
function f_right_pad(p_pchar: PChar; var p_length: Integer;
p_pad_pchar: PChar): PChar; cdecl; external k_udf_string;
function f_string_length(p_pchar: PChar): Integer;
cdecl; external k_udf_string;
function f_sub_string(p_pchar: PChar; p_position,
p_length: Integer): PChar; cdecl; external k_udf_string;
implementation end. // u_zzz |
Pour la fonction RPAD, nous avions même du mal à comprendre ce que la fonction faisait (c'était une fonction que nous n'avions pas écrite initialement), et nous avons testé la fonction dans le projet même (pour bénéficier de tous les
affichages de mise au point), avant de la réinjecter dans la .DLL
3.3 - Surprises, Surprises Parmi les quelques curiosités: - les valeurs entières doivent être passées en paramètre VAR
- après avoir placé la .DLL dans le répertoire UDF d'Interbase, il faut rebooter la machine
- comme toujours en Interbase, il vaut mieux rédiger les identificateurs des
requêtes qui écrivent sur la base en MAJUSCULES (nous créons tout en minuscule, mais quelqu'un passe le tout en majuscules. Les SELECT ne posent pas de problèmes, mais les DROP ou autres exigent des
majuscules)
- lorsque nous plaçons certaines UDF entre SELECT et FROM, elles ne sont pas prises en compte.
- RTRIM n'est pas utile dans le WHERE: apparemment, Interbase supprime les espaces à droite sans avoir besoin de RTRIM
- pour SUBSTR, la fonction extrait bien la sous-chaîne, mais retourne une chaîne de la taille maximale (85 dans notre cas), avec au début la sous-chaîne
- la fonction SUBSTR fournie avec Interbase a une autre sémantique que notre SUBSTR: nous utilisons
SUBSTR(p_chaîne, p_position, p_taille) et eux
SUBSTR((p_chaîne, p_indice_debut, p_indice_fin)
Car il y a, en effet des UDF fournies par défaut par Interbase. Pour
l'anectdote, nous nous en sommes apperçus, car le projet fonctionnait sans avoir à copier la .DLL dans le répertoire UDF, car les fonctions existaient dans la .DLL par défaut IB_UDF.DLL. Toutes sauf RPAD, qui a donc provoqué une
erreur (pas lors de CREATE EXTERNAL, mais lors de l'appel dans un SELECT ) La preuve, voici le répertoire des UDF (avant ajout de notre .DLL):
Pour connaître le contenu de ces UDF, nous avons utiliser notre analyseur de fichier PE (Portable Executable), qui affiche les fonctions exportés par
une .DLL (ou tout autre .EXE). Voici ce qu'il nous a fourni pour IB_UDF.DLL: Mais quelle est la syntaxe de ces UDF: en fait elles sont fournies comme
exemple d'UDF dans le répertoire: Vous trouverez le source (C) des UDF, avec des commentaires bien écrits, ainsi que le script qui incorpore ces UDF dans une base quelconque.
4 - Télécharger le code source Delphi Vous pouvez télécharger: - d_string_udf.zip : la DLL (sources et le fichier .DLL) (66 K)
- test_udf.zip : le projet de test complet (création de la base, de la table d'essai, création des EXTERNAL FUNCTIONs, test direct, test dans des requêtes et la .DLL) (84 K)
Ce .ZIP qui comprend:
- le .DPR, la forme principale, les formes annexes eventuelles
- les fichiers de paramètres (le schéma et le batch de création)
- dans chaque .ZIP, toutes les librairies nécessaires à chaque projet (chaque .ZIP est autonaume)
Ces .ZIP, pour les projets en Delphi 6, contiennent des chemins RELATIFS. Par conséquent: - créez un répertoire n'importe où sur votre machine
- placez le .ZIP dans ce répertoire
- dézippez et les sous-répertoires nécessaires seront créés
- compilez et exécutez
Ces .ZIP ne modifient pas votre PC (pas de changement de la Base de Registre, de DLL ou autre). Pour supprimer le projet, effacez le répertoire.
La notation utilisée est la notation alsacienne qui consiste à préfixer les identificateurs par la zone de compilation: K_onstant, T_ype, G_lobal,
L_ocal, P_arametre, F_unction, C_lasse. Elle est présentée plus en détail dans l'article La
Notation Alsacienne
Comme d'habitude: - nous vous remercions de nous signaler toute erreur, inexactitude ou
problème de téléchargement en envoyant un e-mail à jcolibri@jcolibri.com. Les corrections qui en résulteront pourront aider les prochains lecteurs
- tous vos commentaires, remarques, questions, critiques, suggestion d'article, ou mentions d'autres sources sur le même sujet seront de même les bienvenus à jcolibri@jcolibri.com.
- plus simplement, vous pouvez taper (anonymement ou en fournissant votre e-mail pour une réponse) vos commentaires ci-dessus et nous les envoyer en cliquant "envoyer" :
- et si vous avez apprécié cet article, faites connaître notre site, ajoutez un lien dans vos listes de liens ou citez-nous dans vos
blogs ou réponses sur les messageries. C'est très simple: plus nous aurons de visiteurs et de références Google, plus nous écrirons d'articles.
5 - Références L'article dont nous sommes partis jadis:
6 - L'auteur John COLIBRI est passionné par le développement Delphi et les applications de Bases de Données. Il a écrit de nombreux livres et articles, et partage son temps entre le développement de projets (nouveaux projets, maintenance, audit, migration BDE, migration Xe_n, refactoring) pour ses clients, le
conseil (composants, architecture, test) et la
formation. Son site contient des articles
avec code source, ainsi que le programme et le calendrier des stages de formation Delphi, base de données, programmation objet, Services Web, Tcp/Ip et
UML qu'il anime personellement tous les mois, à Paris, en province ou sur site client. |