Rebol Documentation Project

Aller au contenu | Aller au menu | Aller à la recherche

Mot clé - Articles Techniques

Fil des billets

vendredi 06 avril 2007

Guide du développeur REBOL/Services

Historique de la traduction

Date Version Commentaires Auteur Email
jeudi 6 avril 2006 1.0.0 Traduction initiale Philippe Le Goff lp—legoff—free—fr

Introduction

Ce document a été écrit pour ceux d'entre vous qui veulent comprendre les REBOL/Services et comment les utiliser, mais n'ont pas le temps de lire des dizaines de pages de documentation technique. Les informations présentées ici devraient être suffisantes pour vous permettre de démarrer avec vos propres scripts et applications, mais les utilisateurs expérimentés auront intérêt à lire certains autres documents techniques concernant les REBOL/Services.

Que sont les REBOL/Services ?

Il serait facile juste de se lancer et de démarrer dés à présent avec les REBOL/Services, mais avant de faire cela, vous devriez acquérir un minimum de compréhension de ce qu'ils sont et de comment ils fonctionnent.

Les REBOL/Services fournissent un moyen simple, élégant de partager de l'information entre les programmes informatiques. Ils peuvent être utilisés pour échanger de l'information entre des clients et des serveurs qui peuvent être éloignés de milliers de kilomètres, ou simplement entre applications fonctionnant localement sur votre ordinateur.

Les REBOL/Services implémentent un concept appelé "Service Orienté Architecture" ou SOA pour faire court. L'idée de base d'un SOA est que vous envoyez un message, une requête à un autre programme ("un service") qui va tenter de répondre à la requête et de renvoyer le résultat.

Avantages des Services

Les premiers avantages des REBOL/Services sont les suivants :

Facilité d'emploi : Vous pouvez accéder au service, ou implémenter un service personnalisé avec une seule ligne de code

Sécurité : L'authentification forte et le cryptage des données sont pré-inclus, les commandes sont émises en utilisant l'approche par dialecte spécifique à REBOL (aucune exécution directe de fonction ne se produit). Pour des systèmes extrêmement sécurisés, des clés privées peuvent être utilisées, ainsi le fait de connaître le nom et le mot de passe d'un utilisateur n'est pas suffisant.

Flexibilité : Les REBOL/Services sont basés sur le concept propre à REBOL, de dialecte (dialecting) qui permet un grand degré de liberté et de contrôle avec un minimum de code. De plus, les services peuvent être utilisés au-dessus de divers protocoles de transports (TCP et HTTP sont fournis en standard).

Fiabilité : Plus votre code est court, plus votre application est fiable. Lorsque votre code fait seulement quelques Kos, comparé à quelques Mos, il y a de bonnes probabilités d'éliminer tous les bogues.

Commodité : Chaque version de REBOL aura cette technologie incluse en standard, de sorte que vos scripts et vos applications peuvent tirer parti d'elle sans ajouter de librairies dédiées.

Un standard ouvert : Pour permettre aux développeurs d'apporter des améliorations et des suggestions, tout comme de fournir des jeux de services particuliers.

Une liste plus détaillée des avantages qu'apportent les REBOL/Services peut être trouvée par ailleurs.

Types de Services

En tant que SOA, les REBOL/Services vous offrent un moyen facile de créer une grande diversité d'applications telles que :
Partage sécurisé de fichiers
Administration à distance de serveurs
Gestion de code source
Collaboration sur des documents
Mise à jour de site Web
Echange de messages (incluant l'IM, l'Instant messaging)
Gestion de tâches
Traitement de cartes de crédits
Services d'horodatage, de synchronisation.
Vote et participation à un scrutin
Allocation de ressources systèmes
Application de comptabilité
Constitution de tableaux de bords

Il n'y a pas réellement de fin à cette liste, car cela prend seulement une page ou deux pour implémenter la plupart des services. Je suis certain que vous saurez imaginer encore d'autres usages.

Pour plus d'information

Beaucoup d'autres informations à propos des REBOL/Services seront documentées dans les mois à venir. Nous fournirons un lien vers ces informations sur notre site Web.

Comment fonctionnent les REBOL/Services ?

Pour utiliser un service, vous envoyez simplement une requête. Le service traite la requête et retourne un résultat. Le service peut être n'importe où. Il peut se trouver sur un serveur distant, ou sur un autre client dans votre réseau locale, ou être un autre processus sur la même machine. Il n'y a pas de différences.

Le séquencement entre la requête et le résultat

Regardons plus attentivement ce qu'il se passe :

Requête : La requête est émise vers le service. Le format de la requête est très simple : c'est un bloc REBOL. Le bloc commence avec un mot (indiquant la commande) et contient d'autres mots et des valeurs qui sont structurées dans l'ordre où le service les attend. Il s'agit d'un dialecte en REBOL, qui offre tous les avantages de ce concept.

Traitement : La requête est traitée par le service. Il doit analyser la requête (la commande), agir en conséquence, et mettre en forme le résultat à renvoyer. Tout cela est habituellement déterminé par la fonction PARSE, mais d'autres méthodes sont également autorisées.

Résultat : Lorsque le traitement est complet, le résultat est retourné vers le client. Le format du résultat est quelque chose d'assez similaire à celui de la requête, mais pas identique.Le format permet au client de connaître rapidement si la requête a été un succès et d'obtenir les valeurs résultant du traitement.

Tous les transferts depuis et vers le service sont cryptés. Divers niveaux de cryptage sont possibles. Ils sont décrits dans un autre document.

Exemples de requêtes

A quoi ressemble le bloc d'une requête ? Cela dépend du service. Par exemple, la requête peut être aussi simple que ce bloc :

[date]

La requête demande la date courante au service. Cette commande fait partie des commandes fournies en standard pour tous les services. Le bloc d'une requête plus complexe pourrait être comme ceci :

[file/get %maui.jpg 12-Mar-2005/10:00 %hawaii.jpg 14-Mar-2005/2:20]

Ce bloc demande le transfert de deux fichiers vers le client, si les fichiers sont plus récents que les dates indiquées. Si les fichiers sont plus anciens, ils ne sont pas récupérés.

Une requête peut aussi inclure plusieurs blocs de commandes. Voici trois commandes émises dans la même requête :

[date]

[info title]

[file/put %photo.jpg (read/binary %photo.jpg)]


Le résultat qui sera retourné au client contiendra trois blocs de résultats.

Exemples de résultats

Les résultats renvoyés par un service sont constitués de deux parties :
un indicateur de succès,
et un bloc qui comprend les valeurs résultant du traitement.

Par exemple, la commande date ci-dessus va retourner :

ok [date 25-Mar-2005/12:08:12-8:00]

L'indicateur ok signifie que le traitement de la commande s'est correctement déroulé. S'il échoue, pour une raison quelconque, le mot fail est renvoyé et si une erreur se produit, le mot error est retourné.

Le bloc qui suit l'indicateur de succès rappelle la commande et vous donne son résultat. Ce bloc est aussi exprimé dans un dialecte, car le format du bloc dépend du service. Cependant, le premier item dans le bloc est toujours la commande. (Ceci rend aisé pour des clients complexes la possibilité de générer des événements en callback, au sein d'une application.)

Pour des commandes multiples dans vos requêtes, vous obtiendrez de multiples commandes dans vos résultats. L'exemple du paragraphe précédent conduira à ces résultats :

ok [date 25-Mar-2005/12:08:12-8:00]

ok [info "Default REBOL Service"]

ok [file/put %photo.jpg 20456 ]


Il y a une chose supplémentaire à savoir à propos du bloc de résultat et que nous n'avons pas montrés ci-dessus (pour simplifier l'exemple).

Il contient un indicateur et un bloc récapitulatifs de la requête. Un exemple de récapitulatif ressemble à ceci :

done [reply seq 1 service "Generic Service" commands 1 time 0:00:01]

Le mot done indique qu'aucune erreur ne s'est produite et que la requête a été complètement traitée. Le suivi du succès de la requête en est facilité, même si elle contient de multiples commandes. Le bloc contient une information qui récapitule l'état du traitement de la requête. La plupart du temps, vous pourrez ignorez sans souci cette information.

Accéder à un service

Bon, à présent, voyons comment accéder à un REBOL/Service. Les services sont implémentés en REBOL au travers d'une interface basée sur des fonctions (function-based interface). Cette approche permet de conserver une interface très simple, en minimisant ce que l'utilisateur à besoin de savoir pour faire son travail. D'autre part, sous la surface visible, un mécanisme de port asynchrone en REBOL est utilisé, permettant aux développeurs expérimentés un très grand niveau de contrôle.

Le plus simple exemple

Pour débuter, le mieux est de regarder une ligne de code qui accède à un service :

result: do-service tcp://server:8000 [date]

