Confronté récemment à une problématique d’intéropérabilité VB / .NET, je souhaitais poster ce ticket qui je l’espère pourra servir à certains.

Nous avons tous un jour ou l’autre dû dans nos développements sous .NET invoquer un « vieux » composant COM réalisé en VB6, en C++ ou en Delphi… Cela se fait assez facilement dans Visual Studio 2005 ou 2008, et l’objet COM exposé apparait comme un objet .NET. Ceci est rendu possible grâce au wrapper RCW (Runtime Callable Wrapper) dont la fonction principale est de « marchaliser » les appels entre un client .NET et l’objet COM invoqué.

Mais nous sommes surement moins nombreux à avoir été confronté à la problématique inverse. A savoir, comment exposer un objet .NET à un composant COM existant.

La problématique est moins triviale car un composant COM développé en VB6 ou en C++ unmanaged n’est pas capable de dialoguer nativement avec une assembly .NET.  Il existe heureusement un Wrapper .NET qui permet d’exposer un objet .NET sous la forme d’un composant COM. C’est la fonction du CCW «COM Callable Wrapper ».

 Je ne vais pas dans ce ticket décrire toutes les étapes nécessaires pour réaliser cela, car il existe des liens, notamment sur MSDN qui le font très bien. Cf : http://msdn.microsoft.com/en-us/library/f07c8z1c(VS.71).aspx

Comme souvent, les exemples que l’on trouve nous montrent comment renvoyer une simple chaine de caractères ou un objet. Toutefois, lorsqu’il s’agit de passer une collection d’objets complexes définis en VB6 en paramètre d’une méthode d’une classe .NET ou de renvoyer une collection d’objets cela se complique un peu…

En fait, même si vous avez fait tout ce qu’il faut pour exposer une classe .NET en COM via CCW en décorant votre classe avec les attributs qui vont bien  [GUID()] [ComVible()] etc… et utilisé l’utilitaire Regasm.exe, vous arrivez bien à renvoyer un objet vers votre client COM mais pas une collection de cet objet.

Euh… un petit exemple peut être…

Voilà, je souhaite invoquer la méthode « GetUtilisateur » de  la classe .NET « ProxyUtilisateur  » d’une assembly « ClassProxyRefCOM » exposée en COM  pour qu’elle me renvoie un objet « MappingUtilisateur ».

En VB6, cela donne un truc du genre :

 
Dim oProxyService As ClassProxyRefCOM.ProxyUtilisateur
Dim oMappingUser As ClassProxyRefCOM.MappingUtilisateur
Set oProxyService = CreateObject("ClassProxyRefCOM.ProxyUtilisateur")
Set oMappingUser = oProxyService.GetUtilisateur(UserNT)

Pour se faire, il a fallu nous seulement décorer et exposer la classe ProxyUtilisateur, mais également la classe « MappingUtilisateur » pour qu’elle soit connue comme un objet COM.

En C#, cela donne un truc comme cela :

 

[Guid("3661703D-2BB9-46a2-8723-72B78B7E0390")]

[ComVisible(true)] 

public class MappingUtilisateur : ClassProxyRefCOM.IMappingUtilisateur 

{

     private string _prenom;

     private string _nom;

      private string _email;

        private string _profilNT; 

       public MappingUtilisateur()        {}

       public string Prenom   {    get { return _prenom; }    set { _prenom = value; }        }

       public string Nom        {    get { return _nom; }     set { _nom = value; }        }

        ……  

} 

Au passage, n’oubliez pas de cocher l’option « Register for COM Interop » dans les propriétés de votre projet .NET. Cette option fait la registration de la dll en COM dans la base des registres pour vous et crée le fichier tlb que vous pouvez ensuite utiliser dans Visual Studio 6 en référence de votre projet VB6. Enfin je ne vais pas vous refaire le cours…

Au final cela fonctionne correctement !!!

Toutefois imaginer maintenant que vous vouliez renvoyer une collection d’objet MappingUtilisateur, via une méthode GetListUtilisteurs(). Et bien vous ne pouvez pas exposer en COM une collection d’objets sans passer par une astuce. Celle-ci consiste à exposer un objet qui hérite d’une …. ArrayList .NET.

En C#, cela donne du code du style :

[Guid("F5113128-784F-4125-8AA4-A00F9762C73C")]

 [ComVisible(true)]

 public class MyCollectionCOM :ArrayList

 {

        public MyCollectionCOM() : base()        { }  

       public object GetByIndex(int idx)        return this[idx];   }

       public int NbElem()    {     return this.Count;     }

  }

De ce fait, vous pouvez déclarer un objet « MyCollectionCOM » en VB6 capable de recevoir votre collection de « MappingUtilisateur » :

Dim oProxyService As ClassProxyRefCOM.ProxyUtilisateur 
Set oProxyService = CreateObject("ClassProxyRefCOM.ProxyUtilisateur")
Dim arr As ClassProxyRefCOM.MyCollectionCOM
Set arr = oProxyService.GetListUtilisateurs()

Dans un prochain ticket, je parlerai du mécanisme de libération des objets CCW et .NET et du « profiler » de mémoire qui m’a permis de m’en assurer…

Laurent Nyffels