2013-02-10

Interopérabilité : Composants Et Clients

Téléchargement
On peut télécharger le code-source à l’adresse #01 ou alternativement à l’adresse #02.


Avant-propos

Bien que francophone, j’utilise beaucoup les noms des concepts, technologies, produits dans leur langue d’origine qui se trouve être l’anglais. Même si on ne parle pas cette langue, ces noms sont si populaires qu’on a dû les rencontrer au moins une fois pour peu qu’on soit du domaine de l’informatique. Je considère donc que l’emploi de ces noms ne pose pas de problème majeur à la compréhension de ce document; surtout qu’il m’arrive de donner ce que je pense être la bonne traduction en français.

Est considéré ici comme composant un fichier contenant des classes dont les objets sont utilisables par le biais des interfaces qu’ils exposent.


Avertissement
Le code source a été réalisé sous Windows 7 Home Basic à l’aide de NotePad++ comme éditeur accompagné d’un compilateur pour les applications managées et de Visual Studio 2005 pour les applications non-managées.
Si vous possédez un Visual Studio récent, vous êtes bien loti et vous pouvez m’imaginer la langue pendante d’envie. Ceux qui n’ont pas Visual Studio peuvent utiliser d’autres outils qu’il m’arrive aussi d’utiliser, comme CodeBlocks, DevCpp, SharpDevelop.

Introduction


Avec l’avènement de .Net (dotNet) beaucoup de personnes pessimistes ont craint la mort de COM (Component Object Model). Mais cette crainte s’est heureusement révélée non-avérée. En fait on pourrait bien dire qu’avec .Net Microsoft a procédé à un upgrade de COM dans son modus operandi, en apportant une nouvelle formule à la réalisation de composants.

Concernant le composant, il n’y a pas de clivage entre environnement managé et environnement non- managé. La couche d’interopérabilité garant d’une communication entre les deux permet à un composant Classic COM d’être utilisé par un client managé et permet qu’un composant managé soit consommé par le type de clients habituellement dédiés aux composants COM traditionnel.

1ère partie: Composant Classic COM et client managé