Cette ligne envoie une requête date, attend la réponse, et renvoie le résultat. On appelle ceci une requête synchrone. La fonction do-service va attendre, jusqu'au moment où le serveur a fini son traitement, avant de retourner le résultat.

Accès asynchrone (No-wait)

Pour certains types de programmes, vous voudrez peut-être utiliser une requête asynchrone. Dans ce cas, la fonction n'attend pas le résultat . Vous pouvez soit fournir une fonction qui sera appelée lorsque le résultat sera reçu (fonction en callback), soit attendre la réponse.

Voici un exemple de requête asynchrone :

send-service/action tcp://server:8000 [date] [print mold result]

Ici, une requête date est émise vers le service, mais sans attendre le résultat. Lorsque le résultat est reçu, le second bloc est évalué, pour afficher le résultat. Cette approche est très similaire avec le principe des interfaces graphiques, qui sont par nature asynchrones (plusieurs choses réalisées en même temps). S'il est nécessaire que vous attendiez le résultat d'une requête faite précédemment, vous pouvez utiliser la fonction wait-service.

Voici un exemple :

req: send-service tcp://server:8000 [date]

do-some-other-stuff

result: wait-service req

La requête est envoyée, puis tandis qu'elle est traitée, vous pouvez faire d'autres choses. Lorsque vous avez besoin du résultat, vous pouvez l'attendre et le placer dans la variable result.

Emissions de requêtes multiples

L'exemple ci-dessus illustre comment utiliser une URL pour appeler un service. Cependant, la plupart du temps, dans les applications réelles, vous n'aurez pas à fournir une URL à chaque fois. Au lieu de cela, vous établirez une connexion qui restera active pour de multiples requêtes. Ceci peut être réalisé avec la fonction open-service :

port: open-service tcp://server:8000

Elle renvoie un port qui peut être utilisé comme argument pour d'autres fonctions :

result: do-service port [date]

send-service/action port [date] [print mold result]

Lorsque vous aurez fini, vous enverrez un ordre pour fermer la connexion :

close-service port

Il s'avère également que vous pouvez employer les fonctions send-service et login-service pour ouvrir le port et pour le maintenir ouvert. Il n'est pas nécessaire d'utiliser uniquement open-service ; cependant, open-service permet de spécificier des options particulières, susceptibles d'être employées pour la connexion.

Les fonctions utiles pour les requêtes à un service

Voici un résumé des fonctions qui sont utilisées pour manipuler les requêtes émises vers un service. Elles sont implémentées dans l'API client (Application Programming Interface).

Function Description
do-service Émet une requête vers un service et attend le résultat. Renvoie le résultat. Le résultat sera simplifié si c'est possible (Les champs de la réponse pourront ne pas être tous renvoyés. Voir plus loin.)
send-service Émet une requête vers un service mais n'attend pas le résultat. Renvoie un objet req-message qui peut être utilisé par d'autres fonctions listées ci-dessus. Un bloc (ou une fonction) peut être aussi spécifié en option pour le callback, en utilisant le raffinement /action.
wait-service Attend le résultat d'une requête émise précédemment avec send-request et renvoie le résultat. Le résultat n'est pas simplifié (il contient tous les champs de la réponse faite par le service.)
query-service Retourne le résultat d'une requête émise précédemment avec send-request, si elle a été reçue. Sinon, retourne NONE. Cette fonction vous permet de tester la disponibilité d'un résultat sans l'attendre. (NdT : une sorte de "polling" sur la réponse, en quelque sorte)
open-service Ouvre une connexion à un service et renvoie un port REBOL, qui peut être utilisé avec toutes les autres fonctions de l'API. Permet de spécifier des options supplémentaires relatives au service, comme des délais pour des time-outs, ou des clés, des méthodes de cryptage.
close-service Ferme le port lié au service ouvert précédemment. Aucune erreur ne se produit si le service a déjà été fermé.
abort-service Interrompt une requête send-service précédente, si c'est possible. Renvoie TRUE si la fonction s'est exécutée correctement. Renvoie FALSE si la requête a déjà été envoyée au service et ne peut être interrompue.
login-service Il s'agit d'une fonction qui facilite l'authentification lors de l'accès à un service et initie une session complètement cryptée. Cette fonction ne rend la main que lorsque l'étape de login est compléte (l'implémentation est faite en tant que fonction synchrone). Le login asynchrone est implémenté par l'envoi d'une requête login au service avec la fonction send-service.
logout-service Termine une session authentifiée et cryptée.

Requêtes à un service

Une requête à un service est un bloc de commande(s) qui est émis vers le service par les fonctions do-service et send-service.

Requêtes uniques

Pour conserver une certaine simplicité aux exemples ci-dessous, seule la requête date est présentée :

result: do-service tcp://server:8000 [date]

Les requêtes uniques peuvent être émises ainsi dans un bloc. Ici, le mot date est la commande demandée, et elle ne nécessite pas d'arguments. D'autres commandes peuvent nécessiter des arguments additionnels. Ceux-ci peuvent également être fournis au sein du bloc :

result: do-service tcp://server:8000 [info title]

result: do-service tcp://server:8000 [login "carl"]

Les fonctions de requête à un service effectuent un COMPOSE/deep sur le bloc. Ceci vous permet d'insérer des termes à évaluer à l'intérieur du bloc. Par exemple :

result: do-service tcp://server:8000 [

file/put %photo.jpg (read/binary %photo.jpg)

]

Dans cet exemple, le fichier photo.jpg est lu comme un fichier binaire, puis est inséré dans la requête avant son envoi.

Requêtes multiples

Le format des requêtes à commande unique permet de conserver un code simple. La forme la plus générale permet cependant l'envoi de commandes multiples à un service dans une seule requête. Pour cela, chaque commande doit être encapsulée dans un sous-bloc :

result: do-service tcp://server:8000 [

[date]

[info title]

[file/put %photo.jpg (read/binary %photo.jpg)]

[file/get %manual.txt]

]

Cet exemple envoie les quatre commandes via une service avec une seule requête. Les résultats retournés seront dans un format similaire, voir la section "Résultats de services" plus loin.

L'écriture utilisée (paths) pour les commandes file/put et file/get indique qu'elles sont trouvées dans le contexte du REBOL/Service file (le service de fichier ), pas dans le contexte global par défaut. Voir la section "Contexte d'un service" ci-dessous.

Les requêtes utilisent un dialecte

Il est important de comprendre que les requêtes faites aux REBOL/Services sont des dialectes REBOL. Ceci étant, elles n'appellent pas les fonctions REBOL directement au sein du service, mais sont interprétées comme un sous-langage spécifique à un domaine.

Cette approche :

  • Permet une meilleure sécurité car l'API du langage REBOL n'est jamais directement utilisée. 
  • Permet d'avoir une plus grande diversité d'expression qu'en RPC (remote procedure call) ou RMI (remote method invocation). Les jeux de commandes peuvent être des sous-langages complets. 
  • Nécessite très peu de code pour implémenter des services en comparaison d'autres approches. 
  • Les rend plus facile à tester. Les dialectes de commandes sont manipulés par la fonction PARSE qui être facilement appelée et déboguée de manière autonome durant leur développement. Le service n'a pas besoin d'être mis en ligne pour être massivement testé.

Résultats des Services

Le résultat d'un service est une ou plusieurs valeurs retournées par le traitement de la requête.

Résultats simples et uniques

La fonction do-service fournit une simplification des résultats pour des requêtes particulières. Ceci se voit dans les exemples suivants :

print mold do-service tcp://server:8000 [date]

25-Mar-2005/12:08:12-8:00

print mold do-service tcp://server:8000 [info title]

"Default REBOL Service"

Dans la plupart des cas, le résultat est retourné sous la forme d'une valeur unique. Ceci est uniquement vrai pour le cas où do-service est utilisé avec une seule commande. Les requêtes à plusieurs commandes renvoient des blocs à plusieurs résultats, comme décrit ci-dessous.

Note de conception : Nous pouvons être amenés à réévaluer cette façon d'opérer. Elle est similaire dans le comportement à la fonction LOAD, laquelle si elle n'est pas bien connue par l'utilisateur, peut conduire à des erreurs. Me contacter pour donner votre avis sur ce point.

Résultats multiples

Lorsque des commandes multiples sont envoyées à un service dans une seule requête, leurs résultats sont retournés sous forme de plusieurs blocs. Voici un exemple de résultat issu d'une requête à plusieurs commandes.

probe do-service tcp://server:8000 [

[date]

[info title]

[file/put %photo.jpg (read/binary %photo.jpg)]

