DynaWrapperX v2.2.0.0 (mise à jour du 03.08.2018)

| Yuri Popov | Traduction française : omen999 |

Accéder aux API de Win32 en VBScript/JScript grâce à DynamicWrapperX.
Après son premier composant DynamicWrapperX, Yuri Popov nous gratifie d'une nouvelle version qui apporte quelques améliorations intéressantes. Les connaisseurs apprécieront tout particulièrement la possibilité d'exécuter du code machine à partir d'une adresse arbitraire ou d'une variable chaîne. Cette version existe désormais dans les deux variantes x86 et x64.
Licence : freeware.

Après une version référencée 2.1.0.0 qui ajoutait quelques méthodes essentiellement destinées à le rendre encore plus binary friendly pour le plus grand bonheur des quelques allumés qui considèrent que le Vbscript/JScript est la voie naturelle qui mène au langage assembleur, Yuri Popov a rectifié la méthode LastError() et complété la méthode RegisterCallBack() afin de la rendre compatible avec les bibliothèques qui adoptent la convention d'appel cdecl.

Vous pouvez également poser vos questions et laisser vos commentaires et suggestions sur le forum : Commentez

- Titre : DynaWrapperX v2.2.0.0 (mise à jour du 03.08.2018)

- Auteurs : Yuri Popov, Traduction française : omen999

- Parution : 3 août 2018

- Licence : Copyright ® 2018 Yuri Popov. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. Enregistrement du composant

- III. Liste des méthodes exposées - (les méthodes de la version 2 sont affichées en rouge)

- IV. Référence

- IV-1. Register(DllName, FuncName[, i = paramTypes][, r = RetValType][, f = Flag])

- IV-2. RegisterAddr(Addr, FuncName[, i = paramTypes][, r = RetValType][, f = Flag])

- IV-3. RegisterCode(HexStr[, FuncName][, i = paramTypes][, r = RetValType][, f = Flag])

- V. Types de paramètres d'entrée et de valeurs de retour :

- VI. Type de paramètres de sortie :

- VII. À propos des chaînes de caractères

- VIII. RegisterCallback(FuncRef [, i = ParamTypes][, r = RetValType][, f = Flag])

- IX. Autres méthodes (gestion des variables)

- IX-1. NumGet(Address [, Offset] [, Type])

- IX-2. NumPut(Var, Address [, Offset] [, Type])

- IX-3. StrPtr(Var[, Type/Codepage])

- IX-4. StrGet(Address[, Offset][, Type/Codepage])

- IX-5. StrPut(String, Address[, Offset][, Type/Codepage])

- IX-6. Space(Count [, Char])

- IX-7. ObjPtr(Object)

- IX-8. TabPtr(Array)

- IX-9. VarPtr(Var)

- IX-10. MemAlloc(Bytes [, ZeroMem])

- IX-11. MemFree(MemPtr)

- IX-12. Bitness()

- IX-13. Version([Field])

- IX-14. LastError([Flag])

- IX-15. MemZero(Address,Bytes)

- IX-16. MemCopy(SrcAddr, DestAddr, Bytes)

- IX-17. MemRead(Address, Bytes[, BytesPerGroup][, GroupsPerLine])

- IX-18. MemWrite(HexStr, DestAddr[, Bytes])

- X. Exemples d'utilisation

- XI. Liens et téléchargements

- XII. Remerciements

I - Introduction

DynamicWrapperX est un composant ActiveX (serveur COM) écrit dans le but d'étendre et d'améliorer les possibilités du composant initial Dynawrap. Il permet aux langages JScript et VBScript d'appeler les fonctions exportées par les fichiers bibliothèques DLL, en particulier les fonctions de l'API Windows comme s'il s'agissait des méthodes d'un objet. Depuis la version 2, vous pouvez également appeler une fonction à partir de son adresse en mémoire ou directement son code machine représenté sous la forme d'une chaîne hexadécimale. Ce composant n'est pas une adaptation du code original de Dynawrap et a été écrit en partant de zéro en assembleur GoAsm. La version 2 a été testée sous Windows XP SP3, Windows 7 SP1 et Windows 8.1.

Améliorations et modifications de la version 1 :

- possibilité d'inscrire le composant dans la base de registres uniquement pour l'utilisateur courant. Cela peut être utile si l'utilisateur n'a pas les droits d'administrateur ;

- ajout de nouveaux types de paramètres d'entrée et de valeurs de retour ;

- ajout des paramètres de sortie ;

- gestion identique des chaînes de caractères dans JScript et VBScript ;

- support des fonctions de rappel (callback) utilisées notamment par les fonctions d'énumération de l'API Windows comme EnumWindows (voir les exemples ci-dessous) ;

- suppression d'un bug sous Win98. Il n'est plus nécessaire d'instancier un objet pour chaque fonction enregistrée ;

- ajout de méthodes utiles de gestion de variables : NumGet(), NumPut(), StrPtr(), StrGet(), Space().

Améliorations de la version 2 :

- Windows 98 n'est plus officiellement supporté (cette version n'a pas été testée dans cet environnement) ;

- le composant est désormais disponible en deux variantes pour chaque plate-forme : x86 et x64 ;

- son CLSID a été modifié ;

- lors de l'inscription dans la base de registre, la présence de la version 1 est vérifiée. Les conséquences seront différentes selon le résultat de cette vérification (voir enregistrement du composant ci-dessous) ;

- nouveaux types de paramètres et de valeurs de retour : m (Int64), q (UInt64) et v (pointeur vers une structure VARIANT) ;

- le nombre de fonctions de rappel est désormais illimité ;

- la méthode Register() est maintenant capable d'enregistrer une fonction par son numéro ordinal ou sous un nom différent (alias) ;

- le paramètre flag f est réintroduit ;

- nouvelles méthodes : RegisterAddr(), RegisterCode(), StrPut(), ObjPtr(), ObjGet(), TabPtr(), VarPtr(), MemAlloc(), MemFree(), Bitness(), Version() ;

- pour les méthodes StrPtr(), StrGet() et StrPut() vous pouvez spécifier la page de code de la chaîne au lieu de son type.

Améliorations de la version 2.0.0.1 :

- les valeurs numériques de type « f » (float) sont désormais correctement définies dans les variables de la version 64 bits (valeurs retournées par les fonctions et celles lues dans la mémoire par la fonction NumGet()) ;