Dans cette partie on se propose de réaliser un composant Classic COM créé en C++ et un client managé créé en CSharp pour dotNet (C#.Net).

a./ Composant Classic COM

On entame la réalisation de notre composant par la déclaration d’une interface unique du nom de IKungFu que voici:
interface IKungFu : IUnknown
{
 virtual HRESULT __stdcall SpellCheck(BSTR* UserWordPtr, BSTR* RightWordPtr) = 0;
};
La déclaration est conforme à une spécification qui veut que toute interface COM hérite directement ou indirectement de IUnknown qui est l’interface fondamentale de COM.

On déclare ensuite la classe qui implémente l’interface:
class KungFuCC : public IKungFu
{
 KungFuCC(void);
 virtual ~KungFuCC(void);
 
 HRESULT __stdcall QueryInterface(const IID& IIDRef, void** VoidPtrPtr);
 ULONG __stdcall AddRef();
 ULONG __stdcall Release();

 HRESULT __stdcall SpellCheck(BSTR* UserWordPtr, BSTR* RightWordPtr);

protected:
 LONG KungFuCCRefCount;
};
Cette classe contient la fonction SpellCheck apportée par l’interface déclarée et les fonctions QueryInterface, AddRef, et Release qui proviennent de l’interface IUnknown.
HRESULT __stdcall KungFuCC::QueryInterface(const IID& IIDRef, void** VoidPtrPtr)
{
 ...
 if (IIDRef == IID_IUnknown) { *VoidPtrPtr = static_cast (this); }
 else if (IIDRef == IID_IKungFu) { *VoidPtrPtr = static_cast (this); }
 else { return E_NOINTERFACE; }
 ...
}
QueryInterface permet la demande des interfaces fournies par un composant. Lorsqu’une interface demandée n’est pas supportée, l’erreur E_NOINTERFACE est renvoyée.

Le binôme AddRef et Release permet de contrôler la durée de vie des objets créés et de les supprimer.
ULONG __stdcall KungFuCC::AddRef()
{
 return InterlockedIncrement(&KungFuCCRefCount);
}
Précisément la fonction AddRef incrémente d’une unité le nombre des références à un objet actif.
ULONG __stdcall KungFuCC::Release()
{
 if (InterlockedDecrement(&KungFuCCRefCount) == 0) delete this;
 return KungFuCCRefCount;
}
La fonction Release a pour rôle de décrémenter d’une unité le nombre des références. Si ce nombre est égal à zéro (0), l’objet est supprimé.

Incrémentation et décrémentation se font un thread à la fois; ce qui garantit la fiabilité du nombre des références.
HRESULT __stdcall KungFuCC::SpellCheck(BSTR* UserWordPtr, BSTR* RightWordPtr)
{
 ...
 switch(WordCode)
 {
  ...
  default:
  {
   UserWord = _T("Serious mispelling detected. Please, think of going back to school!");
   *UserWordPtr = UserWord;
   ...
  }
 }
}
La fonction SpellCheck a comme arguments deux pointeurs de chaînes de caractères BSTR. Le premier pointe sur la variable portant l’écriture proposée par l’utilisateur et est utilisé plus tard pour stocker une observation. Le second pointeur contient l’adresse de la bonne orthographe du mot "Kung-fu".

Maintenant on arrive à la class factory (fabrique de classe) qui permettra la création non pas de classes mais d’objets. Il ne faut donc pas se faire avoir par cette dénomination.

Comme l’a dit ce monsieur, à moins que ce soit un autre membre de CodeProject (je ne me rappelle plus mais je suis d’accord avec la personne), on ne devrait même pas appeler ça class factory mais plutôt object factory. Seulement le fait est qu’il arrive parfois que les noms attribués aux produits et technologies par Microsoft (et autres fabricants) ne cadrent pas très exactement avec la réalité qu’ils désignent. Mais bon, on n’est pas là pour faire un procès aux uns et aux autres.

Alternativement on pourrait créer une fonction spéciale de création d’objets et l’exporter mais une class factory est plus recommandée. Cette classe doit implémenter l’interface IClassFactory contenant les fonctions CreateInstance et LockServer. IClassFactory étant elle-même une interface COM, on retrouve les fonctions QueryInterface, AddRef et Release.
HRESULT __stdcall KungFuCCFactory::CreateInstance(IUnknown* OuterIUnkPtr, const IID& IIDRef, void** VoidPtrPtr)
{

 ...
 
 IUnknown* IUnkPtr = static_cast (new KungFuCC());
 if (IUnkPtr == NULL) return E_OUTOFMEMORY;

 HRes = IUnkPtr->QueryInterface(IIDRef, VoidPtrPtr);
 
 ...
}
La fonction CreateInstance est appelée en interne pour la création des objets de la classe correspondante.
HRESULT __stdcall KungFuCCFactory::LockServer(BOOL LockOK)
{
 LockOK ? ObjectsCount++ : ObjectsCount--;
 return S_OK;
}
Cette fonction comptabilise le total des objets créés restant en mémoire suivant la valeur booléenne de son argument.

Il reste à doter le composant d’une entry point et d’autres fonctions propres à un serveur COM: DllGetClassObject pour obtenir la class factory, DllCanUnloadNow pour le déchargement du serveur, DllRegisterServer et DllUnregisterServer pour l’inscription et la désinscription du composant.

Suite à cela on compile le projet pour générer le fichier de composant.

La prochaine tâche consiste à enregistrer le fichier dans la base de registre afin que le serveur COM puisse le repérer et servir les clients. Pour ce faire, dans une fenêtre DOS, exécutons la commande:
RegSvr32 Debug\KungFuCC.dll
On peut également exécuter cette commande directement depuis la boîte de dialogue ‘Exécuter’ de Windows (Démarrer -> Exécuter). Mais là il faudra indiquer le chemin complet du composant.

RegSvr32 dans la boîte ‘Exécuter’
Le composant est prêt à l’emploi. Les clients traditionnels peuvent déjà l’utiliser sans plus d’exigence mais pour les clients managés il reste à générer un fichier de metadata d’informations sur les types du composant.

La Type Library (bibliothèque de types) d’un composant COM comporte les renseignements sur les types du composant. Cette Type Library ou le composant lui-même (s’il contient sa propre Type Library) est la ressource à utiliser pour générer les metadata dont on a besoin sous dotNet. Le hic est que notre projet initial ne contient pas de Type Library. Si on exécute par exemple la commande pour produire un fichier de metadata "TlbImp /out:KungFuCCMetadata.dll KungFuCC.dll", on se heurte à une erreur disant que notre DLL ne contient pas une Type Library valide. Si on essaie aussi d’examiner cette même DLL avec OleView on a un message d’erreur similaire.

Le prochain objectif est donc de pourvoir notre composant d’une Type Library en bonne et due forme. Dans la poursuite de cet objectif, créons un fichier IDL et mettons-y le contenu suivant:

import "oaidl.idl";
import "ocidl.idl";

[
 uuid(74CE0EB1-4A2C-4C70-90EF-55D305E9B02F),
 helpstring("KungFuCC Type Library 1.0"),
 version(1.0)
]

library KungFuCCLib
{
 #ifdef WIN32
 importlib("stdole32.tlb");
 #else
 importlib("stdole.tlb");
 #endif

 [
  object,
  uuid(2C9F2BBC-0BEF-4B5A-A8BC-AF64750B40E7),
  helpstring("IKungFu Interface"),
  pointer_default(unique)
 ]
 interface IKungFu : IUnknown
 {
  [id(0x01), helpstring("SpellCheck method")] HRESULT SpellCheck([in] BSTR* UserWordPtr, [out,retval] BSTR* RightWordPtr);
 }

 [
  uuid(38C2F24D-27FC-4B4D-8D4E-8BFF14A4742F),
  helpstring("KungFuCC Class")
 ]
 coclass KungFuCC
 {
  [default] interface IKungFu;
 }
};
Sauvegardons ce document sous le nom ‘KungFuCC.idl’ et recompilons.

On a maintenant un fichier à extension TLB qui représente la Type Library recherchée. On peut déjà avoir un fichier de metadata mais on va encore faire une chose pour intégrer la Type Library dans la DLL; de cette façon on aura deux possibilités pour la production des metadata. Allons-y créer un fichier ressource très simple du nom de ‘KungFuCC.rc’ dont le contenu est une ligne vide précédée de l’instruction ci-après:
1 TYPELIB "Debug\KungFuCC.tlb"
Ridiculement simple; isn’t it?

À nouveau compilons pour avoir le nouveau composant auquel est désormais incorporée la Type Library.

b./ Client managé

Comparativement au client C++ traditionnel l’implémentation sous .Net a les particularités suivantes:

- L’initialisation et la dés-initialisation (appel de CoInitialize et de CoUninitialize) ne se font pas directement par l’application-client.
- La commande new est utilisée à la place de CoCreateInstance pour la création d’objets.
- Le cast(ing) est l’équivalent de QueryInterface.
- Le comptage des objets et leur suppression sont gérés par la Framework. (On imagine que le mécanisme de Garbage Collection entre en jeu ici).

i-/ Méthodes secondaires
J’ai annoncé que pour que le client managé accède au composant Classic COM il faut un fichier de metadata mais ce n’est pas tout à fait vrai. Il existe des méthodes permettant d’utiliser ce composant autrement, sans dépendre d’un fichier de metadata. Avec votre permission je vais d’abord aborder ces techniques avant de revenir sur la méthode la plus formelle et la plus répandue.

i.1-/ Accès par Reflection
Le client peut utiliser la méthode de Reflection pour consommer un composant Classic COM pour les types duquel on n’a pas de metadata. Voici un exemple de code:
...
string UserWord = "Kung-fu";
string RightWord = "";
Type CLSType;
object CLSObject;
object[] Params = {UserWord, RightWord};
CLSType = Type.GetTypeFromCLSID( new Guid("38C2F24D-27FC-4B4D-8D4E-8BFF14A4742F") );
CLSObject = Activator.CreateInstance(CLSType);
CLSType.InvokeMember("SpellCheck", BindingFlags.Default | BindingFlags.InvokeMethod, null, CLSObject, Params);
...
Il s’agit d’une méthode de late binding qui nécessite que le composant implémente l’interface IDispatch, ce qui n’est pas le cas de notre exemple.

i.2-/ Approche par le code
Que le composant implémente IDispatch ou non, la méthode de l’approche par le code est convenable. Elle consiste à importer la déclaration des types dans le client avant leur utilisation:
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("2C9F2BBC-0BEF-4B5A-A8BC-AF64750B40E7")]
interface IKungFu
{
 void SpellCheck(ref string UserWordPtr, out string RightWordPtr);
}

