Vous ne trouvez pas de réponse à votre problème ? Alors posez la question dans le forum. Souvenez-vous qu'il n'y a jamais de question bête, mais rester dans l'ignorance parce que l'on n'ose pas poser une question, ça c'est une erreur !

APPELER DES POINTEURS DE FONCTIONS DEPUIS VB


Information sur le tutorial

Catégorie :Trucs & Astuces Date de création : 06/07/2005 10:29:16 Vu : 6 501 fois

Note :
Aucune note

Commentaire sur cette source (5)
Ajouter un commentaire et/ou une note


Description

Ce tutorial explique comment appeler des pointeurs de fonctions depuis VB...

Tutorial

Un petit rappel sur les registres, le mode protégé et lapile d’exécution

Un CPU x86, c’est à dire la plus part des processeurs (Intelet AMD) des PCs, comporte des registres qui sont les suivants :

  • Registres généraux

o      EAX, le registre à tout faire et utilisépour le retour de la valeur lors d’appel de fonction (instruction CALL).Il n’est donc pas sauvegardé lors d’appels de fonctions

o      EBX, registre qui doit être sauvegardé avantutilisation dans une fonction

o      ECX, registre compteur, utilisé pour les bouclesLOOP et REP. Il n’est pas sauvegardé

o      EDX, registre utilisé pour le retour devaleur de 64 bits (avec EAX). Il n’est pas sauvegarder

  • Registres d’index : ils sont sauvegardés pendant les appels de fonctions
    • ESI est le registre qui sert à contenir l’adresse source pour la copie de mémoire avec les instructions MOVS (dites de chaines) (et aussi, STOS, LODS, SCAS et CMPS)
    • EDI est le registre qui sert à contenir l’adresse destination pour la copie de mémoire avec les instructions MOVS (et aussi, STOS, LODS, SCAS et CMPS)
  • Registres de piles
    • ESP est le registre qui sert à pointer le sommet de la pile, c’est à dire la dernière valeur empilée
    • EBP est le registre qui sert dans les fonctions, à conserver l’adresse de pile du début de l’appel de façon à ce que les paramètres gardent le même offset (relativement à EBP) si l’on doit faire des PUSH et des POP (qui modifient ESP). Si l’on utilise ESP, on doit tenir compte de ses variations. Ce registre est conservés lors des appels.
  • Registre pointeur d’instruction
    • EIP est registre qui pointe la prochaine instruction à exécuter. Il n’est pas accessible ni modifiable directement.

Si vous avez déjà entendu parlé de registres de segmentsen mode réel, tels que CS, DS, ES, FS, GS, SS…et bien oubliezles, car en mode 32bits protégé, il n’y a pas de segments…ou plutôt, unseul de 4Go (en théorie)…

La pile sert à stocker temporairement les informationsnécessaires au fonctionnement du programme : adresse de retour defonction, variables locales…

La pile est une zone mémoire qui croit vers le bas.Au départ, la pile est au plafond et à chaque fois que l’on empile, le sommetdescend de 4 octets du plafond. Lorsque l’on dépile, le sommet remonte de 4octets. Il s’en suit que la mémoire libre est en ESP – 4, ESP – 8…

Et la mémoire utilisée en ESP, ESP + 4, ESP + 8…

Lorsque l’on fait un appel de fonction, l’adresse del’instruction suivant le CALL (adresse de retour) est poussée sur la pile, puissuivent les paramètres suivant la convention d’appel. La pile ressembledonc à ceci (si passage des paramètres par la pile) :

            ESP + 4* n + 4 : Paramètre-n

           

            ESP +8 : Paramètre 2

            ESP +4 : Paramètre 1

            ESP +0 : Adresse de retour

 

Si l’on utilise EBP, on écrira le code suivant au début dela procédure :

            PUSHEBP  //on doit sauvegarder EBP avantchangement

            MOV EBP,ESP//on garde l’emplacement de pile actuel

            SUB ESP,taille_des_variables_locales

 

Et l’on écrit à la fin de la procédure :

            MOV ESP,EBP//on remet ESP à sa place de départ

            POP EBP

            RET

 

Il s’en suit la disposition de pile suivante (sansvariables locales) :

            ESP + 8* n + 4 : Paramètre-n

           

            ESP +12 : Paramètre 2

            ESP +8 : Paramètre 1

            ESP +4 : Adresse de retour

            ESP +0 : valeur ancienne de EBP

 

