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 !

LES OBJETS LÉGERS : UN CONCEPT POUR OPTIMISER LES CLASSES VB


Information sur le tutorial

Catégorie :Optimisation du code Date de création : 05/07/2005 11:42:11 Vu : 5 290 fois

Note :
5,5 / 10 - par 4 personnes
5,50 / 10

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

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


Description

Ce tutorial expose les bases de la conception d'objets légers pour VB...Ceci a pour but de créer des objets pour gérer des interfaces, appeler des pointeurs de fonctions...

Tutorial

Juste un rappel sur la pile et le tas

Lapilesert à stocker temporairement des informationsnécessaires àl’exécutiondes fonctions. C’est, en général, le compilateur quigère lapile.

Letas sert à stocker toute donnée quidoit être allouéependant l’exécutiondu programme et qui n’est pasconnu à la compilation (enprincipe). C’estle système d’exploitationqui alloue cette mémoire. VB se sertde la pilepour les tableaux et leschaînes principalement.

Le concept d’objet « léger »

D’abord, il faut savoir qu’en plus de la lourdeur de lagestion de l’interface IDispatch,toutmodule de classe utilise au moins96 octets de mémoire (sanscompter lecode) à la base ce qui fait beaucoupquand on pense que(hormis le codedes méthodes) que la structure minimum d’unobjet qui nesupporte que IUnknown est :

‘le contenu dela vtable

Private Type VTable

            Table(0 To 2) As Long

End Type

Private Type Objet

            ‘le pointeur vers la vtable

            pVTable as long

            ‘un compteur d’instance interne           

            cRefCountas long

End type

Cequi nous fait unetaille de 4 + 4 = 8 octets. On ne compte pas la vtablecar elle est commune àtoutes les instances de l’objet.

Ensuite, il fautsavoir que, bien que VB n’est pas type correspondant à IUnknown, il esttout à fait capable de gérer cette interface sans IDispatch. On peutdonc déclarer des interfaces à base de IUnknown dans une typelib et lesimplémenter dans VB.

Unobjetléger à nméthodes sera donc un objet qui, une fois instancié,paraîtracomme un objetnormale. La seule différence est qu’il ne feraque 8 + lataille des donnéesoctets pour une instance.

L’implémentation d’un objet léger

Eh bien là ça n’estpas si simple. Vous allez me dire « C’est simple, une typelib et unImplements et le tour est joué ». Eh bien non ! Un objet léger ça se gère dans un moduleBAS et avec une typelib.

Vousdéclarervotreinterface en langage ODL, vous compilez avec mktyplib.exe(pas avecMIDL.exe)et cela vous donne votre typelib. Vous devez définirtous vosinterfaces (mêmeceux déjà existants, comme IEnumVARIANT). Voir le tutorial sur lesbibliothèques de types.

Dans votre projet,vous devez ajouter une référence à la bibliothèque de types crée.

Dans un module BAS,vous définissez une fonction, disons InitObjet,dutype de l’interfaceavec les paramètres pour les données initialesdel’objet (qui seraient passéesau constructeur en C++). Danscettefonction, il faut :

  • Initialiser la vtable si ce n’est pas déjà fait
  • Initialiser la structure de l’objet pour la vtable et le nombre de références à 1
  • Copier l’adresse de la structure dans la variable InitObjet avec CopyMemory

La gestion des objets léger dansle tas

Lastructuredel’objet sera alloue dans le tas, c’est à dire dans une zonede mémoireallouéedynamiquement par le système d’exploitation. Ilfaudra donccontrôler lafonction Release pour libérer la mémoire au bon moment.

Il nous faudra lesdéclaration suivantes :

Private Type VTable

    Methods(0 To 2) As Long

End Type

Private m_pVTable As Long

Private m_VTable AsVTable

Private TypetypObjetHeap

    pVTable As Long

    cCount As Long

   

    'données attachées

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

    ‘…

End Type

Toutes lesdéfinitions sont privées car la gestion de la structure se fait en interne. Onexpose uniquement l’interface.

Notons les pointssuivants :

  • lapremièrestructure Vtable qui contient seulement un tableau. Onpourrait se direque l’on pourrait simplifier en déclarant simplementun tableau mais lestableaux sont libérer avant les structure à la finde l’exécution. Ilpourrait alors arriver que la vtable soit libéréeavant l’objet, ce quiconduirait à un crash. La variable globalem_VTable sera donc détruiteassurément après l’objet.
  • La variable globale m_pVTable sert à connaître l’adresse de la vtable et ainsi savoir si elle a été initialisée.
  • lastructuretypObjetHeap qui est la structure d’un objet en mémoire. EllecomprendpVTable, le pointeur vers la vtable et cCount, le compteurd’instances.Dans l’exemple je n’ai pas repris les données de l’objet.