[ComImport]
[Guid("38C2F24D-27FC-4B4D-8D4E-8BFF14A4742F")]
class KungFuCC
{
 ...
}
Notons l’absence d’instructions relatives à l’héritage et à l’implémentation. Cela est bien normal car on n’a pas l’intention de fournir de codes de définition. En effet, il est juste question de déclaration de types. L’attribut ComImport signifie que la définition est importée d’un composant externe. Pour les GUIDs (Globally Unique Identifier) on peut être amené à aller les chercher dans la base de registre si on n’est pas l’auteur du composant et qu’on n’a pas le code-source; ce qui peut être embêtant.

Une fois les déclarations bien faites, utiliser le composant devient facile.

Remarquons aussi au passage que ce n’est qu’à l’exécution qu’une probable non-conformité de types est détectée.

ii-/ Méthode principale
On arrive à la méthode plus conventionnelle où il faut se rappeler qu’on a besoin du fichier de metadata qui s’obtient avec la commande:
TlbImp Debug\KungFuCC.dll /out:KungFuCCMetadata.dll
On mettra ce fichier dans le dossier du client et on indiquera la référence à ce fichier avec la directive using KungFuCCMetadata; insérée quelque part en haut du code.

N.B: On peut utiliser TlbImp.exe sans indiquer le chemin complet si on a au préalable ajouté son chemin d’accès dans la variable d’environnement PATH sinon il faudrait spécifier le nom complet à l’invite de commandes.