[file/get %manual.txt]

][

ok [date 25-Mar-2005/12:08:12-8:00]

ok [info "Default REBOL Service"]

ok [file/put %photo.jpg 20456]

ok [file/get %manual.txt 25-Mar-2005 #^{...^}]

]

Chaque résultat commence avec un indicateur de succès (ok), suivi d'un bloc, le contenu du résultat. Pour rendre plus facile l'identification des résultats, ceux-ci sont toujours retournés dans le même ordre que les commandes, et chaque résultat inclut la commande requise : c'est le premier item du bloc. Il vous est possible, selon les options particulières choisies, d'avoir des résultats mixtes comprenant des requêtes réussies et d'autres qui ont échouées :

[

ok [date 25-Mar-2005/12:08:12-8:00]

ok [info "Default REBOL Service"]

fail [file/put not-allowed]

fail [file/get not-exists]

]

De plus, le bloc de résultat peut inclure un en-tête optionnel qui récapitule les résultats de la requête, en comprenant d'autres détails qui sortent du cadre de ce document.

Contextes propres aux commandes des REBOL/Services

Un serveur peut incorporer de multiples contextes pour les commandes des services. Chaque contexte fournit un lot de commandes pour un service spécifique. Par exemple, le serveur par défaut (pré inclus) fournit les contextes suivants :

Home - implémente des commandes qui font partie de tous les services. C'est le contexte par défaut et c'est un service requis. Son principal objet est de fournir des informations sur un serveur.

Admin - fournit les commandes pour contrôler votre service, sa mise à jour, redémarrage, gestion des utilisateurs, récupération des logs et d'autres encore.

File - Un ensemble de commande pour accéder ou transférer de petits fichiers (moins de quelques Mos). Ce service peut être utilisé pour uploader ou télécharger des pages web, des graphiques, du code et d'autres fichiers.

Les commandes au sein d'une requête peuvent explicitement spécifier un contexte en utilisant la notation avec les paths :

[admin/reset]

[file/get %photo.jpg]

(NdT : on a une notation du type /)

Ou, vous pouvez sélectionner un contexte différent avec la commande SERVICE :

[service admin]

[add-user "Bob" "Robert Smith" ...]

[change-user 47 user "Jenny"]

[date]

Ici les commandes add-user et change-user font partie du contexte admin. Vous pouvez changer le contexte de la commande autant de fois que vous le voulez au sein de la même requête. De plus, les commandes du contexte home sont toujours utilisables au sein des autres contextes, tant qu'elles n'ont pas été écrasées par des commandes dans le contexte courant. par exemple, ceci est valide :

Ici la commande date fait partie du service (contexte) home, pas du service (contexte) admin.

Créer un service

A présent que vous savez comment accéder aux REBOL/Services, voici quelques bases pour créer un service.

Un simple serveur

Cet exemple démarre un serveur, mais n'appelle aucun service spécifique. Il utilisera que les services standards (home, admin, et file, comme indiqué précédemment.)

service: start-service tcp://:8000

Le serveur utilise une connexion directe en TCP sur le port 8000. Immédiatement, il commencera à traiter des traiter des requêtes.

Voici un exemple de ce que vous pouvez saisir dans un script et tester :

REBOL [Title: "Example Server"]

service: start-service tcp://:8000

ask "PRESS ENTER TO QUIT"

stop-service service

Le serveur continuera à fonctionner jusqu'à ce que vous pressiez la touche "Enter". Lorsque celle-ci est activée, le service s'arrête.

Créer votre propre service

Pour créer votre propre service, tout ce dont vous aurez à faire est de créer un contexte de service avec un dialecte pour vos commandes.

Un contexte de service est un objet REBOL qui est mis dans un fichier, ce fichier étant chargé lorsque votre serveur démarre. Voici un exemple de service qui implémente un petit système de bulletins électroniques (Bulletin board).name: 

'bbs-example

title: "Micro-BBS Service"

description: " un BBS très simplifié."

; Message format: [date author message]

messages: any [attempt [load %messages.r] copy []]

commands: [

'put "Store a new message"

arg: string! "Author" string! "Message" (

write/append messages mold reduce [now arg/1 arg/2]

result: true

)

| 'get "Get a messages by number"

(result: copy [])

some [

arg: integer!

(repend result [arg/1 pick messages arg/1])

]

| 'list "List new message numbers since a given date or number"

(result: copy []) [

arg: date! (

num: 1

foreach msg messages [

if msg/1 >= arg/1 [append result num]

num: num + 1

]

)

| arg: integer! [

repeat n length? skip messages arg/1 [

append result n

] ]

] ]

]

Notez que les chaînes de caractères à l'intérieur du dialecte pour les commandes sont utilisées pour documenter le code, et elles sont extraites lorsque le service est initialisé. Le bloc de règles passé à la fonction PARSE ne contient pas de chaînes littérales. Il existe aussi une notation spéciale à utiliser pour inclure des chaînes spécifiques à une langue. (Plus d'information plus tard sur ce point). Les variables result et arg sont définies localement dans la fonction là où le bloc de la commande est évalué. La variable resultat contient la valeur ou le bloc qui sont retournés depuis la commande et renvoyés au client.

Pour mettre le service en ligne, vous devez l'indiquer dans le bloc des services qui est fourni en tant qu'option à la fonction start-service.

service: start-service/options tcp://:8000 [

services: [%bbs-example.r]

]

Il est également possible d'ajouter le service dans la configuration du serveur en utilisant la fonction handle-service, mais cette caractéristique sera décrite dans un document à part.

jeudi 14 juillet 2005

ETUDE DE PERFORMANCES Uniserve - Apache

Une étude de performance sur le service http d'Uniserve, vs Apache.

jeudi 07 juillet 2005

Interfacer Rebol et les bibliothèques dynamiques

Historique du document

Date Version Commentaires Auteur Email
mardi 30 juin 2005 0.0.1 Version initiale J.C.A Miranda [aka Bouba] jca—miranda—gmail—com
mardi 05 juillet 2005 0.0.2 Premier jet de corrections J.C.A Miranda [aka Bouba] jca—miranda—gmail—com

Introduction

Cet article ne se veut absolument pas exhaustif sur le sujet, et prend comme parti pris :

  • que vous êtes familier avec le C,
  • que vous avez déjà lu la documentation disponible en ligne sur le site de Rebol Technologies.

Pour illustrer le propos (en tout cas en partie), nous développerons un lecteur de fichier audio sans prétention dans la deuxième partie de l'article. Avis aux amateurs qui veulent s'essayer à en faire un produit fini, le code est fourni absolument libre de droit. :)

Les exemples ont été compilés et testés avec succès sous Linux (Rebol/View 1.2.47).

Règles et rappels

Commençons la partie un peu rébarbative par quelques trucs et astuces qui peuvent toujours servir. Si vous en connaissez d'autres, n'hésitez pas à les envoyer, ils seront les bienvenus. :)

Le type integer!

Ce paragraphe pour souligner une remarque importante dans la documentation de Rebol Technologies. Dans le paragraphe concernant les pointeurs C (paragraphe 4.4), il est dit :


"For return values, use long instead of void*"

Le type integer! peut donc être substitué à un pointeur lors de la création de vos interfaces.

