Project

General

Profile

Actions

Coder une bibliothèque Scol en C - Niveau 3

Objectif : nouvel objet Scol, fonctions callbacks

Moyens :

  • définir un nouveau type Scol;
  • fonctions de création et de destruction d'un objet;
  • manipulation de l'objet;
  • fonctions réflexes (callbacks) sur cet objet;
  • intégration simple de la librairie libcurl
  • code source de la bibliothèque;
  • exemple d'utilisation dans une application Scol.

STATUT : COMPLET
Version : 1.0
Date : Novembre 2010 (initialement diffusé sur http://www.irizone.net)
Auteur : iri
Licence du tutoriel : GNU FDL v1.3
Licence du code source : GNU/GPL v3
Téléchargement : http://www.irizone.net/dl/tutos/vmscol/vm_example_3.tar.gz
Langage : C
Ok pour toute version de Scol Windows et GNU/Linux (> 4.0).

La bibliothèque libcurl, exemple d'intégration

Libcurl est une bibliothèque libre
et gratuite pour de très nombreux systèmes et langages. Elle est très utilisée et
a atteint un bon degré de robustesse. Elle est capable d'utiliser de nombreux
protocoles, sous divers formats.

En Scol, les connexions au réseau sont actuellement codées en bas niveau, grâce
aux sockets. Peu de protocoles sont disponibles (http et telnet) et sous certaines
conditions. Utiliser une telle librairie de plus haut niveau permettrait de s'affranchir
du code bas niveau, toujours plus fastidieux, d'intégrer une biliothèque robuste
et éprouvée et d'élargir les capacités de Scol. En contrepartie, son utilisation
rend Scol plus dépendant. C'est pourquoi les "anciennes" fonctions ne devraient
pas être dépréciées.

Comment intégrer une telle bibliothèque ?

Au niveau de Scol, il n'y a rien de particulier à faire. Au niveau du code
source, il faut bien évidemment inclure les fichiers d'en-tête nécessaires. Au
niveau du binaire à diffuser, deux solutions s'offrent à vous :

  1. inclure la bibliothèque dans la votre. Cela rend l'utilisateur final indépendant,
    il n'a pas à installer libcurl de façon disjointe. Cependant, s'il la possède déjà,
    c'est un peu inutile. De plus, le poids de votre bibliothèque sera plus important.
  2. ne pas inclure la bibliothèque dans la votre. Cela la rendra plus légère au
    niveau du poids et si l'utilisateur final la possède déjà, il n'y aura pas d'effet
    doublon. En revanche, s'il ne la possède pas, elle ne fonctionnera pas tant qu'il
    ne l'aura pas installé.

Il n'y a pas de solutions idéales mais un compromis à faire. À vous de voir.

Exemple : Se connecter à un serveur et récupérer un fichier

Il s'agit de l'utilisation la plus simple de libcurl : lui donner une url et
enregistrer la réponse du serveur dans un fichier local. Nous allons donc le faire.

Il n'y a rien de particulier par rapport à ce qui a été vu dans les niveaux
précédents. Le reste du code C est pour appeler et interfacer la bibliothèque libcurl.

  1. On récupère les données depuis la pile Scol et on teste leur validité;
  2. On initialise la bibliothèque;
  3. On alloue dynamiquement nos ressources (il ne faudra donc pas oublier de les
    libérer !);
  4. On envoie la requête à libcurl et on définit la callback d'écriture des données
    reçues. À ce sujet, nous ne verrons pas ici (mais plus bas dans ce tutoriel)
    l'ajout d'une callback Scol. C'est libcurl qui se chargera d'écrire dans notre
    fichier. En effet, par défaut, celle-ci écrit les données reçues dans un fichier
    (et si ce fichier n'est pas défini, ce sera sur la sortie standard).</li><br />
  5. Nous libérons nos ressources;
  6. Nous empilons notre réponse dans la pile Scol.

La fonction Scol retournera 0 si tout s'est correctment passé. Autrement, nil
sera retourné si l'url ou le fichier local valent eux-même nil, si le fichier local
ne peut être ouvert en écriture ou si la libcurl n'a pas pu s'initialiser. Dans les
autres cas, elle retournera un entier positif indiquant le type d'erreur que
libcurl a rencontré. Pour avoir une liste complète des erreurs possibles, consultez
cette page.

Notez que plus loin une méthode plus conviviale de gestion des erreurs sera
proposée (voir dans l'archive téléchargeable).

Code source de la fonction :

int sc_getFile (mmachine m)
{
    int murl, mlocalfile, len;
    char * url, * localfile, * err = NULL;

    CURL * hCurl;     /* handle système */
    CURLcode rCurl;   /* result */
    FILE * fp = NULL;

    MMechostr (MSKDEBUG, "sc_getFile : entering ...n");

    mlocalfile = MTOP (MMpull (m));
    murl = MTOP (MMpull (m));

    if ((mlocalfile == NIL) || (murl == NIL))
    {
        MMechostr (MSKDEBUG, "sc_getFile error : url or localfile is niln");
        MMpush (m, NIL);
        return 0;
    }

    hCurl = curl_easy_init();
    if (hCurl)    /* init ok */
    {
        /* Nous n'avons pas besoin d'avoir cette donnée dans la pile,
        nous n'utilisons donc pas les fonctions d'allocation de la machine Scol
        mais les fonctions d'allocation (et de libération) standard du C.*/
        len = sizeof (char) * (MMsizestr (m, mlocalfile));
        localfile = (char *) malloc (len+1);
        strncpy (localfile, MMstartstr (m, mlocalfile), len);
        localfile[len] = '\0';

        fp = fopen (localfile, "w");
        if (fp == NULL)
        {
            curl_easy_cleanup (hCurl);
            free (localfile); localfile = NULL;
            MMpush (m, NIL);
            return 0;
        }

        err = (char *) malloc (sizeof (char) * (CURL_ERROR_SIZE +1));
        len = sizeof (char) * (MMsizestr (m, murl));
        url = (char *) malloc (len+1);
        strncpy (url, MMstartstr (m, murl), len);
        url[len] = '\0';

        curl_easy_setopt (hCurl, CURLOPT_URL, url);
        curl_easy_setopt (hCurl, CURLOPT_WRITEFUNCTION, fwrite);
        curl_easy_setopt (hCurl, CURLOPT_WRITEDATA, (FILE *) fp);
        curl_easy_setopt (hCurl, CURLOPT_USERAGENT, "libcurl-agent/1.0");
        curl_easy_setopt (hCurl, CURLOPT_ERRORBUFFER, err);
        rCurl = curl_easy_perform (hCurl);

        MMechostr (MSKDEBUG, "sc_getFile : err = %s ...n", err);
        curl_easy_cleanup (hCurl);
        fclose (fp);

        free (localfile); localfile = NULL;
        free (url); url = NULL;
        free (err); err = NULL;

        MMpush (m, ITOM (rCurl));
        return 0;
    }

    MMpush (m, NIL);
    return 0;
}

Exemple d'utilisation avancée

Nouveau type Scol

À présent, nous allons voir comment ajouter un nouveau type Scol et comment
gérer des callbacks Scol, le tout en utilisant libcurl comme fil rouge.

Pour créer un nouveau type Scol, il faut :

  1. L'enregistrer au sein de la machine Scol, afin qu'il soit connu;
  2. Le paramétrer et, notamment, définir sa fonction de destruction (ou plus
    exactemement sa fonction réflexe de destruction).

L'enregistrement se réalise grâce à la fonction OBJregister : elle est
définie dans le kernel, au sein du fichier "kernel/scolobj.c".

Elle prend 4 arguments :

  1. Le nombre de callbacks attendues, associées aux objets Scol de ce type. Si vous
    définissez par exemple 4 callbacks associées aux objets de ce type (par exemple,
    création, destruction, affectation et récupération), alors il faudra indiquer 4;
  2. Un flag indiquant si les objets de ce type sont détruits si leur objet parent
    est détruit (si un tel type objet parent est défini, ce qui est rare). Généralement,
    on laisse à 1, ce qui implique la destruction si le parent est détruit (et comme
    il n'y a généralement pas de parent, cela ne change rien !);
  3. La callback interne de destruction, appelée lorsqu'un objet de ce type est
    implicitement ou explicitement détruit;
  4. Le nom interne de ce type (qui sera utilisé dans certains cas particuliers, autant
    lui donner un nom suffisamment explicite).

Par exemple :

#define NB_REFLEX 4
int MyObjectType;
MyObjectType = OBJregister (NB_REFLEX, 1, MyObjTypeDestroy, "MyNewObjectType");

Comment est définie la callback de destruction ?

Son prototype C est :

int MyObjTypeDestroy (mmachine, int, int);

Son premier argument est bien sur la machine Scol concernée, son second est le
pointeur vers l'objet système et le troisième est le pointeur vers l'objet Scol.
Cette fonction n'a théoriquement pas à être appelée directement, son appel devrait
rester transparente pour le programeur.
Nous pouvons lui faire faire tout un tas de choses, selon les cas. De manière
générale, son application minimale consiste à supprimer la référence de l'objet
dans la machine Scol, via MMstore à NULL. Le code habituellement minimal est le suivant :

int MyObjTypeDestroy (mmachine m, int handlesys, int handlem)
{
    TYPE_C * obj;

    obj = (TYPE_C *) MMfetch (m, MTOP (handlem), OBJTYPE_C_HANDLE);
    if (obj == NULL)
    {
        MMechostr(MSKDEBUG, "MyObjTypeDestroy : object already destroyedn");
        return 0;
    }

    MMstore (m, MTOP (handlem), OBJTYPE_C_HANDLE, (int) NULL);
    MMechostr(MSKDEBUG, "MyObjTypeDestroy: object has been destroyedn");

    return 0;
}

Le flag OBJTYPE_C_HANDLE est un flag utilisé lors de la création de l'objet, ce que nous verrons ci-après.

Nouvel objet Scol

Pour créer un objet d'un type Scol, il est nécessaire de suivre ces étapes :

  1. Création de l'objet système (en C ou en C++), avec tout ce que cela peut impliquer.
    Ici, l'utilisation de libcurl va nous simplifier la tâche et nous n'aurons donc
    pas à coder des processus de plus ou moins bas niveaux comme l'allocation mémoire
    et tout le reste;
  2. Allouer la mémoire au sein de la machine Scol. Nous utiliserons pour cela la
    fonction <em>MMalloc</em> (ne confondez pas avec l'allocation système, via malloc);
  3. Stocker l'objet dans la machine Scol, grâce à la fonction MMstore;
  4. Empiler l'objet dans la pile Scol, afin de le rendre disponible au programmeur
    Scol (MMpush);
  5. Créer la référence de l'objet avec le nouveau type avec OBJcreate.

Exemple :

sizetab = sizeof (TYPE_OBJECT_C) + 1;
objtab = MMalloc (m, sizetab, TYPETAB);
if (objtab == NIL)
{
    MMpush (m, NIL);
    return 0;
}
MMstore (m, objtab, OBJTYPE_C_HANDLE, (int) handle_sys);
MMpush (m, PTOM (objtab));
OBJcreate (m, MyObjectType, (int) handle_sys, parent_type, parent);

MMalloc prend donc trois arguments. Les deux premiers sont logiques :
respectivement la machine Scol dans laquelle allouée et la taille à allouer. Le
troisième est un flag qui peut prendre la valeur de TYPETAB (array) ou TYPEBUF
(buffer).

MMstore attend 4 arguments : la machine Scol, le pointeur qui vient
d'être alloué et qui contiendra l'objet, un flag spécifique que vous définissez
vous-même et le pointeur système pour la liaison.

MMpush empile le pointeur de l'objet (normal !).

OBJcreate s'approprie 5 arguments : la machine Scol, une variable
interne qui contient le type Scol, le pointeur système et, de façon optionnelle,
le type de l'objet parent et le parent lui-même, s'il y a lieu (sinon, une valeur
comme -1 ou NIL pour ses deux derniers convient).

Il y a un élément dont nous n'avons absolument pas parler, c'est le canal, qui est
à la base de Scol, dans lequel cet objet doit être crée. Il est impératif
qu'il soit parmi les éléments de la pile, typiquement présent dans les arguments
de la fonction Scol appelant à la création de l'objet. Lors de l'appel à OBJcreate,
il faut que le canal considéré soit à l'étage 1 de la pile, l'étage
0 étant occupé par le pointeur objet (MMpush précédent). Si le canal n'est pas présent
à cet étage 1 (assurez vous en !), vous devez l'y placer, grâce à MMset ou à la macro
INVERT suivant les cas. Ou, à défaut, en récupérant le canal courant dans la pile.
N'oubliez pas !

L'histoire du canal est cruciale. C'est la principale raison de plantage ou de
non création d'objet lorsque vous testerez votre code. Le canal n'est jamais
explicitement utilisé et il est ainsi facilement oublié. Il convient également
de tester sa validité <strong>avant</strong> toutes manipulations de création C ou
Scol. De plus, ce test permet de ne pas l'oublier ....

En étudiant le code existant, la "ruse" suivante est souvent utilisée : tous
les arguments envoyés par la fonction Scol sont dépilés (MMpull) excepté le dernier
(qui est donc le prmier argument dans la fonction Scol). Remarquez que ce premier
argument est le canal. Ainsi, alors qu'on a utilisé MMpull pour les autres arguments,
l'utilisation de MMget (m, 0) pour tester le canal permet de le garder à l'étage 0.
Ensuite, grâce au MMpush sur le pointeur, il remonte à l'étage 1 ...:) Ceci dit,
cette technique n'est pas toujours faisable et nous ne l'appliquerons pas.

Ajouter une callback à un objet Scol

Lorsqu'on crée un objet Scol, il est généralement utile de lui associer une
ou plusieurs fonctions réflexes pour traiter des évènements.

Cela passe par OBJaddreflex avec ses trois arguments : la machine Scol,
la variable qui contient le type Scol et un flag spécifique à votre callback.
Ce flag, vous le définissez vous-même et sa valeur devrait être inférieure au nombre
total de callbacks définis pour ce type Scol (voir plus haut la création d'un type
Scol avec OBJregister). Dans un exemple donné lui aussi plus haut, nous
avions défini 4 callbacks pour un type Scol (création, destruction, affectation et
récupération). Nous pourrions alors définir (via un simple #define)
la valeur 0 pour la création, 1 pour la destruction, 2 pour l'affectation et 3
pour la récupération.

La configuration de la callback se fait donc en amont de cet appel, par
empilement ou affectation dans la pile. Nous avons vu tout à l'heure la position
du canal dans la pile lors de la création d'un objet. C'est un peu la même chose ici.
À l'étage 2 doit se trouver l'objet de type Scol,
À l'étage 1 doit se trouver la référence à la callback (le @myCallback
dans les arguments de la fonction Scol),<br />
À l'étage 0 doit se trouver le paramètre utilisateur, laissé à la discrétion du
programmeur Scol.
Là encore, si nécessaire, usez de la fonction MMset ou de la macro INVERT.

Par exemple, soit la fonction Scol <em>_CBfunction</em> qui demande en argument :
l'objet d'un type A, une fonction callback et un paramètre utilisateur. C'est un cas
fréquent dans l'API Scol. Nous avons en langage Scol :

typeof objA = A;;
...
_CBfunction objA @cbA "toto";
...

Le pseudo-code C correspondant à _CBfunction :
CBfunction (mmachine m)
{
    int mobj;
    mobj = MTOP (MMget (m, 2));
    if (mobj == NIL)
        return 0
    OBJaddreflex (m, typeA, FLAG_CB_TYPE_A);
    return 0;
}

Appeler une callback d'un objet Scol

Avant d'appeler une telle callback, il convient de s'assurer qu'elle existe !
Si tel est le cas, elle est "préparée" à être exécutée puis enfin lancée.

Pour tester sa présence et la préparer éventuellement, OBJbeginreflex est prévue.
Elle attend quatre argument : la machine Scol, la variable contenant le type objet
concerné, le pointeur système et le flag qui a servi à l'ajouter (voir ci-dessus).
Les premier, second et quatrième arguments sont aisément accessibles.
Le troisième, le pointeur système de l'objet Scol, est parfois plus retors. On
peut faire en sorte qu'il soit inclus dans la pile, à une position connue. Ce
n'est cependant pas toujours réalisable lorsqu'une bibliothèque tierce gère les
évènements de l'objet. Plusieurs méthodes s'offrent à vous ; nous emploierons ici
une structure qui le contiendra et qui sera passé en paramètre de la fonction tierce.

OBJbeginreflex doit retourner 0 en cas de succès. N'oubliez donc pas de tester
son retour.

int cb = 0;
cb = OBJbeginreflex (m, MyObjectType, (int) handle_sys, FLAG_CB_TYPE_A);
if (cb)    /* pas de callback définie dans le script Scol */
    return 0;

Une fois la callback préparée, il ne reste plus qu'à l'appeler avec <em>OBJcallreflex</em>.
Celle-ci ne prend que deux arguments : la machine Scol et le nombre de paramètres
supplémentaires de la callback.

Arguments ?

Que sont ces paramètres supplémentaires de la callback ?

Toutes les callbacks Scol ont au moins deux arguments : l'objet Scol sur lequel
l'évènement a été généré et le paramètre utilisateur, paramètre à la convenance
du développeur Scol.
Il est tout à fait possible de donner au programmeur Scol d'autres paramètres.
Par exemple, lors d'un clic sur un objet Scol graphique, il est probablement
intéressant non seulement de fournir les deux paramètres cités mais aussi d'y adjoindre
les coordonnées du clic, du bouton de la souris qui a été cliqué, etc. Ce sont ça
les <em>paramètres supplémentaires</em>.

Reprenons notre exemple de clic dans un élément graphique. Nous désirons joindre
les coordonnées du clic (x, y), le bouton de souris (button) et la touche de clavier
éventuellement appuyée (mask). Nous pouvons décider que chacune de ces informations
correspondra à un argument de la callback : x y button mask. Cela fera donc 4 paramètres
supplémentaires. Nous pouvons aussi décider de grouper les coordonnées en un seul argument
pour former un tuple : [x y] button mask. Cela fera donc 3 arguments supplémentaires.
Ou encore de les regrouper tous en un seul tuple : [[x y] button mask]. Cela fera
1 argument supplémentaire.

Bien entendu, il peut très bien n'y avoir aucun paramètres supplémentaires à
transmettre et nous noterons, dans ce cas, 0.

Ces fameux paramètres doivent être connus de la pile Scol lors de l'appel à
OBJcallreflex. Vous devez donc, si ce n'est déjà fait, les empiler au
préalable. Cela se code de la façon habituelle (MMpush avec construction de
tuple(s) ou de liste(s) éventuel(s)). Cet empliment doit se faire dans l'ordre
des arguments de la callback.

Avec notre exemple précédent :

int cb;
cb = OBJbeginreflex (m, MyObjectType, (int) handle_sys, FLAG_CB_TYPE_A);
if (cb)
{
    MMechostr ("No callback ...n");
    MMpush (m, NIL);
    return 0;
}
MMpush (m, ITOM (x));
MMpush (m, ITOM (y));
MMpush (m, ITOM (button));
MMpush (m, ITOM (mask));
OBJcallreflex (m, 4);

donnera un prototype Scol : fun [ObjTypeA u0 I I I I] u1
ou

int cb;
cb = OBJbeginreflex (m, MyObjectType, (int) handle_sys, FLAG_CB_TYPE_A);
if (cb)
{
    MMechostr ("No callback ...n");
    MMpush (m, NIL);
    return 0;
}
MMpush (m, ITOM (x));
MMpush (m, ITOM (y));
MMpush (m, ITOM (2));
MBdeftab (m);
MMpush (m, ITOM (button));
MMpush (m, ITOM (mask));
OBJcallreflex (m, 3);
<

donnera un prototype Scol : fun [ObjTypeA u0 [I I] I I] u1
ou encore

int cb;
cb = OBJbeginreflex (m, MyObjectType, (int) handle_sys, FLAG_CB_TYPE_A);
if (cb)
{
    MMechostr ("No callback ...n");
    MMpush (m, NIL);
    return 0;
}
MMpush (m, ITOM (x));
MMpush (m, ITOM (y));
MMpush (m, ITOM (button));
MMpush (m, ITOM (mask));
MMpush (m, ITOM (4));
MBdeftab (m);
OBJcallreflex (m, 1);

donnera un prototype Scol : fun [ObjTypeA u0 [I I I I]] u1

Détruire un objet Scol

Lors de la création d'un nouveau type, nous avons codé une callback de destruction.
Cette dernière sera appelée à chaque objet de ce type détruit. Mais comment détruire
un tel objet une fois que le programmeur Scol n'en a plus besoin ?

C'est très simple. La fonction OBJdelTM est faîte pour ça ! Elle prend
3 arguments : la machine Scol, le flag utilisé lors de la création (étape de stockage
avec MMstore) et le pointeur Scol de l'objet (c'est bien le pointeur
Scol et non le pointeur système qui est attendu).

Généralement, les fonctions Scol de destruction ne prennent qu'un seul argument :
l'objet à détruire. Le code minimal est alors :

int obj;
obj = MTOP (MMpull (m));
if (obj == NIL)
{
    MMechostr ("Object already destroyedn");
    MMpush (m, NIL);
    return 0;
}
OBJdelTM (m, OBJTYPE_C_HANDLE, PTOM (obj));
MMpush (m, 0);
return 0;

Code source complet

Il est trop long pour être écrit sur cette page déjà ... longue ! Veuillez donc
vous reporter à l'archive associée à ce tutoriel (en haut de page).

Si vous souhaitez le compiler, n'oubliez pas d'indiquer à votre compilateur les
headers de libcurl et de linker vers les librairies de libcurl. Le linkage peut
se faire simplement avec un -lcurl.
Curl-config peut
vous apporter une aide précieuse.

À l'exécution, si vous avez décidé de ne pas inclure libcurl à votre bibliothèque,
il faudra avoir préalablement installé les binaires libcurl sur la machine de test.
Sous GNU/Linux, l'installation du paquet libcurl de votre distribution est
suffisante. Pour MS Windows, les quatre dll suivantes sont requises pour tester
cet exemple (mais si vous rajoutez des fonctionalités, d'autres pourront être
nécessaires) : libcurl.dll, libeay32.dll (ou 64 sur 64 bits), libidn-11.dll,
libssl32.dll (64 sur 64 bits).

Exemple de code Scol</h2>

typeof w_file = W;;

fun cbNewUrl (objCurl, user_param, data_received, size_data, url, error)=
    _appendpack data w_file;
    _fooS user_param;
    0;;

fun main ()=
    _showconsole;

    set w_file = _getmodifypack "tests/seeks.txt";
    example_getFile 
        "http://www.seeks.fr/" 
        w_file;

    _fooS "example_getFile : ok !";

    example_newUrl 
        _channel 
        "http://www.seeks.fr/search?q=irizone&expansion=1&action=expand" 
        nil
        @cbNewUrl
        "Youpi !!";

    _fooS "example_newUrl : ok !";
    0;;

Updated by iri about 11 years ago · 1 revisions