Substantiellement le code du client C#.Net pour accéder à la fonctionnalité désirée est:
...
IKungFu IKungFuInterface;
KungFuCC KungFuCCObject = new KungFuCC();

IKungFuInterface = (IKungFu) KungFuCCObject;
IKungFuInterface.SpellCheck(ref UserWord, out RightWord);
...
On compile avec la commande:
csc /out:KungFuCCClient.exe KungFuCCClient.cs
Exécutons maintenant ce client en saisissant son nom complet ou relatif à l’invite de commande suivi d’une validation.

Là on obtient l’erreur d’une prétendue absence d'interface. Dans le code, l’erreur est déclenchée à l’endroit du cast en IKungFu qui correspond à un traditionnel QueryInterface pour avoir un pointeur sur l’interface IKungFu qu’on a définie.

Croyez-moi si je vous dis que ce truc m’a au début donné des céphalées; et c’est pour ça que j’ai choisi de vous en faire part. J’ai mis un certain temps à me figurer le problème; surtout que dans le temps je n’avais pas la possibilité d’aller sur Internet pour chercher une solution. J’ai créé et recréé différents clients C++ qui consomment le composant sans problème mais chaque fois que j’essaie un client C#.Net je me heurte à la même erreur. Et puis il est arrivé que j’ai repris un de mes précieux bouquins: "Teach Yourself the C# Language (In 21 Days)" de Bradley Jones, qui est justement le livre que j’ai utilisé pour me mettre à la programmation en C#.Net. En le parcourant une énième fois je me suis rappelé l’attribut STAThread et je me suis juste dit: tiens, moi-aussi je vais “décorer” ma Main (entry point) de ce petit attribut; ça ne ferait de mal à personne. J’inclus l’attribut, j’enregistre et je compile. Je ré-exécute et qu’est-ce que je vois? Ça marche! Je suis content d’avoir résolu mon problème mais je suis quand-même tellement surpris et choqué qu’au lieu de jubiler je reste bien un moment hébété devant mon écran à me demander "mais qu’est-ce que ça signifie?". Et soudain tout devient clair; enfin presque. On tente de faire fonctionner un objet STA (Single Thread Apartment) comme un MTA (Multi Thread Apartment). Après nous avoir habitués dans le temps aux modules mono-thread par défaut, les gens de chez Microsoft ont eu l’idée déroutante de rendre les applications C#.Net des MTA par défaut. Ce qui a pour conséquence de déboussoler des gens comme moi qui étaient allés un peu errer dans d’autres activités et ont perdu le fil des choses. Aaaahh Microsoft, Microsoft, Microsoft. C’est toi Microsoft qui nous fait ça? Si je n’apprends pas à contrôler mes nerfs ils vont un jour finir par me faire péter les plombs, ces braves gens de chez Microsoft. Mais bon on les excuse hein; on est dans une dynamique progressiste; lol.

En Interop l’appel proprement dit à un objet COM se fait par le biais d’une RCW (Runtime Callable Wrapper). Il se crée une RCW par objet. Il me semble; je dis bien: il me semble, que notre RCW doit être ici de type STA (peut-être pour rester conforme à l’appartement de l’objet dont elle est mandataire). N’ayant notablement pas un support inter-appartements, cela oblige à une compatibilité entre appartements. D’où le problème.