Cette propriété s'avère très utile dans le cas où :

  • une interface directe de votre fonction n'est pas possible (par exemple, si la routine que vous définissez vous retourne une structure dont la taille ne vous est connu qu'au retour de l'appel).
  • le pointeur qui est retourné référence une structure opaque (un exemple-type : le pointeur sur la structure définissant le morceau a jouer dans l'exemple que nous développerons plus loin).

Le type decimal!

Le type decimal! a la particularité d'être converti en flottant au format IEEE.
En utilisant une struct! ont peut écrire facilement une fonction de conversion :


to-ieee: func [
"Conversion en float standard IEEE d'un decimal! Rebol"
arg [decimal!]
/double "Conversion en type double"
][
third make struct! compose/deep [
f [(either double ['double]['float])]
] reduce [arg]
]

Pensez-y si vous devez échanger des données binaires contenant des valeurs flottantes.

Le type struct !

Comme indiqué dans la documentation, le type struct! permet de définir une passerelle entre les struct C et Rebol. Comme dans l'exemple qui suit :


Code C :
struct ^{
int x;
int y;
char point_desc[20];
^}Point;

qui donne :


Code Rebol :
point: make struct! [
x [integer!]
y [integer!]
point-desc [string!]
] none ; initialisation par défaut de la structure

Jusqu'ici, rien d'inconnu !

Mais, le type struct! a d'autres utilisations qui peuvent s'avérer très intéressantes. Essayons d'en voir certaines.

Définition de pointeurs

Imaginons la fonction C suivante :


void dummy_func(int x , int * y) ^{
*y = x;
^}

Comment traduire le pointeur sur entier y en Rebol ?

Si nous prions pour que Saint Rebol nous fasse le travail en lui donnant comme prototype :


dummy-func: make routine! [
x [integer!]
y [integer!]
] dummy-lib "dummy_func"

Voilà ce qui se passe lorsque nous tentons d'utiliser notre fonction révolutionnaire (Tracing sous Linux) :


>> dummy-lib: load/library %dummy_lib.so
>> dummy-func: make routine! [
[ x [integer!]
[ y [integer!]
[ ] dummy-lib "dummy_func"
>>
>> a: make integer! none
== 0
>> dummy-func 3 a

zsh : segmentation fault rebol/view47 —secure allow

A priori, loin du résultat que nous espérions. :(

En fait, Rebol a considéré que l'entier passé en tant que paramètre y était le pointeur (Cf plus haut). Malheureusement, nous n'avons pas alloué l'espace nécessaire au stockage d'un entier à l'adresse 0 (ce qui aurait de toute façon été un peu difficile !).

Heureusement, le type struct! peut nous aider à résoudre ce problème.

Une struct C n'étant qu'une interprétation d'une zone mémoire, nous pouvons imaginer que la représentation suivante peut résoudre notre problème :


int-ptr: make struct! [value [integer!]] none

Redéfinissons donc notre routine! Rebol avec cette modification.


dummy-func: make routine! compose/deep/only [
x [integer!]
y [struct! (first int-ptr)]
] dummy-lib "dummy_func"

L'utilisation de compose/deep/only nous permet ici d'éviter de réécrire la structure int-ptr.
En faisant appel à first int-ptr, nous récupérons la spécification de la structure.

Une bonne habitude dès lors que vous faites appel à des structures récurrentes dans votre code.

Et maintenant, essayons donc notre nouvelle fonction inutile :


>> int-ptr: make struct! [value [integer!]] none ; initialisation à NULL.
>>
>> dummy-lib: load/library %dummy_lib.so
>> dummy-func: make routine! compose/deep/only [
[ x [integer!]
[ y [struct! (first int-ptr)]
[ ] dummy-lib "dummy_func"
>>
>> a: make struct! int-ptr none
>>
>> dummy-func 4 a
>>
>> a/value
== 4

Cela fonctionne !
Nous voilà donc avec un moyen simple de définir des pointeurs tout en allouant l'espace nécessaire au stockage de la valeur. :)

La fonction get-mem ? de Ladislav Mecir

Vous trouverez les fonctions de Ladislav sur le site : RIT - peek_and_poke.r

Cette fonction est un petit bijou, merci à lui ! :)) Vous vous demandez ce qu'elle permet ?

Regardons donc son code pour tenter de comprendre son utilité :


get-mem?: function [
^{get the byte from a memory address^}
address [integer!]
/nts ^{a null-terminated string^}
/part ^{a binary with a specified length^}
length [integer!]
] [m] [
address: make struct! [i [integer!]] reduce [address]
if nts [
m: make struct! [s [string!]] none
change third m third address
return m/s
]
if part [
m: head insert/dup copy [] [. [char!]] length
m: make struct! compose/deep [bin [struct! (reduce [m])]] none
change third m third address
return to string! third m/bin
]
m: make struct! [c [struct! [chr [char!]]]] none
change third m third address
m/c/chr
]

Personne ne s'est perdu ?
L'opération magique de cette fonction est la ligne


change third m third address

qui permet de changer l'initialisation de la struct! pour que l'adresse pointée soit celle que nous avons fourni en paramètre. Il est ainsi possible d'analyser le contenu de la memoire à cette adresse, à vos risques et perils comme le dit Ladislav lui-même. :)

Voyons maintenant son fonctionnement par l'exemple. Considérons te type et la fonction C suivante :


typedef struct type_t^{
int len;
char * arr;
^}Type_t;

Type_t * allocate_byte_array(void);

Comment obtenir le contenu de la zone mémoire contenant le tableau alloué par la fonction C alors que l'on ne connait pas sa taille à l'avance ?
La fonction get-mem ? peut nous y aider.

Considérons dans un premier temps le tableau comme un integer! sans nous intéresser à la zone pointée.


type-t: make struct! [
len [integer!]
arr [integer!]
] none

nous pouvons donc définir notre routine! en utilisant cette structure :


allocate-byte-array: make routine! compose/deep/only [
return: [struct! (first type-t)]
] my-lib "allocate_byte_array"

Et maintenant, utilisons la fonction de Ladislav pour récupérer notre tableau :


>> result: allocate-byte-array
>>
>>
>> probe result
make struct! [
len [integer!]
arr [integer!]
] [256 139123696]
>>
>>
>> print to-binary get-mem?/part result/arr result/len
#^{
000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F
202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F
404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F
606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F
808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9F
A0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBF
C0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDF
E0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF
^}
>>

Voici la fonction utilisée pour faire le test.
Elle fournit certes une table de taille fixe pour éviter un code inutilement complexe, mais elle illustre tout de même le propos.

Après tout, si vous n'aviez que le prototype, auriez-vous fait la supposition que la table faisait 256 octets ?


#include <malloc.h>

typedef struct type_t^{
int len;
char * arr;
^}Type_t;


Type_t * allocate_byte_array(void) ^{
Type_t * ret;
int i;

if (NULL == (ret = malloc(sizeof(Type_t)))) ^{
exit(-1);
^}

ret->len = 256;

if (NULL == (ret->arr = malloc(ret->len))) ^{
exit(-1);
^}

for (i = 0 ; i < ret->len ; i++) ^{
ret->arr[i] = i;
^}

return ret;
^}

En toute rigueur, il faudrait définir une fonction pour libérer la mémoire allouée.

Comme il ne s'agit pas du propos de cet exemple, elle est volontairement omise.

Le type binary !

Oui, je sais, il n'est pas référencé dans la documentation fournie par RT, mais pourtant on peut l'utiliser dans une interface avec le C !
A quoi peut-il bien servir vous demandez-vous ?

Prenons le cas d'une fonction que ceux qui connaissent openGL ont déjà dû voir :


GLAPI void GLAPIENTRY glColor3bv( const GLbyte *v );

Ne prêtez pas attention aux macros GLAPI et GLAPIENTRY, elles ne nous intéressent pas dans le cadre de cet exemple. :)

Cette fonction prend comme paramètre un tableau de 3 octets v représentant une couleur RGB. Bien sûr, on pourrait penser à utiliser une struct! ce qui donnerait ceci :


v: make struct! [r [char!] g [char!] b [char!]] none

notre routine :


gl-color-3bv: make routine! compose/deep/only [
v [struct! (v)]
] libgl "glColor3bv"

et un exemple d'utilisation :


c-red: make struct! v reduce [to-char red/1 to-char red/2 to-char red/3]
gl-color-3bv c-red

ce qui somme toute conviendrait parfaitement !

Oui, mais sachant que dans le cas d'openGL, il existe la même fonction gérant une couleur à 4 composantes (en ajoutant le canal alpha), on s'apercoit bien vite que l'on multiplie les definitions de struct!, pas très léger pour du Rebol !

De plus, il serait agréable de pouvoir utiliser uniquement des tuple! dans notre script pour gérer des couleurs, après tout, ils sont là pour ça ! :)

Le type binary! va pouvoir nous aider à simplifier tout ça. En effet, le type tuple! a une propriété intéressante losqu'on le convertit en binary! :


>> to-binary 1.2.3.4
== #^{01020304^}

On dirait un tableau d'octet ! Vous voyez ou je veux en venir ? :o)
Réécrivons donc notre fonction :


gl-color-3bv: make routine! [
v [binary!]
] libgl "glColor3bv"

Et notre exemple devient :


gl-color-3bv to-binary red

A vous de choisir ce qui vous semble le mieux ! Personnellement, je préfère la deuxième définition, mais à chacun ses goûts. :o)

A noter que le type tuple! n'est pas le seul ayant cette propriété, les block! d'entiers également. Par contre, les éléments d'un block! n'étant pas limités à un octet comme les tuple!, seul l'octet de poids faible est gardé lors de la conversion en binaire.
Amusez-vous bien ! :)

Entracte

Voilà pour ce qui est de faire le tour des trucs et astuces. Si vous en avez d'autres, n'hésitez pas, ils pourront sûrement servir à d'autres.

Un exemple d'utilisation : un player audio.

Bien, maintenant que tout le monde a enfin senti les effets de son aspirine, nous allons pouvoir continuer de manière plus légère cette excursion dans le monde des struct! et des routine!.

Pour cela, nous allons construire un lecteur audio sans prétention basé sur la bibliothèque SDL_mixer. Celle-ci est portée sur les principales plate-forme que supporte Rebol, donc tout le monde devrait pouvoir jeter son WinAmp, Xmms et autres RythmBox pour utiliser le fruit de son travail, ou presque ! :o)

Un très rapide tour d'horizon de SDL

SDL : qu'est ce que c'est ?

