Retour menu KMD / Retour menu assembleur

Services

2.1 Services
2.2 L'administrateur de contrôle de service
2.3 Le programme de contrôle de service 2.4 Macros de chaîne
 Code Source : KmdKit\examples\simple\Beeper

Vous vous demandez peut être comment les services du mode utilisateur se relient aux drivers en mode Kernel. En fait ce sont des bestioles tout à fait différentes. Mais avant que nous puissions communiquer avec le module de gestion de périphérique [ device driver ] nous devons l'installer et le démarrer. Nous devrons ici nous conformer aux règles d'interface destinées aux services.

2.1 Services

Windows NT a un mécanisme pour démarrer les processus qui fournissent des services non attachés à un utilisateur interactif. De tels processus s'appellent les services. Un bon exemple d'un service pourrait être un serveur Web. La plupart des services temporels [ time services ] n'ont absolument aucune interface utilisateur. C'est la seule catégorie d'applications fonctionnant d'une telle manière. Les services peuvent être démarrés au moment même du démarrage de système ou bien ils peuvent également être démarrés manuellement. Dans ce sens les modules de gestion de périphérique sont très semblables aux services.

Windows NT supporte également un service de drivers [ drivers services ] qui se conforme aux protocoles de module de gestion de périphérique pour Windows NT. Il est semblable au service du mode utilisateur. Ainsi, les services peuvent se référer à un processus de serveur ou encore à un module de gestion de périphérique. Microsoft a mélangé les drivers en mode Kernel et ceux en mode utilisateur. Par conséquent la suite de ce tutorial peut sembler être un imbroglio, puisque je parlerai parfois de "service" et parfois de "driver". Cet article traite uniquement des modules de gestion de périphérique (sous entendu "drivers"). Ainsi vous devriez toujours lire "driver". Je le notifierai évidemment s'il est nécessaire de séparer l'appellation "service" de celle de "driver". Considérez en outre que la documentation décrivant les fonctions en relation avec les services, est parfois plutôt ambiguë. Beaucoup de fonctions discutées dans cette section s'appliquent aussi bien aux services qu'aux modules de gestion de périphérique, mais je me concentrerai sur des modules de gestion de périphérique et éviterai de discuter des services.

Il y a trois types de composants impliqués dans le fonctionnement des services de Windows NT:

Comme il a déjà été dit, nous étudierons le driver lui-même dans la prochaine partie, mais maintenant concentrerons nous sur les deux premiers composants.

2.2 L'administrateur de controle de services [SCM]

Le SCM réside dans \%SystemRoot%\System32\Services.exe. Le processus de Winlogon démarre le SCM tôt pendant le boot du système. Il balaye alors le contenu de HKLM\SYSTEM\CurrentControlSet\Services, créant une entrée dans la base de donnée de service pour chaque clef qu'il rencontre. Une entrée de base de donnée inclut tous les paramètres connexes définis pour un service. Si le service ou le driver est marqué comme ayant un démarrage automatique le SCM le démarre et détecte les échecs au démarrage.

Pour vous faire une certaine représentation à ce sujet, démarrez l'éditeur de registre (\%SystemRoot%\regedit.exe), ouvrez HKLM\SYSTEM\CurrentControlSet\Services\ et explorez son contenu.

Pour énumérer les services installés (et non les drivers), choisissez les outils d'administration du panneau de configuration, et choisissez « services ».