- la fonction ObjGet() levait une exception si elle était appelée sans que la valeur retournée fût assignée à une variable.

Améliorations de la version 2.1.0.0 :

- nouvelles méthodes : LastError(), MemZero(), MemCopy(), MemRead() et MemWrite() ;

- un paramètre optionnel Offset a été ajouté aux fonctions StrGet() et StrPut() ;

- les noms de page de code comme « utf-8 » peuvent désormais être utilisés dans les fonctions StrPtr(), StrGet() et StrPut() ;

- le paramètre optionnel Offset des fonctions NumGet() et NumPut() peut être omis de la liste des paramètres.

Améliorations de la version 2.1.1.0 :

- aucunes, le composant n'est plus compressé avec MPress afin d'éviter les faux positifs signalés par certains programmes de protection…

Améliorations de la version 2.1.1.1 :

- la méthode LastError() qui levait une exception lorsqu'elle était appelée avec le paramètre Flag = 1 alors qu'il n'existait pas de description de l'erreur, a été corrigée.

Améliorations de la version 2.2.0.0 :

- la méthode RegisterCallBack() accepte désormais le type de paramètre « f » pour pouvoir gérer les bibliothèques respectant la convention d'appel cdecl.

Bien que le composant DynamicWrapperX ait été testé soigneusement, l'auteur ne peut garantir un fonctionnement sans défaut. L'usage se fait aux risques et périls de son utilisateur.

II - Enregistrement du composant

Pour inscrire le composant, deux options sont possibles :

pour tous les utilisateurs

regsvr32.exe <chemin-du-composant>\dynwrapx.dll

pour l'utilisateur courant

regsvr32.exe /i <chemin-du-composant>\dynwrapx.dll

Si dynwrapx.dll réside dans le répertoire System32, System, Windows, dans le répertoire courant, ou dans un de ceux qui sont énumérés dans la variable d'environnement PATH, le chemin peut être omis. Si le composant est enregistré pour l'utilisateur courant, il ne sera disponible que pour cet utilisateur. En outre, à partir de Windows Vista, le composant ne sera pas non plus disponible pour les applications s'exécutant avec des droits d'administrateur.

Sur un système 64 bits, les deux variantes x86 et x64 devraient être enregistrées parallèlement afin que les applications 32 bits et 64 bits puissent utiliser chacune leur composant respectif.

Au cours du processus d'enregistrement, la version 2 vérifiera la présence de la version 1. Si c'est le cas, la version 2 s'installera en parallèle avec la version 1 et ne sera disponible que par son nom de version dénommé « DynamicWrapperX.2 ». Pour remplacer la version 1 par la version 2, vous devez désinscrire le premier. Dans ce cas, la version 2 sera également disponible par son nom de classe générique « DynamicWrapperX ».

Pour désinscrire le composant, procéder comme suit :

pour tous les utilisateurs

regsvr32.exe /u <chemin-du-composant>\dynwrapx.dll

pour l'utilisateur courant

regsvr32.exe /u /i <chemin-du-composant>\dynwrapx.dll

III - Liste des méthodes exposées - (les méthodes de la version 2 sont affichées en rouge)

1. Register(DllName, FuncName[, i = paramTypes][, r = RetValType][, f = Flag])
- référence une fonction de la DLL comme une méthode de l'objet ;

2. RegisterAddr(Addr, FuncName[, i = paramTypes][, r = RetValType][, f = Flag])
- référence une adresse mémoire comme une méthode de l'objet ;

3. RegisterCode(HexStr[, FuncName][, i = paramTypes][, r = RetValType][, f = Flag])
- référence du code binaire défini sous la forme d'une chaîne hexadécimale comme une méthode de l'objet ;

4. RegisterCallback(FuncRef[, i = paramTypes][, r = RetValType][, f = Flag])
- référence une fonction du script comme fonction de rappel (callback) ;

5. NumGet(Address[, Offset][, Type])
- lit une valeur numérique en mémoire à l'adresse donnée Address + Offset ;

6. NumPut(Var, Address[, Offset][, Type])
- écrit en mémoire le contenu d'une variable numérique Var à l'adresse donnée Address + Offset ;

7. StrPtr(Var[, Type/Codepage])
- renvoie le pointeur vers la chaîne Var ;

8. StrGet(Address[, Type/Codepage])
- renvoie une chaîne de caractères à partir d'une adresse donnée Address ;

9. StrPut(Str, Address[, Type/Codepage])
- écrit une chaîne de caractères dans la mémoire à partir de l'adresse spécifiée Address ;

10. Space(Count[, Char])
- renvoie un pointeur vers une chaîne de Count caractères Char ;

11. ObjPtr(Object)
- renvoie un pointeur de l'objet ;

12. ObjGet(ObjPtr)
- récupère un objet à partir de son pointeur ;

13. ArrPtr(Array)
- renvoie un pointeur vers la structure SAFEARRAY du tableau spécifié ;

14. VarPtr(Variable)
- renvoie un pointeur vers la structure VARIANT d'une variable de script ;

15. MemAlloc(Bytes[, ZeroMem])
- alloue Bytes octets de la mémoire ;

16. MemFree(MemPtr)
- libère la mémoire précédemment allouée ;

17. Bitness()
- retourne la largeur de bits (32 ou 64) de l'objet DynamicWrapperX ;

18. Version([Field])
- retourne la(es) valeur(s) spécifiée(s) des quatre parties du numéro de version de DynamicWrapperX ;

19. LastError([Flag])
- renvoie le code d'erreur obtenu auprès de la fonction de l'API GetLastError immédiatement après le retour de la fonction initiale ;

20. MemZero(Address, Bytes)
- remplit une zone mémoire à partir de l'adresse spécifiée Address avec Bytes octets de zéros binaires ;

21. MemCopy(SrcAddr, DestAddr, Bytes)
- copie le contenu de Bytes octets d'une zone mémoire d'adresse initiale SrcAddr vers une adresse DestAddr ;

22. MemRead(Address, Bytes[, BytesPerGroup][, GroupsPerLine])
- lit le contenu d'une zone mémoire sous la forme d'une chaîne hexadécimale avec des options de format ;

23. MemWrite(HexStr, DestAddr[, Bytes])
- écrit une chaîne hexadécimale en mémoire après conversion sous forme binaire.

IV - Référence

IV-1 - Register(DllName, FuncName[, i = paramTypes][, r = RetValType][, f = Flag])

