Project

General

Profile

Actions

Coder une bibliothèque Scol en C - Niveau 1

Objectif : les bases de la création d'une telle bibliothèque

Moyens :
  • arborescence de base;
  • exemple de code interne à la bibliothèque;
  • fonction de chargment et de déchargement;
  • définition de l'API de la bibliothèque (nouvelles fonctions Scol);
  • code source de la bibliothèque;
  • édition du fichier usm.ini;
  • exemple d'utilisation dans une application Scol.

STATUT : COMPLET
Version : 1.0, 1.1
Date : novembre 2010, décembre 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_1.tar.gz (8 ko)
Langage : C
Ok pour toute version de Scol Windows et GNU/Linux (>= 4.x).

{To do : avec les headers Scol C}

Arborescence de base

Comme vu dans un chapitre précédent, il est impérative de définir un header
contenant les "communications" de base entre votre future bibliothèque et le noyau.
Pour éviter de réinventer la roue, le plus simple et de le récupérer depuis le
Scolring.
À l'heure actuelle, ce header pour la version MS Windows est différent de celui de
la version GNU / Linux. Il vous faudra user d'une directive de preprocesseur pour
aiguiller le compilateur vers le bon header. À ce sujet, vous trouverez à cette
adresse une liste des macros
prédéfinies pour la plupart des systèmes d'exploitation et des compilateurs (vous
trouverez les macros pour les différentes architectures (x86, AMD64, ...) sur le même
site). Il est sans doute préférable de définir votre propre macro testant ces
différentes macros, la lisibilité pouvant en prendre un coup. Ici, je n'ai utilisé
que deux macros pour chacun des deux OS :

  • pour GNU / Linux : linux et __linux, communs à la plupart des compilateurs;
  • pour MS Windows : WIN32_ et __WIN32, respectivement commune à la plupart des
    compilateurs et spécifique à Borland.

Note : Utilisez désormais les flags inclus dans les headers Scol !

Code interne

<blockquote>Le code source fourni en téléchargement est abondamment commenté, ce tutoriel
et le code source étant complémentaire.</blockquote>

La fonction, très simple, incrémentera simplement de 1 tout entier passé en
paramètre.

On a donc : une fonction Scol qui prend un entier en argument et qui retourne
la valeur de cet entier incrémenté de 1.

En C, nous aurions quelque chose comme (de façon extensive) :

#include <stdio.h>

void sc_increment (int number)
{
   number++;
   printf ("number = %dn", number);
   return;
}