Et relativement à EBP (avec ou sans variables locales):

            EBP + 8* n + 4 : Paramètre-n

           

            EBP +12 : Paramètre 2

            EBP +8 : Paramètre 1

            EBP +4 : Adresse de retour

            EBP +0 : valeur ancienne de EBP

            EBP -4 : variable locale 1

            EBP –8 : variable locale 2

           

            EBP – 4* m : variable locale m

 

Un appel de fonction se déroule comme suit, indépendammentde la convention d’appel  :

  • Tous les arguments sont ajustés à la taille du bus de donnée soit 32bits pour les PC
  • Tous les arguments sont places soit sur la pile, soit dans des registres
  • L’adresse de retour est empilée sur la pile
  • Un saut est fait vers la fonction
  • La fonction crée sont cadre de pile avec EBP si nécessaire (si oui, elle sauvegarde EBP)
  • Si la fonction utilise ESI, EDI et EBX, elle les sauvegarde sur la pile
  • La fonction s’exécute
  • La valeur de retour est mise :
    • Dans le registre EAX pour des entiers ou pointeurs <= 4octets
    • Dans les registres EDX :EAX pour des entiers ou pointeurs de 8 octets
    • Dans les registres du coprocesseur arithmétique  pour les réels (ST(0))
    • Dans une zone mémoire dont l’adresse est passée en tout premier paramètre (toujours sur la pile) à la fonction pour des valeur de retour de taille > 8 octets. Cette adresse est renvoyée dans EAX.
  • Les registres sauvegardés sont restaurés
  • Le cadre de pile est restauré
  • Suivant la convention d’appel, la fonction retire les paramètres de la pile et rend la main à l’appelant, OU rend simplement la main sans retirer les paramètres.

 

Les objets légers et les pointeurs de fonctions

Je ne sais pas si vous avez remarquer le nombre de AddressOfque l’on peut faire pour les objets légers…mais cela veut dire que l’on a despointeurs de fonctions dans la vtable…Nous pouvons donc penser à utiliser unevtable pour appeler des pointeurs de fonctions…le seul problème restant est quel’état de la pile et des registres change suivant la convention d’appel…

La méthode sera donc la suivante : on fait crée unobjet léger avec une interface possédant une seule fonction qui à la signature(ou à peu près) de la fonction à appeler. On aura donc une vtable à 4 entrées (lesméthodes de IUnknown et notre fonction). La quatrième entrée de lavtable sera donc un pointeur vers un morceau de code asm (compilé biensûr) pourtransformer un appel méthode COM en un appel de pointeur de fonction avec labonne convention d’appel…

Mais vous avez dit « convention d’appel »…sansêtre indiscret, qu’est-ce que cela peut être ?

Je part du principe que la valeur de retour tient dans lesregistres et qu’un pointeur vers le la mémoire n’est pas nécessaire…Sinon, çase complique encore un peu plus…on verra ça en fin de tutorial…

 

Les conventions d’appels

Une convention d’appel est la façon dont les paramètres sontpassés à la fonction appelée. Il en existe un certain nombre.

Nom de la convention

Passage des paramètres

Nettoyage de la pile

Décoration des noms

Notes

Stdcall

Sur la pile de droite à gauche

Appelé

Un _ devant le nom

Un @ et la taille des paramètres après le nom

Utilisé par VB et par les APIs Windows

Cdecl

Sur la pile de droite à gauche

Appelant

Un _ devant sauf si extern « C »

Utilisé par défaut par les compilo C

Fastcall

Dans les registres ECX et EDX et sur la pile de droite à gauche

Appelé

Un @ devant

Un @ et la taille des paramètres après le nom

Utilisé par les compilo Borland

Pascal

Sur la pile de gauche à droite

Appelé

????

Obsolète

Thiscall

Sur la pile de droite à gauche et un pointeur This dans ECX

Appelé

Un ? avant

Un @ suivi d’un bazar indiquant la signature de la fonction

Utilisé pour les objet C++ (class)

Register

Dans trois registres et sur la pile

Appelé

????

Utilisé par Delphi

 

La convention stdcall

C’est la seule convention d’appel que VB supporte defaçon native. Tous les paramètres sont passés sur la pile de droite àgauche (par rapport à l’ordre de déclaration). Le premier argument seradonc à ESP+4. Le deuxième argument sera à esp+8…et ainsi desuite…

