Dépannage des problèmes liés à l'interopérabilité (Visual Basic)

Lorsque vous interagissez entre COM et le code managé de .NET Framework, vous pouvez rencontrer un ou plusieurs des problèmes courants suivants.

Marshaling d’interopérabilité

Parfois, vous devrez peut-être utiliser des types de données qui ne font pas partie du .NET Framework. Les assemblies d’interopérabilité gèrent la plupart du travail pour les objets COM, mais vous devrez peut-être contrôler les types de données utilisés lorsque des objets managés sont exposés à COM. Par exemple, les structures des bibliothèques de classes doivent spécifier le type non managé BStr sur les chaînes envoyées aux objets COM créés par Visual Basic 6.0 et versions antérieures. Dans ce cas, vous pouvez utiliser l’attribut MarshalAsAttribute pour exposer des types managés en tant que types non managés.

Exportation de chaînes de longueur fixe vers du code non managé

Dans Visual Basic 6.0 et versions antérieures, les chaînes sont exportées vers des objets COM sous forme de séquences d’octets sans caractère d’arrêt Null. Pour la compatibilité avec d’autres langages, Visual Basic .NET inclut un caractère d’arrêt lors de l’exportation de chaînes. La meilleure façon de traiter cette incompatibilité consiste à exporter des chaînes qui n’ont pas le caractère d’arrêt sous forme de tableaux Byte ou Char.

Exportation des hiérarchies d’héritage

Les hiérarchies de classes managées s’aplatissent lorsqu’elles sont exposées en tant qu’objets COM. Par exemple, si vous définissez une classe de base avec un membre, puis héritez de la classe de base dans une classe dérivée exposée en tant qu’objet COM, les clients qui utilisent la classe dérivée dans l’objet COM ne pourront pas utiliser les membres hérités. Les membres de classe de base sont accessibles à partir d’objets COM uniquement en tant qu’instances d’une classe de base, puis uniquement si la classe de base est également créée en tant qu’objet COM.

Méthodes surchargées

Bien que vous puissiez créer des méthodes surchargées avec Visual Basic, elles ne sont pas prises en charge par COM. Lorsqu’une classe qui contient des méthodes surchargées est exposée en tant qu’objet COM, de nouveaux noms de méthodes sont générés pour les méthodes surchargées.

Imaginez par exemple une classe qui a deux surcharges de la méthode Synch. Lorsque la classe est exposée en tant qu’objet COM, les nouveaux noms de méthode générés peuvent être Synch et Synch_2.

Le changement de nom peut entraîner deux problèmes pour les consommateurs de l’objet COM.

  1. Les clients peuvent ne pas s’attendre à ces noms de méthode générés.

  2. Les noms de méthode générés dans la classe exposée en tant qu’objet COM peuvent changer lorsque de nouvelles surcharges sont ajoutées à la classe ou à sa classe de base. Cela peut entraîner des problèmes de contrôle de version.

Pour résoudre les deux problèmes, donnez à chaque méthode un nom unique au lieu d’utiliser une surcharge lorsque vous développez des objets qui seront exposés en tant qu’objets COM.

Utilisation d’objets COM via des assemblies d’interopérabilité

Vous utilisez des assemblies d’interopérabilité presque comme s’ils étaient des remplacements de code managé pour les objets COM qu’ils représentent. Toutefois, étant donné qu’ils sont des wrappers et non de véritables objets COM, il existe des différences entre l’utilisation d’assemblies d’interopérabilité et l’utilisation d’assemblies standard. Ces domaines de différence incluent l’exposition des classes et les types de données pour les paramètres et les valeurs de retour.

Classes exposées en tant qu’interfaces et classes

Contrairement aux classes dans les assemblies standard, les classes COM sont exposées dans les assemblies d’interopérabilité comme une interface et une classe qui représente la classe COM. Le nom de l’interface est identique à celui de la classe COM. Le nom de la classe d’interopérabilité est le même que celui de la classe COM d’origine, mais avec le mot « Classe » ajouté. Par exemple, supposons que vous disposez d’un projet avec une référence à un assembly d’interopérabilité pour un objet COM. Si la classe COM est nommée MyComClass, IntelliSense et l’Explorateur d’objets affichent une interface nommée MyComClass et une classe nommée MyComClassClass.

Création d’instances d’une classe .NET Framework

En règle générale, vous créez une instance d’une classe .NET Framework à l’aide de l’instruction New avec un nom de classe. La présence d’une classe COM représentée par un assembly d’interopérabilité est le seul cas dans lequel vous pouvez utiliser l’instruction New avec une interface. À moins que vous n’utilisiez la classe COM avec une instruction Inherits, vous pouvez utiliser l’interface comme vous le feriez pour une classe. Le code suivant montre comment créer un objet Command dans un projet qui a une référence à l’objet COM de bibliothèque Microsoft ActiveX Data Objects 2.8 :

