PureBasic COM Object Framework
Contents
Introduction
Avec l'introduction d'interfaces dans PureBasic, accéder aux objets externes COM (Component Object Model)
est devenu assez simple (en termes de quantité de code nécessaires à utiliser). Cependant, créer des objets
COM dans PB pour être utilisé à l'extérieur, ou avec des commandes API est encore quelque chose qui requiert
beaucoup de connaissances au sujet de PB et de COM et un certain travail à faire. Ce framework essaie de
changer ca. Cela fournit une série de macros pour facilement définir un objet et aussi automatiser la
plupart des tâches communes pour l'implémentation de tous les objets (comme l'implémentation de l'interface
IUnknown). En outre, cela fournir une fonctionnalité de débogage supplémentaire pour tracer les bogues dans
votre implémentation. Il y a quelques macros qui sont utiles pour le développement général COM.
Haut
Caractéristiques
Général
- Aucun préprocesseur utile. Tout est uniquement en PB.
- Tout est contenu dans les fichiers residents et includes, minimisant les problèmes possibles avec
les versions futures de PB
- Fonctionnel en mode Threadsafe et Unicode
- Fonctionne avec l'option du compilateur EnableExplicit
Implémentation de l'objet
- Macros simples pour définir la structure de la classe
- Interfaces multiples supportées dans une classe (supérieur à 20)
- Implementation d'IUnknown est complétement faite par le framework
- Support du Constructeur/Destructeur
- Pour les méthodes non implémentés, une méthode par défaut est automatiquement
insérée qui retourne #E_NOTIMPL
- Une VTable est créé avec un simple appel de macro
- Macros pour une définition facile et manipulation de valeurs de type GUID (IID, CLSID, ...)
Débogage
- Suivi complet de tous les appels pour les méthodes des objets, incluant l'affichage des valeurs de retour
- Suivi des appels pour les objets morts (déjà libérés)
- Capture des appels de méthode en dehors de la VTable (pour trouver des appels de mauvaises interfaces)
- La totalité de la sortie du debug pour le suivi peut être customisé avec 'DebugLevel'
- Conversion des valeurs GUID et HRESULT en texte pour un débogage plus simple
- Avec le débogueur inactif, la sortie du debug est sortie via OutputDebugString_() pour un débogage
plus facile des dlls
Haut
Licence
Copyright (c) 2006 Timo Harter
L'autorisation est accordée, à titre gratuit, à toute personne obtenant une copie de ce
logiciel et des fichiers de documentation (le "Logiciel"), pour faire face le Logiciel sans restriction, y compris,
sans s'y limiter, les droits d'utiliser, copier, modifier, fusionner, publier, distribuer, concéder sous
licence, et / ou vendre des copies du Logiciel, et à autoriser les personnes auxquelles le Logiciel est
fourni de faire de ainsi, sous réserve des conditions suivantes :
L'avis de copyright ci-dessus et la présente autorisation doivent être inclus dans toutes les copies
ou parties substantielles du Logiciel.
LE LOGICIEL EST FOURNI "EN L'ETAT", SANS GARANTIE DE QUELQUE NATURE QUE CE SOIT, EXPLICITE OU IMPLICITE, Y COMPRIS,
MAIS SANS S'Y LIMITER LES GARANTIES DE QUALITE MARCHANDE, D'ADEQUATION A UN USAGE PARTICULIER ET D'ABSENCE DE
CONTREFAÇON. EN AUCUN CAS LES AUTEURS OU DROIT D'AUTEUR TITULAIRES ETRE TENUE RESPONSABLE DE TOUT DOMMAGE,
RéCLAMATION OU AUTRE RESPONSABILITé, QUE CE SOIT DANS UNE ACTION DE CONTRAT, UN DéLIT OU AUTRE,
DéCOULANT DE OU EN RELATION AVEC LA LOGICIEL OU LES RAPPORTS D'UTILISATION OU D'UNE AUTRE DANS LE LOGICIEL.
Haut
Installation
Pour installer le framework, décompressez le fichier zip directement dans votre dossier PureBasic. Le dossier
'ComFramework' doit être placé dans le dossier principal de PureBasic et le contenu du dossier 'Residents'
doit être copié dans le dossier des résidents de PureBasic. L'emplacement et le nom du dossier
'ComFramework' est important, car c'est de là que les fichiers d'include seront
automatiquement inclus si les macros du framework sont utilisés.
Fichiers inclus dans le package :
\Residents\ - Fichiers Resident contenant les macros de base du framework
\ComFramework\ - Fichiers d'includes. A inclure dans la source si le framework est utilisé
\ComFramework\Interfaces\ - Information sur chaque interface supporté. A inclure dans la source au besoin
\ComFramework\Exemples\ - Sources d'exemple
Haut
Informations générales
Le framework COM utilise des noms commencant avec '__COM' pour toute utilisation interne, donc pour éviter les
conflits, vous ne devrez pas utiliser de noms dans votre code qui commencent par '__COM'.
NOTE: Le but de ce framework est d'implémenter les interfaces qui sont définies dans le Microsoft Component
Object Model. L'intention n'est pas de fournir des fonctionnalités générales OOP pour PureBasic. Pour automatiser
la plupart du travail, le framework a besoin d'informations sur les interfaces données, c'est pourquoi seules
les interfaces prédéfinies peuvent être implémentées, et aucune personnalisées (actuellement 855 interfaces sont
connues dans le framework).
Le framework est implémenté comme des macros dans des fichiers résidents et un série de fichiers à inclure. Vous
n'avez pas besoin d'inclure de fichiers manuellement. Ils seront inclus automatiquement avec les macros des
fichiers résidents, dés que nécessaire.
Haut
Macros de gestion des GUID
Les macros de GUID sont indépendants du reste du framework (les utiliser ne causera pas l'inclusion des fichiers
include), donc elles peuvent aussi être utilisées uniquement pour faciliter l'accès à des objets
externes COM.
Les macros sont définies sous 3 noms, finissant par ...GUID, ...IID et...CLSID. Ce sont exactement les mêmes, elles
existent juste séparément pour permettre d'écrire du code lisible.
DefineGUID(Name, long, word1, word2, byte1, byte2, byte3, byte4, byte5, byte6, byte7, byte8)
DefineIID(Name, long, word1, word2, byte1, byte2, byte3, byte4, byte5, byte6, byte7, byte8)
DefineCLSID(Name, long, word1, word2, byte1, byte2, byte3, byte4, byte5, byte6, byte7, byte8)
Ce sont les mêmes que les macros correspondantes en C. A utiliser pour définir une GUID dans une ligne. La
macro crée actuellement une DataSection avec un label appelé 'Name' et les données suivantes,
donc le GUID peut être accédé plus tard via '?Name', où Name est le nom utilisé dans la
définition. La macro est sûre pour un usage multiple. De multiples définitions de la même GUID
seront ignorées, ce qui permet de simplement définir une GUID où c'est utile sans s'inquiéter de ce
qui a été défini dans une autre partie du projet (d'autres includefile par exemple).
Exemple:
; Définit l'IID IUnknown
;
DefineIID(IID_IUnknown, $00000000, $0000, $0000, $C0, $00, $00, $00, $00, $00, $00, $46)
;
; Utilise la valeur IID
;
SomeObject\QueryInterface(?IID_IUnknown, @NewPointer.IUnknown)
DefineKnownGUID(Name)
DefineKnownIID(Name)
DefineKnownCLSID(Name)
Le framework inclue un fichier resident appelé 'GuidList.res', qui contient une énorme macro contenant une
liste des valeurs GUID extraites du Microsoft Platform SDK aussi bien que du DirectX9 SDK. (Nombre total : 2705 GUIDs)
Ces macros permettent de facilement définir une valeur connue par son nom. La plupart des valeurs principales
relatif à COM sont connues. Comme la liste est contenue dans une seul macro, la rapidité générale
de compilation n'est pas affectée par la liste énorme. Ces macros sont protégés contre des
utilisations multiples de la même manière que les macros Define...(), donc définir deux fois une valeur
avec une de ces macros n'aura pas d'effet.
Exemple:
; Définit l'IDD IUnknown, de la même manière
; Le résultat est le même que l'exemple ci dessus
;
DefineKnownIID(IID_IUnknown)
CompareGUID(guid1, guid2)
CompareIID(iid1, iid2)
CompareCLSID(clsid1, clsid2)
Compare les deux valeurs GUID et retourne 1 si elles sont égales et 0 autrement. C'est juste un simple wrapper de
CompareMemory() afin d'écrire un code plus lisible.
Exemple:
; Retourne 1 bien sûr
;
DefineKnownIID(IID_IUnknown)
Debug CompareIID(?IID_IUnknown, ?IID_IUnknown)
Haut
Implémentation de l'objet
Les étapes suivantes sont nécessaires pour implémenter un objet COM en utilisant le framework
Définir la classe
La suite est le squelette d'une définition pour une classe d'objet COM. L'ordre est important. Ce bloc complet
peut ne pas être mis à l'intérieur d'une procédure.
COMClass(<ObjectName>)
; définit les interfaces implémentées. Au moins une telle déclaration est nécessaire
COMInterface(<ObjectName>, <InterfaceName> [, <ExtendedInterface> [, <ExtendedInterface> ...]])
; définit les variables locales de la classe (facultatif)
COMClassData(<ObjectName>)
Value1.l
Value2$
EndComClassData
; définit le constructeur (facultatif)
COMConstructor(<ObjectName> [, (<Argumentlist>)])
; retourne une interface différente du constructeur de IUnknown (facultatif)
COMConstructorReturn <InterfaceName>
EndCOMConstructor
; définit le destructeur (facultatif)
COMDestructor(<ObjectName>)
EndCOMDestructor
EndCOMClass(<ObjectName>, <Interface1>, <Interface2>, ...)
Détais :
COMClass(<ObjectName>)
Commence la définition d'une nouvelle classe dans le framework COM. Une structure avec le nom sera
créée pour représenter la structure des objets et les données privées.
COMInterface(<ObjectName>, <InterfaceName> [, <ExtendedInterface> [, <ExtendedInterface> ...]])
Définit quelles interfaces seront implémentées dans cette classe. Si une interface qui sera
implémenté s'étend à une autre, le framework ne saura pas que l'interface de base étendu
est supporté aussi bien. Vous pouvez la faire reconnaître ces interfaces, mais aussi en spécifiant les
interfaces qui sont contenues au sein de l'interface implémentée aussi bien que les paramètres
facultatifs (jusqu'à 5 paramètres facultatifs sont possibles).
NOTE: IUnknown est toujours automatiquement implémentée par le framework, donc ne le spécifiez pas !
Une classe peut supporter seulement 20 interfaces (en incluant les étendues).
Pour toute interface qui est implémentée ici (ou spécifiée comme une interface étendue),
la valeur de l'IID correspondante sera automatiquement défini avec la macro DefineIID(), comme cela est
nécessaire par le framework. DefineIID() est protégé contre l'utilisation multiple, donc une double
éfinition n'est pas un problème.
COMClassData(<ObjectName>) / EndComClassData
C'est facultatif et doit être défini après toutes les définitions de COMInterface(). Cela permet
d'ajouter des données privées à la structure qui est définie par la macro COMClass().
étant donné que les valeurs à l'intérieur seront membres de la structure, toutes les valeurs
doivent inclure le type. Donc les tableaux statiques comme les structures sont possibles.
COMConstructor(<ObjectName> [, (<Argumentlist>)]) / EndCOMConstructor
C'est facultatif et doit être mis aprés COMClassData(). Cela permet de définir un constructeur
personnalisé pour la classe. Cela permet de définir une liste facultative d'argument pour la procédure
du constructeur (cela doit être le second argument dans l'appel et DOIT inclure les parenthèses).
Pour créer un nouvel objet plus tard dans le cas, 'New_ObjectName()' est utilisé. Si le constructeur
spécifie une liste d'arguments, ces arguments seront utilisés dans la procédure New comme cela :
'New_ObjectName()'
A l'intérieur du constructeur, un pointeur de structure local appelé *THIS. est disponible
pour accéder aux données privées du nouvel objet.
COMConstructorReturn <InterfaceName>
Par défaut, la fonction New_ObjectName() retournera un pointeur vers l'interface IUnknown d'un nouvel objet.
Si un pointeur d'une autre interface peut être retourné ici, cela peut être fait en appelant COMConstructorReturn
à l'intérieur du constructeur. Cela quitte le constructeur, tout comme ProcedureReturn.
COMDestructor(<ObjectName>) / EndCOMDestructor
Permet de définir un destructeur personnalisé qui est appelé une fois toutes les références
de l'objet libérées, et avant que l'objet soit libéré. Comme le constructeur, vous pouvez
utiliser le pointeur *THIS. pour accéder aux données privées.
EndCOMClass(<ObjectName>, <Interface1>, <Interface2>, ...)
C'est actuellement une grosse macro qui fera tout le travail d'implémentation principale pour la classe. Cela
définira les méthodes de IUnknown qui gèrent la durée de vie des objets. Cela définira
aussi la procédure New_() qui est utilisé pour créer une nouvelle instance de la classe.
Pour les raisons de l'implémentation, vous avez besoin de spécifier encore toutes les interfaces qui sont
implémentées dans la classe dans l'appel de la macro. Cela inclue les interfaces qui sont spécifiées
comme des interfaces étendues dans les appels de COMInterface().
Exemple:
; C'est un exemple basique qui définit une classe qui implémente seulement l'interface IDispatch
;
COMClass(MyDispatch)
COMInterface(MyDispatch, IDispatch) ; étends seulement IUnknow, qui est géré automatiquement
COMClassData(MyDispatch) ; définit nos entrées dans la structure de l'objet
Value1.l
Value2.l
EndCOMClassData
COMConstructor(MyDispatch, (Value1, Value2)) ; définit un constructeur qui prend les deux valeurs comme arguments
*THIS\Value1 = Value1
*THIS\Value2 = Value2
COMConstructorReturn IDispatch; Nous voulons que le pointeur IDispatch soit retourné
; NOTE: IDispatch s'étend vers IUnknown, donc on pourrait penser que l'implémentation des pointeurs de IUnknown
; et de IDispatch serait identique. Ce n'est pas le cas. Le framework implémente une interface IUnknown séparée
; qui n'est pas étendue par n'importe quoi. Ce pointeur est initialement retourné si COMConstructorReturn n'est
; pas utilisé. Il est également renvoyé à tout moment que IUnknown est demandé pour l'objet, puisque c'est un
; principe basique de COM qu'une requête pour IUnknown sur un objet doit toujours retourner le même pointeur.
EndCOMConstructor
COMDestructor(MyDispatch)
; fait n'importe quelle action de libération ici.
; *THIS\Value1 et *THIS\Value2 peuvent être aussi être accédé ici.
EndCOMConstructor
EndCOMClass(MyDispatch, IDispatch)
; après que toutes les autres opérations soient faites, after all other stuff is done, un tel objet est créé avec
Dispatch.IDispatch = New_MyDispatch(1, 2) ; où 1,2 sont les deux arguments pour le constructeur
; NOTE: Cette partie seule est inutile. Pour créer des objets fonctionnels, vous avez besoin d'implémenter les
; méthodes et appeler les macros de création de VTable nécessaires.
Haut
Implémenter les méthodes
Les méthodes sont de simples procédures, qui doivent être nommées d'aprés un schéma
de nommage spécifique, et avoir un pointeur appellé *THIS. comme premier argument (une méthode
avec 0 arguments aura seulement le pointeur *THIS dans la définition). De plus, la macro 'COMMethodOf(ObjectName)'
doit être appelé tout d'abord à l'intérieur de la procédure. Pour du code lisible, la
macro peut être directement sur la ligne de la procédure, même sans aucun séparateur.
Une procédure méthode ressemble à cela :
Procedure <ObjectName>_<InterfaceName>_<MethodName>(*THIS.<ObjectName>, <Argument list>) COMMethodOf(<ObjectName>)
EndProcedure
Exemple:
Procedure MyDispatch_IDispatch_Invoke(*THIS.MyDispatch, ...) COMMethodOf(MyDispatch)
EndProcedure
C'est important que le nom ressemble exactement à ca, pour que la macro de création de VTable puisse
détecter quelles méthodes sont implémentées ou pas. C'est important de spécifier le
type. pour le pointeur *THIS, autrement la macro COMMethodOf() ne fonctionnera pas correctement. Le but de
cette macro est d'ajuster le pointeur *THIS. Si la macro n'est pas utilisé, *THIS pointera vers la base de l'Interface
appelé, pas vers la base de la structure de l'objet. Il y a deux pointeurs diffférents, donc accéder
aux valeurs locales à l'intérieur de la structure de l'Objet (défini avec COMClassData() ci-dessus)
échouera. La macro COMMethodOf() assure que le pointeur est défini à la structure de base, donc les
valeurs peuvent être atteintes avec cela. Donc théoriquement, cette macro peut être laissé de
côté si rien n'est atteint avec le pointeur *THIS à l'intérieur de la procédure, mais il est
recommandé de l'ajouter partout pour la consistance, et ainsi éviter d'introduire une bogue difficile
à trouver si on le laisse là où c'est vraiment nécessaire.
Notes:
Ne jamais implémenter les méthodes IUnknow QueryInterface(), AddRef(), Release() pour n'importe quel Interface.
C'est toujours mis en place par le framework.
Si une méthode de l'interface n'est pas défini dans la source, la macro de création de la VTable le
détectera et insérera automatiquement une méthode par défaut qui retourne #E_NOTIMPL. Donc la
VTable ne contiendra jamais aucun pointeur NULL qui pourrait diriger vers un crash. #E_NOTIMPL est accepté comme un
résultat de plusieurs parties facultatives des Interfaces COM, donc vous pouvez laisser ces méthodes que
vous n'avez pas besoin d'implémenter pour l'interface, et la méthode par défaut sera insérée.
Si une interface s'étend à une autre, les méthodes doivent seulement être implémentées
pour la grosse qui s'étend aux autres. Ces méthodes seront réutilisées pour l'interface étendue.
Si une méthode devait seulement retourner une certaine valeur mais rien d'autre, la macro COMEmptyMethod() peut
être utilisée pour éviter le besoin de déclarer la procédure méthode complètement :
COMEmptyMethod(<ObjectName>, <InterfaceName>, <MethodName>, <ReturnValue>)
doit être une constante de type long. La macro implémentera une méthode avec le bon nombre
d'arguments pour cette méthode qui ne fera rien mais retournera la valeur de retour donnée.
Si les interfaces dans la classe on la même méthode (par exemple si les deux s'étendent vers la même
interface de base), la macro COMReuseMethod() peut être utilisée pour dire au framework d'utiliser la méthode
qui est implémentée pour une interface pour une autre aussi bien, donc le code n'a pas besoin d'être dupliqué.
COMReuseMethod(<ObjectName>, <ImplementingInterface>, <MethodName>, <ReusingInterface>)
est l'interface qui a implémentée la procédure méthode actuelle.
est celle qui l'on utilisera aussi. Une méthode peut être réutilisée par
beaucoup d'interfaces, mais seulement par la même classe.
Haut
Appeler les macro pour créer les VTables
En raison des limitations des macros PB (bien, elles sont assez puissantes comme le prouve ce framework, mais ici j'ai
rencontré un mur), les VTables pour la classe ne peuvent pas être créées automatiquement. Vous
avez besoin d'appeler une macro par Interface dans la classe pour les construire. Espérons qu'il y aura une
manière plus confortable de le faire dans le futur, mais actuellement, c'est mieux que de créer les VTables
manuellement.
Les macros ont cette forme :
BuildCOMVTable_<InterfaceName>(<ObjectName>)
Le nom de l'Interface est une partie du nom de la macro La macro requise pour chaque interface est automatiquement incluse
avec la source une fois que c'est inclus dans la classe avec la macro COMInterface().
Notes importantes :
Ces macros doivent être mises dans le code en dessous de TOUTES les implémentations de méthodes et
autres macros relatives aux méthodes. C'est parce que les macros vérifieront quelles méthodes seront
actuellement implémentées pour sélectionner les méthodes adéquates par défaut
ou les méthodes réutilisées dans la VTable.
Ces macros produisent le code qui doit être exécuté, contrairement les macros de définition
de la classe qui produisent seulement les déclarations et les procédures.
Les macros doivent être exécutées AVANT que n'importe quelle instance de la classe soit créé.
Ils peuvent être placés à l'intérieur des procédures. Pour une application, cela peut
être quelque part avant que le code réel de l'application ne commence. Pour une DLL COM, cela peut être
la procédure AttachProcess(). Vous pouvez aussi placer ces macros à l'intérieur d'une procédure
et l'appeler à partir du constructeur de la classe. Cela fonctionnera aussi, comme ensuite les VTables sont remplies
avant la première interface de l'objet qui est en fait appelé. Vous ne pouvez pas les placer à
l'intérieur du constructeur directement, comme elles ont besoin d'être en dessous de toutes les
implémentations de méthode.
Exemple :
BuildCOMVTable_IDispatch(MyDispatch) ; construit la VTable pour l'interface IDispatch de la classe MyDispatch
Haut
Créer des instances d'objets
Une fois que toutes les étapes ci-dessus sont réalisées (définition de la Classe,
implémentation des méthodes, création de la VTable), l'implémentation est complète et
les objets peuvent être créés. C'est fait au travers d'une procédure appelée
'New_()'. Cela retourne le pointeur de l'objet IUnknown à moins que COMConstructorReturn soit
utilisé dans le constructeur pour retourner un pointeur différent de l'interface.
Si le constructeur était défini avec des paramètres supplémentaires, ceux-ci doivent être
transmis aussi à la procédure New_().
Les nouveaux objets sont créés avec un nombre de références à 1. Un objet est
détruit quand son nombre de références atteint 0. Cela signifie qu'une fois que l'utilisation de
l'objet est complétée, vous devez libérer votre propre référence de l'objet en appelant
la méthode Release() de l'interface.
Exemple:
Dispatch.IDispatch = New_MyDispatch(1, 2) ; utiliser la définition de la classe des exemples ci-dessus
; fait quelque chose avec l'objet (passage à la fonction API, etc)
Dispatch\Release() ; libère notre référence de l'objet. Si aucune autre référence reste,
il sera libéré.
Haut
Débogage
Pour activer la fonctionnalité du débogage du frameword, ajouter le mot-clé 'EnableCOMDebugging'
au début de la source (ou avant les définitions de classe). Utiliser cette macro entraînera le
framework à ajouter le code de déboggage nécessaire pour suivre les appels et autres comme
décrit ci-dessous.
NOTE: Quand le débogage est activé, le framework incluera de grands morceaux de données avec
l'exécutable (comme les listes de toutes les valeurs d'IID et de HRESULT), donc ce n'est pas recommandé
de laisser cette appel à l'intérieur d'une compilation finale.
Sortie du débogage
Si le débogueur de PureBasic est activé lors de la compilation de la source, la sortie du débogage
sera affiché dans le débogueur. Si le débogueur n'est pas activé, mais que EnableCOMDebugging
est défini, la sortie du débogage, la sortie du débogage sera affiché avec l'API
OutputDebugString_(). Cela permet de déboguer des DLLs de type COM où le débogueur PB ne peut être
utilisé. Cette sortie peut être lu avec des programmes de débogage comme DebugView (http://www.sysinternals.com/Utilities/DebugView.html)
Haut
Informations fournis sur le débogage
Plusieurs niveaux d'informations sur le débogage peuvent être fournis. Ils peuvent être
définis avec la commande PB 'DebugLevel'. Les niveaux les plus hauts incluent aussi la sortie de tous les
niveaux ci-dessous . Les niveaux suivants sont disponibles (dans l'ordre croissant).
#COM_DEBUG_Critical
#COM_DEBUG_UnknownInterface
#COM_DEBUG_ObjectStats
#COM_DEBUG_MethodEntry
#COM_DEBUG_MethodLeave
#COM_DEBUG_FormatResult
#COM_DEBUG_All
#COM_DEBUG_Critical
Débogue seulement les informations au sujet des conditions critiques (cas qui sont d'habitude suivis d'un crash).
Une telle situation existe si un appel pour une interface en dehors de sa VTable est rencontré. Cela se passe quand
l'appelant s'attendait à une interface plus importante qu'elle ne l'était réellement.
Même si le suivi des objets morts est activé (cf. ci-dessous), un appel capturé pour un objet mort sera
reporté comme critique.
#COM_DEBUG_UnknownInterface
Affiche un message quand quelque chose interroge votre objet (avec QueryInterface()) pour une interface que l'objet ne
supporte pas. Le message inclue le nom de l'IID qui a été interrogé. Cette condition n'est pas nécessairement
un bogue, si l'implémentation d'IUnknown retourne correctement #E_NOTIMPL, et que les fonctions API interrogent
communément des interfaces multiples pour tester quel fonctionnalité est disponible. L'information de débogage
peut encore être très intéressante, car cela montre quelles interfaces l'appelant attendait effectivement.
#COM_DEBUG_ObjectStats
Affiche l'information au sujet de la vie (nombre de références) de l'objet. Cela suit les appels de AddRef()/Release()
aussi bien que QueryInterface(), et affiche le nombre courant. Cela affiche aussi un message quand l'objet est finalement
libéré.
#COM_DEBUG_MethodEntry
Affiche tous les appels de méthode sur l'objet. Trés intéressant pour suivre quel séquence
d'appel dirige vers certains crashs par exemple.
#COM_DEBUG_MethodLeave
Affiche les valeurs retournées pour tous les appels de méthode.
#COM_DEBUG_FormatResult
Affiche la constante équivalent à HRESULT qui correspond à la valeur de retour (si c'est une valeur
d'erreur), aussi bien que une représentation au format texte de la valeur.
#COM_DEBUG_All
Le plus haut niveau, inclue tous les autres. C'est le niveau par défaut si 'DebugLevel' n'est pas utilisé.
Note: DebugLevel est une déclaration au moment de la compilation, donc cela a un effet sur toutes les lignes
compilées après cela, pas les lignes exécutés après ca. Changer le niveau de débogage
entre l'implémentation des classes COM entraine les deux à avoir un niveau différent lors de la sortie.
Donc les appels d'une classe peuvent être suivi plus strictement, tandis que pour les autres, seulement les conditions
critiques sont attrapés par exemple.
Puisqe DebugLevel affecte seulement la sortie du débogueur PureBasic, la totalité des sorties pour
OutputDebugString_() ne peut être changé avec cela. Si le débogueur PB est désactivé,
les déclarations DebugLevel sont ignorées, et le framework sortira simplement toutes les données de
débogage (égal à utiliser #COM_DEBUG_All).
Haut
Suivi des objets morts
Une source commune des bogues est lié aux problèmes de duré de vie des objets. Par exemple si Release() est
appelé trop souvent, le dernier appel sera sur un appel qui aura été libéré (un objet
mort). D'habitude de tels appels résultent en un crash, bien que quelques fois ils réussissent parce que la
mémoire de l'objet n'a pas été écrasé par quelque chose d'autre, ce qui rend plus difficile
de trouver de tels bogues.
Ce framework fournit un suivi de tous les objets libérés pour identifier de tels appels. Tout appel sur une
méthode d'un objet qui a été libéré sera reporté avec le niveau de débogage
#COM_DEBUG_Critical. Pour atteindre cela, la mémoire de tous les objets précédemment existants devra
être alloué jusqu'à la fin du programme. Cela peut conduire à une grande consommation de
mémoire des objets qui sont créés/libérés fréquemment. C'est pourquoi cette
option n'est pas activé par défaut. Pour l'activer, utilisez la déclaration 'EnableCOMDeadTracking' statement.
Haut
Autres fonctions de débogage
En outre du suivi automatique des objets, le framework fournit quelques fonctions supplémentaires à des fins
de débogage (seulement disponible si 'EnableCOMDebugging' est utilisé).
DisplayCOMObjectList()
Cette fonction affiche simplement une liste de tous les objets actuellement existants sur la sortie de débogage. La
sortie contient le type de l'objet, aussi bien que le nombre de références.
Cette fonction peut être utile par exemple à la fin d'un programme pour trouver les objets qui n'ont pas
été correctement libérés.
DisplaySupportedInterfaces(*Object)
Cette fonction affiche toutes les interfaces que l'objet donné supporte (fonctionne seulement pour les interfaces qui
sont connus du framework, et non pour les personnalisées). C'est très utile de connaî les caractéristiques
supportées par un objet.
Constant$ = GetCOMErrorConstant(ErrorCode)
Retourne le nom de la constante pour une valeur de type HRESULT (valeur de retour pour les méthodes)
Cela couvre seulement les valeurs négatives, si les valeurs >=0 indiquent le succès et peuvent avoir de
nombreuses significations en fonction de la méthode.
Message$ = GetCOMErrorMessage(ErrorCode)
Retourne une représentation textuelle pour la valeur de l'erreur de type HRESULT. Ces messages étaient extraits
de WinError.h.
GUIDFromName(Name$)
IIDFromName(Name$)
CLSIDFromName(Name$)
Retourne une valeur de type GUID pour le nom donné. IIDFromName("IID_IUnknown") par exemple retourne l'IID de IUnknown.
Name$ = NameFromGUID(guid)
Name$ = NameFromIID(iid)
Name$ = NameFromCLSID(clsid)
Retourne le nom pour le GUID donné. C'est trés utile quand des valeur inconnues sont rencontrés afin de
savoir ce qu'elles représentent actuellement.
NOTE: Contrairement aux autres macros (où ...GUID(), ...IID() et ...CLSID() font la même chose), ici chaque fonction est
différente. La raison est que les valeurs IID et CLSID existent avec les mêmes noms, donc NameFromGUID() recherche
seulement les noms commencant avec 'GUID_', NameFromIID() recherche seulement les valeurs 'IID_' et NameFromCLSID() recherche
seulement les valeurs 'CLSID_'.
Haut