Voici,maintenant,lecode pour initialiser un objet (notez que cetteconstruction apporteplusque New et donne un équivalent desconstructeurs du C++) :

Public Function FuncPtr(ByVal addr As Long) As Long

   FuncPtr = addr

End Function

Public Function InitObjetHeap() As

    'pointeur vers la structure de l'objet

    DimptrObjet As Long

    'contenu de l'objet

    Dim Objet As typObjetHeap

 

    'si la vtable n'est pasinitialisée

    If m_pVTable = 0 Then

        'on la remplie

        Withm_VTable

            'IUnknown

           .Methods(0) = FuncPtr(AddressOfQueryInterface)

           .Methods(1) = FuncPtr(AddressOf AddRef)

           .Methods(2) = FuncPtr(AddressOf Release)

           

            ‘initialisation des autres méthodes de l’interface àimplémenter dans l’ordre de définition dans la typelib

        End With

        'et on en garde l'adresse

        m_pVTable = VarPtr(m_VTable)

    End If

   

    'on contruit l'objet

    With Objet

        'le pointeur vers lavtable

        .pVTable = m_pVTable

        'le compteur de référence: on crée un objet donc il est à un

        .cCount = 1

       

        'initialisation des autresdonnées de l’objet

        ‘…

    End With

   

    'on alloue de l'espace mémoirepour l'objet

    ptrObjet = CoTaskMemAlloc(LenB(Objet))

    'si succès

    IfptrObjet Then

        'on remplit l'objet

        CopyMemory ByValptrObjet, Objet, LenB(Objet)

    End If

   

    'on assigne la référence à lavariable de retour de la fonction

    CopyMemory ByVal VarPtr(InitObjetHeap), ptrObjet, 4&

   

    ZeroMemory Objet,LenB(Objet)

End Function

Notons les pointssuivants :

  • ilnousfaut une variable temporaire pour stocker la structure de l’objetavantde la transférer dans la zone mémoire allouée à cet effet.
  • nousnepouvons pas affecter l’adresse d’une fonction, obtenue avecAddressOf,directement à une variable. Il faut donc une fonction quirenvoie leparamètre que l’on lui passe. Si la vtable n’est pasinitialisée, onremplit le tableau correspondant.
  • nousinitialisonsla structure de l’objet : on pointe la vtable et onmet à 1 lenombre d’instance puisque l’objet renvoyé est une instance.
  • Nous allouons une zone mémoire dans le tas, puis nous copions l’objet dans cette zone.
  • Commeunevariable de type objet (ou interface) es un pointeur, nouscopionsl’adresse de la structure dans le tas dans la variable de retourde lafonction.
  • Le ZeroMemoryestnécessaire pour que les éventuelles instances d’objets présentesdans lastructure de l’objet ou des tableaux ne soient pas supprimé dufait dela destruction de la structure temporaire.

        Utilisation

On écrira le codesuivant :

Dim objet As

Set objet = InitObjetHeap

‘l’utilisation

Set objet = Nothing

 

La gestion des objets léger dansla pile

Lastructurede l’objetsera alloue dans la pile, par le biais d’unevariable locale.Il faudra doncveiller à ne pas utiliser l’objet endehors de la portéede la fonction danslaquelle est déclarée lavariable locale. Sinon, onrisque de libérer la vtableavant l’appel àRelease ce qui produit uncrash de VB ou de l’exe. A NOTER,il faut toujours déclarerlavariable du type de l’interface de l’objetAVANT la structure afin dene paslibérer la structure avant l’objet. ILEST AUSSI NECESSAIRE DEFAIRE Set variable_objet= Nothing A LA FIN DE LA FONCTION.

Public TypetypObjetStack

    'le pointeur vers la vtable

    pVTable AsLong

   

    'données attachées

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

    ‘…

End Type

Notons que cettefois la structure est publique puisqu’il faudra déclarer une variable locale de ce type pour stockerl’objet (en plus de la référence d’objet).

Public FunctionInitObjetStack(ByRef lpStruct As typObjetStack, ByValcMaxSize As Long)As

    Dim ptrObjet As Long

   
   
