menu
  Home  ==>  articles  ==>  bdd  ==>  interbase  ==>  interbase_udf   

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:

image

Pour ajouter une UDF, il faut

  • écrire la DLL qui exporte la routine qui nous intéresse

    image

  • placer la DLL dans le répertoire des UDF Interbase

    image

  • ajouter la routine en tant que EXTERNAL FUNCTION aux objets des bases qui souhaitent l'utiliser

    image

  • utiliser la routine dans nos requêtes SQL

    image



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 SysUtilsWindowsClasses;

    function f_trim_right(p_pcharPChar): PCharcdeclexport;
    function f_right_pad(p_pcharPCharvar pv_target_lengthInteger;
        p_pad_pcharPChar): PCharcdeclexport;
    function f_string_length(p_pcharPChar): Integercdeclexport;
    function f_sub_string(p_pcharPChar;
        VAR pv_positionpv_lengthInteger): PCharcdeclexport;

  implementation
    uses u_udf_globals;

    function f_trim_right(p_pcharPChar): PChar;
      begin
        Result:= f_build_pchar(PChar(TrimRight(String(p_pchar))), nil, 0);
      end// f_trim_right

    function f_right_pad(p_pcharPCharvar pv_target_lengthInteger;
        p_pad_pcharPChar): PCharcdeclexport;
        // -- complement the string to the right up to the desired length
      var l_stringl_padded_stringString;
          l_padded_lengthl_lengthl_indexInteger;
      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_lengthpv_target_lengthand (l_padded_length<= pv_target_lengthl_length)
          then
            begin
              // -- if padding is a string, can return a string longer than pv_target_length
              for l_index:= 1 to (pv_target_lengthl_lengthdiv l_padded_length do
                l_string:= l_stringl_padded_string;
            end;

        Result:= f_build_pchar(PChar(l_string), nil, 0);
      end// f_right_pad

    function f_string_length(p_pcharPChar): Integer;
      begin
        Result:= StrLen(p_pchar);
      end// f_string_length

    function f_sub_string(p_pcharPChar;
        VAR pv_positionpv_lengthInteger): PCharcdeclexport;
      var l_stringl_substringString;
      begin
        l_string:= String(p_pchar);
        l_substring:= Copy(l_stringpv_positionpv_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 WindowsSysUtils;

    function f_build_pchar(p_source_pcharp_destination_pcharPChar;
        p_lengthDWORD): PChar;

  implementation

    function malloc(SizeInteger): Pointercdeclexternal 'msvcrt.dll' ;

    function f_build_pchar(p_source_pcharp_destination_pcharPChar;
        p_lengthDWORD): PChar;
      begin
        result:= p_destination_pchar;

        if (resultnil)
          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_pcharnilor (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:

image



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(SenderTObject);
  begin
    initialize_ib_database(k_database_namek_userk_passwordIbDatabase1);

    if f_open_ib_database(''IbDatabase1)
      then begin
          display('ok_open');

          with IbSql1 do
          begin
            Close;
            Database:= IbDatabase1;
          end;

          if f_execute_ibsql(IbSql1k_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(SenderTObject);
    // -- extract all the stored procedures
  var l_itemInteger;
  begin
    initialize_ib_database(k_database_namek_userk_passwordIbDatabase1);
    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:

image



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_locationFROM 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(SenderTObject);
  var l_rtrim_namel_nameString;
  begin
    initialize_ib_database(k_database_name,
        k_userk_passwordIbDatabase1);

    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_pcharPChar): PChar;
        cdeclexternal k_udf_string;
    function f_right_pad(p_pcharPCharvar p_lengthInteger;
        p_pad_pcharPChar): PCharcdeclexternal k_udf_string;
    function f_string_length(p_pcharPChar): Integer;
        cdeclexternal k_udf_string;
    function f_sub_string(p_pcharPCharp_position,
        p_lengthInteger): PCharcdeclexternal 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):

image

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:

image

Mais quelle est la syntaxe de ces UDF: en fait elles sont fournies comme exemple d'UDF dans le répertoire:

image

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" :
    Nom :
    E-mail :
    Commentaires * :
     

  • 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.
Créé: oct-06. Maj: aou-15  148 articles, 471 sources .ZIP, 2.021 figures
Contact : John COLIBRI - Tel: 01.42.83.69.36 / 06.87.88.23.91 - email:jcolibri@jcolibri.com
Copyright © J.Colibri   http://www.jcolibri.com - 2001 - 2015
Retour:  Home  Articles  Formations  Développement Delphi  Livres  Pascalissime  Liens  Download
l'Institut Pascal

John COLIBRI

+ Home
  + articles_avec_sources
    + bases_de_donnees
      + programmation_oracle
      + interbase
        – interbase_blobs
        – interbase_tutorial
        – interbase_dbexpress
        – interbase_ibx_net
        – ib_dbexpress_net
        – delphi_8_ado_net
        – borland_data_provider
        – sql_script_extraction
        – interbase_udf
        – sql_script_executer
        – ib_blob_extraction
        – insert_blob_script
        – ib_stored_procedures
      + sql_server
      + firebird
      + mysql
      + xml
      – paradox_via_ado
      – mastapp
      – delphi_business_objects
      – clientdataset_xml
      – data_extractor
      – rave_report_tutorial
      – visual_livebindings
      – migration_bde
    + web_internet_sockets
    + prog_objet_composants
    + office_com_automation
    + colibri_utilities
    + uml_design_patterns
    + graphique
    + delphi
    + outils
    + firemonkey
    + vcl_rtl
    + colibri_helpers
    + colibri_skelettons
  + formations
  + developpement_delphi
  + présentations
  + pascalissime
  + livres
  + entre_nous
  – télécharger

contacts
plan_du_site
– chercher :

RSS feed  
Blog

Formation Perfectionnement Delphi Les techniques avancées : la programmation objet, l'écriture de composants, l'accès aux bases de données, Xml, le multi-tâche, la programmation Internet - 5 jours