Cette méthode référence les fonctions exportées par la bibliothèque DllName comme une méthode de l'objet. Après cela, vous pouvez appeler la fonction de la même façon qu'une méthode de l'objet, c'est-à-dire par son nom précédé d'un point. Le premier paramètre DllName est le nom de la DLL, le deuxième FuncName est le nom de la fonction qui doit correspondre à une fonction existante de la DLL. Les trois autres paramètres sont facultatifs et dépendent de la fonction concernée. Leur ordre est libre car ce sont des arguments nommés. Le préfixe i indique le type des paramètres de la fonction, r spécifie le type de la valeur de retour et f définit les indicateurs qui caractérisent le mode d'appel de la fonction.

JScript

DWX = new ActiveXObject("DynamicWrapperX");                  // instancie l'objet
DWX.Register("user32.dll", "MessageBoxW", "i=hwwu ", "r=l"); // référence une fonction de la dll
res = DWX.MessageBoxW(0, "Bonjour, monde!", "Test", 4);      // appel de la fonction

VBScript

Set DWX = CreateObject("DynamicWrapperX")                    ' instancie l'objet
DWX.Register "user32.dll", "MessageBoxW", "i=hwwu", "r=l"    ' référence une fonction de la dll
res = DWX.MessageBoxW(0,"Bonjour, monde!", "Test", 4)        ' appel de la fonction

Le nom de la bibliothèque DllName sans indication de répertoire signifie une recherche par nom, en premier lieu dans la mémoire du processus parmi les bibliothèques déjà chargées - par ordre de leur chargement - puis sous Win XP l'ordre de recherche sur le disque sera le suivant :

1. Répertoire de l'application (dans ce cas, soit wscript.exe ou cscript.exe) ;

2. Répertoire courant ;

3. System32 ;

4. System ;

5. Windows ;

6. Répertoires de la variable d'environnement PATH.

Si la bibliothèque est un fichier comportant l'extension « .dll », cette extension peut être omise. Ainsi dans l'exemple ci-dessus, on pourrait simplement écrire « user32 ». Si la bibliothèque est un fichier sans extension, il faudra ajouter un point à la fin du nom, par exemple : « mylib. ».

Si la fonction a été référencée par son numéro ordinal, ce nombre est placé après le nom de la bibliothèque séparé par des deux-points. Le même procédé s'applique au référencement sous un nom différent. Celui-ci peut être utile si le nom de la fonction contient des caractères illégaux pour les noms de méthode.

Conservez à l'esprit que les nombres ordinaux des fonctions peuvent varier selon les différentes versions d'une bibliothèque et entre les variantes x86 et x64 d'une même version.

Le nombre ordinal peut être décimal ou hexadécimal avec le préfixe « 0x ». Si le nom de la bibliothèque n'a pas d'extension, « .dll » est ajouté par défaut.

JScript

DWX.Register("user32:110", "MethodName", "i=hwwu", "r=l");   // référence par ordinal
DWX.Register("user32:BadName", "GoodName", "i=hwwu", "r=l");  // référence sous un nom différent

VBScript

DWX.Register "user32:110 ", "MethodName" , "i=hwwu", "r=l"   ' référence par ordinal
DWX.Register "user32:BadName", "GoodName", "i=hwwu ", "r=l"  ' référence sous un nom différent

Le nom de la fonction FuncName peut présenter des variantes selon que vous souhaitez la version Unicode ou son équivalent ANSI. Cela ne concerne normalement que les fonctions utilisant des paramètres de type chaîne. Par exemple, la fonction décrite dans la documentation telle que MessageBox, est effectivement présente dans user32.dll sous deux formes : MessageBoxW (pour les chaînes de caractères Unicode) et MessageBoxA (pour les chaînes de caractères classiques ANSI). Conformément à l'usage, le mode de recherche classique des fonctions a été maintenu : par exemple, si vous définissez le nom MessageBox et que cette fonction n'est pas trouvée dans user32.dll, la recherche se fera également pour la fonction MessageBoxA. Les noms de fonctions Unicode imposent en revanche de préciser le suffixe W.

Les paramètres peuvent être omis quand les fonctions ne réclament aucun paramètre sans qu'il soit nécessaire de conserver la virgule.

La valeur de retour peut également être omise, quelle que soit la valeur renvoyée.

JScript

DWX = new ActiveXObject("DynamicWrapperX");
DWX.Register("kernel32", "GetCommandLine", "r=s"); // cette fonction n'a pas de paramètres
CmdLine = DWX.GetCommandLine();                    // ligne de commande de démarrage du process
WScript.Echo(CmdLine);

VBScript

Set DWX = CreateObject("DynamicWrapperX")
DWX.Register "kernel32 ", "Beep", "i=uu"          ' Beep retourne une valeur inutile
DWX.Beep 800, 1000                                ' le haut-parleur du PC émet un son (bip)

Le paramètre indicateur n'a actuellement qu'une seule valeur possible « t » qui signifie que la convention d'appel de la fonction est « thiscall ». Il est uniquement utilisé par la version 32 bits de DynamicWrapperX et est ignoré par la version 64 bits.

JScript

DWX.Register("SomeLib", "SomeFunc", "i=hwwu", "r=l", "f=t");  // appel de la fonction en mode "thiscall"

VBScript

DWX.Register "SomeLib", "SomeFunc", "i=hwwu", "r=l", "f=t"    '  appel de la fonction en mode "thiscall"

IV-2 - RegisterAddr(Addr, FuncName[, i = paramTypes][, r = RetValType][, f = Flag])

Cette méthode référence une adresse mémoire comme une méthode de l'objet. Les autres paramètres sont identiques à ceux de la fonction Register().

JScript

DWX.RegisterAddr(Address, "FuncName", "i=hwwu", "r=l");

VBScript

DWX.RegisterAddr Address, "FuncName", "i=hwwu", "r=l"

IV-3 - RegisterCode(HexStr[, FuncName][, i = paramTypes][, r = RetValType][, f = Flag])

Cette méthode référence du code binaire défini sous la forme d'une chaîne hexadécimale comme une méthode de l'objet.