Dim cmd As New ADODB.Command

Toutefois, si vous utilisez la classe COM comme base pour une classe dérivée, vous devez utiliser la classe d’interopérabilité qui représente la classe COM, comme dans le code suivant :

Class DerivedCommand
    Inherits ADODB.CommandClass
End Class

Notes

Les assemblies d’interopérabilité implémentent implicitement des interfaces qui représentent des classes COM. Vous ne devez pas essayer d’utiliser l’instruction Implements pour implémenter ces interfaces, sous peine qu’une erreur se produise.

Types de données pour les paramètres et les valeurs de retour

Contrairement aux membres d’assemblies standard, les membres d’un assembly d’interopérabilité peuvent avoir des types de données qui diffèrent de ceux utilisés dans la déclaration d’objet d’origine. Bien que les assemblies d’interopérabilité convertissent implicitement les types COM en types common language runtime compatibles, vous devez prêter attention aux types de données utilisés par les deux côtés pour empêcher les erreurs d’exécution. Par exemple, dans les objets COM créés dans Visual Basic 6.0 et versions antérieures, les valeurs de type Integer supposent le type équivalent .NET Framework, Short. Il est recommandé d’utiliser l’Explorateur d’objets pour examiner les caractéristiques des membres importés avant de les utiliser.

Méthodes COM au niveau du module

La plupart des objets COM sont utilisés en créant une instance d’une classe COM à l’aide du mot clé New, puis en appelant des méthodes de l’objet. Une exception à cette règle implique des objets COM qui contiennent des classes COM AppObj ou GlobalMultiUse. Ces classes ressemblent à des méthodes de niveau module dans les classes .NET Visual Basic. Visual Basic 6.0 et versions antérieures créent implicitement pour vous des instances de ces objets la première fois que vous appelez l’une de leurs méthodes. Par exemple, dans Visual Basic 6.0, vous pouvez ajouter une référence à la bibliothèque d’objets Microsoft DAO 3.6 et appeler la méthode DBEngine sans commencer par créer une instance :

Dim db As DAO.Database  
' Open the database.  
Set db = DBEngine.OpenDatabase("C:\nwind.mdb")  
' Use the database object.  

Visual Basic .NET nécessite que vous créiez toujours des instances d’objets COM avant de pouvoir utiliser leurs méthodes. Pour utiliser ces méthodes dans Visual Basic, déclarez une variable de la classe souhaitée et utilisez le nouveau mot clé pour attribuer l’objet à la variable objet. Le mot clé Shared peut être utilisé lorsque vous souhaitez vous assurer qu’une seule instance de la classe est créée.

' Class level variable.
Shared DBEngine As New DAO.DBEngine

Sub DAOOpenRecordset()
    Dim db As DAO.Database
    Dim rst As DAO.Recordset
    Dim fld As DAO.Field
    ' Open the database.
    db = DBEngine.OpenDatabase("C:\nwind.mdb")

    ' Open the Recordset.
    rst = db.OpenRecordset(
        "SELECT * FROM Customers WHERE Region = 'WA'",
        DAO.RecordsetTypeEnum.dbOpenForwardOnly,
        DAO.RecordsetOptionEnum.dbReadOnly)
    ' Print the values for the fields in the debug window.
    For Each fld In rst.Fields
        Debug.WriteLine(fld.Value.ToString & ";")
    Next
    Debug.WriteLine("")
    ' Close the Recordset.
    rst.Close()
End Sub

Erreurs non gérées dans les gestionnaires d’événements

Un problème d’interopérabilité courant implique des erreurs dans les gestionnaires d’événements qui gèrent les événements déclenchés par des objets COM. Ces erreurs sont ignorées, sauf si vous recherchez spécifiquement des erreurs à l’aide des instructions On Error ou Try...Catch...Finally. Par exemple, l’exemple suivant provient d’un projet Visual Basic .NET qui a une référence à l’objet COM de bibliothèque Microsoft ActiveX Data Objects 2.8.

' To use this example, add a reference to the
'     Microsoft ActiveX Data Objects 2.8 Library
' from the COM tab of the project references page.
Dim WithEvents cn As New ADODB.Connection
Sub ADODBConnect()
    cn.ConnectionString = "..."
    cn.Open()
    MsgBox(cn.ConnectionString)
End Sub

Private Sub Form1_Load(ByVal sender As System.Object,
    ByVal e As System.EventArgs) Handles MyBase.Load

    ADODBConnect()
End Sub

Private Sub cn_ConnectComplete(
    ByVal pError As ADODB.Error,
    ByRef adStatus As ADODB.EventStatusEnum,
    ByVal pConnection As ADODB.Connection) Handles cn.ConnectComplete

    '  This is the event handler for the cn_ConnectComplete event raised
    '  by the ADODB.Connection object when a database is opened.
    Dim x As Integer = 6
    Dim y As Integer = 0
    Try
        x = CInt(x / y) ' Attempt to divide by zero.
        ' This procedure would fail silently without exception handling.
    Catch ex As Exception
        MsgBox("There was an error: " & ex.Message)
    End Try