'si la vtable n'est pas initialisée

    If m_pVTable = 0 Then

        'on la remplie

        Withm_VTable

            'IUnknown

            .Methods(0) =FuncPtr(AddressOf QueryInterface)

            .Methods(1) =FuncPtr(AddressOf AddRefRelease)

            .Methods(2) = FuncPtr(AddressOfAddRefRelease)

            ‘Le reste des méthodes del’interface

        End With

        'et on en garde l'adresse

        m_pVTable = VarPtr(m_VTable)

    End If

   

    'onconstruit l'objet

    WithlpStruct

        'le pointeur vers la vtable

        .pVTable = m_pVTable

       

        ‘on remplie le reste des membres del’objet       

    End With

   

    ptrObjet = VarPtr(lpStruct)

    'on assigne la référence à lavariable de retour de la fonction

    CopyMemory ByVal VarPtr(InitObjetStack), ptrObjet,4&

End Function

Notons les pointssuivants :

  • La structure est publique puisqu’il faut passer une variable de ce type en paramètre pour instancier l’objet
  • Onn’alloue plus d’espace mémoire dans le tas : on se sert del’espace mémoire de la variable locale passée en paramètre
  • On copie l’adresse de la variable dans la référence d’objet

        Utilisation

On écrira le codesuivant :

Dim structObjet AstypObjetStack

Dim objet As

Set objet = InitObjetStack(structObjet)

‘l’utilisation

Set objet = Nothing

 

Gérer les méthodes de l’interfacede base IUnknown

            Si le constructeur renvoie un objet du type de l‘interface

La méthode QueryInterfacede l’interface IUnknown est codée comme suit SI la fonction constructeurrenvoie un type « interface de l’objet » :

'cettefonction sert à demander à l'objet s'il sait gérer l'interface iid (c'est unGUID)

'normalementVB n'appelle jamais QueryInterface

'puisquel'on assigne à une variable du type de l'interface et que l'on ne supporte (àpart IUnknown) qu'une seule interface

Private FunctionQueryInterface( _

  ByRefThis As typObjet, _

  ByValiid As Long, _

  ByRefppvObject As Long_

) As Long

    'on se contente de refuser l'interface

    ppvObject = 0

    QueryInterface = E_NOINTERFACE

End Function

Voyons donc QueryInterfacedanscecas…Dans l’utilisation normale des objets légers, cetteméthodenedevrait jamais être appelée. En effet, on ne peut pasréellementaffecter leretour de la fonction constructeur (InitObjet) à une variable Object(puisque les interfaces que nous pouvons implémentons ne supportent pas IDispatch).Nousnouscontenterons donc de ne pas retourner de référence d’objetetrenvoyerE_NOINTERFACE pour signaler à VB que l’on ne veut pasdeQueryInterface.

            Si la fonction constructeur renvoieun IUnknown

Si la fonctionconstructeur renvoie un IUnknown on codera QueryInterface commesuit :

Private FunctionQueryInterface( _

  ByRefThis As typObjet, _

  ByValiid As Long, _

  ByRefppvObject As Long_

) As Long

If This.cCount > 1 Then

    ppvObject = 0

    QueryInterface = E_NOINTERFACE

Else

    This.cCount= This.cCount + 1

    ppvObject = VarPtr(This)

    QueryInterface = 0

End If

End Function

Danscecas, il fautautoriser un seul QueryInterface pour l’affectation àlavariable qui varéférencer l’objet. Pour cela, on regarde s’il y amoinsde deux instancesd’objet. Si oui, on incrémente le nombre deréférenceet on renvoie l’adressede This (la référence) dans ppvObject.Puis ilfaut  refuser tout autre cast pour éviter leserreursde cast quel’objet ne supporte pas. Dans ce cas, on met 0dansppvObject et on renvoieE_NOINTERFACE pour dire que l’on ne veut pasdece cast.

 

            AddRef et Release pour les objets gérés dans le tas

Voyons maintenant,AddRef et Release :

'cettefonction incrémente un compteur de référence (nombre d'instance) de l'objet

Private FunctionAddRef(ByRef This AstypObjetHeap) As Long

This.cCount = This.cCount + 1

AddRef = This.cCount

End Function

'cettefonction décrémente un compteur de référence (nombre d'instance) de l'objet

'quandle compteur atteind 0, sa structure est libérée

Private FunctionRelease(ByRef This AstypObjetHeap) As Long

This.cCount = This.cCount - 1

Release = This.cCount

‘Si l’onest dans le cas des objets sur pile, on n’a pas besoin de ce qui suit.

If This.cCount = 0 Then

    'on libère éventuellement lesressources allouées pour l’objet

    ‘…

    'et celle de l'objet

    CoTaskMemFree ByVal VarPtr(This)

End If

End Function