Le premier paramètre de cette méthode conserve le code machine d'une fonction sous la forme d'une chaîne hexadécimale. La mémoire nécessaire est allouée et le code y est écrit sous forme binaire. Après cela, vous pouvez l'appeler par le nom spécifié dans le deuxième paramètre. Cette méthode renvoie l'adresse du code. Le code machine est différent selon les plates-formes x86 et x64, d'où la nécessité de vérifier le nombre de bits du processus - via la vérification du nombre de bits de l'objet DynamicWrapperX - afin de déterminer quelle version du code utiliser. Une application possible pour ce type de fonction est de permettre le traitement de grandes quantités de données, où le code machine peut s'exécuter des dizaines voire des centaines de fois plus rapidement que le code de script.

Si vous voulez utiliser le code par son adresse, sans pour autant créer une nouvelle méthode, vous pouvez omettre tous les arguments sauf le premier. Mais gardez à l'esprit que la mémoire allouée pour le code est toujours affectée à l'objet et sera libérée avec lui.

La chaîne hexadécimale peut être continue ou divisée par des espaces, des tabulations ou des retours à la ligne :

JScript

DWX = new ActiveXObject("DynamicWrapperX");
if (DWX.Bitness == 32) {
Code = "8B442404F76C2408C3 "   // la fonction multiplie ses arguments et retourne le résultat
}
else {
Code = "4889C8 48F7EA C3"      // mov rax,rcx ; imul RDX; ret
}
CodeAddr = DWX.RegisterCode(Code, "Multiply", "i=ll" , "r=l");
WScript.Echo(DWX.Multiply(5,4));

VBScript

Set DWX = CreateObject("DynamicWrapperX")
If DWX.Bitness = 32 Then
Code = "8B442404F76C2408C3 " ' la fonction multiplie ses arguments et retourne le résultat
Else
Code = "4889C8 48F7EA C3"    ' mov rax,rcx; imul rdx; ret
End If
CodeAddr = DWX.RegisterCode(Code, "Multiply", "i=ll", "r=l")
WScript.Echo(DWX.Multiply(5,4))

Vous pouvez également insérer des commentaires directement dans la chaîne hexadécimale en les entourant de parenthèses :

Code = "4889C8 (mov rax,rcx) 48F7EA(imul rdx) C3(ret)"

Si la chaîne hexadécimale est multiligne, vous pouvez signaler les commentaires avec un point-virgule :

JScript

Code = "4889C8 ; mov rax,rcx \n" +
"48F7EA ; imul rdx \ n" +
"C3     ; ret"

VBScript

Code = "4889C8 ; mov rax,rcx" & vbCrLf & _
"48F7EA ; imul rdx" & vbCrLf & _
"C3     ; ret"

Mais notez que dans ce cas, chaque ligne - sauf la dernière - doit avoir un ou plusieurs caractères de saut de ligne à la fin car ils sont utilisés comme fin de marqueur de commentaire.

V - Types de paramètres d'entrée et de valeurs de retour :

1. m - entier signé 64 bits - INT64, LONGLONG, etc. ;

2. q - entier non signé 64 bits - UINT64, ULONGLONG, etc. ;

3. l - entier signé 32 bits - LONG, INT, LPARAM, LRESULT, etc.
compris entre : -2 147 483 648 … 2 147 483 647 ;

4. u - entier non signé 32 bits - ULONG, UINT, DWORD, WPARAM, etc.
compris entre : 0 … 4 294 967 295 ;

5. h - valeur handle - HANDLE, HWND, HMODULE, HINSTANCE, HICON, etc.
compris entre : -2 147 483 648 … 4 294 967 295 ;

6. p - valeur identique à 'u' pour les nombres mais peut également pointer vers une interface IDispatch* ou une chaîne ;

7. n - entier signé 16 bits - SHORT
compris entre : -32 768 … 32 767 ;

8. t - entier non signé 16 bits - USHORT, WORD, WCHAR, OLECHAR, etc.
compris entre : 0 … 65 535 ;

9. c - entier signé 8 bits - CHAR
compris entre : -128 … 127 ;

10. b - entier non signé 8 bits - UCHAR, BYTE
compris entre : 0 … 255 ;

11. f - réel virgule flottante simple précision (32 bits) - FLOAT ;

12. d - réel virgule flottante double précision (64 bits) - DOUBLE ;

13. w - chaîne Unicode - BSTR, LPWSTR, LPOLESTR, OLECHAR*, WCHAR*, etc. ;

14. s - chaîne codage ANSI / Windows (code page par défaut) - LPSTR, LPCSTR, char*, etc. ;

15. z - chaîne codage OEM / DOS (code page par défaut) - LPSTR, LPCSTR, char*, etc. ;

16. v - pointeur vers une structure de type VARIANT.

Outre les handles et les pointeurs, il existe d'autres types qui adaptent leur largeur de bits en fonction du nombre de bits du processus. Par exemple : LPARAM, WPARAM et SIZE_T. Ils doivent être définis et renvoyés comme type h ou p de telle sorte que votre code puisse fonctionner correctement, quel que soit le nombre de bits de l'interpréteur de script.

Les types de variable correspondant à m et q seraient VT_I8 et VT_UI8, mais ils ne sont pas pris en charge par JScript et VBScript, ce qui limite ce que vous pouvez faire avec des entiers 64 bits dans les scripts. Tant que la valeur retournée par la fonction le permet, DynamicWrapperX le convertit en VT_I4 (entier 32 bits signé) ou VT_R8 (nombre double précision en virgule flottante). Dans la mesure où la mantisse du VT_R8 a seulement 53 bits, elle ne peut représenter tous les nombres dans l'intervalle de l'entier 64 bits. Dans ce cas, VT_I8 ou VT_UI8 est retourné. Tout ce que vous pouvez faire avec ces types est de les transmettre comme paramètres à une autre méthode ou de les afficher via WScript.Echo ou MsgBox. Aucun calcul sur ces données n'est possible.

Quand un grand nombre entier est retourné en tant que VT_R8 et que vous voulez afficher sa valeur dans une boîte de message, cet affichage peut ne pas être correct parce que le nombre de chiffres après la virgule dans la représentation de chaîne d'un nombre à virgule flottante est limité. En conséquence, si le nombre est 9 223 372 036 854 775 808 vous verrez dans la boîte de message la chaîne « 9,22337203685478E+18 » au lieu de « 9,223372036854775808E+18 ». Toutefois, la valeur numérique réelle de la variable n'est pas arrondie et conserve sa précision.

Si la valeur d'un entier 64 bits ne peut être représentée par un type numérique disponible, vous pouvez le spécifier sous forme de chaîne décimale ou hexadécimale (avec un préfixe 0x).