Depuis la « Gestion de l'ordinateur » vous pouvez énumérer les drivers installés. (Menu Démarrer, choisissez Programmes, outils d'administrations, gestion de l'ordinateur ; ou du panneau de configuration, outils d'administrations, gestion de l'ordinateur.) Depuis « gestion de l'ordinateur », ouvrez « Information système » puis « Environnement logiciel », et ouvrez « Pilotes ». (Malheureusement, cette option n'est plus présente depuis Windows XP).

Après avoir analysé le contenu de ces trois fenêtres, vous remarquerez qu'ils coïncident à bien des égards.

L'entrée HKLM\SYSTEM\CurrentControlSet\Services\ contient des sous-clés, désignées en interne par le nom du driver ou du service. Chaque sous-clé inclut tous les paramètres en relation avec le service.

Considérons un ensemble minimum possible de paramètres nécessaires pour installer un module de gestion de périphérique. Comme exemple, nous prendrons le driver beeper.sys (nous parlerons du driver en lui-même la fois prochaine).



Figure 2-1. Clés de registre pour le driver beeper.sys

Paramètre Description

DisplayName

- nom du service à employer par des programmes d'interface utilisateur. Si aucun nom n'est indiqué, le nom de la clef d'enregistrement du service devient son nom.

ErrorControl

- si un driver reporte une erreur en réponse à la commande de démarrage de SCM, cette valeur indique le niveau de la commande d'erreur et détermine comment le SCM réagit.

Deux de ces valeurs recèlent un certain intérêt pour nous :

SERVICE_ERROR_IGNORE (0)

- Le Manager d'I/O ignore les erreurs de retour du driver mais continue l'opération de démarrage. Rien n'est noté [logged];

SERVICE_ERROR_NORMAL (1)

- Si le driver ne se charge pas ou ne s'initialise pas, le démarrage devrait s'en suivre mais un avertissement est affiché à l'utilisateur. Un événement est écrit dans le System Event Log [notification des événements système].

Vous pouvez voir une description des événements système en choisissant « Outils d'administration » > « Observateur d'événement » et en double-cliquant sur un événement.

Le driver du beeper fait tout un travail utile durant l'étape d'initialisation (dans la routine DriverEntry), puis il renvoie un code d'erreur pour être enlever de la mémoire puisqu'il ne peut faire rien davantage. Le paramètre ErrorControl pour le driver du beeper est égal à SERVICE_ERROR_IGNORE, ainsi aucune notification [log] n'est faite.

ImagePath

- indique le chemin complet de l'image du driver.

Si ImagePath n'est pas indiqué, le Manager d'I/O recherche les drivers dans le dossier \Winnt\System32\Drivers.

Start

- indique quand démarrer le driver.

Ici, seules deux valeurs peuvent nous être utiles :

SERVICE_AUTO_START (2)

- Le driver démarre en même temps que le système.

SERVICE_DEMAND_START (3)

- Le driver est démarré sur demande par le SCM en réponse à une demande explicite d'utilisateur.

N'importe quel driver ayant valeur Start indiquée comme étant SERVICE_AUTO_START (2) est démarré par le SCM pendant le démarrage du système. De tels drivers s'appellent Services auto démarrant [ auto-start services ]. Si le driver dépend d'autres drivers, le SCM démarre également ces drivers (les modules de gestion de périphérique peuvent employer les valeurs Group et Tag pour commander l'ordre de chargement, mais à la différence des services, ils ne peuvent pas indiquer des valeurs de DependOnGroup ou de DependOnService). Il y a également d'autres drapeaux [flags] indiquant le démarrage automatique, par exemple, SERVICE_BOOT_START (0). Seuls les modules de gestion de périphérique peuvent l'indiquer. Le Manager d'I/O charge de tels drivers avant que tous les processus en mode utilisateur ne s'exécutent, et donc avant que le SCM ne démarre.

Type

- indique le type de service.

Puisque nous allons traiter de module de gestion de périphérique la seule valeur que nous pouvons employer est SERVICE_KERNEL_DRIVER (1).

Après avoir regardé le schéma 2-1, que pouvons nous dire au sujet du driver beeper.sys ? Et bien, le driver en mode Kernel du beeper réside dans le dossier C:\masm32\Ring0\Kmd\Article2\beeper. Son nom d'affichage est "Nice Beeper Melody", son démarrage est sur demande, et les erreurs possibles sont ignorées et non notifiées.

Nous verrons plus tard ce que le préfixe «  \?? » situé dans le chemin du dossier de l'image du driver signifie.