SDL est une bibliothèque multimédia multi-plateforme qui permet l'accès aux claviers, souris, joystick, périphériques audio et vidéo (2D/3D). Vous trouverez plus d'informations sur SDL et ses petits sur le site www.libsdl.org

De quoi avons-nous besoin ?

Pour les besoins de notre application, nous aurons besoin des bibliothèques suivantes :

Les deux dernières ne seront pas chargés par notre script mais sont indispensables si l'on veut lire respectivement des MP3 et des fichiers Ogg. Leur chargement est géré par SDL_mixer.

Pour les utilisateurs de Windows, l'ensemble des bibliothèques compilées est disponible sur le site PyGame (oui je sais, Python c'est maaaal ! Mais là, le serpent nous aide ! :op).

Les specifications de notre player

Le player restera volontairement simple, il doit permettre :

  • d'initialiser l'audio,
  • d'obtenir la configuration obtenue,
  • de lire un fichier audio,
  • d'arrêter la lecture,
  • de faire une pause/reprendre la lecture,
  • de contrôler le volume,
  • de quitter "proprement".

Création du wrapper Rebol

Etape essentielle à notre travail, il faut jeter un coup d'oeil au(x) fichier(s) d'include. Je ne saurais trop conseiller de regarder également les documentations associées, histoire que je ne me sente pas trop seul ! :)

Dans notre exemple, deux fichiers d'include sont à analyser :

  • SDL.h qui regroupe les fonctions communes à tout applicatif utilisant SDL,
  • SDL_mixer.h qui regroupe les fonctions audios qui nous interessent.

SDL.h

Dans ce fichier, nous trouvons les fonctions nécessaires à l'intialisation et à la fermeture du système SDL :


/* This function loads the SDL dynamically linked library and initializes
* the subsystems specified by 'flags' (and those satisfying dependencies)
* Unless the SDL_INIT_NOPARACHUTE flag is set, it will install cleanup
* signal handlers for some commonly ignored fatal signals (like SIGSEGV)
*/
extern DECLSPEC int SDLCALL SDL_Init(Uint32 flags);

/* This function cleans up all initialized subsystems and unloads the
* dynamically linked library. You should call it upon all exit conditions.
*/
extern DECLSPEC void SDLCALL SDL_Quit(void);

Les macros DECLSPEC et SDLCALL ne doivent pas vous inquiéter, elles servent uniquement à assurer la portabilité du code. Si vous voulez plus de détails sur ces macros, jetez un oeil au fichier begin_code.h qui se trouvent parmi les include SDL.

Dans ce fichier, sont également déclarés un certain nombre de "constantes" qui servent à calculer le paramètre flags de SDL_init, une seule nous intéresse dans notre cas :


#define SDL_INIT_AUDIO 0x00000010

Nous pouvons d'ores et déjà définir les bases de notre wrapper :


sdl: context [
libsdl: compose [main (load/library %/usr/lib/libSDL.so)]

c-sdl: context [
;; constants
INIT_AUDIO: to-integer #^{00000010^}
protect 'SDL_INIT_AUDIO
;; functions
init: make routine! [flags [int] return: [int]] libsdl/main "SDL_Init"
quit: make routine! [] libsdl/main "SDL_Quit"
get-error: make routine! [return: [string!]] libsdl/main "SDL_GetError"
]
INIT_AUDIO: get in c-sdl 'INIT_AUDIO

init: func [flags [integer! block!] /local value] [
value: 0
foreach flag compose [(flags)] [value: or~ value flag]
if 0 <> c-sdl/init value [
make error! "SDL initialization problem!"
]
]
quit: does [
c-sdl/quit
foreach [name lib] libsdl [free lib]
]

get-error: does [make error! c-sdl/get-error]
]

SDL_mixer.h

Pour l'instant, notre wrapper bien qu'utilisable ne fait pas grand chose, passons à l'include qui nous permettra de faire un peu de musique.

Nous n'utiliserons pas les capacités de mixage de la librairie SDL_mixer, donc concentrons-nous sur les types et les routines liées à la gestion d'un morceau de musique.

Commençons par les constantes et les types :


/* Good default values for a PC soundcard */
#define MIX_DEFAULT_FREQUENCY 22050
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
#define MIX_DEFAULT_FORMAT AUDIO_S16LSB
#else
#define MIX_DEFAULT_FORMAT AUDIO_S16MSB
#endif
#define MIX_DEFAULT_CHANNELS 2
#define MIX_MAX_VOLUME 128 /* Volume of a chunk */

Ce groupe de "constantes" définit des valeurs par défaut pour la fréquence, le format et le nombre de canaux à utiliser lors de l'ouverture de l'audio.

Comme nous pouvons le voir, MIX_DEFAULT_FORMAT est une redéfinition, on trouve les valeurs utilisées dans l'include SDL_audio.h :


#define AUDIO_S16LSB 0x8010 /* Signed 16-bit samples */
#define AUDIO_S16MSB 0x9010 /* As above, but big-endian byte order */

On s'aperçoit que le format est dépendant du "byte order" de la machine. Il nous faudra en tenir compte lors de notre construction de l'interface Rebol pour maintenir la portabilité.


typedef enum ^{
MUS_NONE,
MUS_CMD,
MUS_WAV,
MUS_MOD,
MUS_MID,
MUS_OGG,
MUS_MP3
^} Mix_MusicType;

Ce type énuméré défini le type du morceau joué, il nous servira à fournir cette information dans l'interface. Aucune valeur initiale n'est spécifiée, donc MUS_NONE vaut 0.


/* The internal format for a music chunk interpreted via mikmod */
typedef struct _Mix_Music Mix_Music;

Ce type sert à stocker les informations d'un morceau, la structure _Mix_Music nous est inconnue.
Nous pourrons donc utiliser un integer! pour représenter les données de ce type.

Passons aux fonctions nécessaires à notre player.


extern DECLSPEC int SDLCALL Mix_OpenAudio(int frequency, Uint16 format, int channels,
int chunksize);
extern DECLSPEC int SDLCALL Mix_QuerySpec(int *frequency,Uint16 *format,int *channels);

extern DECLSPEC Mix_Music * SDLCALL Mix_LoadMUS(const char *file);

extern DECLSPEC void SDLCALL Mix_FreeMusic(Mix_Music *music);

extern DECLSPEC Mix_MusicType SDLCALL Mix_GetMusicType(const Mix_Music *music);

extern DECLSPEC int SDLCALL Mix_PlayMusic(Mix_Music *music, int loops);

extern DECLSPEC int SDLCALL Mix_VolumeMusic(int volume);

extern DECLSPEC void SDLCALL Mix_PauseMusic(void);
extern DECLSPEC void SDLCALL Mix_ResumeMusic(void);
extern DECLSPEC int SDLCALL Mix_PausedMusic(void);

extern DECLSPEC int SDLCALL Mix_PlayingMusic(void);

extern DECLSPEC void SDLCALL Mix_CloseAudio(void);

Que font-elles ?

Mix_OpenAudio : Cette fonction permet l'ouverture du système audio par le mixer.

Mix_QuerySpec : Cette fonction permet de connaître les paramètres avec lesquels le système audio a effectivement été initialisé. Ces valeurs peuvent être différentes des paramètres que vous avez demandé lors de l'ouverture en fonction des capacités de votre système.

Mix_LoadMUS : Cette fonction permet de charger le fichier sonore à jouer, elle nous retourne un pointeur sur la structure opaque Mix_Music.

Mix_FreeMusic : Cette fonction nous permet de demander à SDL_mixer de libérer la mémoire allouée pour le morceau, indispensable si vous ne voulez pas que votre mémoire soit envahie par les résidus de votre derniere séance de détente musicale.

Mix_GetMusicType : Cette fonction nous permet de déterminer le type du fichier sonore manipulé au moment de son appel. S'agit-il d'un MP3, d'un WAV standard ?

Mix_PlayMusic : Cette fonction, un peu indispensable pour notre player, permet de jouer le morceau préalablement chargé.

Mix_VolumeMusic : Cette fonction nous sera indispensable pour éviter de réveiller les voisins, en nous permettant de contrôler le volume.

Mix_PauseMusic/ Mix_ResumeMusic/ Mix_PausedMusic : Cet ensemble de fonctions nous permettra d'interrompre/de reprendre/de savoir si nous avons interrompu la lecture de notre fichier.

Mix_PlayingMusic : Cette fonction nous permet de savoir si nous sommes en cours de lecture d'un fichier et nous permettra de détecter la fin d'un morceau.

Mix_CloseAudio : Et enfin, cette fonction nous permettra de quitter proprement notre application en évitant de polluer le système audio de notre machine.