C’est la fonction qui retire les paramètres de la pile avantle RET.

La pile ressemble à ceci :

Paramètre-n

Paramètre-2

Paramètre-1

ReturnAddress

 

La convention d’appel des méthodes COM

C’est comme la convention stdcall. La seule différence estqu’il y a en plus, le paramètre this de type pointeur (32 bits) qui esttoujours en premier.

La pile ressemble à ceci :

Paramètre-n

Paramètre-2

Paramètre-1

Thispointer

ReturnAddress

 

La convention d’appel cdecl

La convention cdecl est identique à stdcall à l’exceptionque ce n’est pas la fonction qui retire les paramètres de la pile mais lafonction qui appelle la fonction.

La pile ressemble à ceci :

Paramètre-n

Paramètre-2

Paramètre-1

ReturnAddress

 

La convention d’appel fastcall

La seule différence avec la convention stdcall, c’est que lesdeux premiers paramètres entiers ou pointeurs (<= 4 octets) sont passés dansles registres ECX et EDX. Les types réels et supérieurs à4 octets sont passés sur la pile. Cela signifie que l’ordre des paramètresne sera pas forcement le même dans : ECX, EDX, la pile…etdans la déclaration.

La pile ressemble à ceci :

Paramètre-n

Paramètre-4

Paramètre-3

ReturnAddress

À noter que le parameter 3 n’est pas forcement celui quiest le troisième dans la déclaration de la fonction. Il faut tenir compte destypes qui peuvent entrer dans un registre de CPU.

 

La convention d’appel thiscall

C’est comma la convention d’appel stdcall et un pointeurThis (vers une structure qui n’est pas celle d’un objet COM mais d’uneclasse C++) se trouve dans ECX. C’est la convention en C++ pour lesobjets.

La convention d’appel pascal

C’est comme la convention stdcall avec les paramètres enordre inverse, passés de gauche à droite.

 

Transformer un stdcall méthode en …

Je n’expose ici que le principe et pas le code sinon letutorial ferait 30 pages. Pour retrouver le code complet allez voir lesdifférents modules des versions pile (Stack) et tas (Heap).

L’adresse de la fonction à appeler sera stocké dans lastructure de l’objet léger dans le troisième DWORD, c’est à dire à l’offset 8par rapport au début de la structure.

 

            Stdcall

La pile ressemble à ceci pour un appel d’une méthoded’objet:

Paramètre-n

Paramètre-2

Paramètre-1

Thispointer

ReturnAddress


Et nous voulons ceci :

Paramètre-n

Paramètre-2

Paramètre-1

ReturnAddress

 

Il faut donc supprimer le pointeur This de la pile etfaire un JUMP à l’adresse de la fonction qui est stockée dans la structurede l’objet (pointée par This) à l’offset 8.

Il faudra donc le code suivant en ASM :

      pop ecx               //conserve lareturn address

      pop eax               //conserve le pointeur this

      push ecx               //remetd'adresse de retour sur la pile

      jmp DWORD ptr [eax + 8] //on appellela fonction

 

Voici donc un moyen d’appeler des fonctions stdcall par unobjet.

Le code suivant n’est pas spécifique à la fonction appelée.On pourra donc le mettre dans une variable globale au module. Il faudradéclarer un type contenant un tableau fixe de Long (pour être aligné) afind’être sûr que la variable ne soit pas détruite avant l’objet qui pourraitencore en avoir besoin. Et pourquoi pas une constante ? Parce que le codeexécutable doit être dans une zone mémoire en lecture écriture…

Nous aurons donc pour tous les code ASM, le typesuivant :

Type asmCode

            Code(0 To Taille) As Long

End Type

Private m_asmCode as asmCode

On ajustera Taille au nombre de DWORDs nécessaires aucode compilé. Si le dernier morceau du code fait moins qu’un DWORD, on ajouterades NOPs (&H90) ou des int 3 (&HCC) après le code ASM

 

            Cdecl

Comme pour stdcall, il faut aussi retirer le pointeurThis de la pile avant d’appeler la fonction. Le problème restant estq’au retour de la fonction appelée, les paramètres ne seront pas retirés dela pile. Il faut donc pouvoir exécuter du code juste après l’appel àla fonction avant de rendre la main à VB.