Je n’ai pas encore trouvé la documentation suffisante pour me faire une idée plus précise de la chose; alors si vous le voulez bien, ça reste de la pure spéculation.

Avec notre [STAThreadAttribute] en place, on peut maintenant exécuter notre application sans problème et voir l’output suivant:

Output du client managé
C’est terminé pour cette section et on va de ce pas enchaîner avec la seconde partie; c’est sans pause-café je vous signale.

2ème partie: Composant managé et client traditionnel


Cette section est consacrée à l’étude d’un composant réalisé en C#.Net qui sera utilisé par un client C++ non-managé. Sans plus tarder allons-y.

a./ Composant managé

On va démarrer par la réalisation d’un composant sans indication explicite d’interface comme cela se fait en Visual Basic 6.
public class KungFuMC
{
 public string SpellCheck(ref string UserWord)
 {
  UserWord = "We\’re out of service. Please come back later.";
  return " ___________ ";
 }
}
Une interface de type IDispatch, du nom de la classe précédé d’un underscore (barre de 8 d’un clavier AZERTY), est généré par défaut. Les objets de cette classe sont par conséquent utilisés en mode late binding; pour le bonheur des scripting-clients j’imagine.

Il est à mon avis plus intéressant de déclarer des interfaces dans lesquelles on pourra séparément répartir des fonctions et au besoin modifier le mode d’accès. Prenons donc la responsabilité de créer notre propre interface.
interface IKungFu
{
 string SpellCheck(ref string UserWord);
}
La fonction de l’interface possède un argument string (par référence) et renvoie une donnée de même type. On crée ensuite la classe:
public class KungFuMC : IKungFu
{
 public string SpellCheck(ref string UserWord)
 {
  ...
 }
}
Outre les constructeur et destructeur, la classe implémentant l’interface contient la définition de la fonction SpellCheck dans laquelle la variable du mot proposé par l’utilisateur est plus tard utilisée dans la fonction pour stocker une observation. Aussi, l’orthographe correcte est renvoyée.

Quid de l’implémentation de IUnknown? N’est-on pas censé avoir COM à l’esprit et satisfaire à ses spécifications? Si; mais on est en environnement managé et d’une certaine manière le travail est partagé entre l’application et la Framework qui effectue (pour nous) certaines tâches inhérentes à COM.

Ce code est donc suffisant et on peut valablement procéder à la compilation avec la commande:
csc /t:library /out:KungFuMC.dll KungFuMC.cs
Il faut alors enregistrer la DLL obtenue et générer une Type Library pour l’environnement non-managé. Cette Type Library est le moyen par lequel le composant managé expose ses types aux applications non-managées qui ne comprennent pas les metadata.

Avec l’utilitaire TlbExp.exe, on produit une Type Library comme suit:
TlbExp /out:KungFuMCTypeLib.tlb KungFuMC.dll
Avec l’utilitaire RegAsm.exe on peut faire d’une pierre deux coups: générer une TLB et créer les clefs de registre relatives au composant. La commande est:
RegAsm /tlb:KungFuMCTypeLib.tlb KungFuMC.dll
Utilisation d’attributs
Le composant réalisé peut faire l’affaire mais bien souvent on a besoin de procéder à certains réglages personnels, notamment à l’aide d’attributs. C’est ce qu’on verra dans cette partie sur l’attribution.

Un GUID est auto-magiquement associé aux interfaces et classes si on omet d’en indiquer. Mais avec GuidAttribute, l’attribut de GUID, on peut assigner soi-même des GUIDs généralement créés avec des utilitaires disponibles dans les kits de développement.
[Guid("F895D258-CCE9-436D-A9AA-21DC4FFB1D39")]
interface IKungFu
{
 ...
}
[Guid("EA2621B6-A125-47BE-A3A8-B632CAB1D162")]
class KungFuMC : IKungFu
{
 ...
}
Les attributs d’interface, InterfaceType et ClassInterface, servent à indiquer le type d’interface voulu.
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IKungFu
{
 ...
}
[ClassInterface(ClassInterfaceType.None)]
class KungFuMC : IKungFu
{
 ...
}
Les valeurs possibles de ClassInterface sont:
  • AutoDispatch qui fait générer une interface par défaut de nature IDispatch avec pour résultat une accessibilité par late binding.
  • AutoDual qui rend l’interface duale. Son inconvénient est qu’une édition du composant oblige la recompilation de ses clients. Elle peut être idéale pour des composants qui ne changeront pas dans le futur.
  • None (ma préférée) qui signifie que l’interface héritée (déclarée par l’utilisateur) est l’interface par défaut.

