|
|
Utilisation avancée du CLR et de l'API Profiling par Patrick Smacchia |
mscorlib.dll,
mscorsvr.dll et mscorwks.dll
Notion
d’hôte du moteur d’exécution
Plusieurs
versions du CLR sur la même machine
Chargement
du CLR avec CorBindToRuntimeEx()
Le
domaine d’application par défaut
Créer
votre propre hôte du moteur d’exécution
Modifier
la configuration du CLR à partir d’un hôte du moteur d’exécution
propriétaire
Créer
un objet .NET à partir d’un hôte du moteur d’exécution propriétaire
Profiler
vos applications en utilisant le CLR
Réécrire
du code CIL à l’exécution
Le but de cet article est de démystifier une partie du fonctionnement interne du CLR et d’exposer certaines de ses manipulations à la fois non conventionnelles et utiles. Vous pouvez télécharger le code présenté ci-desous sous forme d’un projet VS.NET compilable et exécutable ici.
Pour une bonne compréhension de cet article, il est nécessaire de posséder les notions suivantes:
· vue globale du CLR (domaine d’application, compilation JIT, ramasse-miettes…)
· la technologie COM
Ces notions sont notamment expliquées dans mon ouvrage Pratique de .NET et C# (O’Reilly 2003).
Chaque version du CLR est publiée
avec deux DLLs :
·
la DLL mscorsvr.dll contient le CLR spécialement optimisée pour
les machines ayant plusieurs processeurs (‘svr’ est employé pour
‘serveur’).
·
la DLL mscorwks.dll contient le CLR spécialement optimisée pour
les machines ayant un seul processeur (‘wks’ est employé pour
‘workstation’ ou ‘station de travail’).
Ces deux DLLs ne sont pas des assemblages. En conséquence, elles ne contiennent pas de code CIL, et elles ne peuvent être analysées avec l’outil ildasm.exe. Chaque processus exécutant une ou plusieurs applications .NET, contient une de ces deux DLLs. Nous allons expliquer ici comment le chargement dans le processus d’une de ces DLLs se déroule.
Une autre DLL joue un rôle prépondérant dans l’exécution des applications .NET. C’est la DLL mscorlib.dll. Cette DLL contient les types de base du Framework.NET (comme la classe System.String, la classe System.Object ou la classe System.Int32). Cette DLL constitue le module contenant le manifeste de l’assemblage portant le même nom. Au titre d’assemblage, mscorlib est référencé par tous les assemblages .NET. Cette référence est créée automatiquement par tous les compilateurs qui produisent du code CIL. Au titre d’assemblage partagé, mscorlib se trouve dans le répertoire GAC. Néanmoins vous pouvez aussi trouver la DLL mscorlib.dll dans le répertoire de l’installation de chaque version de .NET. Il est intéressant d’analyser l’assemblage mscorlib avec l’outil ildasm.exe.
Le fait que .NET ne soit pas encore directement intégré au système d’exploitation entraîne que le chargement du CLR, à la création d’un processus, doit être pris en charge par le processus lui-même.
La tâche de charger le CLR dans le processus incombe à une entité appelée l’hôte du moteur d’exécution (runtime host en anglais). L’hôte du moteur d’exécution étant là pour charger le CLR, une partie de son code est forcément non géré, puisque c’est le CLR qui gère le code. Cette partie s’occupe de charger le CLR, de le configurer, et de passer en mode géré. Une fois le CLR chargé dans le processus, l’hôte du moteur d’exécution a d’autres responsabilités, notamment la création de certains domaines d’application et le ‘dispatching’ des requêtes utilisateur vers ces domaines. Précisons que la partie géré d’un hôte du moteur d’exécution contient une couche logicielle appelée hôte d’un domaine d’application (application domain host en anglais). Cette couche s’occupe de fournir les preuves si nécessaire, du chargement d'un assemblage dans un domaine d’application.
Il existe plusieurs hôtes du moteur d’exécution et
vous avez même la possibilité de créer vos propres hôtes du moteur
d’exécution. Le choix de tel ou tel hôte pour
une application a un impact direct sur les performances de l’application et
l’étendue des fonctionnalités utilisables par l’application. Les hôtes du moteur d’exécution déjà existants
sont :
·
L’hôte du moteur d’exécution des applications
Console et Winform : L’assemblage exécutable est
chargé dans le domaine d’application par défaut. Lorsqu’un
assemblage est chargé implicitement, il est chargé dans le même domaine
d’application que l’assemblage qui le sollicite. En général ce type
d’application n’a pas à utiliser d’autres domaines
d’application que le domaine d’application par défaut.
·
L’hôte du moteur d’exécution ASP.NET :
Crée un domaine d’application pour chaque application Web. Une
application Web est identifiée par son répertoire virtuel racine ASP.NET. Si
une requête web s’adresse à une application déjà chargée dans un domaine,
la requête est automatiquement routée vers ce domaine.
·
L’hôte du moteur d’exécution Microsoft
Internet Explorer : Par défaut, il crée un domaine d’application
par site web visité. Ainsi les assemblages de différents sites peuvent
s’exécuter avec différents niveaux de sécurité. Notez que le CLR
n’est chargé que lorsque Internet Explorer a besoin d’exécuter un
assemblage pour la première fois.
Bien qu’il soit difficile de confirmer cette information, il semble qu’il y aura à terme un hôte du moteur d’exécution dédié à Yukon qui permettra lui aussi de gérer la sécurité des accès SGBD au niveau du domaine d’application.
mscorlib étant un assemblage à nom fort, plusieurs versions peuvent cohabiter ‘côte à côte’ sur la même machine. De plus, toutes les versions du CLR sont dans le répertoire ‘%windir%\Microsoft.NET\Framework’. Tous les fichiers concernant une version du Framework .NET sont dans un sous répertoire portant le numéro de version. Comme il existe un répertoire par version du Framework .NET installée sur la machine, il peut y avoir aussi plusieurs versions des DLLs mscorsvr.dll et mscorwks.dll sur la même machine. Cependant une seule version du CLR peut être chargée par processus.
Le fait d’avoir potentiellement plusieurs versions du
CLR entraîne l’existence d’une petite couche logicielle qui prend
en paramètre la version désirée du CLR et la charge. Ce code est appelé cale (shim en anglais) et est stocké dans la DLL mscoree.dll (MSCOREE veut dire Microsoft
Component Object Runtime Execution Engine).
Il ne peut y avoir qu’une seule DLL cale par machine.
La cale est directement appelée par l’hôte du moteur d’exécution par
la fonction CorBindToRuntimeEx(). La DLL mscoree.dll contient des interfaces et des
classes COM. La fonction CorBindToRuntimeEx() crée un objet COM, instance de la classe
COM CorRuntimeHost. C’est cet objet qui va
s’interfacer avec le CLR et la fonction CorBindToRuntimeEx() retourne
l’interface COM ICorRuntimeHost.
L’appel à CorBindToRuntimeEx() pour créer l’objet COM s’interfaçant avec le CLR transgresse le fait que normalement il faut appeler la fonction CoCreateInstance() pour créer un objet COM. De plus, les appels aux méthodes AddRef() et Release() sur l’interface ICorRuntimeHost n’ont pas d’effet.
Voici le prototype de la fonction CorBindToRuntimeEx() qui charge la DLL cale, qui charge à son tour la DLL contenant le CLR :
HRESULT CorBindToRuntimeEx(
LPWSTR pwszVersion,
LPWSTR pwszBuildFlavor,
DWORD flags,
REFCLSID rclsid,
REFIID riid,
LPVOID * ppv);
Ce prototype est spécifié dans le
fichier mscoree.h et le code de cette fonction est dans la DLL cale mscoree.dll.
·
PwszVersion : Le numéro de version du CLR sous la forme d’une chaîne de caractères
commençant par ‘v’ (exemple "v1.0.2194"). Si cette chaîne n’est pas précisée (i.e si on
passe un pointeur nul), la version la plus récente disponible du CLR sera alors
choisie.
·
PwszBuildFlavor : Ce paramètre indique si l’on souhaite
charger le CLR pour workstation (mscorwks.dll) en précisant la
chaîne de caractères "wks" ou le CLR pour serveur (mscorsvr.dll) en précisant la
chaîne de caractères "svr". Si vous n’avez qu’un seul
processeur, le CLR pour workstation sera chargé quelle que soit la valeur de ce
paramètre. Microsoft prévoit d’autres types de CLR, donc
d’autres valeurs pour ce paramètre.
·
flags : Ce paramètre est constitué par un ensemble de drapeaux.
En utilisant le
drapeau STARTUP_CONCURRENT_GC on indique que l’on souhaite que le ramasse-miettes
(GC est utilisé pour garbage collector) soit exécuté en mode
concurrent, dit aussi mode simultané. Ce mode permet à un thread de réaliser
une bonne partie du travail du ramasse-miettes sans avoir à interrompre les
autres threads de l’application. Dans le cas d’une exécution non
simultanée du ramasse-miettes, le CLR utilise régulièrement les threads de
l’application pour effectuer le travail du ramasse-miettes. Les
performances globales du mode non concurrent sont meilleures. En revanche le
mode concurrent est souhaitable dans le cas d’interfaces utilisateurs,
car il permet une plus grande réactivité de l’interface.
On peut aussi
positionner d’autres drapeaux pour indiquer que l’on souhaite que
les assemblages soient chargés d’une manière neutre par rapport aux
domaines (loaded domain-neutrally en anglais) ou non. Cela signifie que
toutes les parties en lecture des assemblages (le code, les structures…)
ne seront présentes physiquement qu’une seule fois dans le processus,
même si plusieurs domaines d’application chargent le même assemblage
(dans le même esprit du mapping des DLLs entre processus sous les systèmes
d’exploitation Windows). Un assemblage chargé d’une manière neutre
par rapport aux domaines n’appartient donc pas à un domaine spécifique.
En revanche, il existe une copie de chaque champ statique de chaque classe pour
chaque domaine d’application, et donc une table d’indirection à
gérer. Ceci implique une petite baisse des performances. Cependant, charger un
assemblage d’une manière neutre par rapport aux domaines, permet de ne le
charger qu’une seule fois. En conséquence, cette pratique est plus
économique en terme de consommation de la mémoire, d’où un effet positif
sur les performances.
Vous avez trois drapeaux possibles :
STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN : Aucun assemblage
n’est chargé d’une manière neutre par rapport aux domaines.
C’est ce comportement qui est pris par défaut.
STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN : Tous les
assemblages sont chargés d’une manière neutre par rapport aux domaines.
STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN_HOST : Seuls les
assemblages partagés contenus dans le GAC sont chargés d’une manière
neutre par rapport aux domaines.
L’assemblage mscorlib a un traitement spécial puisqu’il est chargé
d’une manière neutre une seule fois, quelle que soit la valeur de ce
paramètre.
·
rclsid : Le classe ID (CLSID) de la classe COM
(coclass) qui implémente
l’interface que vous cherchez. Dans la version initiale du CLR, seules
les valeurs CLSID_CorRuntimeHost ou null sont acceptées.
·
pwszBuildFlavor : L’interface ID (IID) de
l’interface COM dont vous avez besoin. Dans la version initiale du CLR,
seules les valeurs IID_CorRuntimeHost ou null sont acceptées.
·
ppv : Un pointeur vers une interface COM retournée, de type ICorRuntimeHost.
Notez que la cale ne fait que charger le CLR dans le
processus. Le cycle de vie du CLR est contrôlé par la partie non gérée du code
de l’hôte du moteur d’exécution grâce à l’interface COM ICorRuntimeHost renvoyée par la fonction CorBindToRuntimeEx(). Cette
interface présente entre autres deux méthodes Start() et Stop() dont les noms illustrent leurs actions.
Un domaine d’application par défaut est créé par le CLR dès que ce dernier est chargé dans un processus. Ce domaine d’application par défaut charge la partie de l’hôte du moteur d’exécution à exécuter en mode géré. Comme tous code géré, cette partie de l’hôte du moteur d’exécution est contenue dans un assemblage.
Lorsqu’un assemblage exécutable est lancé, le comportement de la partie gérée de l’hôte du moteur d’exécution des applications Consoles et Winform est de charger l’assemblage exécutable dans le domaine d’application par défaut. De plus, le nom du domaine d’application par défaut est le nom de l’assemblage. Nous précisons que le nom d’un domaine d’application est contenu dans la propriété FriendlyName de la classe AppDomain. Il est préférable de ne pas charger les autres assemblages utilisateur dans ce domaine par défaut. En effet ce domaine sera détruit seulement lorsque le CLR est déchargé du processus. On se priverait alors de la possibilité de décharger ces assemblages du processus. En effet, précisons qu’un assemblage est déchargé seulement lorsque le domaine d’application dans lequel il est contenu est déchargé.
Pour être bien compris, ce code nécessite une bonne connaissance de la technologie COM. N’oublions pas que les systèmes d’exploitation type Windows utilisent encore COM comme mécanisme de communication/modélisation. Le CLR présente la possibilité d’être configuré et manipulé par du code non géré, par l’intermédiaire d’interfaces COM. Parmi ces interfaces COM, l’interface ICorRuntimeHost joue un rôle prépondérant. Nous avons vu un peu plus haut qu’une telle interface peut être obtenue en appelant la fonction CorBindToRuntimeEx().
#include <windows.h>
#include <stdio.h>
#include <mscoree.h>
#import <mscorlib.tlb>
raw_interfaces_only
high_property_prefixes("_get","_put","_putref")
rename("ReportEvent","ReportEventGéré")
rename_namespace("A")
// Utilise l'espace de nom
ComRuntimeLibrary;
using namespace A;
// Les interfaces COM
ICorRuntimeHost * pRuntimeHost = NULL;
void main (void)
{
//
Obtient une interface COM ICorRuntimeHost sur le CLR.
HRESULT hr = CorBindToRuntimeEx(
NULL,
// On demande la dernière version du CLR
NULL,
// On demande la version workstation du CLR
0,
CLSID_CorRuntimeHost,
IID_ICorRuntimeHost,
(LPVOID *)
&pRuntimeHost);
if (FAILED(hr)){
printf("Echec de l'obtention du pointeur ICorRuntimeHost!");
return;
}
printf("Pointeur ICorRuntimeHost obtenu.\n");
printf("Lance le CLR.\n");
pRuntimeHost->Start();
// Ici, on
peut utiliser notre pointeur COM sur le CLR
pRuntimeHost->Stop();
printf("CLR stoppé.\n");
pRuntimeHost->Release();
StopperCLR();
printf("Ciao!\n");
}
C’est tout! Comme vous le voyez, il n’est pas très difficile de créer son propre hôte du moteur d’exécution. Vous allez me dire OK mais à quoi ça sert ? Voici des utilisations possibles d’un hôte du moteur d’exécution
Voyons tout cela plus en détails.
Il faut savoir que lorsque le CLR est vu comme un objet COM, l’interface ICorRuntimeHost n’est pas la seule interface présentée par cet objet COM. Vous pouvez en fait manipuler le CLR à travers les interfaces COM suivantes :
ATTENTION : ces interfaces ne sont pas documentées. Il y a cependant peu de chances qu’elles soient sujettes à changements tant que les applications Windows chargeront le CLR comme un objet COM. Pour savoir quelles sont les méthodes présentées par ces interfaces, il suffit d’observer les fichiers mscoree.h ivalidator.h et gchost.h.
Tout l’avantage d’utiliser ces interfaces réside dans le fait que la plupart des fonctionnalités qu’elles présentent ne sont pas accessibles directement à partir du Framework .NET. Par exemple, il est impossible de modifier le nombre de threads du pool de threads à partir du Framework .NET. En fait il y a deux limites supérieures du nombre de threads du pool de threads : le nombre maximal de threads ouvrier/processeur et le nombre maximal de threads pour les opérations asynchrones (IO Completion)/processeur. Ces deux limites sont par défaut positionnées à 25. Voici comment positionner ces deux limites à 50 threads ouvriers et à 10 threads IO Completion à partir de notre hôte du moteur d’exécution propriétaire :
...
ICorThreadpool * pThreadPool =
NULL;
...
void StopperCLR()
{
if (pThreadPool)
pThreadPool ->Release();
if (pRuntimeHost)
{
pRuntimeHost->Stop();
printf("CLR stoppé.\n");
pRuntimeHost->Release();
}
}
...
pRuntimeHost->Start();
//
// Modifie le nombre maximum de threads du pool de thread
//
hr = pRuntimeHost->QueryInterface(IID_ICorThreadpool,(void**)&pThreadPool);
if (FAILED(hr)){
StopperCLR();
printf("Echec de l'obtention du pointeur ICorThreadpool!");
return;
}
printf("Pointeur ICorThreadpool obtenu.\n");
hr =
pThreadPool->CorSetMaxThreads(50,10);
if (FAILED(hr)){
StopperCLR();
printf("Echec de la modification du nombre maximum de threads du pool de thread!");
return;
}
printf("Nombre maximum de threads du pool de thread modifié.\n");
StopperCLR();
...
Nous précisons que l’interface ICorRuntimeHost permet aussi de manipuler la notion de fibre win32, aussi appelée thread logique. L’article [Co-routines] explique comment utiliser des fibres win32 pour avoir la notion de co-routines dans les langages .NET. Cependant, il faut savoir que les co-routines seront prises en charge par le CLR dés Whidbey, aussi nous vous conseillons d’attendre cette release.
Il peut être très intéressant de créer et de manipuler un objet .NET à partir de code non géré. L’intérêt principal de cette possibilité est de migrer petit à petit un projet win32 vers .NET. Bien évidemment, il faut encapsuler une classe .NET dans une classe COM pour réaliser cette opération. Vous n’êtes pas obligé de créer un hôte du moteur d’exécution pour réaliser ceci. Cependant, le fait de créer un hôte du moteur d’exécution vous donne des possibilités intéressantes comme le choix du domaine d’application dans lequel créer votre objet .NET.
Voici le code d’un assemblage bibliothèque de classes .NET contenant une classe que nous allons instancier à partir de notre hôte du moteur d’exécution :
MyManagedLib.cs
using System;
using System.Reflection;
using System.Runtime.InteropServices;
[assembly:ClassInterface(ClassInterfaceType.AutoDual)]
namespace MyManagedLibNS
{
public class MyManagedClass
{
public void WriteOnConsole(string s)
{
Console.WriteLine(s);
}
}
}
L’attribut de classe ClassInterface positionné à AutoDual signifie que si une classe COM est créée pour encapsuler notre classe .NET, alors elle supportera les deux types de lien COM, à savoir les liens précoces (vtbl) et les liens tardifs (IDispatch).
Il faut maintenant compiler cet assemblage et créer une librairie de type COM (avec l’outil tlbexp.exe), avec les commandes suivantes à exécuter dans une fenêtre de commandes :
csc.exe /target:lib MyManagedLib.cs // créé MyManagedLib.dll
tlbexp.exe MyManagedLib.dll // créé MyMangedLib.tlb
Vous pouvez maintenant instancier et utiliser la classe MyManagedClass à partir de notre hôte du moteur d’exécution si les deux conditions suivantes sont satisfaites :
Voici le code à ajouter:
...
#import "MyManagedLib.tlb"
raw_interfaces_only
high_property_prefixes("_get","_put","_putref")
rename_namespace("B")
// Utilise l'espace de nom MyManagedLibNM;
using namespace B;
// Les interfaces COM
ICorRuntimeHost * pRuntimeHost = NULL;
IUnknown * pUnkAppDomain = NULL;
_AppDomain * pAppDomain = NULL;
_MyManagedClass * pMyManagedClass = NULL;
_ObjectHandle
* pObjectHandle = NULL;
void StopperCLR()
{
if (pMyManagedClass)
pMyManagedClass->Release();
if (pObjectHandle)
pObjectHandle->Release();
if (pAppDomain)
pAppDomain->Release();
if (pUnkAppDomain)
pUnkAppDomain->Release();
if (pRuntimeHost)
{
pRuntimeHost->Stop();
printf("CLR stoppé.\n");
pRuntimeHost->Release();
}
}
...
pRuntimeHost->Start();
//
// Obtient un pointeur sur le domaine d'application par défaut
//
hr =
pRuntimeHost->GetDefaultDomain(&pUnkAppDomain);
if (FAILED(hr)){
StopperCLR();
printf("Echec de l'obtention du pointeur pUnk sur le domaine d'application par défaut!");
return;
}
printf("Pointeur pUnk sur le domaine d'application par défaut obtenu.\n");
hr =
pUnkAppDomain->QueryInterface(__uuidof(_AppDomain),
(void**) &pAppDomain);
if (FAILED(hr)){
StopperCLR();
printf("Echec de l'obtention du pointeur pAppDomain sur le domaine d'application par défaut!");
return;
}
printf("Pointeur pAppDomain sur le domaine d'application par défaut obtenu.\n");
//
// Créer une instance de MyManagedClass dans le domaine d'application par défaut
//
hr = pAppDomain->CreateInstance( _bstr_t("MyManagedLib"),
_bstr_t("MyManagedLibNS.MyManagedClass"),
&pObjectHandle);
if (FAILED(hr)){
StopperCLR();
printf("Echec dans la création d'une instance de MyManagedClass dans le domaine d'application par défaut.");
return;
}
printf("Une instance de MyManagedClass vient d'être crée dans le domaine d'application par défaut\n");
//
// Obtient un pointeur sur l'instance de MyManagedClass.
//
VARIANT vt;
VariantInit(&vt);
hr = pObjectHandle->Unwrap(&vt);
if (FAILED(hr)){
StopperCLR();
printf("Echec dans l'obtention d'un ObjectHandle sur l'instance de MyManagedClass.");
return;
}
printf("ObjectHandle sur l'instance de MyManagedClass obtenu.\n");
hr = vt.pdispVal->QueryInterface(__uuidof(_MyManagedClass),
(void **)&pMyManagedClass);
if (FAILED(hr)){
StopperCLR();
printf("Echec dans l'obtention d'un pointeur sur l'instance de MyManagedClass.");
return;
}
printf("Pointeur sur l'instance de MyManagedClass obtenu.\n");
//
// Invoque la méthode WriteOnConsole() sur l'instance de MyManagedClass
//
hr =
pMyManagedClass->WriteOnConsole(_bstr_t("T'as le bonjour du runtime
host..."));
if (FAILED(hr))
{
StopperCLR();
printf("Echec dans l'invocation de la méthode WriteOnConsole().");
return;
}
printf("La méthode WriteOnConsole() vient d'être invoquée\n");
StopperCLR();
...
Oui, je sais, personne ne regrette la syntaxe de COM utilisée à partir de C++ ;)
Cette section à pour but de vous sensibiliser à une fonctionnalité particulièrement utile du CLR : la possibilité de profiler très finement son exécution. Cela veut dire que vous pouvez demander au CLR d’exécuter une de vos méthodes non gérées lorsqu’un événement particulier survient (par exemple au commencement de la compilation JIT d’une méthode ou à la fin du chargement d’un assemblage). Il est assez logique que ces callbacks soient des méthodes non gérées. En effet, ces méthodes sont censées nous permettre d’observer l’état du CLR donc il vaut mieux que ce ne soit pas ce dernier qui les exécute.
Pour exploiter le profiling du CLR il faut que vous construisiez une classe COM implémentant l’interface ICorProfilerCallback. Cette interface est définie dans le fichier corprof.h. Elle contient les nombreuses méthodes de callback dont la liste est exposée un peu plus bas. Une fois l’interface ICorProfilerCallback implémentée, il faut que le CLR sache que c’est cette implémentation qu’il doit utiliser pour réaliser les callbacks. Pour communiquer le CLSID de cette implémentation au CLR, vous n’avez pas besoin de créer votre hôte du moteur d’exécution. Il suffit de positionner correctement les deux variables d’environnement Cor_Enable_Profiling et Cor_Profiler. Cor_Enable_Profiling à une valeur non nulle pour spécifier que le CLR doit réaliser les callbacks. Cor_Profiler doit être positionnée avec le CLSID ou le ProgID de l’implémentation de ICorProfilerCallback.
Nous n’allons pas plus détailler le profiling du CLR car l’article [CLRProfiling] le fait déjà. Nous vous conseillons vivement de télécharger le code de cet article est de faire quelques essais sur vos propres applications .NET. Vous prendrez ainsi toute la mesure de la quantité de taches réalisées par le CLR ! Le code fournit avec cet article a aussi l’avantage de montrer comment utiliser un masque pour n’avoir accès qu’à certaines catégories de callback. La manipulation nécessaire pour utiliser ce code est très facile :
· Enregistrez la classe COM sur votre machine,
· ouvrez une fenêtre de commande,
· positionnez les variables d’environnement en exécutant le fichier bat fournit,
· lancez votre application à partir de cette fenêtre de commande.
Pour que vous ayez une idée précise des callbacks réalisables par le CLR, voici les méthodes exposées par l’interface ICorProfilerCallback:
// Initialisation/shutdown
STDMETHOD(Initialize)(IUnknown *
pICorProfilerInfoUnk);
STDMETHOD(Shutdown)();
// callback concernant les domaines d’applications
STDMETHOD(AppDomainCreationStarted)(UINT
appDomainId);
STDMETHOD(AppDomainCreationFinished)(UINT
appDomainId, HRESULT hrStatus);
STDMETHOD(AppDomainShutdownStarted)(UINT
appDomainId);
STDMETHOD(AppDomainShutdownFinished)(UINT
appDomainId, HRESULT hrStatus);
// callback concernant les assemblages
STDMETHOD(AssemblyLoadStarted)(UINT
assemblyId);
STDMETHOD(AssemblyLoadFinished)(UINT
assemblyId, HRESULT hrStatus);
STDMETHOD(AssemblyUnloadStarted)(UINT
assemblyId);
STDMETHOD(AssemblyUnloadFinished)(UINT
assemblyId, HRESULT hrStatus);
// callback concernant les modules des assemblages
STDMETHOD(ModuleLoadStarted)(UINT moduleId);
STDMETHOD(ModuleLoadFinished)(UINT
moduleId, HRESULT hrStatus);