Pour Scol, 'number' est dans la pile lors de l'appel à la fonction. Il s'agit
même du dernier élément (notre fonction ne prenant qu'un seul argument).
En effet, tous les arguments d'une fonction Scol d'un package sont présents dans
la pile Scol interne : le dernier élément de la pile étant le dernier argument de
lafonction. Ainsi,

/* Fonction Scol dans un package */
fonctionScol argument_1 argument_2 argument_3

/* Fonction interne codant 'fonctionScol' 
Dans la pile,nous avons : */
pile_element_position_0 : argument_3
pile_element_position_1 : argument_2
pile_element_position_2 : argument_1
  • En premier lieu, on passe donc la structure de la machine Scol dans son état actuel
    à la fonction :
int sc_increment (mmachine m);

Nous avons ici le prototype invariable de notre fonction.

  • Nous récupérons l'entier situé à l'index 0 de la pile :
int mnumber = MTOI (MMget (m, 0));

la macro MTOI permettant de convertir la valeur issue de la machine Scol en un int
directement exploitable.

  • On incrémente classiquement :
    mnumber++;
  • Et on modifie la valeur du dernier élément de la pile (d'index toujours 0)
    avec la nouvelle valeur de mnumber :
MMset (m, 0, ITOM (mnumber));

Nous obtenons alors le code suivant :

int sc_increment (mmachine m)
{
    int mnumber;
    MMechostr (MSKDEBUG, "sc_increment: enteringn");
    mnumber = MTOI (MMget (m, 0));
    mnumber++;
    MMset (m, 0, ITOM (mnumber));
    return 0;
}

La macro MMechostr permet d'écrire des informations dans le log. Elle accepte,
en premier argument, une valeur de masque (voir dans l'archive à télécharger).

Nous aurions également pu retirer le dernier élément (MMpull) et ajouter un
élément contenant la nouvelle valeur (MMpush) :

int sc_increment (mmachine m)
{
    int mnumber;
    MMechostr (MSKDEBUG, "sc_increment: enteringn");
    mnumber = MTOI (MMpull (m));
    mnumber++;
    MMpush (m, ITOM (mnumber));
    return 0;
}

Ici, cela revient au même.

Chargement et libération de la bibliothèque

On l'a vu, il faut initialiser une bibliothèque afin qu'elle puisse communiquer
dans les deux sens avec le noyau. Cette initialisation est légèrement différente
entre MS Windows et GNU/Linux (comme c'est le cas avec le header vu plus haut).

Chargement

Sous MS Windows

__declspec (dllexport) int SCOLloadEXAMPLE (mmachine m, cbmachine w)
{
    int k = 0;
    ww = w;
    mm = m;

    MMechostr (0, "EXAMPLE library : loadingn");

    if ((k = SCOLinitEXAMPLEclass (m))) return k;

    MMechostr (0, "\nEXAMPLE library loaded !n");
    return k;
}

Le nom de la fonction est important : c'est lui qui sera à entrer dans le fichier
usm.ini. Cette fonction prend toujours deux arguments : les deux structures
partagées par le noyau et qui sont définies dans le header vu plus haut. Ces dernières
sont alors affectées à la bibliothèque.
On appelle ensuite une fonction qui initialisera l'API de la bibliothèque SCOLinitEXAMPLEclass :
celle-ci prend simplement en argument la machine Scol (note : on aurait pu la définir directement
ici mais c'est tout de même plus propre). Elle est commune aux deux plateformes.

Sous GNU/Linux

int SCOLloadEXAMPLE (mmachine m)
{
    int k = 0;
    mm = m;

    MMechostr (0, "EXAMPLE library : loadingn");

    if ((k = SCOLinitEXAMPLEclass (m))) return k;

    MMechostr (0, "\nEXAMPLE library loaded !n");
    return k;
}

Seule la structure mmachine est partagée. Puis on appelle, comme sous MS
Windows, la même fonction SCOLinitEXAMPLEclass pour initialiser l'API.

Note : pour le serveur Scol 4 sous GNU / Linux, le chargement est similaire à
celui de la version Windows. De ce fait, le header est lui aussi différent de celui
utilisé pour la version c;iente. Un tutoriel spécifique aus codes pour le serveur
Scol 4 est prévu.

Libération

Lorsque Scol se termine, les différentes bibliothèques sont libérées dans le même
ordre qu'elles ont été chargées si une fonction de libération a été déclarée
dans le fichier <em>usm.ini</em>. Celle-ci n'est, en effet, pas obligatoire.

Scol gère lui-même les différentes allocations / libérations mémoire des variables
et structures qu'il manipule. Cependant, les objets des APIs doivent être libérés
manuellement lors de leur destruction : c'est donc au développeur de s'en charger
(nous verrons cela dans le tutoriel de niveau supérieur). Cette libération des
objets de l'API ne doit pas se faire lors de la libération de la bibliothèque mais
en amont. Ici, en revanche, nous libérons tout objet externe que nous aurions
pu utiliser, notamment par l'inclusion de bibliothèques tierces.

Ici, nous n'avons rien à libérer, la fonction ne fait rien si ce n'est écrire
un message dans le log. Elle n'est donc présente que pour l'exemple.

#if defined _WIN32 || defined __WIN32__
__declspec (dllexport) int SCOLfreeEXAMPLE ()

#elif defined linux || defined __linux
int SCOLfreeEXAMPLE ()

#else
#error no platform supported
#endif

{
    MMechostr(MSKDEBUG, "nEXAMPLE library release !n");
    return 0;
}

Note : Utilisez dorénavant les fags présents dans les headers Scol C !

Remarquez le prototype différent de la fonction entre les deux plateformes.

Définition de l'API

Ici, vous allez définir les fonctions, objets et autres flags de votre API.
Celle-ci sera ajoutée, si tout va </p>bien, à l'environnement minimal de Scol et donc
manipulable par tout script.

Cette définition se déroule en deux étapes : quatre tableaux pour la définir
et une fonction pour la charger. Les tableaux tout d'abord :

Première étape

  1. les noms des objets de cette API (fonction, objets Scol, flags, ...);
  2. les fonctions internes correspondantes (rigoureusement dans le même ordre !);
  3. le nombre d'argument de chaque objet, si cela a un sens;
  4. le prototype de chaque élément, si cela a un sens.

La taille de chaque tableau doit être strictement et impérativement identique entre
eux et chaque tableau doit être rempli (i.e. il ne doit y avoir aucune "case" indéfinie).
Tout erreur a ce niveau provoquera invariablement soit un crash ou, plus
généralement, une absence de chargement de la bibliothèque. Soyez attentif ! Au cas
où la taille d'un tableau serait inférieure, le compilateur devrait vous avertir lors de
la phase de compilation de la bibliothèque (s'il est correctement configuré comme on
l'a vu dans un autre tutoriel).
De plus, les données doivent être cohérentes entre elles. Par exemple, si vous
entrez 2 arguments (tableau 3) pour une fonction et que son prototype (tableau 4)
indique 3 arguments, il y aura une erreur (et donc un plantage) à l'exécution.

Exemple :

#define NOMBRE_OBJET 2

char* nom_fonction[NOMBRE_OBJET] =
{
    "ma_fonction",    /* fonction */
    "MonObjetScol"   /* nouvel objet Scol */
};

int (*fonction_interne[NOMBRE_OBJET]) (mmachine m) =
{
    ma_fonction_interne,
    NULL
};

int nombre_arguments[NOMBRE_OBJET] =
{
    3,
    TYPTYPE
};

char* fonctiontype[NOMBRE_OBJET] =
{
    "fun [Chn S I] MonObjetScol",
    NULL
};

Vous remarquerez surement que le terme tableau est abusif mais ce tutoriel a surtout
vocation à être pratique pour coder une biliothèque Scol et non apprendre le C. Ici,
je pense que l'expression "quatre tableaux" est mieux mémorisable. Autrement, n'hésitez
pas à reformuler ! :)

Dans le tableau 3, on indique le nombre d'arguments attendu par la fonction,
si c'est une fonction. Si c'est un type d'objet Scol, vous indiquerez, comme dans
l'exemple, TYPTYPE. Les possibilités, dont les noms suffisammet parlant :

  • TYPTYPE, pour les types d'objets Scol (tels que ObjWin, h2d, CompBitmap, ...)
  • TYPVAR, pour les valeurs numériques simples (tels que _SHOWwindow, SQLITE_ONLYREAD,
    ...)
  • TYPCONS, pour des drapeaux alphanumériques qui induisent une modification
    d'un type d'objet Scol (très peu usité, à vérifier)
  • TYPFIELD, inusité
  • TYPCOMV, pour définir des objets de Communication (en parallèle aux "defcomvar" ?).
    Inusité
  • TYPCOM, pour définir des objets de Communication (en parallèle aux "defcom" ?).
    Inusité
  • TYPCONS0, pour définir des drapeaux alphanumériques de sortie, peu usité
  • TYPSTRUC inusité.

Seconde étape

La fonction de chargement est PKhardpak qui devrait retourner 0 en cas
de succès (cf code source "kernel/loadpack.cpp", fonction PKhardpak puis
PKaddpak. Son prototype est :

int PKhardpak (mmachine m, char *name, int n, char **namefun, int (**fun)(mmachine z), int *nargfun, char **typfun);

qui correspond bien aux 4 "tableaux" précédents. Le second argument est un nom
interne du package (de l'API), le troisième est le nombre d'éléments de l'API
(donc la taille de chaque tableau).

Avec le dernier exemple, on aura :

PKhardpak (m, "BLABLAengine", NOMBRE_OBJET, nom_fonction, fonction_interne, nombre_arguments, fonctiontype);

Si cette fonction retourne 0, l'API sera chargée et disponible. Autrement, les APIs qu'elle contient ne seront pas disponibles et un message apparaîtra dans le fichier de log lors du chargement de la VM (si le log est activé sur le client).

Pour notre bibilothèque

Le code source de définition et de chargement de l'API (bien réduite !) de notre
bibliothèque :

#define EXAMPLE_PKG_NB  1

char* example_name[EXAMPLE_PKG_NB] =
{
    "example_increment"     /* fonction de l'API appelable depuis l'extérieur */
};

int (*example_fun[EXAMPLE_PKG_NB]) (mmachine m) =
{
    sc_increment            /* nom interne où le code de cette fonction est développé */
};

int example_narg[EXAMPLE_PKG_NB] =
{
    1                       /* nombre d'argument attendu par cette fonction */
};

char* example_type[EXAMPLE_PKG_NB] =
{
    "fun [I] I"             /* prototype Scol de cette fonction : on notera que le nombre d'argument est bien 1 */
};

int SCOLinitEXAMPLEclass (mmachine m)
{
    int k = 0;

    MMechostr (MSKDEBUG, "SCOLinitEXAMPLEclass library : loadingn");

    k = PKhardpak (m, "EXAMPLEengine", EXAMPLE_PKG_NB, example_name, example_fun, example_narg, example_type);
    return k;
}

Le header (optionnel)

Afin de coder plus proprement, créons un fichier d'en-tête :

#ifndef _EXAMPLE_MAIN_H_
#define _EXAMPLE_MAIN_H_

/* On inclut le header de définition des structures partagées avec le noyau (impératif) */
#if defined _WIN32 || defined __WIN32__
#include "scol_plugin_win.h" 

#elif defined linux || defined __linux
#include "scol_plugin_lin.h" 

#else
#error no platform supported
#endif

/* On inclut la définition des macros, c'est optionnel mais chaudement recommandé */
#include "macros.h" 

/* Nombre d'éléments de l'API Scol définit dans cette bibliothèque. Ce nombre doit
être rigoureusement exact sous peine de crash ou de non chargement de la bibliothèque.
N'oubliez pas de le mettre à jour à chaque fois que vous ajouter ou supprimer un élément de l'API ! */
#define EXAMPLE_PKG_NB  1

#endif

Note : voir la méthode standardisée pour inclure proprement les headers Scol C !

Code source de la bibliothèque

Je ne donne ici, non commenté, que le code source spécifique de la bibliothèque
afin de ne pas trop surcharger l'affichage. L'archive à télécharger contient non
seulement la version commentée mais aussi les différents headers (Windows et Linux)
des structures partagées et le header contenant les macros les plus usuelles.
Au sujet des macros, vous pouvez en retirer et en ajouter comme bon vous semble. Le
fichier n'a rien de figé, à l'inverse des deux autres headers dont il vaut mieux
éviter l'édition intempestive.

Ce code est normalisé ISO C99 et valable tant pour la version MS Windows que GNU / Linux.

main.c

#include "main.h" 

#if defined _WIN32 || defined __WIN32__
cbmachine ww;
#endif
mmachine  mm;

int sc_increment (mmachine m)
{
    int mnumber;

    MMechostr (MSKDEBUG, "sc_increment: enteringn");

    mnumber = MTOI (MMpull (m));
    mnumber++;

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

char* example_name[EXAMPLE_PKG_NB] =
{
    "example_increment" 
};

int (*example_fun[EXAMPLE_PKG_NB]) (mmachine m) =
{
    sc_increment
};

int example_narg[EXAMPLE_PKG_NB] =
{
    1
};

char* example_type[EXAMPLE_PKG_NB] =
{
    "fun [I] I" 
};

int SCOLinitEXAMPLEclass (mmachine m)
{
    int k = 0;

    MMechostr (MSKDEBUG, "SCOLinitEXAMPLEclass library : loadingn");

    k = PKhardpak (m, "EXAMPLEengine", EXAMPLE_PKG_NB, example_name, example_fun, example_narg, example_type);
    return k;
}

#if defined _WIN32 || defined __WIN32__
__declspec (dllexport) int SCOLloadEXAMPLE (mmachine m, cbmachine w)
{
    int k = 0;
    ww = w;
    mm = m;

    MMechostr (0, "EXAMPLE library : loadingn");

    if ((k = SCOLinitEXAMPLEclass (m))) return k;

    MMechostr (0, "nEXAMPLE library loaded !n");
    return k;
}

#elif defined linux || defined __linux
int SCOLloadEXAMPLE (mmachine m)
{
    int k = 0;
    mm = m;

    MMechostr (0, "EXAMPLE library : loadingn");

    if ((k = SCOLinitEXAMPLEclass (m))) return k;

    MMechostr (0, "nEXAMPLE library loaded !n");
    return k;
}

#else
#error no platform supported
#endif

#if defined _WIN32 || defined __WIN32__
__declspec (dllexport) int SCOLfreeEXAMPLE ()
#elif defined linux || defined __linux
int SCOLfreeEXAMPLE ()
#else
#error no platform supported
#endif
{
    MMechostr(MSKDEBUG, "nEXAMPLE library release !n");
    return 0;
}

main.h

#ifndef _EXAMPLE_MAIN_H_
#define _EXAMPLE_MAIN_H_

#if defined _WIN32 || defined __WIN32__
#include "scol_plugin_win.h" 

#elif defined linux || defined __linux
#include "scol_plugin_lin.h" 

#else
#error no platform supported
#endif

#include "macros.h" 

#define EXAMPLE_PKG_NB  1

#endif

TODO : exemple avec les headers Scol C !

Édition du fichier usm.ini

Maintenant que la bibliothèque a été compilée (pour la compilation voyez le
tutoriel suivant), il faut la copier dans le sous-répertoire "plugins" du dossier
"Scol". Ceci fait, éditons le fichier <em>usm.ini</em> et rajoutons-lui la ligne
suivante :

  • Pour MS Windows :
    plugin plugins/libexample.dll SCOLloadEXAMPLE SCOLfreeEXAMPLE
  • Pour GNU / Linux :
    <div class="code3">
    plugin ./plugins/libexample.so SCOLloadEXAMPLE SCOLfreeEXAMPLE

Relancez Scol en ayant pris soin (mais en tant que développeur vous aviez déjà
du y penser n'est-ce pas !) de mettre les logs au maximum : toujours dans le fichier
usm.ini modifiez les clés "echo", "log" et "logwin" comme suit

echo ffff
log yes
logwin yes

la clé "logwin" permettant d'afficher, ou non, la console.
Si tout s'est correctement passé, vous devriez lire, vers le début du fichier de
log, les messages de chargement de la bibliothèque :)

http://www.irizone.net/img/prog_example_1_log_lin.png (image 1)
exemple d'extrait de log de cette bibliothèque sous GNU/Linux : son chargement est correct</p>

Exemple d'application

Ce sera un exemple des plus basiques que je ne commenterai pas (si besoin est, vous
avez les tutoriels sur ce wiki) :

fun main ()=
    _showconsole;
    let example_increment 3 -> n in
    _fooS strcat "example_increment de 3 est : " itoa n;
    0;;

Le résultat devrait être 4 (3+1=4 !)

http://www.irizone.net/img/prog_example_1.png (image 2)

Updated by iri about 11 years ago · 1 revisions