DWX.Register("lib.dll", "func", "i=m")
DWX.func("0xFFFFFFFFFFFFFFFF")
DWX.func("-0x7FFFFFFFFFFFFFFF")
DWX.func("18446744073709551615")
DWX.func("-9223372036854775807")

VI - Type de paramètres de sortie :

1. M - pointeur 64 bits vers son adresse en mémoire - LONGLONG*, PLONGLONG, etc. ;

2. Q - comme ci-dessus mais valeur non signée - ULONGLONG*, PULONGLONG, etc. ;

3. L - pointeur 32 bits vers son adresse en mémoire - LONG*, LPLONG, etc. ;

4. H - idem - HANDLE*, PHANDLE, LPHANDLE, etc. ;

5. U - idem - ULONG*, LPDWORD, etc. ;

6. P - idem ;

7. N - idem - SHORT* ;

8. T - idem - USHORT*, LPWORD, WCHAR*, OLECHAR*, etc. ;

9. C - idem - CHAR*, etc. ;

10. B - idem - UCHAR*, LPBYTE, etc. ;

11. F - idem - FLOAT*, PFLOAT ;

12. D - idem - DOUBLE*, PDOUBLE ;

13. W - pointeur vers chaîne Unicode ;

14. S - pointeur vers chaîne ANSI ;

15. Z - pointeur vers chaîne OEM/DOS.

Les types de sortie ci-dessus peuvent être utilisés dans les langages de script qui passent les arguments des méthodes par référence comme VBScript. Dans ce cas DynamicWrapperX peut obtenir un pointeur vers la valeur de l'argument qui est ensuite passé à la fonction enregistrée, laquelle peut alors modifier la valeur à cette adresse. Si les arguments sont passés par valeur comme dans JScript, ils sont préalablement copiés et il n'y a donc pas moyen de retrouver et de modifier l'argument original. Dans ce cas, vous pouvez allouer en premier lieu un tampon en mémoire au moyen de MemAlloc() puis transmettre le pointeur retourné par cette fonction (en tant que type p), et après l'appel de la fonction lire la valeur qu'elle a écrite dans le tampon avec NumGet().

Certains moteurs de script copient également les chaînes avant de les passer à des méthodes. Dans ce cas, l'utilisation de types de sortie pour les chaînes n'a pas de sens non plus. La solution est similaire à celle qui a été décrite ci-dessus : un tampon en mémoire passé sous la forme d'un pointeur de type p et une lecture de la chaîne qui en résulte via la fonction StrGet().

VII - À propos des chaînes de caractères