End Sub

Cet exemple lève une erreur comme prévu. Toutefois, si vous essayez le même exemple sans le bloc Try...Catch...Finally, l’erreur est ignorée comme si vous utilisiez l’instruction OnError Resume Next. Sans gestion des erreurs, la division par zéro échoue silencieusement. Étant donné que ces erreurs ne lèvent jamais d’erreurs d’exception non gérées, il est important d’utiliser une certaine forme de gestion des exceptions dans les gestionnaires d’événements qui gèrent les événements à partir d’objets COM.

Compréhension des erreurs d’interopérabilité COM

Sans gestion des erreurs, les appels d’interopérabilité lèvent souvent des erreurs qui fournissent peu d’informations. Dans la mesure du possible, utilisez la gestion structurée des erreurs pour fournir plus d’informations sur les problèmes lorsqu’ils se produisent. Cela peut être particulièrement utile lorsque vous déboguez des applications. Par exemple :

Try
    ' Place call to COM object here.
Catch ex As Exception
    ' Display information about the failed call.
End Try

Vous trouverez des informations telles que la description des erreurs, HRESULT et la source des erreurs COM en examinant le contenu de l’objet exception.

Problèmes de contrôle ActiveX

La plupart des contrôles ActiveX qui fonctionnent avec Visual Basic 6.0 fonctionnent sans problème avec Visual Basic .NET. Les principales exceptions sont les contrôles de conteneur ou les contrôles qui contiennent visuellement d’autres contrôles. Voici quelques exemples de contrôles plus anciens qui ne fonctionnent pas correctement avec Visual Studio :

  • Contrôle Frame Microsoft Forms 2.0

  • Contrôle haut/bas, également appelé contrôle Spin

  • Contrôle Tab Sheridan

Il n’existe que quelques solutions de contournement pour les problèmes de contrôle ActiveX non pris en charge. Vous pouvez migrer des contrôles existants vers Visual Studio si vous possédez le code source d’origine. Sinon, vous pouvez vérifier auprès des fournisseurs de logiciels s’il est possible que des versions compatibles .NET mises à jour des contrôles remplacent les contrôles ActiveX non pris en charge.

Passage des propriétés ReadOnly des contrôles ByRef

Visual Basic .NET lève parfois des erreurs COM telles que « Erreur 0x800A017F CTL_E_SETNOTSUPPORTED », lorsque vous passez des propriétés ReadOnly de certains contrôles ActiveX plus anciens en tant que paramètres ByRef à d’autres procédures. Les appels de procédure similaires de Visual Basic 6.0 ne lèvent pas d’erreur et les paramètres sont traités comme si vous les aviez passés par valeur. Le message d’erreur Visual Basic .NET indique que vous essayez de modifier une propriété qui ne dispose pas d’une procédure Set de propriété.

Si vous avez accès à la procédure appelée, vous pouvez empêcher cette erreur à l’aide du mot clé ByVal pour déclarer les paramètres qui acceptent les propriétés ReadOnly. Par exemple :

Sub ProcessParams(ByVal c As Object)
    'Use the arguments here.
End Sub

Si vous n’avez pas accès au code source de la procédure appelée, vous pouvez forcer le passage de la propriété par valeur en ajoutant un ensemble supplémentaire de crochets autour de la procédure appelante. Par exemple, dans un projet qui a une référence à l’objet COM de bibliothèque Microsoft ActiveX Data Objects 2.8, vous pouvez utiliser :

Sub PassByVal(ByVal pError As ADODB.Error)
    ' The extra set of parentheses around the arguments
    ' forces them to be passed by value.
    ProcessParams((pError.Description))
End Sub

Déploiement d’assemblies qui exposent l’interopérabilité

Le déploiement d’assemblies qui exposent des interfaces COM présente des défis uniques. Par exemple, un problème potentiel se produit lorsque des applications distinctes référencent le même assembly COM. Cette situation est courante lorsqu’une nouvelle version d’un assembly est installée et qu’une autre application utilise toujours l’ancienne version de l’assembly. Si vous désinstallez un assembly qui partage un DLL, vous pouvez involontairement le rendre indisponible pour les autres assemblies.

Pour éviter ce problème, vous devez installer des assemblies partagés dans le Global Assembly Cache (GAC) et utiliser un MergeModule pour le composant. Si vous ne pouvez pas installer l’application dans le GAC, elle doit être installée sur CommonFilesFolder dans un sous-répertoire spécifique à la version.

Les assemblies qui ne sont pas partagés doivent se trouver côte à côte avec l’application appelante dans le répertoire.

Voir aussi