Bien !
En nous souvenant de ce qui a été dit dans la partie précédente (oui, je sais c'est dur !), on peut sans trop de difficultés enrichir notre contexte SDL :


int-ptr: make struct! [value [integer!]] none

sdl: context [
libsdl: compose [
main (load/library %/usr/lib/libSDL.so)
mixer (load/library %/usr/lib/libSDL_mixer.so)
]

c-sdl: context [
;; constants
INIT_AUDIO: to-integer #^{00000010^}
protect 'SDL_INIT_AUDIO

;; functions
init: make routine! [flags [int] return: [int]] libsdl/main "SDL_Init"

quit: make routine! [] libsdl/main "SDL_Quit"

get-error: make routine! [return: [string!]] libsdl/main "SDL_GetError"
]

INIT_AUDIO: get in c-sdl 'INIT_AUDIO

init: func [flags [integer! block!] /local value] [
value: 0
foreach flag compose [(flags)] [value: or~ value flag]
if 0 <> c-sdl/init value [
get-error
]
]

quit: does [
c-sdl/quit
foreach [name lib] libsdl [free lib]
]

get-error: does [make error! c-sdl/get-error]

mixer: context [

c-mixer: context [

MIX_DEFAULT_FREQUENCY: 22050

AUDIO_U8: to-integer 16#^{0008^} ;; Unsigned 8-bit samples
AUDIO_S8: to-integer 16#^{8008^} ;; Signed 8-bit samples

set [AUDIO_U16SYS AUDIO_S16SYS] switch get-modes system/ports/system 'endian compose/deep [
little [[(to-integer 16#^{0010^}) (to-integer 16#^{8010^})]]
big [[(to-integer 16#^{1010^}) (to-integer 16#^{9010^})]]
]

MIX_DEFAULT_FORMAT: AUDIO_S16SYS

MIX_DEFAULT_CHANNELS: 2

MIX_MAX_VOLUME: 128

Music-type: [
0 NONE
1 CMD
2 WAV
3 MOD
4 MIDI
5 OGG
6 MP3
]

open-audio: make routine! [
frequency [integer!]
format [integer!]
channels [integer!]
chunk-size [integer!]
return: [integer!]
] libsdl/mixer "Mix_OpenAudio"

query-spec: make routine! compose/deep/only [
frequency [struct! (first int-ptr)]
format [struct! (first int-ptr)]
channels [struct! (first int-ptr)]
return: [integer!]
] libsdl/mixer "Mix_QuerySpec"

load-music: make routine! [
file [string!]
return: [integer!]
] libsdl/mixer "Mix_LoadMUS"

free-music: make routine! [music [integer!]] libsdl/mixer "Mix_FreeMusic"

get-music-type: make routine! [
music [integer!]
return: [integer!]
] libsdl/mixer "Mix_GetMusicType"

play-music: make routine! [
music [integer!]
loops [integer!]
return: [integer!]
] libsdl/mixer "Mix_PlayMusic"

halt-music: make routine! [return: [integer!]] libsdl/mixer "Mix_HaltMusic"

volume-music: make routine! [
volume [integer!]
return: [integer!]
] libsdl/mixer "Mix_VolumeMusic"

pause-music: make routine! [] libsdl/mixer "Mix_PauseMusic"

resume-music: make routine! [] libsdl/mixer "Mix_ResumeMusic"

paused-music: make routine! [return: [integer!]] libsdl/mixer "Mix_PausedMusic"

playing-music: make routine! [return: [integer!]] libsdl/mixer "Mix_PlayingMusic"

close-audio: make routine! [] libsdl/mixer "Mix_CloseAudio"

]

MIX_DEFAULT_FREQUENCY: get in c-mixer 'MIX_DEFAULT_FREQUENCY
MIX_DEFAULT_FORMAT: get in c-mixer 'MIX_DEFAULT_FORMAT
MIX_DEFAULT_CHANNELS: get in c-mixer 'MIX_DEFAULT_CHANNELS
MIX_MAX_VOLUME: get in c-mixer 'MIX_MAX_VOLUME

open-audio: func [frequency format channels chunk-size] [
if 0 <> c-mixer/open-audio frequency format channels chunk-size [
get-error
]
]

query-spec: has [frequency format channels] [
frequency: make struct! int-ptr none
format: make struct! int-ptr none
channels: make struct! int-ptr none

c-mixer/query-spec frequency format channels
reduce [frequency/value format/value channels/value]
]

load-music: func [file [file!]] [
c-mixer/load-music to-local-file file
]

get-music-type: func [music [integer!]] [
select c-mixer/Music-type c-mixer/get-music-type music
]

play-music: get in c-mixer 'play-music

halt-music: get in c-mixer 'halt-music

free-music: get in c-mixer 'free-music

volume-music: get in c-mixer 'volume-music

pause-music: get in c-mixer 'pause-music

resume-music: get in c-mixer 'resume-music

paused-music: get in c-mixer 'paused-music

playing-music: get in c-mixer 'playing-music

close-audio: get in c-mixer 'close-audio

]

]

Bien, maintenant, nous pouvons construire notre application en utilisant notre bébé. :)


music-fmt-filt: ["*.mp3" "*.ogg" "*.wav" "*.mod" "*.mid"]

mms-quit: does [
sdl/mixer/close-audio
sdl/quit
]

free-music: does [
sdl/mixer/free-music song
song: make none! none
]

init: does [
sdl/init sdl/INIT_AUDIO
sdl/mixer/open-audio 44100 sdl/mixer/MIX_DEFAULT_FORMAT sdl/mixer/MIX_DEFAULT_CHANNELS 4096

song: make none! none
song?: false
volume-ctl/data: 128
show volume-ctl

status/text: "Stopped" show status
main/effect: [gradient 1x1 255.255.255 160.160.160]
]


mstyles: stylize/master [
mbtn: btn 40 ivory
sep: box 280x2 black
]

main: layout [
styles mstyles
h1 "audio player" black rate 0:0:1 feel [
engage: func [f a e] [
if all [a = 'time song? 0 = sdl/mixer/playing-music] [
song?: false
status/text: "Stopped" show status
]
]
]
sep
across
mbtn "select" [
if song [free-music]
if song: request-file/filter music-fmt-filt [
song: sdl/mixer/load-music first song
type/text: mold sdl/mixer/get-music-type song
show type
]
]
mbtn "play" [
if song [
sdl/mixer/play-music song 0
status/text: "Playing" show status
song?: true
]
]
mbtn "pause" [
either 0 = sdl/mixer/paused-music [
sdl/mixer/pause-music
status/text: "Paused" show status
][
sdl/mixer/resume-music
status/text: "Playing" show status
]
]
mbtn "stop" [
sdl/mixer/halt-music song?: false
status/text: "Stopped" show status
]
mbtn "halt" [free-music mms-quit quit]
mbtn "info" [
mixer-info: sdl/mixer/query-spec
view/new info-win: layout [
across
label black 80 "frequency" info 80 mold first mixer-info return
label black 80 "format" info 80 mold second mixer-info return
label black 80 "channels" info 80 mold third mixer-info return
btn 170 ivory "Close" [unview]
]
info-win/effect: [gradient 1x1 255.255.255 160.160.160]
show info-win
]

return sep return

label black "volume"
volume-ctl: scroller 150x15 [volume: to-integer multiply volume-ctl/data 128 sdl/mixer/volume-music volume]

return sep return

type: info 130x25 status: info 130x25
]

init
view main

A vous de jouer maintenant ! :)

Conclusion

Nous voilà arrivé au bout.
J'espère que cette lecture vous aura servi et vous permettra d'enrichir Rebol et d'en faire profiter tous vos amis codeurs de par le monde, en attendant l'interface plugin promise par Carl ! :)

Amusez-vous bien !

jeudi 28 avril 2005

Utilisation des drapeaux (flags) avec les styles View

Version : 1.0.2

Date : 28 avril 2005

Historique

Date Version Commentaires Auteur Email
10-03-2005 1.0.0 Première diffusion Didier Cadieu didec-at-wanadoo-dot-fr
23-04-2005 1.0.1 Corrections mineures Didier Cadieu didec-at-wanadoo-dot-fr
28-04-2005 1.0.2 Grammaire Didier Cadieu didec-at-wanadoo-dot-fr

Introduction

Les styles de View permettent de créer des interfaces graphiques rapidement et simplement grâce au dialecte VID. Mais VID montre vite ses limites lorsqu'on sort du simple formulaire de saisie. Dans ce cas, il faut passer au niveau supérieur qui consiste à définir ses propres styles. On peut également être amené à modifier la gestion des événements afin de les gérer de manière spécifique.

Cet article présente l'utilisation des flags, ou en français "drapeaux", qui sont un des éléments constitutifs des styles. Vous allez découvrir le rôle de chacun d'eux et, j'espère, pourrez ainsi en faire bon usage dans vos réalisations.

Cet article est basé sur mes recherches personnelles. N'hésitez pas à me signaler tout problème afin que je puisse l'améliorer au besoin.

Remarque

La description qui suit est basée sur Rebol/View 1.2.48. Je laisse au lecteur le soin de tester les différences entre cette version et les précédentes ou suivantes.

Néanmoins, les différences notables avec View 1.2.1 (dernière version officielle à la date de cet article) seront signalées.

Présentation des flags

Les flags : c'est (presque) tout bête.

Chaque style (et donc chaque face) a une propriété flags (c'est un block !) dans laquelle sont stockés des mots. Ces mots définissent des caractéristiques de l'objet que View va vérifier et qui vont influer sur la façon de gérer le style.

Il y a plusieurs avantages à cette technique :

  • flags étant un block, on peut ajouter tout un tas de mots sans avoir à modifier le style en lui ajoutant autant de propriétés.
  • On peut changer les flags d'un face pendant le déroulement du programme et ainsi modifier le comportement de ce face selon les besoins.

Définitions

Un style est un objet View utilisé comme modèle pour créer des faces tel que ceux stockés dans system/view/VID/vid-styles. On peut le considérer comme une classe au sens POO.

Un face est un objet View créé à partir d'un style. On peut considérer que c'est une instance de classe.

Mais en Rebol, instance et classe sont équivalents.

Gestion des flags

Les flags étant stockés dans un block, on pourrait les gérer directement avec les opérations insert, remove, etc... Mais cela ne serait pas très pratique.

Donc Rebol/view nous fournit 3 fonctions pour gérer ces drapeaux :

flag-face : ajoute si besoin un flag dans le block flags d'un face.

flag-face ? : teste la présence d'un flag dans le block flags d'un face.

deflag-face : enlève un flag du block flags d'un face s'il s'y trouve.

En utilisant la fonction help sur ces trois fonctions, vous obtiendrez l'usage suivant, que je vous ai francisé :


>> help flag-face
USAGE:
FLAG-FACE face 'flag

DESCRIPTION:
Met un drapeau dans un face VID.
FLAG-FACE est une fonction.

ARGUMENTS:
face -- (Type: object)
flag -- (Type: any)


>> help flag-face?
USAGE:
FLAG-FACE? face 'flag

DESCRIPTION:
Teste un drapeau dans un face VID.
FLAG-FACE? est une fonction.

ARGUMENTS:
face -- (Type: object)
flag -- (Type: any)


>> help deflag-face
USAGE:
DEFLAG-FACE face 'flag

DESCRIPTION:
Supprime un drapeau d'un face VID.
DEFLAG-FACE est une fonction.

ARGUMENTS:
face -- (Type: object)
flag -- (Type: any)

On peut noter que le drapeau n'est pas forcément un word ! puisque le type attendu est any.

Remarque

L'argument flag est précédé d'une apostrophe, ce qui signifie que cet argument ne sera pas réduit avant d'être transmis à la fonction. Voir le Core Manual pour plus de détails.

Soyons fou et regardons le code source de chaque fonction :


>> source flag-face
flag-face: func [
"Sets a flag in a VID face."
face [object!]
'flag
][
if none? face/flags [face/flags: copy [flags]]
if not find face/flags 'flags [face/flags: copy face/flags insert face/flags 'flags]
append face/flags flag
]

>> source flag-face?
flag-face?: func [
"Checks a flag in a VID face."
face [object!]
'flag
][
all [face/flags find face/flags flag]
]

>> source deflag-face
deflag-face: func [
"Clears a flag in a VID face."
face [object!]
'flag
][
if none? face/flags [exit]
if not find face/flags 'flags [face/flags: copy face/flags insert face/flags 'flags]
remove any [find face/flags flag []]
]

Ici, on notera que les fonctions flag-face et deflag-face prennent la précaution de dupliquer le block flags si celui-ci ne contient pas le mot flags, avant de le modifier.

J'avoue ne pas être sûr de la raison qui motive cette précaution. On peut penser que c'est pour éviter de modifier une propriété qui serait partagée par plusieurs faces. Mais les seules valeurs qui restent communes à des faces lors de leur clonage (avec make) sont les object ! (comme font, feel ou edge). Donc il semble que ce ne soit pas ça, ou alors c'est un héritage d'un comportement différent de make à l'aube de la première version de View.

Drapeaux par défaut des styles

Avant de regarder chaque flag en détails, faisons une petite analyse des drapeaux par défaut des styles.

La petite fonction suivante va faire ce travail :


styles: svv/vid-styles
forskip styles 2 [print [styles/1 mold styles/2/flags]]

Et voici son résultat :


face []
blank-face []
IMAGE []
BACKDROP [fixed drop]
BACKTILE [fixed drop]
BOX []
BAR []
SENSOR []
KEY []
BASE-TEXT [text]
VTEXT [text]
TEXT [flags text font]
BODY [flags text]
TXT [flags text]
BANNER [flags text font]
VH1 [flags text font]
VH2 [flags text font]
VH3 [flags text]
VH4 [flags text font]
LABEL [flags text font]
VLAB [flags text font]
LBL [flags text font]
LAB [flags text font]
TITLE [flags text font]
H1 [flags text font]
H2 [flags text font]
H3 [flags text font]
H4 [flags text font]
H5 [flags text]
TT [flags text font]
CODE [flags text font]
BUTTON []
CHECK [check input]
CHECK-MARK [check input]
RADIO [radio input]
CHECK-LINE [flags check input font]
RADIO-LINE [flags check input]
LED [input]
ARROW []
TOGGLE [toggle input]
ROTARY [input]
CHOICE [input]
DROP-DOWN [flags text font]
ICON []
FIELD [field return tabbed on-unfocus input]
INFO [field]
AREA [tabbed on-unfocus input]
SLIDER [input]
SCROLLER [input]
PROGRESS [input]
PANEL [panel]
LIST []
TEXT-LIST [flags text as-is input]
ANIM []
BTN []
BTN-ENTER []
BTN-CANCEL []
BTN-HELP [flags font]
LOGO-BAR []
TOG [toggle input][/CODE]

Description de chaque flag

Il n'y a malheureusement pas (à ma connaissance) de document officiel sur les différents mots possibles et leur influence. Donc ce qui suit est issu de mes expériences et recherches.

D'abord quelques drapeaux simples :

flags : est ajouté par les fonctions flag-face et deflag-face et indique que le block flags a été dupliqué et qu'il ne faut pas le refaire (voir source flag-face).

font : est ajouté par la fonction set-font qui sert à modifier la police d'un style. Il indique que l'objet font a été dupliqué et que c'est inutile de le faire à nouveau (voir source set-font).

On continue avec 2 flags liés à VID :

as-is : est ajouté par le mot-clé as-is de VID et indique que le texte du style ne doit pas être modifié par trim lors de son initialisation (voir le block init du style base-text par exemple).

hide : est ajouté par le mot-clé hide de VID. Les fonctions de gestion du texte tiennent compte de ce flag afin de masquer les caractères saisis. On voit des "*" à la place. Typiquement utilisé pour la saisie de mot de passe dans un face de type field.

Ces drapeaux-ci sont en rapport avec les champs de saisie (field et area) :

tabbed : indique que le style est accessible par la touche Tabulation (prise/perte de focus).

return : indique que l'appui sur la touche Entrée valide le champ et active le champ suivant (prise/perte de focus). Cela revient à dire que le champ n'est pas multi-lignes.

on-unfocus : indique que l'action associée au style doit être exécutée lorsque le style perd le focus.

field : indique que le texte du champ est sélectionné lorsqu'il reçoit le focus.

Les flags précédents, ainsi que le flag hide, sont testés et utilisés par les fonctions de gestion du texte et du focus. Voir l'objet ctx-text et particulièrement la fonction ctx-text/edit-text. Voir aussi focus, unfocus et la gestion d'événements de View comme system/view/screen-face/feel/event-funcs/1).

Ceux-là sont utilisés par la fonction layout (voir source layout) :

fixed : indique que la taille du champ ne doit pas être prise en compte dans le calcul de la position du champ qui suit (on bouge pas quoi). Utilisé pour les fonds backdrop et backtile.

drop : indique que le champ est un fond et que sa taille doit être ajustée à la taille de la fenêtre. Utilisé pour les fonds backdrop et backtile.

Celui-ci est un peu du même genre :

text : est utilisé lors du processus d'interprétation des instructions VID. La fonction multi/color du style est appelée pour dispatcher les tuple ! trouvés dans ces instructions. Si le flag est présent, le premier tuple ! est considéré comme la couleur de la police. Dans le cas contraire, ce sera la couleur de fond.

Les trois drapeaux suivants sont utilisés dans le feel/engage des styles où on les trouve. Lorsqu'on groupe les styles avec le mot-clé VID of (ex : radio of 'choix1 radio of 'choix1 radio of 'choix2), le mot qui suit of est mémorisé dans la propriété related du face. Un clic sur ce face provoque la recherche dans toute la fenêtre, des faces du même type et dont la propriété related a la même valeur. Ceci, afin de remettre leur propriété data à false, celle de l'élément cliqué étant mise à true. Ainsi, il n'y a toujours qu'un seul élément d'un groupe qui est "vrai".