AddRefetReleasesont complémentaires. VB appelle AddRef pour dire qu’ilajouteune référence del’objet (un pointeur). VB appelle Release àchaque foisqu’une variable du typede l’interface sort de sa portée ouqu’ellereçoit Nothing. Pour la premièrefonction, on incrémente uncompteur (eton renvoie le compteur, valeur quin’est pas utilisée).Pour la seconde,on décrémente le compteur (et on renvoieaussi lecompteur).

Danslecas desobjets alloués dans le tas, lorsque le compteur atteint 0,ondoit libérer lastructure de l’objet que l’on a allouée dans letas.Ceci est impératif pour nepas avoir de fuites de mémoire.

            AddRef et Release pour les objets gérés sur la pile

Danslecas desobjets alloués dans la pile, on n’a rien à faire puisquelamémoire est libéréeautomatiquement à la fin de la procédurequicontient la variable locale.

Private FunctionAddRefRelease(ByVal This As Long) As Long

‘uncommentaire est nécessaire afin d’empêcher VB de supprimer la fonction

End Function

 

Gérer les autres méthodespersonnelles

1)     Le pointeur This

Touteméthode d’un objet reçoit implicitement un premier paramètre This quiest un pointeur vers la structure de l’objet pour lequel la méthodevient d’être appelé. Il est du type de la structure de l’objet,dans notre cas, il s’agit de typObjetHeap.

2)     La valeur de retour

Il y a deux cas àprendre en compte :

·       Gestiond’erreur minimale

·       Pas de gestion d’erreur

Letroisièmemode degestion d’erreur est la gestion d’erreur riche.Lorsqu’une erreurest lancée,VB essaie de demander une interfaceIsupportErrorInfo àQueryInterface del’objet qui a généré l’erreur. Leproblème est quecette interface est libéréaprès tous les module dansnotre vtablen’existerait plus donc crash. Il fautdonc toujours refusercetteinterface dans QI.

 

a)     Gestion minimaled’erreur

Le premier niveau degestion d’erreur sous VB se fait par le type HRESULT que vous ne pouvez pas utiliser vous même. C’est l’équivalent d’un Long.Danslesmodule de classe, la valeur de retour des fonctions esttoujoursunHRESULT. Mais alors, me dirais vous, comment VB retourne-t-ilunevaleur àl’appelant ?

Eh bien, il utilise un paramètre qui se trouve à la fin de laliste des paramètres. Ce paramètreest de type pointeur et possède les attributs [out,retval] dans le fichier ODL. Dans VB, il sera déclarer comme un ByRef As .

Et Alors dans leHRESULT, on met quoi ? Le code d’erreur biensûr ! Il faut savoir queVB et l’objet Err transpose lescodes d’erreur dans la plage &H0001 à &HFFFFetqu’il transpose plusieurs code d’erreur en un seul de l’objet Err.Ilsetrouve que toutes les erreurs de VB se retrouve dans les valeurdeHRESULTdans la plage &H800A0001 à&H800AFFFF. On peut alors renvoyer le code d’erreur VB (par exemple 7 « Mémoireinsuffisante ») que l’onOr-era avec &H800A0000.Biensûr, s’il n’y a pas d’erreur, on renvoie 0.

C’est le principe debase de la gestion d’erreur dans VB.

Dans le langage ODL,on aura des méthodes définies comme suit :

HRESULT (

) ;

b)     Pas de gestiond’erreur

Bien que VBs’attende à un HRESULT, on peut passerdirectementla valeur de retour dans la valeur de retour si latailleest inférieureou égale à 8 octets. Sinon, il y a unparamètresupplémentaire qui esttoujours passé en tout premierparamètre (avant lepointeur this). Ilcontient un pointeur vers la zonemémoire pour stocker lerésultat.

Si l’on retourne unevaleur dans le retour de fonction, on n’aura pas la possibilité de renvoyer uncode d’erreur.

Dans le langage ODL,on aura des méthodes définies comme suit :

(

) ;

Si l’on n’a nibesoin de valeur de retour, ni de code d’erreur, on remplacera par void.


3)     Les différents typede paramètres

Tous les types debases : Byte, Integer,Long, Single, Double, Boolean, Currency, Date, Enum sont des types qui peuvent être passédirectement par valeur. Pour ces types, si le paramètre est défini :

·       [in] <type> , c’estun ByVal As <type>

·       [in,out] <type>*, c’est un ByRef As <type>

<type>est à choisir parmi un nom d’enum, unsigned char,short, long, float, double, boolean, currency, DATE.

Il n’y pas d’autrescombinaisons. A noter que pour ces types, on peut définir une valeur pardéfaut, qui rend le paramètre optionel, avec l’attribut defaultvalue(<valeur>).

