Obligement - L'Amiga au maximum

Vendredi 29 mars 2024 - 10:21  

Translate

En De Nl Nl
Es Pt It Nl


Rubriques

Actualité (récente)
Actualité (archive)
Comparatifs
Dossiers
Entrevues
Matériel (tests)
Matériel (bidouilles)
Points de vue
En pratique
Programmation
Reportages
Quizz
Tests de jeux
Tests de logiciels
Tests de compilations
Trucs et astuces
Articles divers

Articles in english


Réseaux sociaux

Suivez-nous sur X




Liste des jeux Amiga

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z,
ALL


Trucs et astuces

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Glossaire

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Galeries

Menu des galeries

BD d'Amiga Spécial
Caricatures Dudai
Caricatures Jet d'ail
Diagrammes de Jay Miner
Images insolites
Fin de jeux (de A à E)
Fin de Jeux (de F à O)
Fin de jeux (de P à Z)
Galerie de Mike Dafunk
Logos d'Obligement
Pubs pour matériels
Systèmes d'exploitation
Trombinoscope Alchimie 7
Vidéos


Téléchargement

Documents
Jeux
Logiciels
Magazines
Divers


Liens

Associations
Jeux
Logiciels
Matériel
Magazines et médias
Pages personnelles
Réparateurs
Revendeurs
Scène démo
Sites de téléchargement
Divers


Partenaires

Annuaire Amiga

Amedia Computer

Relec


A Propos

A propos d'Obligement

A Propos


Contact

David Brunet

Courriel

 


Programmation : Conventions des bibliothèques partagées
(Article écrit par Max et extrait d'Amiga News Tech - août 1992)


Certes, le système multitâche de l'Amiga fait des merveilles. Mais pour que tout fonctionne au mieux dans le meilleur des mondes, un certain nombre de restrictions et de conventions a du être arrêté.

Nous ne nous attarderons pas ici sur les restrictions, dont je suis d'ailleurs sûr que personne n'a rien à f..., mais plutôt sur les conventions, qui définissent et régulent le déroulement interne des opérations.

Ainsi par exemple, il a été convenu par les auteurs du logiciel système, que chacune de ses "couches" serait séparée des autres et fournirait au programmeur d'applications diverses un certains nombre de fonctionnalités destinées à l'aider dans sa tâche. C'est normal, il en va ainsi sur tous les ordinateurs du monde, banlieue comprise.

Cependant, l'originalité de l'Amiga (qui fait aussi sa puissance et, à moindre titre, sa complexité) est de permettre à plusieurs applications différentes de tourner "en même temps". Ainsi, il n'est pas rare de voir Deluxe Paint dans un coin, Excellence! dans un autre et Professional Page par-dessus tout ça (c'est ma configuration personnelle que je vous décris là), les trois disposant des mêmes ressources au même moment (écrans, fenêtres, menus, accès disques...).