check : indique que le style est un check box.

radio : indique que le style est un radio bouton.

toggle : indique que le style est un toggle.

Le suivant est sympa :

input : a été rajouté dans la béta de View 1.2.34. Il indique les styles qui contiennent une valeur. Les fonctions get-face et set-face en conjonction avec les accesseurs utilisent ce flag pour mémoriser ou affecter le contenu de tous les champs d'un layout en une seule commande. Vivement que ce soit officiel !!!

Un exemple d'utilisation des flags

Afin d'illustrer le propos, essayons d'utiliser un flag personnalisé.

Nous allons mettre en place un système très simple d'activation/désactivation des éléments d'une fenêtre.

Pour cela, nous allons utiliser un flag pour marquer les faces désactivées. Appelons le disable.

Ce drapeau sera vérifié par une fonction dans la chaine de gestion des événements. Si l'élément auquel est destiné l'événement est marqué par le flag, l'événement sera ignoré.

Pour commencer voici 2 fonctions permettant d'activer ou désactiver un face :


active: func [face] [deflag-face face disable]

desactive: func [face] [flag-face face disable]

Maintenant, réfléchissons à la manière de filtrer les événements.

D'abord, à quel endroit le faire ? Deux possibilités : 1) dans le feel/detect de la fenêtre 2) globalement, avec insert-event-func. La deuxième méthode permet de gérer toutes les fenêtres a un seul endroit, c'est celle que nous allons utiliser.

