I. Introduction▲
D'abord nommés Object d'automation OLE (Object Linking and Embedding), puis COM (Component Object Model), les ActiveX permettent à plusieurs programmes de dialoguer ensemble. Le dialogue se base sur un mode client/serveur, le serveur offre au client des contrats, avec une sémantique qu'il se doit de respecter.
En 2002, Microsoft sort la première version de la Framework .NET qui, par rapport à la technologie COM, apporte des améliorations du référencement client avec une meilleure gestion de version.
Actuellement, les assemblys .NET ne sont pas gérés par nos navigateurs Web. Pour pouvoir profiter de la richesse de .NET et de la gestion des ActiveX on verra que sous Internet Explorer, il est possible de rendre notre code C# compatible avec COM.
On commencera par expliquer l'essentiel des ActiveX. Ensuite, on portera une assembly .NET sur IE qu'on illustrera avec des exemples de codes C# et JavaScript. Enfin, on finira avec le déploiement du composant sécurisé, avec un certificat de test valide.
I-A. L'essentiel sur les ActiveX▲
L'ActiveX est représenté par une librairie orientée objet ; il est référencé par le programme client généralement pour des fonctions génériques et partagées ou pour proposer des contrôles utilisateurs étendus. Le référencement se fait de deux manières.
En liaison tardive
- Le typage et la signature des fonctions, propriétés et événements sont résolus à l'exécution.
- La liaison tardive est plus souple : le client COM n'a pas besoin d'être recompilé à chaque modification (changement du typage, du nom de la fonction) de l'ActiveX serveur.
- L'inconvénient est que ce mode est moins performant que la liaison dynamique, car le client doit résoudre la sémantique du service à chaque instanciation.
En liaison dynamique
- La sémantique des fonctions, propriétés et événements sont résolus à la compilation.
- Ce mode est plus performant. Les informations sont enregistrées au niveau du Runtime à la compilation.
- L'inconvénient est qu'en pratique, chaque évolution demande une recompilation de tous les clients. L'exercice n'est pas toujours évident et génère souvent des conflits de versions, problème bien connu par les entreprises sous le nom de « DLL Hell » en français « l'enfer des DLL ».
Sous Internet Explorer, les langages (JavaScript, VBScript) étant faiblement typés, les ActiveX sont pilotés uniquement par liaison tardive donc pas de souci !
Chaque classe référencée est identifiée soit :
- par un Guid qui est une série héxadécimale, séparée par des tirets. Ex. : {B25649FF-FA21-4006-932E-5BEA968200CB} ;
- par un ProgID, sous la forme MonAssembly.NomDeLaClasse.
I-B. Préparation du projet▲
Pour notre projet, on a besoin d'une librairie qui sera référencée et portée par un site web ASP.NET.
Sous Visual Studio, on crée un projet de type « bibliothèque de classe » que l'on appelle client.
Puis dans notre solution, on ajoute en plus un site web de type « Site Web ASP.NET » que l'on appelle HostAspNet.
I-B-1. Inscription COM Interop▲
Pour comprendre ce qu'est COM (Component Object Model), je vous recommande l'article de J?M Rabilloud ou la FAQ de Christian Casteyde qui l'explique déjà bien.
https://dotnet.developpez.com/cours/interopcom/
https://www.developpez.com/windows/dcom/t1.html#AEN50
COM Interop permet de rendre compatible une assembly avec COM. Nous verrons cette opération au niveau code en détail, au chapitre Déploiement.
Pour l'instant, voyons pour nos premiers tests comment inscrire nos assemblys.
I-B-1-a. Inscription sous Visual Studio 2005.▲
Sous Visual Studio, dans les propriétés du projet du composant > sous-menu Générer, et pour chaque mode de configuration, on a une option « Inscrire pour COM Interop » qu'il suffit de cocher.
L'option à présent activée, à la génération du projet ou de la solution, les opérations suivantes sont exécutées :
- création un fichier TLB qui pourra être référencé par nos clients OLE par exemple Internet Explorer ;
- enregistrement des classes de l'assembly dans la base de registre en tant que composant OLE ;
- dans le cas d'une recompilation, effacement des classes de la base de registre. Cela évite en cas de changement du ProgID, du CLSID ou lors d'une suppression de classes, d'avoir des références ActiveX orphelins.
Dans la partie Déploiement, on développera notre propre routine d'enregistrement. Notez donc que cette option ne doit être activée que pour la phase de développement.
I-B-1-b. Aperçu dans la base de registre▲
Après compilation avec l'option « COM Interop » activée, deux clés sont créées dans la base de registre.
La clé CLSID principale contenant les informations d'accès au niveau COM se compose de :
- une référence vers le ProgId ;
- la version de l'assembly ;
- les catégories implémentées (vues en détail dans la rubrique déploiement).
Aperçu dans la base de registre sous la clé :
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{D0D4D20D-5AD0-4DC4-A9EA-844DFBBA668E}
Il existe également une autre clé qui permet de renvoyer le CLSID à partir du ProgId :
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\MonClient.Controle\CLSID
I-B-1-c. Inscription en ligne de commande▲
On peut également faire la même chose en ligne de commande, pour un batch par exemple. Pour cela, on utilise l'outil RegAsm, disponible avec la Framework.net 2.0.
L'inscription
L'inscription pour faire du COM Interop se fait ainsi :
RegAsm.exe MonAssembly.dll /codebase /tlb
/codebase référence le chemin de l'assembly.
/tlb crée MonAssembly.tlb qui est la forme de bibliothèque de type compatible OLE.
La désinscription
Pour la désinscription, on utilise la même commande avec le commutateur /u.
RegAsm.exe MonAssembly.dll /u
II. Implémentation▲
À présent, passons au code !
II-A. Création de classes▲
Pour commencer, on crée un contrôle qu'on appelle « Controle » dans le projet Client.
Générer un CLSID valide▲
Pour pouvoir être référencée et instanciée via une liaison tardive, chaque classe en COM Interop est identifiée par le CLSID. C'est une série de valeurs alphanumériques et uniques.
Visual Studio possède un outil qui permet de générer des CLSID aléatoires, accessibles via le menu Outils > Create GUID.
En cliquant sur « New GUID », l'outil nous génère des GUID de façon illimitée.
La valeur peut ensuite être copiée dans le presse-papier via le bouton « Copy ».
Déclaration de la CLSID▲
Dans notre classe contrôle, implémentons les attributs suivants pour COM :
- ComVisible, qui permet de contrôler la visibilité de la classe. Une classe invisible ne signifie pas qu'elle ne pourra pas être utilisée. - Guid, qui identifie la classe au niveau CLSID. Pensez à rectifier le format en enlevant le tilde en début et en fin.
Définissons les attributs. Pour le Guid on colle notre Guid généré avec l'outil.
[ComVisible(true)]
[Guid(
"D0D4D20D-5AD0-4dc4-A9EA-844DFBBA668E"
)]
public
partial
class
Controle :
UserControl
{
public
Controle
(
)
{
InitializeComponent
(
);
}
}
II-B. Intégration côté Web▲
À présent que la classe est créée, on compile la librairy afin qu'elle génère l'ActiveX en Interop .NET.
Dans notre projet web, dans la page Default.aspx, on déclare l'ActiveX avec un tag object. On l'identifie avec le même CLSID généré.
Via les propriétés width et height, on règle les dimensions du composant sur la page.
<!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
>
<html xmlns
=
"http://www.w3.org/1999/xhtml"
>
<head>
<title>
Test</title>
</head>
<body>
<object id
=
"clientActiveXDotnet"
classid
=
"clsid:D0D4D20D-5AD0-4dc4-A9EA-844DFBBA668E"
width
=
"450"
height
=
"100"
>
<span style
=
"color:red"
>
Impossible d'installer le composant -- Vérifier les paramètres de sécurités du serveur.</span>
</object>
</body>
</html>
Dans object, on remarque le tag span. Celui-ci est affiché lorsque le contrôle n'a pas pu être instancié par le navigateur.
II-C. Les fonctions▲
Regardons à présent comment implémenter une fonction C# qui pourra être appelée depuis la page en JavaScript.
Déclaration au niveau de la classe▲
L'implémentation de la fonction côté C# se fait comme avec les assembly dotNet cliente. On définit une méthode avec une portée publique.
[ComVisible(true)]
[Guid(
"D0D4D20D-5AD0-4dc4-A9EA-844DFBBA668E"
)]
public
partial
class
Controle :
UserControl
{
public
Controle
(
)
{
InitializeComponent
(
);
}
public
void
DoMessageBox
(
string
message)
{
MessageBox.
Show
(
message,
_caption);
}
}
Appel de la fonction en JavaScript▲
Sur la page web dans les attributs du tag object, on peut directement appeler la méthode ActiveX.
<
input id=
"alertSimpleButton"
type=
"button"
value=
"Alert simple"
onclick
=
"return clientActiveXDotnet.DoMessageBox('Hello ActiveX ^^')"
/>
II-D. Les propriétés▲
Les propriétés tout comme les méthodes se définissent normalement. Au niveau du code du contrôle, dans notre exemple on implémente une propriété caption.
Définition de la propriété au niveau de la balise objet▲
Au niveau du tag object, avec des sous-tags param, on a la possibilité d'initialiser une valeur de base. Le nom de la propriété implémentée dans le composant est défini via l'attribut name, et sa valeur par défaut avec l'attribut value.
Redéfinition de la propriété en JavaScript▲
Depuis une fonction JavaScript, la propriété peut changer de façon dynamique. Dans notre exemple, elle est mise à jour par la fonction alertScriptButton_onclick et est déclenchée par l'événement onclick au niveau du tag input.
<script language
=
"JavaScript"
type
=
"text/JavaScript"
>
function alertScriptButton_onclick
(
) {
clientActiveXDotnet.
Caption =
"Redéfinition du Caption"
clientActiveXDotnet.DoMessageBox
(
'Hello ActiveX ^^'
);
}
</script>
<input id
=
"alertScriptButton"
type
=
"button"
value
=
"Alert dans fonction JavaScript"
onclick
=
"return alertScriptButton_onclick()"
/>
II-E. Les événements▲
Les ActiveX n'implémentent pas le même mécanisme d'abonnement que .NET.
II-E-1. Création de l'interface d'événements▲
Pour pouvoir faire la convergence :
- on déclare une interface représentant le formalisme des fonctions qui s'abonnent ;
- puis on définit le nom de l'événement, les arguments et leurs typages ;
- ensuite on leur affecte un DispId différent sur chacun d'eux ;
- enfin via l'attribut InterfaceType, on déclare en tant qu'interface IDispatch, un choix adapté pour les liaisons tardives.
[ComVisible(true)]
[Guid(
"DF3D6B0D-EFF5-4f72-BBA3-5B26E5520E4E"
)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public
interface
ControleEvents
{
[DispId(
1
)]
void
MessageDisplayed
(
string
message);
}
Création du délégué de notification de message▲
Dans notre contrôle, on implémente les délégués de la même forme que les fonctions de callback de l'interface. On mettra un attribut ComVisible à faux, afin de rendre invisible notre délégué au client COM.
[ComVisible(false)]
public
delegate
void
MessageDisplayedDelegate
(
string
message);
Implémentation de l'événement au niveau du contrôle utilisateur▲
On ajoute ensuite l'attribut ComSourceInterfaces afin de présenter au client COM l'interface à utiliser pour les événements.
///
<
summary
>
/// Controle utilisateur ActiveX
///
<
/summary
>
[ComVisible(true)]
[Guid(
"D0D4D20D-5AD0-4dc4-A9EA-844DFBBA668E"
)]
// Identifiant d'appel de l'ActiveX (CLSID)
[ComSourceInterfaces(
typeof
(ControleEvents))]
// Interface a utiliser pour l'appel des événements
[ClassInterface(ClassInterfaceType.AutoDispatch)]
// JavaScript ne prend en charge que la liaison tardive.
public
partial
class
Controle :
UserControl
{
// Evenement déclenché à chaque nouveau message
public
event
MessageDisplayedDelegate MessageDisplayed;
public
Controle
(
)
{
InitializeComponent
(
);
}
private
string
_caption;
///
<
summary
>
/// Propriété accessible côté JavaScript
///
<
/summary
>
public
string
Caption
{
get
{
return
_caption;
}
set
{
_caption =
value
;
}
}
///
<
summary
>
/// Fonction d'affichage de message accessible coté JavaScript
///
<
/summary
>
///
<
param
name
=
"message"
><
/param
>
public
void
DoMessageBox
(
string
message)
{
// met à jour le Label par le message du dernier appel via JavaScript
LastMessageLabel.
Text =
"Dernier message: "
+
message;
// Affiche le messageBox via les Windows Forms
MessageBox.
Show
(
message,
_caption);
// déclenche l'événement qui alerte la page web que le message est bien recu.
RaiseMessageBox
(
message);
}
///
<
summary
>
/// Déclenche l'événement de notification de réception du dernier message
///
<
/summary
>
///
<
param
name
=
"message"
>
Message à notifier vers la page Web
<
/param
>
private
void
RaiseMessageBox
(
string
message)
{
if
(
MessageDisplayed !=
null
)
{
try
{
MessageDisplayed
(
"message affiché :"
+
message);
}
catch
(
Exception e)
{
// échec lors de l'invocation de la fonction JavaScript
MessageBox.
Show
(
e.
ToString
(
));
}
}
else
MessageBox.
Show
(
"RaiseMessageBox est nulle"
);
}
}
Implémentation côté JavaScript▲
Côté page web, on déclare une fonction de démarrage qui via la méthode attachEvent de la référence objet, abonnera la fonction clientActiveXDotnet_MessageDisplayed à l'événement MessageDisplayed.
Une autre forme possible▲
Une autre solution plus compacte, consiste à déclarer une balise script avec les attributs for et event.
…
<body>
<object id
=
"clientActiveXDotnet"
classid
=
"clsid:D0D4D20D-5AD0-4dc4-A9EA-844DFBBA668E"
width
=
"450px"
height
=
"100px"
>
<span style
=
"color:red"
>
Impossible d'installer le composant
-- Vérifier les paramètres de sécurité du serveur.</span>
</object>
<script for
=
"clientActiveXDotnet"
event
=
"MessageDisplayed(strMessage)"
language
=
"javascript"
>
function clientActiveXDotnet
::
MessageDisplayed
(
strMessage)
{
alert
(
"Retour d'événement (2e méthode) : "
+
strMessage);
}
</script>
</body>
…
Aperçu sous IE▲
Après lancement de la page Default.aspx, on doit obtenir ceci :
Configuration de la sécurité sous IE▲
Dans certains cas, le tag object est désactivé, lorsque l'ActiveX n'est pas encore installé ou qu'une politique de sécurité l'en empêche.
Dans les options Internet, l'exécution peut se faire automatiquement avec ces quelques astuces.
-
En plaçant la page web sur un serveur et en définissant l'URL du site en tant que site de confiance. Pour ce faire :
- connectez-vous sur votre site ;
- allez dans l'onglet sécurité, sélectionnez « Sites de confiance » puis cliquez sur Sites ;
- la fenêtre des sites de confiance se charge. Si vous n'utilisez pas de canal sécurisé SSL, décocher l'option « exiger un serveur sécurisé… » ;
- enfin cliquez sur ajouter.
- Dans le cas d'un client sans l'ActiveX encore installé, en acceptant l'exécution du script et de l'installation de composants.
- En modifiant vos paramètres de sécurité au niveau du navigateur ou en baissant votre niveau de sécurité (déconseillé).
- En installant le certificat que nous allons voir dans le chapitre suivant.
III. Les certificats▲
III-A. Les types de certificats et champs d'application▲
Pour pouvoir être exécuté sous Internet Explorer, le composant ActiveX doit être signé avec un certificat de confiance.
Trois types de certificats existent
Certificat de test : c'est un certificat temporaire comme celui créé dans notre tutoriel. Son utilisation reste locale et ne peut pas être utilisée en production. Ce type de certificat n'est pas approuvé par le navigateur et nécessite d'être installé dans le magasin de certificats, plus précisément, le dossier d'autorité racine de confiance.
Certificat via une autorité interne : ces certificats sont générés par une autorité de certification (ou CA pour Certicate Authority) installée sur votre propre domaine. Pour cela on installe un service PKI (Public Key Infrastructure) qui permet de créer et gérer les certificats. L'ActiveX signée par ce type de certificat ne peut être exécutée qu'à partir de l'Intranet du domaine. Cela peut être par exemple un composant utilisé par le portail interne d'une entreprise.
Pour que l'ActiveX puisse être approuvé dans tout le réseau, le serveur contenant l'autorité doit être approuvé sur le domaine racine.
Le certificat via une autorité externe : plus étendu, ces certificats sont générés par une autorité de certification prestataire. Le champ d'action s'étend sur tout l'Internet. En général après le paiement d'un abonnement annuel, le certificat est généré directement à partir du site de l'organisme tiers. La liste de prestataires est affichée dans le dossier d'autorité racine de confiance. Voir la rubrique Gestion des certificats sous IE plus bas.
Quelques offres de prestataires CA :
- VeriSign : http://www.verisign.com/products-services/security-services/code-signing/digital-ids-code-signing/index.html
- Thawte : http://www.thawte.fr/certificats-numeriques-ssl/certificats-developeurs/index.html
- GlobalSign : http://eu.globalsign.com/digital_certificate/code-signing/index.htm
La liste des prestataires diffère d'un navigateur à l'autre.
III-B. Gestion des certificats sous IE▲
On peut gérer les certificats de deux façons, soit via la MMC certmgr.msc ou soit dans les options d'Internet Explorer.
Accès à la console MMC.
Via démarrer, exécuter, à l'invite, on saisit certmgr.msc puis on clique sur OK
L'outil se présente ainsi :
Accès aux certificats sous Internet Explorer
Internet Explorer contient un module de gestion de certificats via menu Outils > Options Internet.
La liste de certificats est accessible via l'onglet Contenu.
III-C. Emplacement des certificats du magasin▲
Dans la base de registre, les certificats sont stockés à deux emplacements :
- soit au niveau utilisateur : HKEY_CURRENT_USER\Software\Microsoft\SystemCertificates ;
- soit au niveau du poste : HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SystemCertificates.
En voici un aperçu :
Le répertoire SystemCertificates représente la liste des dossiers du magasin.
III-D. Création du certificat de test▲
Grâce à la commande makecert disponible avec la framework.NET, nous allons maintenant créer le certificat de test pour signer notre fichier cab qui sera utilisé pour le déploiement.
Sous l'invite de commande de Visual Studio :
makecert -sk DungTriRootKey -pe
-n "CN=
Dung-Tri LÊ, OU=
Certification,O=
Dung-Tri Corp,E=
Dung-Tri.LE@
dotnet.com"
-ss Root DungTri.cer
Voici quelques explications sur les paramètres renseignés :
- -sk : définit le nom du conteneur de clés : garder le bien en tête pour plus tard ;
- -n : définit le nom en X500. C'est ici qu'on spécifie les informations de provenance du certificat comme le nom du propriétaire, les informations de l'organisation et l'adresse email ;
- -ss Root : spécifie qu'à l'importation, le certificat sera stocké directement dans le dossier d'autorité des certifications racine de confiance. Voir la liste des dossiers possibles dans la clé de registre SystemCertificates ;
- -pe : marque le certificat comme exportable en pfx ;
- Le dernier paramètre spécifie la cible du certificat sur le disque. Le chemin peut être relatif ou absolu.
Sous Windows Vista, pour que la commande puisse fonctionner, vous devez avoir les droits administrateur.
III-E. Installation du certificat▲
En confirmant par Oui, et grâce à l'option -ss Root, le certificat est directement installé dans le CA local.
Sous la MMC de gestion des certificats, vérifions si le certificat est présent.
Dans le dossier « Autorités de certification racine de confiance », on le retrouve dans la liste de certificats. Via un double clic droit, l'utilitaire nous charge les diverses propriétés.
On remarque aussi que tous les certificats de test sont émis par Root Agency. Cette information peut être utile le jour où vous souhaitez les effacer. Mais faites attention à ne pas supprimer celui qui porte Root Agency en libellé. Pour que les certificats de test soient valides, assurez-vous qu'il est bien présent dans ce dossier.
Dans l'onglet « Chemin d'accès de certificat », vérifions les informations du certificat.
Si tout se passe bien, un peu plus bas, dans « État du certificat », vous devriez voir apparaître « Ce certificat est valide ».
IV. Déploiement de l'ActiveX▲
Le déploiement d'ActiveX en .NET Interop peut se faire de deux manières, soit en déployant un fichier CAB, ou soit en utilisant la technologie Click Once qui est plus adaptée pour un exécutable. Notre sujet traitant plutôt des composants, on préfère la première méthode.
Pour plus d'informations sur ces deux choix voici un lien qui présente les différences :
http://msdn.microsoft.com/fr-fr/library/ms973805.aspx
IV-A. Déploiement dans un fichier CAB▲
CAB (Cabinet Archive), est une forme de compression créée par Microsoft. On compresse notre composant et toutes ses dépendances qu'on stocke ensuite sur le serveur. Puis à l'initialisation, le service d'installation ActiveX se charge de le valider puis de l'installer.
IV-B. Description du mécanisme d'approbation▲
Le mécanisme d'approbation procède ainsi :
- lorsqu'un tag object est détecté sur la page, le service vérifie si le GUID existe ;
- si l'ActiveX n'est pas installé, le service vérifie si la ressource définie par l'attribut codebase existe ;
- extrait le certificat avec lequel on a signé le fichier CAB puis lance la séquence d'approbation ;
- si le certificat est valide, le service d'installation ActiveX décompresse le contenu de l'archive CAB dans le répertoire temporaire de la session utilisateur ;
- ce même service lit ensuite le fichier d'information et installe les composants déclarés dans le répertoire «%WinDir%\Downloaded Program Files». Ce fichier nommé comme on le désire, porte l'extension .inf. ex. : MonClient.inf ;
- la page est ensuite réactualisée ;
- si le composant est validé, il est chargé au niveau du tag object ;
- optionnellement, si le niveau de sécurité est moyen ou élevé, IE vérifie la sécurité configurée au niveau du composant.
IV-C. Le Fichier INF▲
À présent, voyons de plus près la structure du fichier d'informations.
Avec une première réflexion, le fichier peut être saisi comme ceci :
[version]
signature
=
"$CHICAGO$"
AdvancedINF
=
2
.0
[Add.Code]
MonClient.dll=MonClient.dll
MonClient.tlb=MonClient.tlb
[MonClient.dll]
FileVersion
=
1
,0
,0
,0
[MonClient.tlb]
FileVersion
=
1
,0
,0
,0
clsid
=
{clsid:D0D4D20D-5AD0-4dc4-A9EA-844DFBBA668E}
RegisterServer
=
Yes
Dans cet exemple, on a déclaré l'assembly et le TLB dans la section App.Code, chaque référence a une section qui décrit l'identité du fichier, et la façon dont il sera installé puis l'inscrit sur le poste client. L'inscription de l'ActiveX dans la base de registre se fait grâce à l'attribut « RegisterServer=Yes » dans notre exemple du fichier MonClient.tlb.
En tant que COM Interop, l'opération est un peu plus difficile. MonClient.dll étant un composant .NET ne pourra pas fonctionner, car le service d'installation ne gère pas l'inscription des assembly .NET. Celui-ci doit être inscrit en tant que COM Interop et générer un TLB. Puis ce fichier TLB doit être enregistré dans la base de registre. C'est ce que fait l'outil regasm.exe.
Il va nous falloir aller un peu plus loin dans notre réflexion…
IV-D. Création du projet d'installation msi▲
La solution pour nous va être de créer une installation, sous Visual Studio, dans nouveau projet > autre type de projet > projet d'installation.
Dans le projet du composant, on implémente une classe Installer qui encapsule tout le code nécessaire pour l'enregistrement en tant que COM Interop.
IV-E. Enregistrement de l'assembly▲
L'enregistrement COM Interop, se fait en deux étapes.
À partir de l'assembly.NET, on enregistre la base de code dans la base de registre. Pour cela on utilise la classe RegistrationServices présente dans le namespace System.Runtime.InteropServices.
Assembly thisAssemply =
Assembly.
GetExecutingAssembly
(
);
RegistrationServices regSvr =
new
RegistrationServices
(
);
regSvr.
RegisterAssembly
(
thisAssemply,
AssemblyRegistrationFlags.
SetCodeBase);
Puis on crée le fichier TLB qu'on enregistre ensuite dans la base de registre.
Cela est équivalent à faire un regsvr32.exe [Fichier TLB].
Déclaration de la fonction API en C#▲
Pour le faire on va utiliser l'API RegisterTypeLib présente dans le fichier système oleaut32.dll.
[DllImport(
"oleaut32.dll"
, CharSet = CharSet.Unicode, PreserveSig = false)]
private
static
extern
void
RegisterTypeLib
(
ITypeLib TypeLib,
string
szFullPath,
string
szHelpDirs);
- szFullPath est le chemin du fichier pour le référencement par les clients COM, ici notre fichier TLB.
- szHelpDirs est le répertoire de travail, ici l'emplacement du fichier TLB.
- ITypeLib est l'interface de base pour convertir notre assembly MonClient.dll en type __ComObject. Pour cela on va implémenter l'interface COM IUnknow. La conversion sera effectuée via la classe TypeLibConverter. L'état de la conversion des types de l'assembly sera remonté via une classe implémentant l'interface ITypeLibExporterNotifySink.
Pour plus d'informations sur l'implémentation de TypeLibConverter :
Déclaration de l'interface IUnknow▲
[ComImport,
ComVisible
(
false
),
Guid
(
"00020406-0000-0000-C000-000000000046"
),
InterfaceType
(
ComInterfaceType.
InterfaceIsIUnknown)]
internal
interface
ICOMITypeLib
{
void
CreateTypeInfo
(
);
void
SetName
(
);
void
SetVersion
(
);
void
SetGuid
(
);
void
SetDocString
(
);
void
SetHelpFileName
(
);
void
SetHelpContext
(
);
void
SetLcid
(
);
void
SetLibFlags
(
);
void
SaveAllChanges
(
);
}
Séquence de conversion en C# via TypeLibConverter▲
string
directoryName =
Path.
GetDirectoryName
(
asm.
Location);
string
name =
asm.
GetName
(
).
Name;
string
tlbFilePath =
Path.
Combine
(
directoryName,
name) +
".tlb"
;
TypeLibExporterFlags tlExpFlags =
TypeLibExporterFlags.
None;
ITypeLibConverter converter =
new
TypeLibConverter
(
);
TypeLibNotifier notifySink =
new
TypeLibNotifier
(
this
);
ITypeLib typeLib =
(
ITypeLib)converter.
ConvertAssemblyToTypeLib
(
asm,
tlbFilePath,
tlExpFlags,
notifySink);
((
ICOMITypeLib)typeLib).
SaveAllChanges
(
);
RegisterTypeLib
(
typeLib,
tlbFilePath,
Path.
GetDirectoryName
(
tlbFilePath));
Déclaration de la classe de notification▲
internal
class
TypeLibNotifier :
ITypeLibExporterNotifySink
{
public
void
ReportEvent
(
ExporterEventKind eventKind,
int
eventCode,
string
eventMsg)
{
// TODO : gestion des erreurs
}
public
object
ResolveRef
(
Assembly asm)
{
// résoudre les références
return
_installation.
RegisterServer
(
asm);
}
}
IV-F. Marquer le composant comme sécurisé pour les langages de scripts▲
Afin de pouvoir initialiser et piloter le composant via du JavaScript dans les niveaux de sécurité moyens ou élevés, le composant doit être répertorié dans les bonnes catégories d'usage. La liste des catégories est présente dans la base de registre sous la clé « HKEY_CLASSES_ROOT\Component Categories ». Une catégorie est identifiée par un GUID, en voici une liste de ceux qui nous intéressent :
CLSID |
Description |
---|---|
{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29} |
« .NET Category » |
{7DD95801-9882-11CF-9FA9-00AA006C42C4} |
« Controls that are safely scriptable » |
{7DD95802-9882-11CF-9FA9-00AA006C42C4} |
« Controls safely initializable from persistent data » |
Les catégories gérées par notre composant sont visibles sous la clé « HKEY_CLASSES_ROOT\CLSID\{D0D4D20D-5AD0-4DC4-A9EA-844DFBBA668E}\Implemented Categories »
Par défaut, le composant enregistré comporte la catégorie « .NET Category », pour les deux clés restantes, on pourrait simplement les créer. Le problème est que les clés doivent être créées par le créateur propriétaire, ce qui n'est pas le cas lorsque l'opération est lancée depuis le service d'installation de IE, et où les droits sont restreints.
Pour ce faire, on utilise le « ComCat manager » gérable depuis ole32.dll que nous avons déjà vu plus haut. On va réutiliser l'exemple dans l'excellent article de robo583 disponible à cette adresse :
http://www.codeproject.com/KB/COM/RobinComCat.aspx
Un grand merci à lui.
On s'intéresse à la classe CatRegister qu'on va réimporter dans notre projet composant avec la référence Comcat.dll.
L'implémentation se fait ainsi :
// marque le composant comme sécurisé pour le scripting et l'initialisation
CatRegister catReg =
new
CatRegister
(
);
catReg.
RegisterClassImplCategories
(
new
Guid
(
"{D0D4D20D-5AD0-4dc4-A9EA-844DFBBA668E}"
),
new
Guid[]
{
new
Guid
(
"{7DD95801-9882-11CF-9FA9-00AA006C42C4}"
),
new
Guid
(
"{7DD95802-9882-11CF-9FA9-00AA006C42C4}"
)
}
);
// démarque le composant comme sécurisé
CatRegister catReg =
new
CatRegister
(
);
catReg.
UnRegisterClassImplCategories
(
new
Guid
(
"{D0D4D20D-5AD0-4dc4-A9EA-844DFBBA668E}"
),
new
Guid[]
{
new
Guid
(
"{7DD95801-9882-11CF-9FA9-00AA006C42C4}"
),
new
Guid
(
"{7DD95802-9882-11CF-9FA9-00AA006C42C4}"
)
}
);
Aperçu du marquage, après enregistrement des catégories, dans la base de registre :
IV-G. Implémentation de la classe Installer▲
Maintenant, implémentons tout cela dans une classe Installer dans le projet de notre composant.
using
System;
using
System.
Collections.
Generic;
using
System.
ComponentModel;
using
System.
Configuration.
Install;
using
System.
Runtime.
InteropServices;
using
System.
Reflection;
using
System.
Runtime.
InteropServices.
ComTypes;
using
System.
IO;
namespace
MonClient
{
[RunInstaller(true)]
public
partial
class
Installation :
Installer
{
Assembly thisAssemply;
// services d'inscription pour l'utilisation via COM
RegistrationServices regSvr;
public
Installation
(
)
{
InitializeComponent
(
);
thisAssemply =
Assembly.
GetExecutingAssembly
(
);
regSvr =
new
RegistrationServices
(
);
}
public
override
void
Install
(
System.
Collections.
IDictionary stateSaver)
{
base
.
Install
(
stateSaver);
// Enregistre l'assembly en tant que COM Interop
regSvr.
RegisterAssembly
(
thisAssemply,
AssemblyRegistrationFlags.
SetCodeBase);
RegisterServer
(
Assembly.
GetExecutingAssembly
(
));
// marque le composant comme sécurisé pour le scripting
// et l'initialisation
CatRegister catReg =
new
CatRegister
(
);
catReg.
RegisterClassImplCategories
(
new
Guid
(
"{D0D4D20D-5AD0-4dc4-A9EA-844DFBBA668E}"
),
new
Guid[]
{
new
Guid
(
"{7DD95801-9882-11CF-9FA9-00AA006C42C4}"
),
new
Guid
(
"{7DD95802-9882-11CF-9FA9-00AA006C42C4}"
)
}
);
}
public
override
void
Uninstall
(
System.
Collections.
IDictionary savedState)
{
base
.
Uninstall
(
savedState);
//désinstall l'assembly en COM Interop
regSvr.
UnregisterAssembly
(
thisAssemply);
// démarque le composant comme sécurisé
CatRegister catReg =
new
CatRegister
(
);
catReg.
UnRegisterClassImplCategories
(
new
Guid
(
"{D0D4D20D-5AD0-4dc4-A9EA-844DFBBA668E}"
),
new
Guid[]
{
new
Guid
(
"{7DD95801-9882-11CF-9FA9-00AA006C42C4}"
),
new
Guid
(
"{7DD95802-9882-11CF-9FA9-00AA006C42C4}"
)
}
);
}
[DllImport(
"oleaut32.dll"
, CharSet = CharSet.Unicode, PreserveSig = false)]
private
static
extern
void
RegisterTypeLib
(
ITypeLib TypeLib,
string
szFullPath,
string
szHelpDirs);
internal
ITypeLib RegisterServer
(
Assembly asm)
{
string
directoryName =
Path.
GetDirectoryName
(
asm.
Location);
string
name =
asm.
GetName
(
).
Name;
string
tlbFilePath =
Path.
Combine
(
directoryName,
name) +
".tlb"
;
TypeLibExporterFlags tlExpFlags =
TypeLibExporterFlags.
None;
ITypeLibConverter converter =
new
TypeLibConverter
(
);
TypeLibNotifier notifySink =
new
TypeLibNotifier
(
this
);
ITypeLib typeLib =
(
ITypeLib)converter.
ConvertAssemblyToTypeLib
(
asm,
tlbFilePath,
tlExpFlags,
notifySink);
((
ICOMITypeLib)typeLib).
SaveAllChanges
(
);
RegisterTypeLib
(
typeLib,
tlbFilePath,
Path.
GetDirectoryName
(
tlbFilePath));
return
typeLib;
}
}
}
Pour plus d'informations sur les classes Installer : http://msdn.microsoft.com/en-us/library/system.configuration.install.installer.aspx
IV-H. Intégration dans une MSI▲
Au niveau du projet MSI, on référence notre classe installer via un clic droit sur le projet > Affichage > Actions personnalisées.
Pour chaque action (Installer, Valider, Restaurer et Désinstaller), via un clic droit, on sélectionne une action personnalisée.
À la fenêtre de sélection d'éléments, on sélectionne comme sortie celui du projet du composant ActiveX (Client dans ce tutoriel).
IV-I. Nouvelle définition du fichier INF▲
Maintenant on met à jour le fichier d'information, de telle sorte que le service d'installation d'ActiveX d'IE lance notre MSI en mode silence, à l'initialisation du composant.
[Version]
Signature
=
"$Chicago$"
AdvancedINF
=
2
.0
[Setup Hooks]
hook1
=
hook1
[hook1]
run
=
msiexec.exe /i "%EXTRACT_DIR%\ClientInst.msi" /qn
IV-J. Configuration du fichier CAB▲
La création du fichier CAB va se faire avec la commande makecab.exe en ligne de commande.
La configuration du compactage se fait via l'édition d'un fichier ddf.
.OPTION EXPLICIT
.Set Cabinet=on
.Set Compress=on
.Set MaxDiskSize=CDROM
.Set ReservePerCabinetSize=6144
.Set DiskDirectoryTemplate=
.Set CompressionType=MSZIP
.Set CompressionLevel=7
.Set CompressionMemory=21
.Set CabinetNameTemplate = MonClient.cab
.Set DiskDirectory1 = .
"D:\Documents\Projets Dev\Article\ClientInst\Release\ClientInst.msi"
"D:\Documents\Projets Dev\Article\MonClient.inf"
Quelques explications : ReservePerCabinetSize réserve de la place pour la signature ; le nom du fichier qui sera créé (MonClient.cab) est défini par l'attribut CabinetNameTemplate. Enfin après les commandes .Set, on renseigne la liste de fichiers à compacter.
IV-K. Préparation du batch▲
On place nos commandes dans un fichier batch qu'on nomme makecab.cmd. Puis au niveau de la commande makecab.exe, on renseigne le fichier ddf. Enfin on enchaine en lançant via signtool.exe, l'assistance de signature pour signer le fichier cab généré.
MAKECAB.EXE /f "D:\Documents\Article\MonClient.ddf" /L "D:\Documents\Article"
signtool.exe signwizard
Afin que MAKECAB soit reconnu, l'invite de commande de Visual Studio doit être utilisée.
IV-L. Utilitaire SignTool▲
Lorsque le cabinet est créé, on lance la commande signtool.exe afin de signer MonClient.cab avec notre certificat de test.
Signature du fichier CAB▲
Signtool avec le paramètre signwizard charge un assistant en mode graphique.
Dans la première étape, on sélectionne le fichier MonClient.cab.
On sélectionne un type de signature personnalisé
Puis à l'étape suivante, via le bouton « à partir d'un fichier », on sélectionne le fichier certificat, portant l'extension .cer, sur le disque.
L'opération nous renvoie les informations du certificat chargé. On clique sur Suivant. À l'étape du choix de la clé privée, on active la seconde option puis on sélectionne le nom du conteneur de clés qu'on a spécifié avec la commande makecert.
On choisit l'algorithme de hachage sha1.
On laisse les paramètres suivants.
Enfin en dernière étape, on clique sur Terminer pour lancer l'opération.
IV-M. Intégration finale▲
Il ne reste plus qu'à copier le fichier cab dans le répertoire du projet web.
Ceci peut se faire rapidement par un copier/coller directement dans le projet ASP.NET.
À présent, relançons la page Default.aspx.
À présent grâce à la validité du certificat le composant est reconnu comme sécurisé et peut donc être installé.
Le composant s'installe, et est sécurisé pour l'exécution via des scripts ;)
Pour que l'installation fonctionne, l'option COM Interop doit être décochée et le composant désinstallé avec la commande RegASM.exe [Fichier] /u.
V. Conclusion▲
Nous avons vu, au cours de cet article, qu'il est possible de déployer des composants ActiveX en dotNET.
Mais sous Internet Explorer, l'utilisation des ActiveX est l'une des premières causes des failles de sécurité. De plus pour avoir un champ d'action étendu sur Internet, celui-ci requiert un certificat qui présente un certain coût. On ne comparera pas avec les autres navigateurs qui ont préféré renoncer à implémenter COM.
Mais si on s'entend bien avec la DSI ;) d'une entreprise de bureautique « Full Windows », sur un réseau Windows, avec une PKI sur un serveur, et des postes clients avec la Framework.NET installée, cette solution peut être envisagée.
V-A. Et sur les autres navigateurs ?▲
Sous FireFox le déploiement d'ActiveX n'est pas possible néanmoins Mozilla a prévu un équivalent nommé XPCOM. L'implémentation peut se faire en C++. Pour les plus courageux d'entre vous, voici un lien :
Comment écrire un composant XPCOM en C++
V-B. Quelques remerciements▲
Je tiens à remercier Myriam GOEPFERT, Louis-Guillaume MORAND et la rédaction de Developpez.com pour m'avoir soutenu et pour toutes les corrections effectuées.
V-C. Références :▲
Événements activeX .NET :
- http://msdn.microsoft.com/en-us/library/dd8bf0x3.aspx
- http://msdn.microsoft.com/fr-fr/magazine/cc301750(en-us).aspx
- http://support.microsoft.com/kb/555687
Outils de gestion de certificats :
- Open ssl pour créer un pvk à partir d'un .cer http://wiki.cacert.org/wiki/Authenticode
- Test certificate / setReg : http://www.suitable.com/docs/signingcerts.html
- Cert2spc.exe : http://msdn.microsoft.com/fr-fr/library/f657tk8f(VS.80).aspx
- Pvk2pfx : http://msdn.microsoft.com/en-us/library/aa906334.aspx
- MakeCert : http://msdn.microsoft.com/fr-fr/library/bfsktky3(VS.80).aspx
Code signing :
- Code signing: http://msdn.microsoft.com/en-us/library/aa140234(office.10).aspx
- Code signing avec le service d'installation ActiveX de Windows Vista : http://technet.microsoft.com/en-us/magazine/cc137994.aspx
Comment marquer des contrôles ActiveX MFC comme étant sécurisé pour Script et d'initialisation :
http://support.microsoft.com/kb/161873/fr
Implémentation sous FireFox :
http://developer.mozilla.org/En/Plugins:_The_first_install_problemhttp://www.dotnetguru.org/modules.php?op=modload&name=News&file=article&sid=447