Si nous voulons démarrer le driver, qui n'est pas présent dans la base de données du SCM, cela peut être fait dynamiquement, à tout moment, avec l'aide du programme de gestion de service ( Device Control Program pour être plus précis, mais il n'existe aucun concept de ce type dans la terminologie Microsoft).

2.3 Le Programme de controle de service [SCP]

Comme l'indique son nom, le programme de gestion de service est prévu pour commander les services ou les modules de gestion de périphérique. Il fait ceci sous la surveillance du SCM, appellant les fonctions appropriées. Tous sont exportés par le module \%SystemRoot%\System32\advapi.dll ( Advanced AP I).

Voici le code du programme de gestion de service qui commandera le driver beeper.sys.

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                                                                                   
;  Programme de contrôle de services pour le driver "beeper"         				  
;                                                                                                   
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

.386
.model flat, stdcall
option casemap:none

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                             I N C L U D E   F I L E S                                             
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

include \masm32\include\windows.inc

include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
include \masm32\include\advapi32.inc

includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\advapi32.lib

include \masm32\Macros\Strings.mac

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                       C O D E                                                     
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

.code

start proc

local hSCManager:HANDLE
local hService:HANDLE
local acDriverPath[MAX_PATH]:CHAR

    invoke OpenSCManager, NULL, NULL, SC_MANAGER_CREATE_SERVICE
    .if eax != NULL
        mov hSCManager, eax

        push eax
        invoke GetFullPathName, $CTA0("beeper.sys"), sizeof acDriverPath, addr acDriverPath, esp
        pop eax

        invoke CreateService, hSCManager, $CTA0("beeper"), $CTA0("Nice Melody Beeper"), \
                SERVICE_START + DELETE, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, \
                SERVICE_ERROR_IGNORE, addr acDriverPath, NULL, NULL, NULL, NULL, NULL
        .if eax != NULL
            mov hService, eax
            invoke StartService, hService, 0, NULL
            invoke DeleteService, hService
            invoke CloseServiceHandle, hService
        .else
            invoke MessageBox, NULL, $CTA0("Can't register driver."), NULL, MB_ICONSTOP
        .endif
        invoke CloseServiceHandle, hSCManager
    .else
        invoke MessageBox, NULL, $CTA0("Can't connect to Service Control Manager."), \
                            NULL, MB_ICONSTOP
    .endif

    invoke ExitProcess, 0

start endp

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                                                                                   
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

end start

2.3.1 Etablir une connexion au SCM

La première étape est d'appeler la fonction OpenSCManager pour établir un raccordement au SCM sur l'ordinateur indiqué et pour ouvrir la base de données adéquate.

OpenSCManager proto lpMachineName:LPSTR, lpDatabaseName:LPSTR, dwDesiredAccess:DWORD
Paramètre Descriptionĺ

lpMachineName

- Pointe vers une chaîne terminée par NULL nommant l'ordinateur cible. Si le pointeur est NULL ou s'il dirige vers une chaîne vide, la fonction se connecte au SCM de l'ordinateur local.

lpDatabaseName

- Pointe vers une chaîne terminée par NULL qui nomme la base de donnée à ouvrir du SCM. Cette chaîne devrait indiquer ServicesActive. Si elle vaut SERVICES_ACTIVE_DATABASE ou NULL (ce qui est la même chose), la base de données ServicesActive est ouverte par défaut.

.const
szActiveDatabase db "ServicesActive", 0
SERVICES_ACTIVE_DATABASE equ offset szActiveDatabase

Puisque nous n'ouvrirons aucune autre base de données de SCM, excepté celle qui est active, nous indiquerons simplement NULL

dwDesiredAccess

- Indique le droit d'accès au SCM.

Ce paramètre indique au SCM ce que nous avons l'intention de faire avec sa base de donnée.

Ici, trois valeurs peuvent nous être utiles :

SC_MANAGER_CONNECT

- Permet se connecter au SCM.

Ce type d'accès est implicitement indiqué par défaut (si vous passez simplement 0). Très étrange, mais la documentation ne dit rien au sujet de ce que nous pouvons faire de particulier en ayant ce type d'accès. Mais cependant beaucoup de choses peuvent être faites. Nous pouvons démarrer et arrêter le driver, et même supprimer son entrée de la base de données du SCM;

SC_MANAGER_CREATE_SERVICE

- Permet d'appeler la fonction CreateService pour créer un objet de service et l'ajouter à la base de donnée.

En fait, avoir ce type d'accès pour la création de service n'est pas la seule chose que nous pouvons faire. Puisque le drapeau SC_MANAGER_CONNECT est placé par défaut, nous pouvons faire toutes les choses possibles avec ce type d'accès. Mais il est vrai que cela n'est pas vraiment implicite;

SC_MANAGER_ALL_ACCESS

- Donne le plein accès à la base de donnée du SCM.

Nous établissons une connexion au SCM comme ceci :

    invoke OpenSCManager, NULL, NULL, SC_MANAGER_CREATE_SERVICE
    .if eax != NULL
        mov hSCManager, eax

Si la fonction OpenSCManager réussit, la valeur de retour est un handle à la base de donnée du SCM indiquée. Nous passerons ce handle à d'autres fonctions pour manipuler la base de données du SCM.

Notez que l'installation de module de gestion de périphérique en mode Kernel exige un compte avec les droits d'administrateur. Ceci assure une sécurité nécessaire, ainsi les utilisateurs normaux ne peuvent pas ajouter et exécuter de code privilégié sans l'autorité compétente. Par conséquent, il est considéré ici que vous êtes administrateur.

2.3.2 Installation d'un nouveau driver

Une fois que le SCM a été ouvert, nous ajoutons notre driver à la base de données du SCM par un appel à CreateService. Voici son prototype. CreateService a treize paramètres. Mais ne paniquez pas. En fait tout ceci est plutôt simple.

CreateService proto hSCManager:HANDLE, lpServiceName:LPSTR, lpDisplayName:LPSTR, \
                    dwDesiredAccess:DWORD, dwServiceType:DWORD, dwStartType:DWORD, \
                    dwErrorControl:DWORD, lpBinaryPathName:LPSTR, lpLoadOrderGroup:LPSTR, \
                    lpdwTagId:LPDWORD, lpDependencies:LPSTR, lpServiceStartName:LPSTR, \
                    lpPassword:LPSTR
Paramètre Descriptionĺ

hSCManager

- Handle de la base de donnée du SCM.

lpServiceName

- Pointe vers une chaîne terminée par NULL qui nomme le service à installer. La longueur maximum de la chaîne est de 256 caractères. Le slash (/) et l'antislash (\) sont ne sont pas des caractères admissibles pour un nom de service.

 

La chaîne correspond à la valeur d'une sous-clé de « service » dans la base de registre.

lpDisplayName

- Pointe vers une chaîne de caractère terminée par NULL qui doit être employée par un programme utilisateur pour identifier le service. Cette chaîne a une longueur maximum de 256 caractères.

Correspond à la valeur DisplayName dans la base de registre.

dwDesiredAccess

- Indique l'accès au service.

Voici les valeurs qui peuvent nous intéresser :

SERVICE_ALL_ACCESS

- Plein accès au service ;

SERVICE_START

- Permet d'appeler la fonction StartService pour démarrer le service ;

SERVICE_STOP

- Permet d'appeler la fonction ControlService pour arrêter le service ;

DELETE

- permet d'appeler la fonction DeleteService pour supprimer le service ;

Nous devons faire seulement deux actions : démarrer le driver et l'enlever de la base de donnée du SCM. Ainsi, nous passons SERVICE_START et DELETE pour ce paramètre. Nous ne devons bien sûr pas arrêter le driver puisque son initialisation échouerait.

dwServiceType

- Specifie le type de service. Nous utiliserons uniquement SERVICE_KERNEL_DRIVER.

Correspond à la valeur type dans la base de registre. .

dwStartType

- Indique quand démarrer le service. Si nous voulons démarrer le driver par nous-mêmes nous passons SERVICE_DEMAND_START. Si le driver doit être démarré juste après le boot du système nous passerons SERVICE_AUTO_START.

Correspond à la valeur Start dans la base de registre. .

dwErrorControl

- Indique la sévérité de l'erreur si le driver ne démarre pas pendant sa phase normal de démarrage. Nous emploierons SERVICE_ERROR_IGNORE pour ignorer les erreurs ou SERVICE_ERROR_NORMAL pour noter les erreurs possibles.

Correspond à la valeur ErrorControl dans la base de registre.

lpBinaryPathName

- Pointe vers une chaîne terminée par NULL qui contient le chemin entier relatif au fichier binaire du driver.

Correspond à la valeur ImagePath dans la base de registre.

lpLoadOrderGroup

- Pointe vers une chaîne terminée par NULL qui nomme le groupe d'ordre de chargement [Load ordering group] dont ce service est un membre. Notre driver n'appartenant à aucun groupe, nous passerons simplement la valeur NULL.

lpdwTagId

- Pointe vers une variable de 32 bits qui reçoit une valeur unique d'étiquette pour un service appartenant au groupe indiqué dans le paramètre lpLoadOrderGroup. Aucune étiquette n'est exigée pour nous et ce paramètre sera donc NULL.

lpDependencies

- Ce paramètre n'a aucune signification pour les drivers de services. Il sera donc toujours NULL.

lpServiceStartName

- Pointeur vers une chaîne terminée par NULL et qui indique le nom du compte sous lequel le service devrait fonctionner. Si le type de service est SERVICE_KERNEL_DRIVER ce nom est le nom d'objet du driver que le système emploie pour charger le module de gestion de périphérique. Nous spécifierons ici NULL car notre driver doit employer un nom d'objet par défaut créé par le système d'I/O.

lpPassword

- Les mots de passe sont ignorés pour les drivers de services. Toujours spécifier NULL.

Résumons. Dans les cinq derniers paramètres nous spécifions toujours NULL, et vous pouvez complètement oublier ceux-ci. Le premier paramètre est le Handle à la base de données du SCM. Le paramètre dwDesiredAccess est très clair aussi. Je pense que vous avez déjà deviné pour ce qui est des autres paramètres. Ils correspondent aux clefs de la base de registre que nous avons analysées ci-dessus. La table ci-dessous est une aide visuelle pour votre convenance.

CreateService Registre
lpServiceName nom de la sous-clé du registre
lpDisplayName Nom d'affichage du driver
dwServiceType Type
dwStartType Démarrage
dwErrorControl Contrôle d'erreur
lpBinaryPathName Chemin vers le driver

Table 2-1. Correspondance de quelques paramètres passés à CreateService et les clefs de la base de registre.

Comme vous pouvez le voir, tout n'est pas aussi complexe. Retournons au code source.

        push eax
        invoke GetFullPathName, $CTA0("beeper.sys"), sizeof acDriverPath, addr acDriverPath, esp
        pop eax

        invoke CreateService, hSCManager, $CTA0("beeper"), $CTA0("Nice Melody Beeper"), \
                SERVICE_START + DELETE, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, \
                SERVICE_ERROR_IGNORE, addr acDriverPath, NULL, NULL, NULL, NULL, NULL
        .if eax != NULL
            mov hService, eax

En appelant GetFullPathName nous obtenons le chemin complet au fichier du module de gestion de périphérique pour le passer ensuite à CreateService.

CreateService ajoute notre driver à la base de données du SCM, et crée une sous-clé d'enregistrement appropriée. Regardez le schéma 2-1 de nouveau. Toutes ces informations ont été ajoutées dans la base de registre par CreateService. Si vous commentez l'appel à DeleteService dans le fichier csp.asm, recompilez ce dernier et le faite fonctionner vous pouvez voir exactement la même chose sur votre ordinateur.

Ne pensez pas qu'en ayant utilisé des fonctions pour la base de registre (fonction Reg*) pour manipuler cette dernière, il soit possible d'obtenir le même résultat. Vous pouvez ajouter des données dans la base de registre, mais celles-ci n'apparaîtront pas dans la base de données du SCM.

Si le module de gestion de périphérique indiqué existe déjà dans la base de données du SCM l'appel à CreateService échouera. Un Appel à GetLastError renvoie ERROR_SERVICE_EXISTS. Si CreateService est réussi après avoir ajouté le driver à la base de données du SCM, le Handle du driver est retourné. Ce Handle est exigée par d'autres fonctions afin de manipuler le driver.

2.3.3 Démarrer le driver

T La prochaine fonction que nous devrons appeler est StartService. En voici son prototype :

StartService proto hService:HANDLE, dwNumServiceArgs:DWORD, lpServiceArgVectors:LPSTR
Paramètre Description

hService

- Identifie le service ouvert.

dwNumServiceArgs

- Ce paramètre est toujours zéro pour des modules de gestion de périphérique.

lpServiceArgVectors

- Les services de driver ne reçoivent aucun argument. Ainsi, il devrait être NULL.

Maintenant nous démarrons le driver comme ceci :

            invoke StartService, hService, 0, NULL

La fonction StartService force le système à faire quelques actions qui rappelle le chargement de DLL en mode utilisateur. Une image du fichier du driver est mappée dans l'espace d'adresse du système. Le driver est toujours mappé à une adresse arbitraire. Ainsi le système exécute quelques relocalisations [relocations] dans l'image du driver en utilisant la section .reloc du fichier PE. Toutes les références aux symboles importés sont réarrangées. Quand l'image du driver est prête, le système appelle le point d'entrée du driver, qui réside dans la routine de DriverEntry. La différence principale ici est que le code de la routine de DriverEntry fonctionne toujours dans le contexte du processus système.

L'appel à la fonction StartService est synchrone. Cela signifie qu'il n'y aura pas de retour de la fonction tant que la routine DriverEntry du driver ne sera pas fini. Si l'initialisation du driver réussit, DriverEntry devrait renvoyer STATUS_SUCCESS, et StartService renverra une valeur non nulle.

La valeur retournée par StartService est sans intérêt pour nous, puisque le driver de beeper a déjà joué sa gentille mélodie et a renvoyé un code d'erreur. Ainsi, nous savons à l'avance que la fonction StartService renverra une erreur.

2.3.4 Désinstallation du driver

            invoke DeleteService, hService
            invoke CloseServiceHandle, hService
        .else
            invoke MessageBox, NULL, $CTA0("Can't register driver."), NULL, MB_ICONSTOP
        .endif
        invoke CloseServiceHandle, hSCManager

Maintenant tout ce qu'il nous reste à faire c'est de restaurer le système dans son état initial. Nous appellerons DeleteService pour enlever le driver de la base de données du SCM. Étrangement, il n'est pas nécessaire de passer le Handle de la base de données du SCM à DeleteService. Le prototype de DeleteService est très simple :

DeleteService proto hService:HANDLE
Paramètre Description

hService

- Identifie le service à enlever. Il est nécessaire d'avoir le droit d'accès approprié. Nous avons ce dernier.

Cette fonction ne supprime pas réellement le service tout de suite ; elle marque simplement le service pour la suppression. Le SCM supprimera le service seulement quand le service cessera de fonctionner et après que tous les Handles au service aient été fermés. Car nous possédons toujours le Handle sur le driver, il n'est pas enlevé de la base de données du SCM. Si vous essayez d'appeler DeleteService à plusieurs reprises la fonction échouera ; Un appel à GetLastError retournera ERROR_SERVICE_MARKED_FOR_DELETE.

Puisque nous n'avons plus besoin de communiquer avec le driver, nous devons fermer le Handle en appelant CloseServiceHandle :

CloseServiceHandle proto hSCObject:HANDLE
Paramètre Description

hSCObject

- Handle du driver ou de la base de donnée du SCM à fermer.

Comme il n'y a plus d'handles ouverts pour le driver à cet instant , l'entrée de ce dernier est enlevée de la base de données du SCM. Le deuxième appel à CloseServiceHandle ferme le Handle au SCM lui-même.

 

2.4 Macros de chaînes

Enfin vous devez savoir ce qu'est $CTA0. C'est une fonction de Macro. Elle vous laisse définir une chaîne ASCII se terminant par NULL dans la section de donnée en lecture seule. Vous pouvez l'employer directement avec la macro Invoke . Cette macro n'est pas la seule. Le dossier \Macros\Strings.mac contient beaucoup d'autres macros utiles pour définir des chaînes de caractères avec une explication détaillée quant à leur emploi. Comme ces macros ne sont pas spécialement liées au développement de drivers je ne prêterai plus attention à ce sujet, mais j'emploierai de telles macros un peu partout.


Copyright © 2002-2004 Four-F, four-f@mail.ru

Traduction par Neitsa , tzcorporation@hotmail.com