JScript et VBScript utilisent des chaînes au format BSTR. Il s'agit d'une chaîne de caractères Unicode, chaque caractère étant codé sur 2 octets. La chaîne est terminée par un caractère spécial constitué de 2 octets nuls. En outre, le premier caractère est précédé par une valeur numérique codée sur quatre octets contenant la longueur de la chaîne en octets (à l'exclusion du caractère terminal). Le pointeur des variables chaînes est l'adresse du premier caractère effectif de la chaîne (c'est-à-dire sans prendre en compte l'en-tête de quatre octets).

La gestion d'un paramètre de type chaîne peut se faire de trois manières différentes :

1. Paramètres d'entrée chaîne : w, s, z. Pour les types s et z la chaîne est copiée et convertie en fonction de l'encodage. La fonction API reçoit alors un pointeur vers cette copie. Après l'exécution de la fonction, cette copie est supprimée. Dans le cas du type w c'est le pointeur vers la chaîne originale qui est transmis.

2. Paramètres de sortie chaîne : W, S, Z. Dans ce cas et pour les trois types, un pointeur vers la chaîne originale est passé, mais S et Z sont d'abord convertis - sur place - selon la page code correspondante. Au retour de la fonction, le contenu des chaînes définies par S et Z est reconverti en Unicode avec réajustement de sa taille, celle de la variable W est simplement recalculée. La longueur (en octets) de la chaîne est enregistrée dans l'en-tête. Cette dernière opération est nécessaire pour éviter les bogues, par exemple lors de la concaténation avec d'autres chaînes. Si la chaîne est destinée à recevoir des données de la fonction appelée, veillez à lui donner une taille suffisante.

3. Pointeur : p. C'est la façon la plus simple. Le pointeur de la chaîne originale est transmis sans conversion. En conséquence, si la fonction écrit dans cette chaîne, les données seront conservées sous leur forme d'origine sans conversion ou ajustement de la longueur. La chaîne devient une simple mémoire tampon pour stocker les données. En apparence, cette option ressemble à l'option w mais ce n'est pas exact. La différence tient dans le fait que l'option p accepte non seulement les variables chaînes, mais aussi les variables numériques.

Retourner une chaîne avec p revient à récupérer une variable numérique contenant un pointeur vers la chaîne. Retourner une chaîne avec w, s ou z revient à récupérer une variable chaîne contenant un pointeur vers une copie de la chaîne retournée par la fonction API. Les chaînes retournées avec les paramètres s et z sont copiées avec conversion préalable. Les chaînes originales ne sont pas libérées car il n'est pas certain que cette libération soit sûre.

Si les éventuelles fuites mémoire vous préoccupent, privilégiez l'utilisation du paramètre p autant que possible pour le retour des chaînes.

Les fonctions API qui réclament des paramètres chaînes existent généralement en deux versions - par exemple, MessageBoxA et MessageBoxW. Il semble plus logique d'utiliser dans les scripts les versions Unicode - celles dont le nom se termine par W - puisqu'on évite alors tout travail de conversion à partir et vers Unicode.

L'utilisation de chaînes comme mémoire tampon peut ne pas être une technique fiable selon la façon dont le moteur de script traite les chaînes en interne. En conséquence, l'utilisation de la fonction MemAlloc() au lieu de Space() est recommandée.

VIII - RegisterCallback(FuncRef [, i = ParamTypes][, r = RetValType][, f = Flag])

Cette méthode permet de référencer un pointeur vers une fonction du script en vue de son exploitation par certaines fonctions de l'API. Par exemple, la fonction API EnumWindows utilise une fonction de rappel (callback). Elle énumère les fenêtres existantes, chaque fenêtre étant traitée par une fonction dont l'adresse est transmise en paramètre. Après chaque appel, la fonction principale attend la valeur renvoyée par la fonction callback. Si 1, l'énumération se poursuit, si 0 elle s'arrête.

En JScript et VBScript, une fonction de script n'est pas directement disponible sous forme de pointeur car ces fonctions sont des objets et leurs valeurs pointent vers une interface de type IDispatch. Aussi, la référence à une fonction de script est initialement transmise à la méthode RegisterCallback(). La fonction API qui utilise une fonction callback obtiendra alors un pointeur vers une procédure interne de dynwrapx.dll qui renverra l'appel à la fonction du script et lira la valeur renvoyée.

Avec JScript, le nom de la fonction - sans parenthèses - servira directement de référence alors qu'avec VbScript, vous devrez utiliser la fonction GetRef(). Outre la référence à la fonction, vous aurez à préciser les éventuels paramètres d'entrée ainsi que la valeur de retour exactement comme pour la méthode Register() (les paramètres en minuscules sont seuls utilisables). Toutefois, le paramètre indicateur « f » qui est optionnel pour la méthode Register() est ici obligatoire si la bibliothèque concernée par la fonction de rappel utilise la convention d'appel cdecl et ceci afin de laisser à la bibliothèque seule le soin de rétablir la pile. Ce paramètre prend alors la valeur c, exemple : « f=c ».

JScript

DWX = new ActiveXObject("DynamicWrapperX");
DWX.Register("user32", "EnumWindows", "i=ph");
DWX.Register("user32", "GetWindowTextW", "i=hpl");          // variante Unicode
// DWX.Register("user32", "GetWindowText", "i=hpl");        // variante ANSI
pCbkFunc = DWX.RegisterCallback(CbkEnumWin, "i=hh", "r=l"); // enregistrement CbkEnumWin
// comme procédure de rappel
// et obtention de son pointeur
n=0, m=0, WinList="";

Buf = DWX.MemAlloc(256);        // buffer de réception pour les titres de fenêtres (une chaîne de sortie)

DWX.EnumWindows(pCbkFunc, 0);    // appel d'EnumWindows et transmission du pointeur
// pour la procédure de rappel
DWX.MemFree(Buf);

WScript.Echo("Windows in total : " + m + "\ nWith a title : " + n + "\n\n" + WinList);

// la procédure de rappel (callback)

function CbkEnumWin(hwnd, lparam)
{
var Title;
DWX.GetWindowTextW(hwnd, Buf, 128);    // Unicode
Title = DWX.StrGet(Buf, "w");
// DWX.GetWindowText(hwnd, Buf, 256);  // ANSI
// Title = DWX.StrGet(Buf, "s");
if (Title.length > 0) {                // si le titre existe, on l'ajoute à la liste
WinList + = hwnd + "\ t" + Title + "\ n";
++n;
}
++m;
return 1; // une valeur nulle arrêtera l'énumération
}

VBScript

Set DWX = CreateObject("DynamicWrapperX")
DWX.Register "user32", "EnumWindows", "i=ph"
DWX.Register "user32", "GetWindowTextW", "i=hpl" ' variante Unicode
'DWX.Register "user32","GetWindowText","i=hpl"   ' variante ANSI

Set Ref = GetRef("CbkEnumWin")  ' référence de la fonction.

pCbkFunc = DWX.RegisterCallback(Ref, "i=hh", "r=l") ' enregistrement CbkEnumWin
' comme procédure de rappel
' et obtention de son pointeur

n = 0 : m = 0 : WinList = ""
Buf = DWX.MemAlloc(256)               ' buffer de réception pour les titres de fenêtres (une chaîne de sortie)

DWX.EnumWindows pCbkFunc, 0           ' appel d'EnumWindows et transmission du pointeur
' pour la procédure de rappel
DWX.MemFree Buf

WScript.Echo "Windows in total :" & m & vbCrLf & " With a title : " & n & _
vbCrLf & vbCrLf & WinList

' la procédure de rappel (callback)

Function CbkEnumWin(hwnd, lparam)
DWX.GetWindowTextW hwnd , Buf , 128  ' Unicode
Title = DWX.StrGet(Buf, "w")
' DWX.GetWindowText hwnd, Buf, 256   ' ANSI
' Title = DWX.StrGet(Buf, "s")
If Len(Titre) > 0 Then               ' si le titre existe, on l'ajoute à la liste
WinList = WinList & hwnd & vbTab & Titre & vbCrLf
n = n + 1
End If
m = m + 1
CbkEnumWin = 1   ' une valeur nulle arrêtera l'énumération
End Function

IX - Autres méthodes (gestion des variables)

Les paramètres entre crochets sont facultatifs mais vous ne pouvez pas les omettre si le suivant est présent à l'exception du paramètre Offset des méthodes NumGet() et NumPut().

IX-1 - NumGet(Address [, Offset] [, Type])

Lit le contenu de la mémoire et l'affecte à une variable numérique.
Address - adresse de base.
[Offset] - décalage (en octets), positif ou négatif : utile dans les cycles de lecture/écriture de nombres consécutifs, par défaut 0.
[Type] - le type de données à lire : même syntaxe que pour la méthode Register(), mais uniquement les lettres minuscules, par défaut l. Seules les lettres minuscules peuvent être utilisées. Le nombre est retourné dans la valeur renvoyée par la méthode.

IX-2 - NumPut(Var, Address [, Offset] [, Type])

Écrit une variable numérique dans la mémoire.
Var - soit une valeur littérale numérique ou la variable contenant ladite valeur. Pour le reste, syntaxe identique à NumGet().
Renvoie l'adresse disponible immédiatement après la valeur écrite.

Dans les deux dernières méthodes, Address peut être soit une valeur numérique soit une chaîne. Dans ce dernier cas, le pointeur de chaîne sert d'adresse de base. Cela vous permet d'utiliser la chaîne comme un tampon pour les données à placer à cet endroit comme des tableaux ou des données structurées, etc.

IX-3 - StrPtr(Var[, Type/Codepage])

Retourne un pointeur vers une chaîne de caractères.
Var - nom de la variable chaîne.
[Type/Codepage] - les valeurs possibles : w (par défaut), s, z, une page de code par exemple « cp65001 » (UTF-8) ou « cp1201 » (UTF-16 Big Endian) ou enfin un nom de page de code comme « UTF-8 » ou « UTF-16 BE ». Si le nom n'est pas reconnu par la méthode, une recherche sera effectuée dans la clé de registre « HKEY_CLASSES_ROOT\MIME\Database\Charset » de telle sorte que vous puissiez choisir comme paramètre un nom parmi les sous-clés.
En cas de valeur s ou z, la chaîne est préalablement convertie au même emplacement. Si la chaîne résultante ne rentre pas dans la mémoire tampon d'origine, elle sera tronquée. Dans la mesure où la chaîne d'origine est en Unicode où chaque caractère prend deux octets, l'espace sera suffisant dans la majorité des cas. Certains textes Est asiatiques sont peut-être plus longs en mode ANSI ou UTF-8 qu'en UTF-16 mais c'est une pure supputation personnelle. (1)

L'intérêt ou non d'obtenir un tel pointeur va dépendre de la façon dont le moteur de script transmet les chaînes à une méthode. S'il s'agit d'une transmission par copie, vous n'obtiendrez qu'un pointeur inutile vers une copie temporaire de cette chaîne.

IX-4 - StrGet(Address[, Offset][, Type/Codepage])

Retourne une chaîne lue à partir d'une adresse augmentée de l'Offset éventuel et renvoie une copie de celle-ci.
Address peut être soit sous la forme numérique soit une variable chaîne.
[Type/Codepage] - les valeurs possibles : w (par défaut), s, z ou le codage de la chaîne de source. En cas de valeur s ou z, une conversion au format Unicode est opérée. Pour plus de détails, voir la fonction StrPtr() ci-dessus.
Offset peut être une valeur négative ; zéro par défaut.

IX-5 - StrPut(String, Address[, Offset][, Type/Codepage])

Écrit une chaîne String dans la mémoire à l'adresse spécifiée Address augmentée de l'Offset éventuel.
[Type/Codepage] - le type ou le codage de la chaîne de destination. Pour plus de détails, voir StrPtr(). La méthode renvoie l'adresse immédiatement après le caractère nul final. Si le paramètre Address est égal à zéro, la méthode retourne la taille de la mémoire tampon requise pour la chaîne de destination, y compris le caractère nul final.
Offset peut être une valeur négative ; zéro par défaut.

IX-6 - Space(Count [, Char])

Retourne une variable chaîne contenant un pointeur vers une chaîne BSTR de longueur donnée.
Count - le nombre de caractères Unicode (2 bits) de la chaîne.
[Char] - le caractère qui remplira la chaîne. Par défaut il s'agit du caractère espace comme dans la fonction VBScript Space(). Pour remplir la ligne de zéros binaires, définissez Char comme une chaîne vide ("").

Cette méthode a été initialement conçue pour créer des chaînes à usage de tampon de mémoire (pour les nombres, les structures ou les chaînes renvoyées par des fonctions). Cependant, la pratique a montré que cette technique est souvent dangereuse. Vous ne savez jamais comment le moteur de script traitera la mémoire allouée à une chaîne. En d'autres termes, vous n'avez pas le plein contrôle sur ce tampon et vos données qui y sont stockées peuvent être perdues ou corrompues. Pour cette raison, la méthode MemAlloc() doit être privilégiée pour allouer de la mémoire (voir ci-dessous).

IX-7 - ObjPtr(Object)

Renvoie un pointeur vers un objet Object.

IX-8 - TabPtr(Array)

Renvoie un pointeur vers la structure SAFEARRAY du tableau Array spécifié. Ne fonctionne pas pour les tableaux JScript parce qu'ils sont en réalité des objets.

IX-9 - VarPtr(Var)

Dans les langages de script qui passent les arguments par référence aux méthodes, comme VBScript, cette méthode peut récupérer un pointeur vers la structure VARIANT de la variable Var.

IX-10 - MemAlloc(Bytes [, ZeroMem])

Alloue de la mémoire et renvoie un pointeur vers le début du tampon créé.
[ZeroMem] - si 1, la mémoire sera remplie avec des zéros binaires.

IX-11 - MemFree(MemPtr)

Libère la mémoire précédemment allouée.
MemPtr - pointeur renvoyé par un appel précédent à MemAlloc().

IX-12 - Bitness()

Récupère la largeur de bits de DynamicWrapperX (et donc la largeur de bits du processus qui l'utilise). Retourne la valeur 32 ou 64.

IX-13 - Version([Field])

Renvoie le(s) champ(s) spécifié(s) des quatre sections du numéro de version de DynamicWrapperX.
[Field] - choisir une valeur appropriée selon le tableau ci-dessous. Si Field est omis, sa valeur par défaut est zéro.

Supposons que la version complète est 2.5.7.10 :

1. 0 - Chaîne de version : "2.5.7.10" ;

2. 1 - Numéro majeur de version : 2 ;

3. 2 - Numéro mineur de version : 5 ;

4. 3 - Numéro de build : 7 ;

5. 4 - Version de révision : 10 (0xA) ;

6. 5 - Numéro majeur + mineur : 0x20005 ;

7. 6 - Build + version de révision : 0x7000A ;

8. 7 - Numéro de version complet : 0x200050007000A.

Chaque champ numérique est un entier 16 bits qui occupe deux octets.

IX-14 - LastError([Flag])

Si la fonction qui vient d'être appelée a été référencée avec le flag « l » (précédente erreur), la méthode renvoie le code d'erreur obtenu auprès de la fonction de l'API GetLastError immédiatement après le retour de la fonction initiale.

Si Flag = 1, la méthode retourne le texte décrivant la nature de l'erreur. Avant l'appel de la fonction référencée, le code d'erreur est toujours mis à zéro par la fonction SetLastError(0). Il vous appartient de vérifier dans la documentation que ce code d'erreur est bien géré par la fonction référencée afin que le test d'erreur ait un sens.

Les quatre méthodes suivantes, introduites par la version 2.1.0.0, autorisent la gestion directe de la mémoire et sont surtout destinées aux utilisateurs avancés désireux de combiner le Vbscript/JScript avec le langage assembleur ; à utiliser avec les précautions d'usage.

IX-15 - MemZero(Address,Bytes)

Remplit une zone mémoire à partir de l'adresse spécifiée Address avec Bytes octets de zéros binaires.
Pas de valeur retournée.

IX-16 - MemCopy(SrcAddr, DestAddr, Bytes)

Copie le contenu de Bytes octets d'une zone mémoire d'adresse initiale SrcAddr vers une adresse DestAddr. Les zones peuvent se chevaucher.
La valeur retournée sera l'adresse située immédiatement après la dernière valeur écrite.

IX-17 - MemRead(Address, Bytes[, BytesPerGroup][, GroupsPerLine])

Lit le contenu d'une zone mémoire sous la forme d'une chaîne hexadécimale avec des options de format.

- Address - adresse initiale de la zone ;

- Bytes - taille de la zone en octets ;

- BytesPerGroup - nombre d'octets de chaque groupe ;

- GroupsPerLine - nombre de groupes par ligne.

Les groupes seront séparés par un espace et les lignes par un saut de ligne.

IX-18 - MemWrite(HexStr, DestAddr[, Bytes])

Écrit une chaîne hexadécimale en mémoire après conversion sous forme binaire.

- HexStr - la chaîne hexadécimale ;

- DestAddr - adresse de base ;

- Bytes - le nombre d'octets à écrire. S'il est omis ou nul, la chaîne entière est écrite.

Dans une chaîne de ce type, chaque octet est représenté par deux caractères. La chaîne peut contenir des espaces, tabulations, sauts de ligne et commentaires acceptés par la fonction RegisterCode().
La valeur retournée sera l'adresse située immédiatement après la dernière valeur écrite.

Si DestAddress est nul, la méthode retourne la taille en octets occupée par la représentation binaire de la chaîne hexadécimale. Ce peut être utile si la chaîne complète contient des espaces ou commentaires qui ne seront pas convertis.

X - Exemples d'utilisation

JScript

DWX = new ActiveXObject("DynamicWrapperX");
str = "Bonjour le monde!";
// lecture de la mémoire et des codes d'une chaîne
codes = "";
for(i=0; i < str.length; ++i)
codes += DWX.NumGet(str, i*2, "t") + " "; // i multiplié par 2 car la chaîne est Unicode et
// l'adresse en octets
WScript.Echo("code des caractères :\n" + codes);
// lecture/écriture, la chaîne est enregistrée dans l'ordre inverse de ses caractères
len = str.length;
buf = DWX.MemAlloc(len*2 + 2);            // tampon pour l'écriture, chaque caractère Unicode = 2 octets
DWX.NumPut(0, buf, len*2, "t");           // écrit 2 octets nuls à la fin (caractère terminal)
for(i=0, j=len-1; i < len; ++i, --j) {
code = DWX.NumGet(str, i*2, "t");
DWX.NumPut(code, buf, j*2, "t");
}
str1 = DWX.StrGet(buf);
DWX.MemFree(buf);
WScript.Echo("chaîne inversée :\n" + str1);

// Manipulation de chaînes

ptr = DWX.StrPtr(str);                  // place le pointeur de chaîne dans une variable numérique
// la chaîne reste en Unicode
WScript.Echo("Addresse de la chaîne : " + ptr);

ptr = DWX.StrPtr(str, "z");             // récupère le pointeur de la même chaîne
// préalablement convertie en OEM/DOS
str1 = DWX.StrGet(ptr, "z");            // lecture de la chaîne reconvertie en Unicode

WScript.Echo("Chaîne reconstituée :\n" + str1);

// Création d'une copie UTF-8 d'une chaîne

bufsize = DWX.StrPut(str1, 0, "cp65001");
buf = DWX.MemAlloc(bufsize);
DWX.StrPut(str1, buf, "cp65001");

// lecture et retour à l'Unicode

str2 = DWX.StrGet(buf, "cp65001");
DWX.MemFree(buf);
WScript.Echo("Converti depuis l'UTF-8 :\n" + str2);

VBScript

Set DWX = CreateObject("DynamicWrapperX")
str = "Bonjour le monde ! C'est moi."
' lecture de la mémoire et des codes d'une chaîne
strlen = Len(str)
codes = ""
For i = 0 To strlen-1
codes = codes & DWX.NumGet(str, i*2, "t") & " "  ' i multiplié par 2 car la chaîne est Unicode et
Next                                                ' l'adresse en octets
WScript.Echo "code des caractères :" & vbCrLf & codes

' lecture/écriture, la chaîne est enregistrée dans l'ordre inverse de ses caractères

buf = DWX.MemAlloc(strlen * 2 + 2)  ' tampon pour l'écriture, chaque caractère Unicode = 2 octets
DWX.NumPut 0, buf, strlen * 2, "t"  ' écrit 2 octets nuls à la fin (caractère terminal)
j = strlen-1                        ' strlen-1 est la position du dernier caractère

For i=0 To strlen-1
code = DWX.NumGet(str, i*2, "t")  ' lecture de la gauche vers la droite (le décalage augmente)
DWX.NumPut code, buf, j*2, "t"    ' écriture de la droite vers la gauche (le décalage diminue)
j = j-1
Next

str1 = DWX.StrGet(buf)
DWX.MemFree buf
WScript.Echo "Chaîne inversée :" & vbCrLf & str1

' Manipulation de chaînes

ptr = DWX.StrPtr(str)                 ' place le pointeur de chaîne dans une variable numérique
' la chaîne reste en Unicode
WScript.Echo "Addresse de la chaîne : " & ptr

ptr = DWX.StrPtr(str, "z")            ' récupère le pointeur de la même chaîne
' préalablement convertie en OEM/DOS
str1 = DWX.StrGet(ptr, "z")           ' lecture de la chaîne reconvertie en Unicode

WScript.Echo "Chaîne reconstituée :" & vbCrLf & str1

' création d'une copie UTF-8 d'une chaîne

bufsize = DWX.StrPut(str1, 0, "cp65001")
buf = DWX.MemAlloc(bufsize)
DWX.StrPut str1, buf, "cp65001"

' lecture et retour à l'Unicode

str2 = DWX.StrGet(buf, "cp65001")
DWX.MemFree buf
WScript.Echo "Converti depuis l'UTF-8:" & vbCrLf & str2

XI - Liens et téléchargements

Le composant (x86 et x64) peut être téléchargé ici : Dynwrapx2.2.0.0.zip (version 2.2.0.0 du 03 .08.2018).

Vous pouvez également poser vos questions et laisser vos commentaires et suggestions sur le forum russe : http://forum.script-coding.com/viewtopic.php?id=7328

ou en français ici : forum Developpez.

XII - Remerciements

Je tiens à remercier bbil, ced ainsi que f-leb pour leur relecture attentive et leurs observations.

(1) NDT : situation avérée en ce qui concerne l'UTF-8.
Retour à l'article