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

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.
http://dotnet.developpez.com/cours/interopcom/
http://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.

option 'COM Interop' sous visual studio

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 comme 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}

l'outil regedit.exe

Il existe également une autre clé qui permet de renvoyer le CLSID à partir du ProgId :
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\MonClient.Controle\CLSID

l'outil regedit.exe

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 :
Sélectionnez

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:
Sélectionnez

RegAsm.exe MonAssembly.dll /u

II. Implémentation

A présent, passons au code !

II-A. Création de classe

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.

Outil intégré dans Visual Studio : '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.

 
Sélectionnez

[ComVisible(true)]
[Guid("D0D4D20D-5AD0-4dc4-A9EA-844DFBBA668E")]    
public partial class Controle : UserControl
{
    public Controle()
    {
        InitializeComponent();
    }
}

II-B. Intégration coté Web

A 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 :
Sélectionnez

<!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 coté C# se fait comme avec les assembly dotNet cliente. On définit une méthode avec une portée publique.

Code C#:
Sélectionnez

[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:
Sélectionnez

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

 
Sélectionnez

<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 leurs 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#:
Sélectionnez

[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#:
Sélectionnez

[ComVisible(false)]
public delegate void MessageDisplayedDelegate(string message);

Implémentation de l'évemenent au niveau du contrôle utilisateur.

On ajoute ensuite l'attribut ComSourceInterfaces afin de présenter au client COM l'interface à utiliser pour les évenements.

Code C#
Sélectionnez

/// <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 évenements
[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 coté 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 dernière appel via JavaScript
        LastMessageLabel.Text = "Dernier message: " + message; 
        // Affiche le messageBox via les Windows Forms
        MessageBox.Show(message, _caption);
		// déclenche l'evenement qui alerte la page web que le message est bien recu. 
        RaiseMessageBox(message); 
    }

    /// <summary>
    /// Déclenche l'evenement de notification de reception du dernier message
    /// </summary>
    /// <param name="message">Message à notifié vers la page Web</param>
    private void RaiseMessageBox(string message)
    {
        if (MessageDisplayed != null)
        {
            try
            {
                MessageDisplayed("message affiché :" + message);
            }
            catch (Exception e)
            {
                // echec lors de l'invocation de la fonction javascript
                MessageBox.Show(e.ToString());
            }

        }
        else
            MessageBox.Show("RaiseMessageBox est nulle");
    }
}

Implémentation côté JavaScript

Coté 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:
Sélectionnez
<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és du serveur.</span>
</object>
<script for="clientActiveXDotnet" 
        event="MessageDisplayed(strMessage)" 
        language="javascript">
   function clientActiveXDotnet::MessageDisplayed(strMessage)
   {
       alert("Retour d'evenement (2eme méthode) : " + strMessage);
   }
</script>
</body>

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 :
    • Connecter vous sur votre site.
    • Aller dans l'onglet sécurité, sélectionner « Sites de confiances » puis cliquer 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 « exigé un serveur sécurisé... »
    • Enfin cliquer 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és 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'applications

Pour pouvoir être exécuté sous Internet Explorer, le composant ActiveX doit être signé avec un certificat de confiance.

3 types de certificat 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 certificat, 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é 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 tous 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:

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 2 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 saisie certmgr.msc puis on click sur Ok

Démarrer > Exécuter...

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.

Image non disponible

La liste de certificats est accessible via l'onglet Contenu.

Image non disponible

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 :

Image non disponible

Le répertoire SystemCertificates représente la liste des dossiers du magasin.

III-D. Création du certificate 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
Sélectionnez

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

III-E. Installation du certificat

En confirmant par Oui, et grâce à l'option -ss Root, le certificat est directement installé dans le CA locale.

Image non disponible

Sous la MMC de gestion des certificats, vérifions si le certificat est présent.

Dans le dossier "Autorités de certification racines de confiance", on le retrouve dans la liste de certificat. Via un double click droit, l'utilitaire nous charge les diverses propriétés.

On remarque aussi que tous les certificats de tests 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 tests soient valide, assurez qu'il est bien présent dans ce dossier.

Image non disponible

Dans l'onglet " Chemin d'accès de certificat " vérifions les informations du certificat.

Image non disponible

Si tout se passe bien, un peu plus bas, dans "Etat 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 2 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

A 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
Sélectionnez

[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 2 étapes :

A 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#
Sélectionnez

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ésent dans le fichier système oleaut32.dll.

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

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

Image non disponible

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#:
Sélectionnez

[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 convertion en C# via TypeLibConverter

Code C#:
Sélectionnez

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#:
Sélectionnez

internal class TypeLibNotifier : ITypeLibExporterNotifySink
{
    public void ReportEvent(ExporterEventKind eventKind, int eventCode, 
	string eventMsg)
    {
        // TODO : gestion des erreurs
    }

    public object ResolveRef(Assembly asm)
    {
        // résoude 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 visible 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 2 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
Sélectionnez

// 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
Sélectionnez

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

Image non disponible

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#
Sélectionnez


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'information 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 click droit sur le projet > Affichage > Actions personnalisées.

Image non disponible

Pour chaque action (Installer, Valider, Restaurer et Désinstaller), via un click droit, on sélectionne une action personnalisée.

Image non disponible

A la fenêtre de sélection d'élément, on sélectionne comme sortie celui du projet du composant ActiveX (Client dans ce tutoriel).

Image non disponible

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
Sélectionnez

[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.
Sélectionnez

.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 fichier à 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 lancant via signtool.exe, l'assistance de signature pour signer le fichier cab généré.

Contenu du batch:
Sélectionnez

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

Image non disponible

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.

Image non disponible

Dans la première étape, on sélectionne le fichier MonClient.cab.

Image non disponible

On sélectionne un type de signature personnalisé

Image non disponible

Puis à l'étape suivante, via le bouton "à partir d'un fichier", on sélectionne le fichier certificat, portant l'extension .cer, sur le disque.

Image non disponible

Image non disponible

L'opération nous renvoie les informations du certificat chargé. On clique sur Suivant. A l'étape du choix de la clé privée, on active la seconde option puis on sélectionne le nom du conteneur de clé qu'on a spécifié avec la commande makecert.

Image non disponible

On choisit l'algorithme de hachage sha1.

Image non disponible

On laisse les paramètres suivants.

Image non disponible

Image non disponible

Image non disponible

Enfin en dernière étape, on clique sur Terminer pour lancer l'opération.

Image non disponible

IV-M. Intégration final

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.

Image non disponible

A présent, relançons la page Default.aspx.

Image non disponible

A présent grâce à la validité du certificat le composant est reconnu comme sécurisé et peut donc être installé.

Image non disponible

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é et le composant désinstallé avec la commande RegASM.exe [Fichier] /u.

V. Conclusion

Nous avons vu, au court 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 requière 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 :

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 sources de l'article :

Source (3ko) Miroir