Tout de suite, la fonction, je vais la détailler ensuite :


insert-event-func ev-filter: func [face event] [
foreach f event/face/pane [
if all [within? event/offset f/offset f/size flag-face? f disable] [
print [f/var "est désactivé"]
event: none break
]
]
event
]

La première ligne crée la fonction ev-filter et l'ajoute à la liste des fonctions de gestion d'événements. Comme ne l'indique pas la documentation de View, où elle est juste mentionnée (mais source est bien utile là encore), les fonctions à passer à insert-event-func reçoivent 2 valeurs : un face qui sera toujours system/view/screen-face et l'événement (event).

A l'intérieur, une boucle foreach parcourt les éléments de la fenêtre où s'est produit l'événement. Cette fenêtre, on ne la trouve pas dans face, qui représente l'écran, mais dans la propriété de l'événement event/face. Les éléments d'une fenêtre sont dans sa propriété pane, donc on parcourt event/face/pane

Dans cette boucle, on vérifie deux conditions (avec all) : est-ce que la souris est sur l'élément en cours grâce à within ? et est-ce que cet élément a le drapeau disable ? Si la réponse est positive, on met la variable event à none et on sort de la boucle avec break. Au passage on affiche un petit message pour indiquer que l'événement a été ignoré (c'est pour la mise au point).

En sortie, la fonction retourne event, l'événement ou none s'il est ignoré.

Mettons tout ça avec un petit exemple pour pouvoir tester :


Rebol [
title: "Simple activation/désactivation de face"
author: "Didier Cadieu"
]

view/new layout [
across
text "b1:" b1: button "Bouton" [print face/text] return
text "b2:" b2: toggle "Basculera" "Basculé" [print face/text] return
text "b3:" b3: field "Champ" return
text "b4:" b4: check return
text "b5:" b5: radio of 'un return
text "b6:" b6: radio of 'un return
box 200x2 black return
button "Active tout" [active-tout] button "Désactive tout" [desactive-tout] return
c: rotary "b1" "b2" "b3" "b4" "b5" "b6"
button "Activer" [active get load c/text]
button "Désactiver" [desactive get load c/text] return
]

active: func [f] [deflag-face f disable]

desactive: func [f] [flag-face f disable]

;*** La liste de tous les mots représentant un champ de la fenêtre que l'on souhaite
;*** activer/désactiver globalement
b-list: [b1 b2 b3 b4 b5 b6]

;*** Active tous les champs de la liste
active-tout: does [
foreach b b-list [active get :b]
]

;*** Désactive tous les champs de la liste
desactive-tout: does [
foreach b b-list [desactive get :b]
]

insert-event-func func [face event] [
if event/type = 'key [print [event/1 event/2 event/3 event/5 event/6 dump-face event/7]]
foreach f event/face/pane [
if all [within? event/offset f/offset f/size flag-face? f disable] [
print [f/var "est désactivé"] event: none break
]
]
event
]

do-events

Attention, je ne prétends pas que cette fonction soit parfaite et j'ai prévenu que l'exemple était simpliste. En effet, elle ne tient pas compte des faces imbriquées, comme les panels. Elle ne filtre que les événements de la souris. Et il n'y a pas d'indication visuelle de l'état désactivé des éléments.

Pour en savoir plus

Si vous voulez en savoir plus, je vous invite à plonger dans le code de View. Mais pour plonger profond, il faut du matériel, alors penser à utiliser l'excellent Anamonitor de Romano Paolo Tenca.

Vous pourrez ainsi aller voir par vous-mêmes comment sont utilisés les différents flags détaillés précédemment.

En résumé

Les flags sont donc utilisés à tous les niveaux dans View :

  • Par layout (drop et fixed) ou des fonctions liées (text)
  • Par les block init des styles de texte (as-is)
  • Par le feel/engage de certains styles "groupables" (check, radio et toggle)
  • Par la gestion de l'entrée du texte (hide, return et tabbed) en conjonction avec les fonctions de gestion du focus (on-unfocus et field)
  • Comme indicateur d'une action effectué à ne pas refaire (flags et font)
  • Pour simplifier le travail du programmeur (input)

mardi 19 avril 2005

Partager une connexion Internet avec REBOL

Cet article vous explique comment partager une connexion ADSL à l'aide de Rebol.

Tutorial - rebXR. (2/3) Conception d'un service web : un forum

On va beaucoup causer PHP et assez peu REBOL dans cette partie. Mais c'est à mon avis une étape indispensable dans ce tutorial. Je ne cherche pas à te convertir au PHP. Ce qui est le plus important, c'est de voir que d'autres langages que REBOL sont capables de "parler" XML-RPC. C'est l'aspect communicant de cette application qui est réllement mis en valeur. Le langage servant à l'implémentation du service reste ensuite à ta discrétion.

Tutorial - rebXR. (1/3) Tour d'horizon de XML-RPC

RPC signifie Remote Procedure Call. C'est une norme d'échange qui permet à des "mondes" du réseau de communiquer entre eux. C'est tout simplement un langage commun, une sorte d'esperanto, de lingua franca entre les différents OS et/ou modes de communication.

PDF Maker : Utilisation des fonctions de dessin (2/3)

lundi 29 novembre 2004

FTP

Une initiation basique à l'utilisation du protocole FTP par REBOL.

samedi 04 mai 2002

Relation entre Rebol et VID (vu par un débutant)

Cela fait plus de 6 mois que j'ai découvert Rebol et j'aimerais par cet article vous commenter mes impressions et mettre sur papier les découvertes que j'ai déjà pu faire avec l'œil d'un novice.En effet, sur internet, la documentation sur rebol est bien rare et plus rare encore est l'explication du " comment-faire " un programme avec interface graphique.

dimanche 31 mars 2002

PDF-Maker (1/3)

dimanche 17 février 2002

Passage de paramètres à une fonction

En REBOL, le concept de passage par valeur ou par référence existe, même s'il n'est pas très clairement présenté dans la documentation disponible. Pour compliquer un peu plus la question, le comportement par défaut dépend du type de donnée ainsi que de l'action réalisée dans la fonction. Cet article a pour but de présenter en détail le comportement de REBOL en matière de passage d'arguments à une fonction.

lundi 14 janvier 2002

Rebol et le protocole SNMP

Vincent Demongodin a développé un protocole SNMP pour Rebol. Dans cet article, il présente SNMP et réalise une application cliente le mettant en oeuvre.

lundi 26 novembre 2001

Création d'un aspirateur de sites Web

Cet article pose les bases d'un aspirateur de pages HTML. Il est également prétexte à la découverte des puissantes fonctions de parsing de Rebol.

samedi 08 septembre 2001

Création et gestion de services sous Linux

Si l'écriture de serveurs TCP/IP est une chose aisée avec Rebol, leur intégration dans un système tel que Linux est considérablement plus complexe. Emmanuel Sterbac nous donne ici les pistes à suivre pour transformer au mieux un script Rebol en un service Linux.

mercredi 06 juin 2001

Cr

Christophe Genser pr