Quant à l’attribut InterfaceType, les valeurs possibles sont:
  • InterfaceIsIDispatch qui fait que l’interface est accessible par late binding.
  • InterfaceIsIUnknown qui restreint l’accessibilité au mode early binding.
  • InterfaceIsDual qui combine les valeurs susmentionnées.

L’attribut MarshalAs permet d’établir une correspondance de types entre environnements managé et non-managé. Lorsque cet attribut est omis Interop procède à une conversion en se basant sur des types par défaut (connus), sauf pour les blittables (types identiques de part et d’autre).

L’attribut ComVisible quant à lui rend un élément invisible, respectivement visible, à COM s’il prend la valeur ‘false’, respectivement ‘true’. Mais il suffit qu’un élément soit public pour que COM le voie. Il est par conséquent superflu d’affecter ComVisible avec la valeur ‘true’ à un élément public. D’un autre coté ComVisible avec ‘true’ est sans effet sur un élément privé.

Notez que le nom d’un attribut se termine par ‘Attribute’ et on peut l’omettre. Le namespace (espace de noms) des attributs décrits est ‘System.Runtime.InteropServices’.

b./ Client C++ traditionnel

Pour accéder au composant managé le client C++ non-managé a besoin des informations relatives aux types du composant aussi bien qu’à ceux de la Framework. Ces informations sont contenues dans les fichiers TLB respectifs.

Concrètement dans le code, après initialisation, on procède à la création de l’objet désiré avec CoCreateInstance. On fait alors la demande d’une interface de l’objet et si on l’obtient avec succès, on appelle ses fonctions qui nous intéressent puis on se défait de l’interface et on libère COM.

À l’exécution le repérage du composant managé se fait par ce qu’on peut se figurer comme un intermédiaire. En général dans la vie je n’aime pas beaucoup avoir affaire aux intermédiaires mais Microsoft a jugé nécessaire d’insérer à ce niveau un intermédiaire du coté managé. Et cet intermédiaire au nom peu élégant de Mscoree se joue vraiment les mecs incontournables. Il va jusqu'à s’enregistrer dans la base de registre en lieu et place du composant. Si vous regardez dans la base de registre après l’exécution de RegAsm, c’est lui que vous voyez dans la clé InprocServer.

Mscoree dans le registre
Vous le voyez? Attendez, je vais faire un petit zoom.

Mscoree dans le registre
Là, c’est lui! MSCOREE. (Tu parles d’une affaire!)

Franchement, la première fois que je l’ai vu, je n’ai pas pu m’empêcher de me dire: "Mais qu’il dégage de là pour céder la place à mon composant!"
Non, mais sérieusement, on n’a pas saisi: RegAsm mscoree.dll On a plutôt exécuté RegAsm pour notre DLL, en occurrence: RegAsm KungFuMC.dll. On retape "RegAsm KungFuMC.dll" et valide, et c’est encore lui qui est là. Pendant un moment je me suis demandé comment faire pour le contourner puisque comme je l’ai dit je n’aime pas beaucoup les intermédiaires. Quand j’ai affaire à quelqu’un ou à quelque chose, j’aime mieux le direct-direct. Ici on a affaire à KungFuMC.dll et lui il s’interpose. Bon, n’ayant pas trouvé le moyen de le contourner j’ai décidé de le laisser là pour voir comment il se comporte et j’ai vu qu’il se comporte plutôt bien. Alors si j’ai un conseil à vous donner c’est de l’ignorer là; lol.

Du coté du client natif on a essentiellement la séquence d’appels suivante:
CoInitialize(NULL);
CoCreateInstance(CLSID_KungFuMC, NULL, CLSCTX_INPROC_SERVER, reinterpret_cast &IUnknownPtr);
IUnknownPtr->QueryInterface(IID_IKungFu, reinterpret_cast &IKungFuPtr);
RightWord = IKungFuPtr->SpellCheck(&UserWord);
IUnknownPtr->Release() ;
IKungFuPtr->Release()
CoUninitialize();
Après compilation, essayons d’exécuter le client.

On reçoit un inattendu message d’erreur de FILE_NOT_FOUND (0x80070002) qui dit assez clairement qu’un fichier est introuvable.