Il fallait donc trouver un moyen de permettre aux applications d'avoir accès aux ressources du système sans entrer en conflit avec d'autres déjà chargées. Ce moyen existe, bien sûr : ce sont les bibliothèques partagées (de l'anglais "shared library").

Quèsaco ?

Le principe est donc simple : la bibliothèque met à la disposition du programmeur ses fonctions et, lorsque deux ou plusieurs programmes y accèdent en même temps, une seule copie de la bibliothèque est effectivement chargée en mémoire (les programmes se partagent, le temps de l'appel de la fonction, les mêmes adresses d'exécution).

Par convention encore, chaque bibliothèque "standard" se charge d'un domaine particulier du système : Exec pour le multitâche, DOS pour les accès disque, Graphics pour l'affichage et le dessin, Intuition pour les fenêtres, menus et compagnie... Ces bibliothèques-là sont brûlées en ROM, mais le système autorise également le chargement de bibliothèques supplémentaires depuis le disque (plus précisément, depuis le répertoire LIBS: de la disquette Workbench). C'est justement cette possibilité qui nous intéresse, car elle permet à l'utilisateur de créer ses propres bibliothèques de fonctions et de les ajouter au système. Cela demande cependant un minimum d'efforts de programmation.

La structure Library

Dans l'Amiga, tout est géré au moyen de structures de données, et les bibliothèques n'échappent pas à la règle.

bibliothèques

Cette structure commence par une autre structure, de type Node, qui permet de relier entre elles toutes les bibliothèques chargées. Elle contient également et notamment le numéro de version de la bibliothèque, ainsi qu'un compteur d'ouverture : lorsqu'une application veut utiliser les fonctions d'une bibliothèque donnée, elle doit d'abord "l'ouvrir", c'est-à-dire prévenir le système qu'elle a besoin de cette bibliothèque et qu'il faudra, le cas échéant, la charger en mémoire (une seule bibliothèque fait exception à cette règle : c'est l'exec.library, qui sert justement à en ouvrir d'autres !). Le compteur d'ouverture est alors incrémenté. Lorsque l'application n'a plus besoin de la bibliothèque, elle la "ferme" : le compteur d'ouverture est décrémenté et, s'il arrive à 0 (c'est-à-dire si plus aucune application ne l'utilise), la bibliothèque est déchargée de la mémoire.

Les champs "RegSise" et "PosSize" indiquent la taille de la mémoire qui a été allouée par le système lors de la création de la bibliothèque. Ils seront utilisés lors de son déchargement pour libérer cette mémoire, tâche qui incombe à la bibliothèque elle-même, et non au système !

L'ouverture d'une bibliothèque retourne ce qu'on appelle son adresse de base : c'est un pointeur sur la structure Library de la bibliothèque, qui est utilisé pour appeler ses fonctions. L'appel se fait via la registre A6, en sautant avec l'instruction JSR à un décalage situé en dessous de la base, dans la zone dite "négative". Les paramètres des fonctions sont passés dans les autres registres, généralement D0, D1, A0 et A1 et la valeur de retour est placée dans D0. La bibliothèque pourra accéder à des données internes située au-dessus de la base, dans la zone dite "positive", également via le registre A6, en utilisant le mode d'adressage indirect avec déplacement, par exemple MOVE.B lib_Flags(A6),d0.

Cette petite description s'arrêtera là. Je vous invite à vous reporter aux articles sur LibTool de Pascal Amiable pour en savoir un peu plus sur ce sujet (LibTool, le créateur de bibliothèque et Utilisation de GadToolsBox).

Le RomTag

Pour que le système reconnaisse une bibliothèque comme telle, elle doit présenter en son tout début un identificateur particulier (il ne suffit heureusement pas de renommer un programme quelconque en machin.library pour qu'il passe pour une bibliothèque !). Cet identificateur s'appelle un RomTag résident et est également représenté par une structure :

bibliothèques

A la base, le RomTag sert à la routine de réinitialisation, à rechercher dans la ROM tous les modules composant le système. La recherche est entreprise de la manière suivante :
  • Rechercher l'identificateur rt_MatchWord ($4AFC).
  • Vérifier que le mot long suivant (rt_MatchTag) pointe bien sur l'identificateur lui-même. Si c'est le cas, on est sûr de se trouver en présence d'un RomTag, sinon, poursuivre la recherche.
  • Initialiser le module trouvé en fonction des données codées dans les champs rt_Flags, rt_Type, rt_Pri et rt_Init.
  • Rechercher le prochain module en ROM à partir de l'adresse pointée par rt_EndSkip (ce qui évite de perdre du temps à le rechercher à l'intérieur de celui qu'on vient juste de trouver).
Pour les bibliothèques résidant sur disque, le RomTag sert seulement à identifier la bibliothèque comme telle, et à fournir au système les informations nécessaires à son initialisation. Toute bibliothèque doit donc contenir une telle structure en son début.

Écrire une bibliothèque

Dans ce qui suit, nous construirons une bibliothèque disposant de quatre fonctions :
  • resultat = Addition(op1, op2)
  • resultat = Soustraction(op1, op2)
  • resultat = Multiplication(op1, op2)
  • resultat = Division(op1, op2)
Ces quatre fonctions travaillent de la même manière : elles prennent en paramètre deux opérandes et retournent le résultat de l'opération dont elles portent le nom.

Nous étudierons le listing source de cette bibliothèque (dénommée operations.library) au fur et à mesure de notre progression.

;
; operations.library
; Démonstration de l'écriture d'une bibliothèque
;
; Copyright © 1992 Max pour ANT
;

; Inclus les fichiers nécessaires à l'assemblage
	INCLUDE	"exec/types.i"
	INCLUDE	"exec/libraries.i"
	INCLUDE	"exec/resident.i"
	INCLUDE	"exec/initializers.i"
	INCLUDE	"exec/memory.i"
	INCLUDE	"exec/exec_lib.i"

	INCLUDE	"libraries/operations.i"

Les deux instructions suivantes ne sont là qu'au cas où quelqu'un essaierait d'exécuter la bibliothèque directement depuis le CLI. On pourrait mettre ici une routine plus complète, qui afficherait par exemple un message d'erreur, ou ce qu'on veut.

RunLib	moveq	#0,d0
	rts

Suit le RomTag qu'Exec recherche dans la bibliothèque pour l'identifier comme telle. Le champs rt_EndSkip pointe sur la fin du code de la bibliothèque, même si cela n'est pas réellement nécessaire puisqu'il n'est utilisé utilisé que pour les modules en ROM, lors de la réinitialisation. Le champs rt_Flags est initialisé à RTF_AUTOINIT. Cela signifie qu'Exec appellera la fonction InitStruct() pour initialiser notre bibliothèque. rt_Init doit pointer sur un tableau composé d'un mot long et de trois pointeurs, qui sera transmis à cette fonction. Si RTF_AUTOINIT n'était pas spécifié, rt_Init devrait pointer directement sur une routine d'initialisation propre.

RomTag	dc.w	RTC_MATCHWORD		; Identificateur
	dc.l	RomTag			; Pointeur sur l'identificateur
	dc.l	EndCode			; rt_EndSkip
	dc.b	RTF_AUTOINIT		; rt_Flags
	dc.b	OP_VERSION		; rt_Version
	dc.b	NT_LIBRARY		; rt_Type
	dc.b	0			; rt_Pri
	dc.l	OpName			; rt_Name
	dc.l	OpIDStr			; rt_IDString
	dc.l	Init			; rt_Init

Voici la table qui sera passée en paramètre à initstruct. Le premier mot long contient la taille de la structure à initialiser. Le second mot long pointe sur le tableau des fonctions. Le troisième pointe sur le tableau des données. Le quatrième, enfin, pointe sur notre routine d'initialisation propre.

Init	dc.l	ob_SIZEOF		; Taille de la structure
	dc.l	FuncTable		; Tableau des fonctions
	dc.l	DataTable		; Tableau des données
	dc.l	InitRoutine		; Routine d'initialisation

Le tableau "FuncTable" n'est ni plus ni moins qu'une table de pointeurs sur toutes les fonctions de la bibliothèque. InitStruct 0 l'utilise pour fabriquer les décalages de sauts (LVOs) de la bibliothèque.

Les quatre premières fonctions sont obligatoires et communes à toutes les bibliothèques. Les autres sont propres à la bibliothèque. La fin du tableau est marquée par un pointeur de valeur -1.

FuncTable:
	dc.l	Open
	dc.l	Close
	dc.l	Expunge
	dc.l	Reserved
	dc.l	Addition
	dc.l	Soustraction
	dc.l	Multiplication
	dc.l	Division
	dc.l	-1

Le tableau "DataTable" contient des octets de commande qui indiquent à initstruct 0 quelles opérations effectuer. Sa structure est assez particulière et indéchiffrable, mais on dispose heureusement de macros (dans exec/initializers.i) pour faciliter sa définition.

Cette table demande à initstruct de placer des valeurs particulières (octet, mot ou mot long) à des décalages données dans la structure. Par exemple, "INITBYTE LN_TYPE,NT_LIBRARY" commande de mettre la valeur "NT_LIBRARY" dans le champs "LN_TYPE".

La fin du tableau est marquée par 0. Dans notre cas, c'est la structure Library qu'il faut initialiser.

DataTable:
	INITBYTE LN_TYPE,NT_LIBRARY	; On est une bibliothèque
	INITLONG LN_NAME,OpName
	INITBYTE LIB_FLAGS,LIBF_SUMUSED!LIBF_CHANGED
	INITWORD LIB_VERSION,OP_VERSION
	INITWORD LIB_REVISION,OP_REVISION
	INITLONG LIB_IDSTRING,OpIDStr
	dc.l	0

Voici enfin la première routine appelée lors de l'initialisation de la bibliothèque. Elle doit ouvrir toutes les ressources utilisées par la bibliothèque (il peut très bien ne pas y en avoir, mais pour l'exemple, nous ouvrons la dos.library et la gfx.library, même si elles ne sont pas utilisées). Cette fonction est appelée avec l'adresse de base de notre propre bibliothèque dans D0, tandis qu'A0 contient la liste de nos segments (n'oubliez pas qu'on réside normalement sur disque ; Exec nous a chargé en mémoire en appelant Loadseg()).

InitRoutine() doit retourner NULL si une erreur survient, auquel cas la bibliothèque sera immédiatement déchargée de la mémoire.

InitRoutine:
	move.l	a5,-(sp)		; Sauve les registres utilisés

	movea.l	d0,a5			; a5 = OpBase
	move.l	d0,ob_SegList(a5)	; Sauve notre SegmentList

	lea	dosname(pc),a1		; Ouvre la dos.library
	moveq	#0,d0
	jsr	_LVOOpenLibrary(a6)	; (A6 = ExecBase)
	move.l	d0,ob_DOSBase(a5)
	beq	.NoDos			; Erreur !!

	lea	gfxname(pc),a1		; Ouvre la graphics.library
	moveq	#0,d0
	jsr	_LVOOpenLibrary(a6)
	move.l	d0,ob_GfxBase(a5)
	beq	.NoGfx			; Erreur !!

	move.l	a5,d0			; Retourne OpBase dans D0
	bra.s	.Done

.NoGfx	movea.l	ob_DOSBase(a5),a1	; Referme la dos.library...
	jsr	_LVOCloseLibrary(a6)

.NoDos	moveq	#0,d0			; et retourne 0 dans D0

.Done	movea.l	(sp)+,a5
	rts

Les quatre premières fonctions d'une bibliothèque sont standard et obligatoires. Ce sont Open(), Close(), Expunge() et Reserved().

Open() est appelée à chaque fois que la bibliothèque est ouverte. Elle incrémente le compteur d'ouverture et efface le drapeau de demande de purge (voir plus loin). Open() est appelée avec l'adresse de base de la bibliothèque dans A6.

Open	addq.w	#1,LIB_OPENCNT(a6)	; Incrémente le compteur
	bclr	#LIBB_DELEXP,LIB_FLAGS(a6)	; Efface le flag de purge
	move.l	a6,d0			; Retourne la base dans D0
	rts

Close() est appelée quand la bibliothèque est refermée. Elle décrémente le compteur d'ouverture et appelle au besoin Expunge() pour décharger la bibliothèque de la mémoire. Elle est appelée avec le pointeur de base dans A6 et retourne soit la SegList initiale, soit 0.

Close	moveq	#0,d0			; Prépare la valeur de retour
	subq.w	#1,LIB_OPENCNT(a6)	; Décrémente le compteur
	bne.s	.ret			; Il reste des utilisateurs !
	btst	#LIBB_DELEXP,LIB_FLAGS(a6)	; Demande de déchargement ?
	bne.s	Expunge			; Si oui, on purge
.ret	rts

Expunge() est le pendant d'InitRoutine() : elle est appelée quand le système a besoin de mémoire et qu'il veut décharger notre bibliothèque.

Cette fonction doit retirer la bibliothèque de la liste gérée par Exec, libérer la mémoire allouée par InitStruct(), libérer les ressources allouées pendant l'initialisation, et enfin retourner la liste des segments dans D0, afin qu'Exec puisse appeler UnLoadSeg(). Si elle retourne NULL, la bibliothèque n'est pas déchargée.

Expunge	movem.l	d2/a5/a6,-(sp)		; Sauve les registres utilisés

	move.l	a6,a5			; OpBase dans a5
	movea.l	$4.w,a6			; ExecBase dans a6

	tst.w	LIB_OPENCNT(a6)		; Bibliothèque encore utilisée ?
	beq.s	.Purge			; Non, on purge

	bset	#LIBB_DELEXP,LIB_FLAGS(a5)	; Place le flag de purge
	moveq	#0,d0
	bra.s	.Ret
.Purge	move.l	ob_SegList(a5),d2	; Prépare la valeur de retour

	movea.l	a5,a1			; Retire la bibliothèque de la
	jsr	_LVORemove(a6)		; liste gérée par Exec

	moveq	#0,d0			; Libère la mémoire allouée par
	movea.l	a5,a1			; InitStruct()
	move.w	LIB_NEGSIZE(a5),d0
	suba.l	d0,a1
	add.w	LIB_POSSIZE(a5),d0
	jsr	_LVOFreeMem(a6)

	move.l	d2,d0			; Retourne la SegList dans D0

.Ret	movem.l	(sp)+,d2/a5/a6
	rts

Enfin, Reserved() porte bien son nom : elle est réservée pour des extensions futures. Pour le moment, elle doit juste retourner 0.

	moveq	#0,d0
	rts

Reste le secteur de données. On peut le placer où l'on veut, mais je trouve plus clair de le mettre entre les fonctions système et les fonctions propres.

OpName	OPERATIONSNAME
OpIDStr	dc.b	"operations.library v1.0 (23 Jun 1992)",13,10,0
dosname	dc.b	"dos.library",0
gfxname	dc.b	"graphics.library",0
	even

A partir d'ici, on trouve les fonctions propres à la bibliothèque.

Addition:
	add.l	d1,d0
	rts

Soustraction:
	sub.l	d1,d0
	rts

Multiplication:
	muls	d1,d0
	rts

Division:
	divs	d1,d0
	rts

Dernière ligne du source, celle qui définit le label EndCode utilisé dans le RomTag.

EndCode	END

Voilà, il ne reste plus qu'à assembler tout ça en un programme exécutable baptisé libs:operations.library.

Fichiers include

Pour rendre votre bibliothèque utilisable par d'autres programmeurs que vous, il faut leur fournir les fichiers include nécessaires. On en trouve généralement deux par langage, à savoir l'include principal, qui définit toutes les constantes et structures propres à la bibliothèque, et l'include secondaire qui définit sa structure de base. Je ne vous livre ici que celui destiné à l'assembleur et laisserai à votre sagacité le soin d'écrire son équivalent pour le C, le Pascal, ou tout autre langage de votre choix.

	IFND	LIBRARIES_OPERATIONS_I
LIBRARIES_OPERATIONS_I	SET	1

	IFND	EXEC_TYPES_I
	INCLUDE	"exec/types.i"
	ENDC

	IFND	EXEC_LIBRARIES_I
	INCLUDE	"exec/libraries.i"
	ENDC

;
; operations.library
; Démonstration de l'écriture d'une bibliothèque
;
; Copyright © 1992 Max pour ANT
;
; Fichier include assembleur ('include:libraries/operations.i')


; Version de la bibliothèque
OP_VERSION	EQU	1
OP_REVISION	EQU	0

; Structure de base
    STRUCTURE	OpBase,LIB_SIZE		; Structure Library
	ULONG	ob_SegList		; Liste de nos segments DOS
	APTR	ob_DOSBase		; Pointeur sur la bibliothèque DOS
	APTR	ob_GfxBase		; Pointeur sur la bibliothèque Gfx
	LABEL	ob_SIZEOF

; Nom de la bibliothèque
OPERATIONSNAME	MACRO
	dc.b	"operations.library",0
	ds.w	0
	ENDM

; décalage de sauts (LVOs)
	LIBINIT
	LIBDEF	_LVOAddition
	LIBDEF	_LVOSoustraction
	LIBDEF	_LVOMultiplication
	LIBDEF	_LVODivision

	ENDC	; LIBRARIES_OPERATIONS_I


[Retour en haut] / [Retour aux articles]