Les types String (BSTR, LPSTR, LPWSTR), Object (IDispatch*)et autres types objets (toutes les interfaces) sont, pardéfinition, des types pointeurs. Pour ces types, on définit :

  • [in] BSTR, [in] LPSTR, [in] LPWSTR, c’est un ByVal As String
  • [in,out] BSTR*, [in,out] LPSTR*, [in,out] LPWSTR*, c’est un ByRef As String
  • [in] IDispatch*, c’est un ByVal As Object
  • [in,out] IDispatch**, c’est un ByRef As Object

A noter que ByRef As Object (ou As <interface>)n’estnécessaire que lorsque l’on comptemodifier la référence d’objet,ou enrenvoyer une. Pour un simple passage deparamètre à desfinsd’utilisation, on optera toujours pour un ByVal

Les types tableauxsont eux aussi des pointeurs :

  • [in] SAFEARRAY(<type>)* et [in,out] SAFEARRAY(<type>)* sont équivalent puisque l’on ne peut pas passer de tableau par valeur.

<type>est n’importe quel type, simple, structure ou tableau.

h)     Les paramètres [out,retval]

 

Le dernier paramètre de la liste peut être attribué avec [out,retval]s’il est du type ByRef c’est à dire pointeur (long*, boolean*, …,BSTR*, IDispatch**, **, …). Il ne peut y en avoir qu’unseul. Dans ce cas, il sert de valeur de retour à la fonction.

i)       Les ParamArray : l’attribut vararg

Pour déclarer que la liste des paramètres n’est pas connue àpartir d’un certain paramètre, on utilise ParamArray suivi d’un nom deparamètre de type tableau de Variant.Enlangage ODL, il faut ajoutervararg dans les attributs de la méthode(etnon dans les attributs duparamètre). Il s’en suit une définitiondeméthode comme suit :

[vararg] <type> <nom>(<listede paramètres connus>,SAFEARRAY(VARIANT)* <nom arg>) ;

j)       Les propriétés : les attributs propput, propputrefet propget

Pourdéclarerune propriété en lecture-écriture, il fautdeuxfonctions : une pourlire et une pour écrite. Nous distingueronsdeuxcas :

·       Les propriétés normales

·       Les propriétés objets

  1. Les propriétés normales : Property Get / Property Let (propget / propput)

Une propriété de ce type aura deux entrées dans la vtable.En langage ODL, elle auront le prototype suivant :

  • Pour la lecture, Property Get :
    • [propget] HRESULT <nom propriété>([out,retval] <type>* <nom>) ;
    • Function get_<nom propriété>(ByRef This, ByRef <nom> As <type>) As Long
  • Pour l’écriture, Property Let :
    • [propput] HRESULT <nom propriété>([in] <type> <nom>);
    • Function put_<nom propriété>(ByRef This, ByVal <nom> As <type>) As Long
  1. Les propriétés objets : Property Get / Property Set (propget /propputref)

Une propriété de ce type aura deux entrées dans la vtable.En langage ODL, elle auront le prototype suivant :

  • Pour la lecture, Property Get :
    • [propget] HRESULT <nom propriété>([out,retval] <interface>** <nom>) ;
    • Function get_<nom propriété>(ByRef This, ByRef <nom> As <interface>) As Long
  • Pour l’écriture, Property Let :
    • [propputref] HRESULT <nom propriété>([in] <interface>* <nom>);
    • Function put_(ByRef This, ByVal <nom> As <interface>) As Long

On peut aussi avoir des propriétés en lecture-seule ouécriture-seule en supprimant une des deux fonctions

 

Conclusion

Bon, eh bien, vousallez sûrement encore me dire : « A part optimiser, qu’est ce quel’on peut faire de tout ça ». Eh bien, utiliser des pointeurs defonctions…mais ça c’est le tutorial suivant…Et puis l’implémentation de ForEach

05 juillet 2005 16:11:33 :
Bug
07 juillet 2005 10:31:26 :
bug
signaler à un administrateur
Commentaire de ShareVB le 14/07/2005 10:51:53

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...

ShareVB

signaler à un administrateur
Commentaire de ShareVB le 14/07/2005 11:32:22

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

ShareVB

signaler à un administrateur
Commentaire de ShareVB le 11/08/2006 21:55:22

salut à tous,

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

ShareVB

Ajouter un commentaire



Nos sponsors

Sondage...

CalendriCode

Décembre 2008
LMMJVSD
1234567
891011121314
15161718192021
22232425262728
293031    

Consulter la suite du CalendriCode



Développement réalisé par Nicolas SOREL (Nix) avec l'aide de : Cyril DURAND et Emmanuel BAÏSE, 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,281 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é.