Ajoutons à tout cela, que dans un soucis d’encapsulation, lataille des paramètres à retirer de la pile est propre à une fonction, donc à unobjet. Le code de suppression des paramètres de la pile devra se trouver dansla structure de l’objet.

Il faudra donc :

  • Retirer le pointeur This de la pile
  • Conserver l’adresse de retour finale dans l’objet pour compléter le code ASM inclu
  • Appeler la fonction
  • Au retour, on se retrouve dans l’objet :
    • On remet sur la pile, l’adresse de retour définitif
    • On fait un RET avec une taille de paramètre contenue dans l’objet

On aura une structure d’objet comme suit :

PrivateType typFunctionCallerHeap

    'le pointeur vers la vtable

    pVTable As Long

    'le compteur de référence pour savoirquand on doit libérer la mémoire de l'objet

    cCount As Long

   

    'données attachées

    '--------------------

    'un pointeur vers la fonction

    lpfn As Long

    'la taille des arguments de la fonction

    cbArgSize As Long

   

    'le code ASM supplémentaire

    lpPushReturnAddressAs Long

   lpRetAddress As Long

    lpRetAs Long

EndType

Cela donne le code ASM suivant :

      pop ecx               //l'adresse deretour finale

      pop eax               //pointeur this

      mov [eax + 20],ecx      //stocke l'adresse de retour finale

      lea ecx,[eax + 16]      //charge l'adresse du code de retourpour

//libérer la pile

      push ecx               //on met cetteadresse de retour sur la pile

      jmp dword ptr [eax + 8] //on va dansla fonction à appeler

 

      //code à mettre dans l'objet

      push 0x12345678      //on remet l'adresse de retour finale sur la pile :

//l'objet est construit de façon à ce que l'adresse //de retourfinale stockée

                        //vienneremplacer 0x12345678

ret0x1234      //onretourne à l'adresse finale en supprimant //les paramètres empilés

//l'objet est construit pour que 0x1234 soit //remplacé par lataille des paramètres

 

            Fastcall

Alors là, ça se complique au niveau prototype defonction…car il faut mettre les paramètre, non pas dans leur ordre dedéfinition mais dans l’ordre de la convention fastcall :

  • S’il y a un ou plusieurs paramètres entiers ou pointeurs (<= 4 octets), on les met en premier
  • On met donc tout autres paramètres en suivant

Par exemple, si l’on a la déclaration suivante :

            IntFct(double d, int a, float f, int* b, int c);

On mettra la déclaration suivant dans le fichier ODL :

            Int Fct([in]int a,[in,out]int*b,[in]double d,[in]float f,[in]int c);

Une fois les paramètres dans le bon ordre, il ne reste plusqu’à :

  • Retirer le pointeur This (comme toujours)
  • On met les deux premiers paramètres dans les registres ECX et EDX si besoin
  • On remet l’adresse de retour
  • On fait un saut dans le fonction

La structure de l’objet sera la suivante :

PrivateType typFunctionCallerHeap

    'le pointeur vers la vtable

    pVTable As Long

    'le compteur de référence pour savoirquand on doit libérer la mémoire de l'objet

    cCount As Long

   

    'données attachées

    '--------------------

    'un pointeur vers la fonction

    lpfn As Long

    'stockage de l'adresse de retour lors del'appel

    lpRet As Long

   

    'nombre d'arguments entiers et pointeurs

    cArgCount As Long

    'code ASM pour gérer l'appel

    asmCode(0 To5) As Long

   'puisque le code ASM est différent pourchaque objet, la vtable aussi

    VTable As VTable

End Type

Cela nous donne le code suivant :

      pop ecx               //conserve lareturn address

      pop eax               //conserve le pointeur this

      mov [eax + 12], ecx   //on conserve temporairement l'adresse de

//retour dans l'objet

 

      //premier paramètre dans ECX : si pasprésent NOP

      pop ecx

 

      //deuxième paramètre dans EDX : sipas présent NOP

      pop edx

 

      push [eax + 12]        //on remetl'adresse de retour sur la pile

      jmp dword ptr [eax + 8] //on va dansla procédure

Comme le code ASM dépend de la fonction à appeler, on doitmettre le code (et donc la vtable qui pointe vers) dans l’objet.

 

            Thiscall

