Suivez-nous sur X
|
|
|
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
|
|
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
|
|
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
|
|
A propos d'Obligement
|
|
David Brunet
|
|
|
|
Programmation : Assembleur - les commodités
(Article écrit par Frédéric Delacroix et extrait d'Amiga News - juin 1995)
|
|
La commodities.library est une autre des bonnes choses que nous ont apportées les ingénieurs de Commodore lors de la sortie du
Kickstart 2.0. Elle permet de regrouper tous les utilitaires système sous une interface standard et leur facilite l'accès aux
"hotkeys" (raccourcis clavier).
Une "hotkey", vous le savez certainement, est une combinaison de touches qui a un certain effet sur un programme donné. Par
extension, ce peut aussi être l'insertion d'une disquette ou l'action sur un bouton de la souris.
La commodities.library a ainsi simplifié la description (ce qui permet à l'utilisateur de configurer facilement ses choix) et
la détection de ces raccourcis clavier, grâce à une syntaxe facile et puissante, et un système de programmation orientée objet performant.
Le but de cet article n'étant pas de décrire la commodities.library, je vous renvoie à l'article d'Olivier Jeannet paru dans
Amiga News n°73 à ce sujet.
Le sujet
Chaque commodité crée généralement (bien que rien ne l'y oblige) au moins un raccourcis-clavier, ne serait-ce que pour appeler
sa fenêtre de configuration.
Certaines (ToolManager par exemple) peuvent en créer des centaines à la demande de l'utilisateur. Comment alors se rappeler qu'il
y a trois mois, j'avais défini quelque part un raccourcis-clavier pour faire quelque chose comme ça ?
C'était "rawkey lcommand rcommand -caps control repeat a" ou bien "rawkey right_mouse lcommand rshift capslock numeric_pad enter" ?
Le programme Exchange fourni avec le Workbench liste toutes les commodités déclarées (par leur "Broker"), mais pas les
raccourcis-clavier créés (sous forme d'objet de type CX_FILTER) par ces commodités. La première raison est qu'il peut y en avoir
énormément, et la seconde est que ces objets CX_FILTER ne sont pas conservés en mémoire sous forme de description écrite mais sous
forme compilée, en utilisant une structure InputXpression.
On peut toutefois imaginer, nous y voilà, pour résoudre ce type de problème crucial, un utilitaire dans le genre de DOSTrace,
IconTrace ou SnoopDOS, qui garderait la trace de la création des objets CX_FILTER en modifiant la commodities.library. C'est
exactement le rôle du programme de ce mois-ci.
Le programme
Il s'agit de modifier la fonction CreateCxObj() de la commodities.library. Son rôle est de créer un nouvel objet, dont le type est
fourni en D0, en utilisant deux arguments supplémentaires fournis dans les registres A0 et A1, qui dépendent entièrement du type
de l'objet. Nous remplaçons donc la fonction originale de la commodities.library par la fonction qui s'appelle NewCreateCxObj()
dans le code source ci-dessous. Celle-ci commence par tester si l'objet à créer est de type CX_FILTER. Si c'est le cas, A0 pointe
sur la chaîne de description qui nous intéresse, et A1 est nul. On affiche donc, grâce à une fonction du DOS, le nom du programme
et la description du raccourci-clavier créé. Cependant, il faut faire très attention : lorsque la fonction NewCreateCxObj() est
appelée, elle l'est par une autre tâche que le programme qui l'a installée, et qui n'est pas censé appeler le DOS. Un minimum de
précautions s'impose donc : il faut tester si cette tâche est en réalité un processus (les simples tâches ne peuvent pas appeler
les fonctions du DOS), ce qui se fait en regardant si le champ ln_Type de la structure Node incluse dans la structure Task
pointée par le champ ThisTask de la structure ExecBase (ouf) est à NT_PROCESS.
J'ai ajouté une difficulté supplémentaire : gérer plusieurs fichiers journal (logs) à la fois. Cela montre un peu l'usage de la
fonction ReadArgs() et permet d'avoir à la fois le compte rendu à l'écran et dans un fichier. La fonction ReadArgs() réserve
ainsi un tableau de pointeur sur les noms de fichiers à créer. Une boucle calcule la taille de la zone mémoire à réserver pour
stocker les pointeurs sur les FileHandles correspondants. On ouvre ensuite ces fichiers en écriture un à un. Une routine
(la bien-nommée Print) se chargera d'écrire dans tous ces fichiers. Ensuite, la fonction CreateCxObj() est modifiée par
SetFunction() et le programme attend un Ctrl-C avant de quitter.
Précautions
A chaque fois qu'on utilise SetFunction(), il faut être très prudent. En effet, rien ne garantit l'exclusivité des modifications
d'une bibliothèque à votre tâche. Il se peut très bien qu'une autre tâche modifie la routine que vous avez vous-même modifiée.
Si vous quittez avant cette autre tâche, vous annulez sa modification en remettant tout comme vous l'avez trouvé ! Il y a aussi
des cas où une tâche se retrouve en train d'accéder à de la mémoire qui a été libérée ! Les programmes polis qui modifient les
fonctions vous en avertissent avant de quitter. Je pense notamment à MagicMenu (qui vous propose de se désactiver) et CycleToMenu
(qui refuse de quitter). D'autres programmes préfèrent laisser une petite routine en mémoire qui ne fait rien d'autre que sauter
à l'ancienne fonction, le reste du programme étant libéré (là aussi il faut faire très attention).
D'autre part, quand on utilise SetFunction() pour modifier une bibliothèque, il faut faire attention à bien respecter l'assignation
des registres de la fonction originale. Tous les registres autres que D0-D1/A0-A1/A6 doivent être sauvegardés absolument. De plus,
il ne faut pas utiliser SetFunction() sur les bibliothèques non standard, c'est-à-dire celles qui ne comportent pas de table de
saut à base de JMP. Je ne connais qu'une telle clownerie : la dos.library pré-2.0, écrite en BCPL (m'enfin !). L'autodoc de la
fonction SetFunction() dit qu'il faut alors tout faire "à la main" (donc en s'occupant des caches du processeur et des sommes
de contrôle) en préservant les six octets du vecteur. Même comme ça, ça ne risque pas de marcher puisque cette table est faite à
coup d'instruction BRA (du moins si on se fie à l'excellent Amiga Guru Book), c'est-à-dire de déplacements relatifs. Il faut
alors décoder les instructions et calculer l'adresse de branchement exacte. Eddy Caroll a eu beaucoup de travail pour son
SnoopDOS !
Enfin, je rappelle que tout code faisant partie d'une bibliothèque partagée est susceptible d'être appelé par plusieurs tâches à
la fois, et donc qu'il doit être réentrant ou comporter un système d'arbitrage à base de sémaphores.
Sur ce, je vous laisse avec le code source du programme. Je vous rappelle qu'il prend comme argument des noms de fichiers journal
à créer (par exemple : HotKeyTrace CONSOLE: RAM:HKT.Log), et ne demande qu'à être amélioré : ajoutez une interface graphique,
des fonctions de remplacement d'un raccourci-clavier par un autre, etc.
include exec/exec.i
include exec/exec_lib.i
include libraries/commodities.i
include libraries/commodities_lib.i
move.l 4.w,a6
lea DOS.Name(pc),a1 ; ouvre le dos
moveq #37,d0
jsr _LVOOpenLibrary(a6)
move.l d0,DOS.Base
beq exit
lea Commodities.Name(pc),a1
moveq #37,d0 ; ouvre commodities
jsr _LVOOpenLibrary(a6)
move.l d0,Commodities.Base
beq closedos
move.l DOS.Base(pc),a6
move.l #Args.Template,d1
move.l #Args.Array,d2
moveq #0,d3 ; lit les arguments
jsr _LVOReadArgs(a6)
move.l d0,Args.RDArgs
beq closecommodities
move.l Args.Array(pc),a4
CountLogs ; allouer de la mémoire pour
tst.l (a4)+ ; les handles des
bne.s CountLogs ; fichiers log
sub.l Args.Array(pc),a4
move.l a4,d0
move.l #MEMF_PUBLIC!MEMF_CLEAR,d1
move.l 4.w,a6
jsr _LVOAllocVec(a6)
move.l d0,Logs.Handles
beq freeargs
move.l Args.Array(pc),a4
move.l Logs.Handles(pc),a5
move.l DOS.Base(pc),a6
LogOpen move.l (a4)+,d1
beq.s LogEnd ; ouvre les fichiers log
move.l #MODE_NEWFILE,d2
jsr _LVOOpen(a6)
move.l d0,(a5)+
bne.s LogOpen
LogEnd move.l Commodities.Base(pc),a1
move.l #_LVOCreateCxObj,a0
move.l #NewCreateCxObj,d0
move.l 4.w,a6 ; patche CreateCxObj()
jsr _LVOSetFunction(a6)
move.l d0,OldCreateCxObj
lea Installed.MSG(pc),a3
move.l d0,-(sp)
pea NewCreateCxObj
move.l sp,a4
bsr.s Print
addq.l #8,sp
move.l #SIGBREAKF_CTRL_C,d0
jsr _LVOWait(a6) ; attend un CTRL-C
move.l Commodities.Base(pc),a1
move.l #_LVOCreateCxObj,a0 ; remet tout
move.l OldCreateCxObj(pc),d0 ; en place
jsr _LVOSetFunction(a6)
move.l DOS.Base(pc),a6
move.l Logs.Handles(pc),a4
LogClose
move.l (a4)+,d1 ; ferme les logs
beq.s freehandles
jsr _LVOClose(a6)
bra.s LogClose
freehandles
move.l Logs.Handles(pc),a1
move.l 4.w,a6
jsr _LVOFreeVec(a6)
freeargs
move.l Args.RDArgs(pc),d1
move.l DOS.Base(pc),a6 ; libère les arguments
jsr _LVOFreeArgs(a6)
closecommodities
move.l Commodities.Base(pc),a1
move.l 4.w,a6 ; ferme tout et quitte
jsr _LVOCloseLibrary(a6)
closedos
move.l DOS.Base(pc),a1
jsr _LVOCloseLibrary(a6)
exit moveq #0,d0
rts
; la routine suivante est un printf() pour tous les fichiers
; log
Print ; Print(Format,Args)(A3,A4)
movem.l d0-d3/a0-a1/a4-a6,-(sp)
move.l DOS.Base(pc),a6
move.l Logs.Handles(pc),a5
.Loop move.l (a5)+,d1
beq.s .End
move.l a3,d2
move.l a4,d3
jsr _LVOVFPrintf(a6)
bra.s .Loop
.End movem.l (sp)+,d0-d3/a0-a1/a4-a6
rts
; nouvelle fonction CreateCxObj(): elle est appelée à la
; place de celle de commodities.
NewCreateCxObj ; Obj=NewCreateCxObj(type,arg1,arg2)(D0,A0,A1)
cmp.l #CX_FILTER,d0
bne.s .Normal
movem.l d0/a1/a3-a4/a6,-(sp)
move.l 4.w,a6
move.l ThisTask(a6),a1
cmp.b #NT_PROCESS,LN_TYPE(a1)
bne.s .Back ; seulement les processus
move.l a0,-(sp) ; touche en question
move.l LN_NAME(a1),d0
bne.s .GoodName
move.l #EmptyName,d0
.GoodName
move.l d0,-(sp) ; nom du prog
lea Creating.MSG(pc),a3
move.l sp,a4
bsr Print
addq.l #8,sp
.Back movem.l (sp)+,d0/a1/a3-a4/a6
.Normal move.l OldCreateCxObj(pc),-(sp)
rts ; saute à la routine originale
DOS.Base dc.l 0
Commodities.Base dc.l 0
Args.Array dc.l 0
Args.RDArgs dc.l 0
Logs.Handles dc.l 0
OldCreateCxObj dc.l 0
DOS.Name dc.b 'dos.library',0
Commodities.Name dc.b 'commodities.library',0
Args.Template dc.b 'LOGS/M',0
Installed.MSG dc.b 'Patch Installé: CreateCxObj().',10
dc.b ' Nouveau: %lx',10
dc.b ' Ancien: %lx',10,0
Creating.MSG dc.b 'Objet CX_FILTER créé par "%s":',10
dc.b '%s',10,0
EmptyName dc.b '«Inconnu»',0
|
|