Un activeX Dotnet en COM Interop de A à Z

Dung-Tri LÊ

La conception d'un ActiveX est une tâche difficile, les ressources sont nombreuses, mais ne traitent souvent qu'une partie du sujet.
Voilà enfin un article complet, qui détaille étape par étape, la conception, la sécurité et le déploiement d'un ActiveX en COM Interop. ?

- Titre : Un activeX Dotnet en COM Interop de A à Z

- Auteur : Dung-Tri LÊ

- Parution : 9 décembre 2008

- Mise à jour : 9 décembre 2008

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2008 Dung-Tri LÊ. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- I-A. L'essentiel sur les ActiveX

- I-B. Préparation du projet

- I-B-1. Inscription COM Interop

- I-B-1-a. Inscription sous Visual Studio 2005.

- I-B-1-b. Aperçu dans la base de registre

- I-B-1-c. Inscription en ligne de commande

- II. Implémentation

- II-A. Création de classes

- Générer un CLSID valide

- Déclaration de la CLSID

- II-B. Intégration côté Web

- II-C. Les fonctions

- Déclaration au niveau de la classe

- Appel de la fonction en JavaScript

- II-D. Les propriétés

- Définition de la propriété au niveau de la balise objet

- Redéfinition de la propriété en JavaScript

- II-E. Les événements

- II-E-1. Création de l'interface d'événements

- Création du délégué de notification de message

- Implémentation de l'événement au niveau du contrôle utilisateur

- Implémentation côté JavaScript

- Une autre forme possible

- Aperçu sous IE

- Configuration de la sécurité sous IE

- III. Les certificats

- III-A. Les types de certificats et champs d'application

- III-B. Gestion des certificats sous IE

- III-C. Emplacement des certificats du magasin

- III-D. Création du certificat de test

- III-E. Installation du certificat

- IV. Déploiement de l'ActiveX

- IV-A. Déploiement dans un fichier CAB

- IV-B. Description du mécanisme d'approbation

- IV-C. Le Fichier INF

- IV-D. Création du projet d'installation msi

- IV-E. Enregistrement de l'assembly

- Déclaration de la fonction API en C#

- Déclaration de l'interface IUnknow

- Séquence de conversion en C# via TypeLibConverter

- Déclaration de la classe de notification

- IV-F. Marquer le composant comme sécurisé pour les langages de scripts

- IV-G. Implémentation de la classe Installer

- IV-H. Intégration dans une MSI

- IV-I. Nouvelle définition du fichier INF

- IV-J. Configuration du fichier CAB

- IV-K. Préparation du batch

- IV-L. Utilitaire SignTool

- Signature du fichier CAB

- IV-M. Intégration finale

- V. Conclusion

- V-A. Et sur les autres navigateurs ?

- V-B. Quelques remerciements

- V-C. Références :

- VI. Téléchargement

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 :

Usage pour l'inscription :

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.

Usage pour la désinscription:

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.

Aperçu de l'outil '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.

Code html :

<!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.

Code C#:

[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.

Code JavaScript:

<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.

Code C#

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.

Code C#

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.

Code C#:

[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.

Code C#:

[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.

Code C#

/// <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.

Code JavaScript

Une autre forme possible

Une autre solution plus compacte, consiste à déclarer une balise script avec les attributs for et event.

Extrait code XHTML/JavaScript:

&#8230;
<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>
&#8230;

Aperçu sous IE

Après lancement de la page Default.aspx, on doit obtenir ceci :

Aperçu avec Internet Explorer 7

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

Ligne de commande

L'outil se présente ainsi :

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 :

Usage

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 :

Exemple d'une première version de MonClient.inf

[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.

Séquence d'enregistrement en C#

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.

Déclaration de l'API en code C#:

[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 :

http://msdn.microsoft.com/fr-fr/library/system.runtime.interopservices.typelibconverter.convertassemblytotypelib.aspx

Déclaration de l'interface IUnknow

Code C#:

[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

Code C#:

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

Code C#:

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 :

L'enregistrement des catégories

// 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}")
});

La désenregistrement

// 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.

Code C#

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.

Fichier INF

[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.

Définition du 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é.

Contenu du batch :

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++

http://blog.monstuff.com/archives/000192.html

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

VI - Téléchargement

Afin de pouvoir tester le déploiement du composant, n'oubliez pas d'installer le certificat de test (Dung-Tri.pfx)
dans l'autorité de certification racine de confiance.

Retrouver le code source de l'article :

Source (3ko) Miroir