Le plus dure est de connaître la structure de l’objet C++, àsavoir les variables privées. Il faut pour cela, disposer du fichier .h ettraduire les membres de type variable de l’objet en structure VB. Cettevariable est à passer au constructeur de l’objet COM d’appel de la méthode dela classe C++. Il ne faut pas confondre le pointeur This des objets COM (etVB) avec le pointeur This des objets C++. Ce dernier a un pointeur versune vtable, uniquement s’il contient des fonctions virtuelles. Un objet C++n’est pas une entité autonome au sens de COM. La seul différence entre unefonction stdcall et une méthode thiscall est la décoration de son nom par lecompilateur et le pointeur this dans ECX.

Le code est pratiquement celui du code pour stdcallpuisqu’il faut seulement mettre un pointeur vers une structure de l’objet C++dans ECX avant l’appel (ce pointeur This est stocké dans la structure del’objet COM après l’adresse de la fonction):

      pop ecx               //conserve lareturn address

      pop eax               //conserve le pointeur this

      push ecx               //remetd'adresse de retour sur la pile

      mov ecx,[eax + 12]   //on copie le pointeur This de l'objet C++

      jmp DWORD ptr [eax + 8] //on appellela méthode de l'objet C++

 

            Pascal

C’est exactement le même code que pour stdcall à l’exceptionque les paramètres de la fonctions doivent être écrits (dans le fichier ODL) àl’envers de leur déclaration en pascal. SI les paramètres sont a, b, c enpascal, il seront c, b, a dans le fichier ODL.

 

Si la fonction renvoie un type de taille supérieure à 8 octets

Dans ce cas, un pointeur vers la zone mémoire prévue pourla valeur de retour est passé :

  • TOUJOURS sur la pile pour n’importe quel convention d’appel
  • TOUJOURS avant tout autre paramètre pour n’importe quel convention d’appel

Il suffit donc de déclarer ce paramètre dans la liste desparamètres comme un long.

Pour stdcall, cdecl, thiscall et pascal, on ajoute unparamètre Long en premier paramètre.

Pour fastcall, on met le paramètre Long après lesdeux premiers paramètres pouvant être passés dans les registre (s’il y en a).Ce paramètre de valeur de retour n’est jamais passé par registres.

signaler à un administrateur
Commentaire de ShareVB le 06/07/2005 10:32:20

Je cite ma source, tout en disant que ce n'est pas du copier coller, ni du texte, ni du code :

Advanced Visual Basic 6
Power Techniques for Everyday Programs
Matthew Curland
www.powervb.com

Si vous voulez encore plus d'infos sur VB, achetez et lisez ce livre...

A l'exception de stdcall, le code ASM est totalement différent de celui du livre (cdecl).

ShareVB

signaler à un administrateur
Commentaire de ShareVB le 14/07/2005 11:43:48

un projet exemple est disponible : http://www.vbfrance.com/code.aspx?ID=32558

ShareVB

signaler à un administrateur
Commentaire de ShareVB le 11/08/2006 22:00:19

salut à tous,

retrouvez ce tuto sur mon site perso à www.sharevb.net...rubrique Programmation\VB\

ShareVB

signaler à un administrateur
Commentaire de jipef le 04/12/2006 14:06:50

bonjour comment entrer dans ton site www.sharevb.net...rubrique Programmation\VB\

je veux m'enregistrer
merci

signaler à un administrateur
Commentaire de ShareVB le 04/12/2006 15:24:04

salut,

simplement : http://www.sharevb.net/-VB-.html...et il n'y a pas d'enregistrement...

ShareVB

Ajouter un commentaire



Nos sponsors

Sondage...

CalendriCode

Juillet 2009
LMMJVSD
  12345
6789101112
13141516171819
20212223242526
2728293031  

Consulter la suite du CalendriCode

Comparez les prix Nouvelle version

Photothèque Nouveau !



Développement réalisé par Nicolas SOREL (Nix) avec l'aide de : Cyril DURAND et Emmanuel (EBArtSoft), Merci à Vincent pour ses précieux conseils
CodeS-SourceS.com© Toute reproduction même partielle est interdite sauf accord écrit du Webmaster
CodeS-SourceS.com© est une marque déposée tous droits réservés
Temps d'éxécution de la page : 0,094 sec

Google Coop CodeS-SourceS Google Coop CodeS-SourceS


Certaines images présentes sur le site (notament certains avatars) sont issues des collections IconShock, donc si vous souhaitez utiliser ces icons vous devez les acheter, ne les copiez pas et ne utilisez pas dans vos sites et applications sans les avoir commandé.