Win32::API - Utilitaire d'importation d'API Win32 |
Win32::API - Utilitaire d'importation d'API Win32
use Win32::API; $function = new Win32::API( $library, $functionname, \@argumenttypes, $returntype, ); $return = $function->Call(@arguments);
Avec ce module, vous pouvez importer et appeler des fonctions quelconques des bibliothèques à liens dynamiques Win32 (DLL), sans devoir écrire une extension XS. Notez cependant que ce module ne peut pas tout faire (les paramètres d'entrée et de sortie sont limités aux cas les plus simples) et, de toute manière, une extension XS régulière est toujours plus sûre et plus rapide.
La version courante de Win32::API est disponible sur mon site web :
http://dada.perl.it/
Elle est aussi disponible sur votre miroir CPAN le plus proche (mais prévoir quelques jours pour que la dernière version soit déployée autour du monde) accessible à:
http://www.perl.com/CPAN/authors/Aldo_Calpini/
Un court exemple de la manière d'utiliser ce module (il obtient juste le PID
du processus courant comme, par exemple, la variable interne $$
de Perl) :
use Win32::API; $GetPID = new Win32::API("kernel32", "GetCurrentProcessId", '', 'N'); $PID = $GetPID->Call();
Les possibilités sont presque infinies (mais toutes ne sont pas bonnes :-). Amusez-vous.
Tous les crédits vont à Andrea Frosini pour l'astuce élégante en assembleur qui fait fonctionner la chose. Un grand merci aussi à Gurusamy Sarathy pour son inestimable aide dans le développement XS, et à toute la communauté Perl pour être ce qu'il est.
Pour utiliser ce module, placez la ligne suivante au début de votre script :
use Win32::API;
Vous pouvez utiliser maintenant la fonction new()
du module Win32::API
pour créer un nouvel objet API (voyez IMPORTER UNE FONCTION) et alors
invoquer la méthode Call()
sur cet objet pour exécuter un appel à l'API
importé (voyez APPELER UNE FONCTION IMPORTÉE).
Vous pouvez importer une fonction d'une bibliothèque à lien dynamique (DLL)
32 bits avec la fonction new()
. Cela crée un objet Perl qui contient la
référence à cette fonction que vous pouvez appeler plus tard avec Call()
.
Vous devez lui passer 4 paramètres :
Pour expliquer le mieux possible leur signification, supposons
que nous voulons importer et appeler l'API Win32 GetTempPath()
.
Cette fonction est définie en C par :
DWORD WINAPI GetTempPathA( DWORD nBufferLength, LPSTR lpBuffer );
Cela est documenté dans le Win32 SDK Reference; vous pouvez le chercher sur le site WWW de Microsoft, ou dans la documentation de votre compilateur C, si vous en avez un.
Vous n'avez donc pas à écrire C:\windows\system\kernel32.dll; kernel32 est suffisant.
$GetTempPath = new Win32::API('kernel32', ...Maintenant pour le deuxième paramètre : le nom de la fonction. Il doit être écrit exactement comme il est exporté par la bibliothèque (la casse des caractères est importante ici). Si vous utilisez Windows 95 ou NT 4.0, vous pouvez utiliser la commande Quick View sur le fichier DLL pour voir les fonctions qu'elle exporte. Souvenez-vous que vous ne pouvez importer que les fonctions des DLL 32 bits : dans Quick View, les caractéristiques du fichier devraient rapporter quelque part «32 bit word machine»; empiriquement, quand vous voyez que tous les noms de fonctions exportées sont en majuscules, la DLL est une bibliothèque 16 bits et vous ne pouvez pas l'utiliser. Si leur capitalisation semble correcte, alors c'est probablement une DLL 32 bits.
Notez aussi que beaucoup d'APIs Win32 sont exportés deux fois, avec l'addition d'un A ou d'un W à la fin de leur nom, pour - respectivement - la version ASCII et la version Unicode. Quand un nom de fonction n'est pas trouvé, Win32::API ajoute un A au nom et essaie encore une fois; si l'extension est construite sur un system Unicode, alors il essaiera avec un W à la place. Donc notre nom de fonction sera:
$GetTempPath = new Win32::API('kernel32', 'GetTempPath', ...
Dans notre cas GetTempPath
est chargé en fait comme comme GetTempPathA
.
"abcd" [a, b, c, d] \@LIST
Mais celles-ci ne le sont pas :
(a, b, c, d) @LIST
Le nombre de caractères de la chaîne, ou d'éléments dans la liste, spécifie le nombre de paramètres, et chaque caractère ou élément de liste spécifie le type d'un argument; les types admis sont:
I
:
la valeur est un entier (integer)N
:
la valeur est un entier longF
:
la valeur est un nombre à virgule flottante (float)D
:
la valeur est un nombre en double précision (double)P
:
la valeur est un pointeur (vers une chaîne, une structure, ...etc.)Notre fonction a besoin de deux paramètres: un nombre (DWORD
) et un
pointeur vers une chaîne (LPSTR
):
$GetTempPath = new Win32::API('kernel32', 'GetTempPath', 'NP', ...Le quatrième et dernier paramètre est le type de la valeur retournée par la fonction. Ce peut être un des types vus au-dessus, plus un autre type nommé V (pour
void
), utilisé pour les fonctions qui ne retournent pas
de valeur. Dans notre exemple, la valeur retournée par GetTempPath()
est
un DWORD
, donc notre type du retour sera N :
$GetTempPath = new Win32::API('kernel32', 'GetTempPath', 'NP', 'N');
Maintenant la ligne est complète, et l'API GetTempPath()
est prêt à être
utilisé dans Perl. Avant de l'appeler, vous devriez tester que $GetTempPath
est defined
, sinon la fonction ou la bibliothèque ne pourraient pas
être chargées; dans ce cas, $!
contiendrait le message d'erreur rapporté
par Windows. Notre définition, avec détection d'erreur ajoutée, devrait
ressembler à ceci :
$GetTempPath = new Win32::API('kernel32', 'GetTempPath', 'NP', 'N'); if(not defined $GetTempPath) { die "Can't import API GetTempPath: $!\n"; }
Pour effectivement faire un appel à une fonction importée, vous devez utiliser
la méthode Call()
sur l'objet Win32::API que vous avez créé. En continuant l'exemple
du paragraphe précédent, l'API GetTempPath()
peut être appelé en utilisant
la méthode:
$GetTempPath->Call(...
Bien sûr, les paramètres doivent être passés comme défini dans la phase
d'import. En particulier, si le nombre de paramètres ne correspond pas
(dans l'exemple, si GetTempPath()
est appelé avec plus ou avec moins de
deux paramètres), Perl va émettre un message d'erreur avec croak
et terminer l'exécution du script avec un die
.
Les deux paramètres demandés ici sont la longueur du tampon qui contiendra le chemin temporaire retourné, et un pointeur sur le tampon lui-même. Pour les paramètres numériques, vous pouvez utiliser ou une expression constante ou une variable, alors que pour les pointeurs vous devez utiliser un nom de variable (pas de référence Perl, seulement un nom de variable ordinaire). Notez aussi que la mémoire doit être allouée avant d'appeler la fonction, exactement comme en C. Par exemple, pour passer un tampon de 80 caractères à GetTempPath(), il faut l'initialiser auparavant avec:
$lpBuffer = " " x 80;
Cela alloue une chaîne de 80 caractères. Si vous ne le faites pas, vous
obtiendrez probablement une erreur Runtime exception
, et généralement rien
ne marchera. L'appel devrait inclure par conséquent :
$lpBuffer = " " x 80; $GetTempPath->Call(80, $lpBuffer);
Et le résultat sera entreposé dans la variable $lpBuffer. Notez que vous
n'avez pas besoin de passer une référence à la variable (c-à-d. vous n'avez
pas besoin de \$lpBuffer
), même si sa valeur est fixée par la fonction.
Un petit problème ici, c'est que Perl ne retaille pas la variable, donc
$lpBuffer contiendra encore en retour 80 caractères; les caractères
en excès seront des espaces, parce que nous avons dit " " x 80
.
Dans le cas présent, nous sommes assez chanceux, car la valeur retournée
par la fonction GetTempPath()
est la longueur de la chaîne, donc, pour
obtenir le chemin temporaire réel, nous pouvons écrire:
$lpBuffer = " " x 80; $return = $GetTempPath->Call(80, $lpBuffer); $TempPath = substr($lpBuffer, 0, $return);
Si vous ne savez pas la longueur de la chaîne, vous pouvez la couper
au premier caractère \0
(zéro ASCII) qui est le délimiteur de chaîne
en C:
$TempPath = ((split(/\0/, $lpBuffer))[0];
# or
$lpBuffer =~ s/\0.*$//;
Une autre note: pour passer un pointeur vers une structure en C, vous devez
empaqueter les éléments exigés dans une variable avec pack()
. Et, bien sûr,
pour accéder aux valeurs entreposées dans une structure, vous devez utiliser
unpack()
comme il se doit.
Un court exemple pour montrer comment cela marche : la structure POINT
est définie en C par :
typedef struct { LONG x; LONG y; } POINT;
Donc, pour appeler une fonction qui utilise une structure POINT
vous
avez besoin des lignes suivantes:
$GetCursorPos = new Win32::API('user32', 'GetCursorPos', 'P', 'V');
$lpPoint = pack('LL', 0, 0); # stocke deux LONGs $GetCursorPos->Call($lpPoint); ($x, $y) = unpack('LL', $lpPoint); # obtient les valeurs actuelles
Le reste est laissé en exercice au lecteur...
Aldo Calpini ( dada@perl.it ).
Cette traduction française correspond à la version anglaise distribuée avec perl 5.6.1. Pour en savoir plus concernant ces traductions, consultez http://www.enstimac.fr/Perl/ .
Jean-Louis Morel <jl_morel@bribes.org>
Win32::API - Utilitaire d'importation d'API Win32 |