Traditionnellement dans COM, le chemin complet du composant est écrit dans le registre. En environnement managé, avec RegAsm on l’a vu, ce n’est pas le composant qui s’inscrit dans le registre. Pour localiser le composant managé, le dossier de l’exécutable du client et le GAC (Global Assembly Cache, soit Cache Global d’Assemblées), sont les endroits où il est cherché. Ainsi à moins de passer par un fichier de configuration (ce que je n’ai encore jamais essayé), on est limité à deux options. Soit on copie le composant dans le dossier de l’application-client et là on parle de "private assembly use", soit on copie le composant dans le GAC pour un "public assembly use".

Veuillez noter que l’erreur FILE_NOT_FOUND peut être due à une autre situation. Dans certains systèmes un chemin trop long pourrait provoquer la même erreur; par exemple un chemin comme: “D:\RootDirectory \SubDirectoryOne\SubDirectoryTwo\SubDirectoryThree\SubDirectoryFour\SubDirectoryFive \SubDirectorySix\SubDirectorySeven\SubDirectoryEight\SubDirectoryNine\SubDirectoryTen \TheComponentThatWeCreatedInCSharpDotNetToDemonstrateCOMInterop.dll”

Il faut donc garder un nom complet relativement court. Microsoft l’a signalé dans le passé dans un de ses articles en ligne.

L’ennui avec le mode "private assembly use", c’est que pour tout client qui utilise n composants, il faudra chaque fois embarquer les n composants dans le dossier du client. Il apparaît alors que la deuxième option est plus économique et avantageuse. La commande pour inscrire un composant dans le GAC se fait à l’aide de l’outil GacUtil.exe de la façon suivante:
GacUtil /i KungFuMC.dll
Mais avant, il faut créer une clef d’assembly avec l’outil SN.exe (StrongName.exe):
sn -k KungFuMC.snk
Ensuite il faut associer cette clef au composant avec l’attribut d’assembly AssemblyKeyFile contenu dans le namespace 'System.Reflection'.
[assembly: AssemblyKeyFileAttribute("KungFuMC.snk")]
On peut aussi y procéder avec le paramètre /keyfile du compilateur:
csc /t:library /out:KungFuMC.dll /keyfile:KungFuMC.snk KungFuMC.cs
En ce qui concerne l’emplacement du composant, on optera pour la copie ou carrément le déplacement (couper-coller) de la DLL vers le dossier du client vu qu’elle ne nous sert à rien là où elle est. Si après cette opération on exécute à nouveau le client, on devrait avoir une exécution sans ennui.

Output du client non-managé
Je vous suggère après test d’enlever les inscriptions relatives aux composants du registre avec RegSvr32.exe pour le composant Classic COM et RegAsm.exe pour le composant managé:
RegSvr32 -u KungFuCC.dll


RegAsm -u KungFuMC.dll

Conclusion


Voilà. On est ainsi parvenu au terme d'une randonnée sur l’interopérabilité au cours de laquelle on a appris à créer un composant Classic COM et son client managé C#.Net, puis à réaliser un composant managé utilisé par un client C++ non-managé.

Merci pour votre attention et bonne programmation à vous dans vos familles respectives.
Eh, quoi? Il y a des familles comme ça où le père, la mère et les enfants sont tous programmeurs et ils déversent leurs diverses émotions dans les instructions qu’ils envoient à l’ordinateur familial commun en utilisant différents langages (Cobol, Java, C++, Pascal, SmallTalk, BigTalk, VeryBigTalk et autres). Et puis comme ça un jour la machine dépassée par cette famille de programmeurs "polyglottes" perd les pédales et se met à dérailler, et c’est sans surprise que vous entendez le père demander à la petite dernière: «Dans quel langage tu converses maintenant avec la machine?» Et la petite dernière d’à peine cinq (05) ans de répondre: «Mais dans le nouveau langage en vogue, le Z++, made in Zimbabwe; faut que tu te mettes à jour, papa.» Le père tout étonné: «Ah bon! Ils se sont aussi mis à créer des langages de programmations ces Africains-là? Le Z++, made in Zimbabwe... Faut l’faire!» Hia hia hia hia hia...

Bon ça c’était la dernière plaisanterie. Pour la route; comme le dirait un ami.
Allez, sérieusement; bonne programmation à vous et à la prochaine.

© Copyright United Kingdom of Love - Tous droits réservés.

Aucun commentaire: