Juste un rappel sur la pile et le tas
Lapilesert à stocker temporairement des informationsnécessaires àlexécutiondes fonctions. Cest, en général, le compilateur quigère lapile.
Letas sert à stocker toute donnée quidoit être allouéependant lexécutiondu programme et qui nest pasconnu à la compilation (enprincipe). Cestle système dexploitationqui alloue cette mémoire. VB se sertde la pilepour les tableaux et leschaînes principalement.
Le concept dobjet « léger »
Dabord, il faut savoir quen plus de la lourdeur de lagestion de linterface 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 dunobjet 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 dinstance 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 lobjet.
Ensuite, il fautsavoir que, bien que VB nest 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 quil ne feraque 8 + lataille des donnéesoctets pour une instance.
Limplémentation dun objet léger
Eh bien là ça nestpas si simple. Vous allez me dire « Cest 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 linterfaceavec les paramètres pour les données initialesdelobjet (qui seraient passéesau constructeur en C++). Danscettefonction, il faut :
- Initialiser la vtable si ce nest pas déjà fait
- Initialiser la structure de lobjet pour la vtable et le nombre de références à 1
- Copier ladresse de la structure dans la variable InitObjet avec CopyMemory
La gestion des objets léger dansle tas
Lastructuredelobjet sera alloue dans le tas, cest à dire dans une zonede mémoireallouéedynamiquement par le système dexploitation. 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 linterface.
Notons les pointssuivants :
- lapremièrestructure Vtable qui contient seulement un tableau. Onpourrait se direque lon pourrait simplifier en déclarant simplementun tableau mais lestableaux sont libérer avant les structure à la finde lexécution. Ilpourrait alors arriver que la vtable soit libéréeavant lobjet, ce quiconduirait à un crash. La variable globalem_VTable sera donc détruiteassurément après lobjet.
- La variable globale m_pVTable sert à connaître ladresse de la vtable et ainsi savoir si elle a été initialisée.
- lastructuretypObjetHeap qui est la structure dun objet en mémoire. EllecomprendpVTable, le pointeur vers la vtable et cCount, le compteurdinstances.Dans lexemple je nai pas repris les données de lobjet.
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 linterface àimplémenter dans lordre 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 lobjet
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 lobjetavantde la transférer dans la zone mémoire allouée à cet effet.
- nousnepouvons pas affecter ladresse dune fonction, obtenue avecAddressOf,directement à une variable. Il faut donc une fonction quirenvoie leparamètre que lon lui passe. Si la vtable nest pasinitialisée, onremplit le tableau correspondant.
- nousinitialisonsla structure de lobjet : on pointe la vtable et onmet à 1 lenombre dinstance puisque lobjet renvoyé est une instance.
- Nous allouons une zone mémoire dans le tas, puis nous copions lobjet dans cette zone.
- Commeunevariable de type objet (ou interface) es un pointeur, nouscopionsladresse de la structure dans le tas dans la variable de retourde lafonction.
- Le ZeroMemoryestnécessaire pour que les éventuelles instances dobjets présentesdans lastructure de lobjet 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
lutilisation
Set objet = Nothing
La gestion des objets léger dansla pile
Lastructurede lobjetsera alloue dans la pile, par le biais dunevariable locale.Il faudra doncveiller à ne pas utiliser lobjet endehors de la portéede la fonction danslaquelle est déclarée lavariable locale. Sinon, onrisque de libérer la vtableavant lappel àRelease ce qui produit uncrash de VB ou de lexe. A NOTER,il faut toujours déclarerlavariable du type de linterface de lobjetAVANT la structure afin dene paslibérer la structure avant lobjet. 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 puisquil faudra déclarer une variable locale de ce type pour stockerlobjet (en plus de la référence dobjet).
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 delinterface
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 delobjet
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 puisquil faut passer une variable de ce type en paramètre pour instancier lobjet
- Onnalloue plus despace mémoire dans le tas : on se sert delespace mémoire de la variable locale passée en paramètre
- On copie ladresse de la variable dans la référence dobjet
Utilisation
On écrira le codesuivant :
Dim structObjet AstypObjetStack
Dim objet As
Set objet = InitObjetStack(structObjet)
lutilisation
Set objet = Nothing
Gérer les méthodes de linterfacede base IUnknown
Si le constructeur renvoie un objet du type de linterface
La méthode QueryInterfacede linterface IUnknown est codée comme suit SI la fonction constructeurrenvoie un type « interface de lobjet » :
'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 lutilisation 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 dobjetetrenvoyerE_NOINTERFACE pour signaler à VB que lon 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 laffectation àlavariable qui varéférencer lobjet. Pour cela, on regarde sil y amoinsde deux instancesdobjet. Si oui, on incrémente le nombre deréférenceet on renvoie ladressede This (la référence) dans ppvObject.Puis ilfaut refuser tout autre cast pour éviter leserreursde cast quelobjet ne supporte pas. Dans ce cas, on met 0dansppvObject et on renvoieE_NOINTERFACE pour dire que lon 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 lonest dans le cas des objets sur pile, on na pas besoin de ce qui suit.
If This.cCount = 0 Then
'on libère éventuellement lesressources allouées pour lobjet
'et celle de l'objet
CoTaskMemFree ByVal VarPtr(This)
End If
End Function
AddRefetReleasesont complémentaires. VB appelle AddRef pour dire quilajouteune référence delobjet (un pointeur). VB appelle Release àchaque foisquune variable du typede linterface sort de sa portée ouquellereçoit Nothing. Pour la premièrefonction, on incrémente uncompteur (eton renvoie le compteur, valeur quinest 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 lobjet que lon 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 na 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 dempêcher VB de supprimer la fonction
End Function
Gérer les autres méthodespersonnelles
1) Le pointeur This
Touteméthode dun objet reçoit implicitement un premier paramètre This quiest un pointeur vers la structure de lobjet pour lequel la méthodevient dêtre appelé. Il est du type de la structure de lobjet,dans notre cas, il sagit de typObjetHeap.
2) La valeur de retour
Il y a deux cas àprendre en compte :
· Gestionderreur minimale
· Pas de gestion derreur
Letroisièmemode degestion derreur est la gestion derreur riche.Lorsquune erreurest lancée,VB essaie de demander une interfaceIsupportErrorInfo àQueryInterface delobjet qui a généré lerreur. Leproblème est quecette interface est libéréaprès tous les module dansnotre vtablenexisterait plus donc crash. Il fautdonc toujours refusercetteinterface dans QI.
a) Gestion minimalederreur
Le premier niveau degestion derreur sous VB se fait par le type HRESULT que vous ne pouvez pas utiliser vous même. Cest léquivalent dun Long.Danslesmodule de classe, la valeur de retour des fonctions esttoujoursunHRESULT. Mais alors, me dirais vous, comment VB retourne-t-ilunevaleur àlappelant ?
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 derreur biensûr ! Il faut savoir queVB et lobjet Err transpose lescodes derreur dans la plage &H0001 à &HFFFFetquil transpose plusieurs code derreur en un seul de lobjet Err.Ilsetrouve que toutes les erreurs de VB se retrouve dans les valeurdeHRESULTdans la plage &H800A0001 à&H800AFFFF. On peut alors renvoyer le code derreur VB (par exemple 7 « Mémoireinsuffisante ») que lonOr-era avec &H800A0000.Biensûr, sil ny a pas derreur, on renvoie 0.
Cest le principe debase de la gestion derreur dans VB.
Dans le langage ODL,on aura des méthodes définies comme suit :
HRESULT (
) ;
b) Pas de gestionderreur
Bien que VBsattende à 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 lon retourne unevaleur dans le retour de fonction, on naura pas la possibilité de renvoyer uncode derreur.
Dans le langage ODL,on aura des méthodes définies comme suit :
(
) ;
Si lon na nibesoin de valeur de retour, ni de code derreur, 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> , cestun ByVal As <type>
· [in,out] <type>*, cest un ByRef As <type>
<type>est à choisir parmi un nom denum, unsigned char,short, long, float, double, boolean, currency, DATE.
Il ny pas dautrescombinaisons. A noter que pour ces types, on peut définir une valeur pardéfaut, qui rend le paramètre optionel, avec lattribut 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, cest un ByVal As String
- [in,out] BSTR*, [in,out] LPSTR*, [in,out] LPWSTR*, cest un ByRef As String
- [in] IDispatch*, cest un ByVal As Object
- [in,out] IDispatch**, cest un ByRef As Object
A noter que ByRef As Object (ou As <interface>)nestnécessaire que lorsque lon comptemodifier la référence dobjet,ou enrenvoyer une. Pour un simple passage deparamètre à desfinsdutilisation, on optera toujours pour un ByVal
Les types tableauxsont eux aussi des pointeurs :
- [in] SAFEARRAY(<type>)* et [in,out] SAFEARRAY(<type>)* sont équivalent puisque lon ne peut pas passer de tableau par valeur.
<type>est nimporte 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]sil est du type ByRef cest à dire pointeur (long*, boolean*,
,BSTR*, IDispatch**, **,
). Il ne peut y en avoir quunseul. Dans ce cas, il sert de valeur de retour à la fonction.
i) Les ParamArray : lattribut vararg
Pour déclarer que la liste des paramètres nest pas connue àpartir dun certain paramètre, on utilise ParamArray suivi dun 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 sen 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
- 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
- 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, quest ce quelon peut faire de tout ça ». Eh bien, utiliser des pointeurs defonctions
mais ça cest le tutorial suivant
Et puis limplémentation de ForEach