Problembehandlung bei Interoperabilität (Visual Basic)

Bei der Interoperabilität zwischen COM und dem verwalteten Code von .NET Framework können die folgenden Probleme auftreten.

Interopmarshalling

Es kann vorkommen, dass Sie Datentypen verwenden müssen, die nicht Teil von .NET Framework sind. Interopassemblys verarbeiten die meisten Aufgaben für COM-Objekte, aber möglicherweise müssen Sie die Datentypen steuern, die verwendet werden, wenn verwaltete Objekte für COM verfügbar gemacht werden. Beispielsweise müssen Strukturen in Klassenbibliotheken den nicht verwalteten Typ BStr für Zeichenfolgen angeben, die an COM-Objekte gesendet werden, die von Visual Basic 6.0 und früheren Versionen erstellt wurden. In solchen Fällen können Sie das Attribut MarshalAsAttribute verwenden, um verwaltete Typen als nicht verwaltete Typen verfügbar zu machen.

Exportieren von Zeichenfolgen mit fester Länge in nicht verwalteten Code

In Visual Basic 6.0 und früheren Versionen werden Zeichenfolgen als Bytesequenzen ohne NULL-Terminierungszeichen in COM-Objekte exportiert. Aus Gründen der Kompatibilität mit anderen Sprachen enthält Visual Basic .NET beim Exportieren von Zeichenfolgen ein Terminierungszeichen. Die beste Möglichkeit zur Beseitigung dieser Inkompatibilität besteht darin, Zeichenfolgen, in denen das Terminierungszeichen fehlt, als Arrays von Byte oder Char zu exportieren.

Exportieren von Vererbungshierarchien

Verwaltete Klassenhierarchien werden vereinfacht, wenn sie als COM-Objekte verfügbar gemacht werden. Wenn Sie beispielsweise eine Basisklasse mit einem Member definieren und eine abgeleitete Klasse, die als COM-Objekt verfügbar gemacht wird, von dieser Basisklasse erbt, können Clients, die die abgeleitete Klasse im COM-Objekt verwenden, die geerbten Member nicht verwenden. COM-Objekte können auf Basisklassenmember nur als Instanzen einer Basisklasse zugreifen, und auch das nur, wenn die Basisklasse ebenfalls als COM-Objekt erstellt wurde.

Überladene Methoden

Sie können zwar überladene Methoden mit Visual Basic erstellen, aber diese werden von COM nicht unterstützt. Wenn eine Klasse, die überladene Methoden enthält, als COM-Objekt verfügbar gemacht wird, werden neue Methodennamen für die überladenen Methoden generiert.

Betrachten Sie beispielsweise eine Klasse mit zwei Überladungen der Synch-Methode. Wenn die Klasse als COM-Objekt verfügbar gemacht wird, können die neuen generierten Methodennamen Synch und Synch_2 lauten.

Die Umbenennung kann bei Consumern des COM-Objekts zu zwei Problemen führen.

  1. Clients erwarten möglicherweise nicht die generierten Methodennamen.

  2. Die generierten Methodennamen in der Klasse, die als COM-Objekt verfügbar gemacht wird, können sich ändern, wenn der Klasse oder deren Basisklasse neue Überladungen hinzugefügt werden. Dies kann zu Versionsverwaltungsproblemen führen.

Um diese beiden Probleme zu lösen, wenn Sie Objekte entwickeln, die als COM-Objekte verfügbar gemacht werden, weisen Sie jeder Methode einen eindeutigen Namen zu, anstatt Überladungen zu verwenden.

Verwenden von COM-Objekten über Interopassemblys

Sie können Interopassemblys fast so verwenden, als wären sie verwalteter Code für die COM-Objekte, die sie repräsentieren. Da sie jedoch keine tatsächlichen COM-Objekte, sondern Wrapper sind, gibt es einige Unterschiede zwischen der Verwendung von Interopassemblys und Standardassemblys. Diese Unterschiede betreffen das Verfügbarmachen von Klassen sowie die Datentypen für Parameter und Rückgabewerte.

Klassen, die sowohl als Schnittstellen als auch als Klassen verfügbar gemacht werden

Im Gegensatz zu Klassen in Standardassemblys werden COM-Klassen in Interopassemblys sowohl als Schnittstelle als auch als eine Klasse verfügbar gemacht, die die COM-Klasse repräsentiert. Der Name der Schnittstelle ist identisch mit dem der COM-Klasse. Der Name der Interopklasse ist identisch mit dem der ursprünglichen COM-Klasse, ihm ist aber das Wort „Class“ angefügt. Ein Beispiel: Angenommen, Sie haben ein Projekt mit einem Verweis auf eine Interopassembly für ein COM-Objekt. Wenn die COM-Klasse MyComClass heißt, zeigen IntelliSense und der Objektbrowser eine Schnittstelle mit dem Namen MyComClass und eine Klasse mit dem Namen MyComClassClass an.

Erstellen von Instanzen einer .NET Framework-Klasse

Im Allgemeinen erstellen Sie eine Instanz einer .NET Framework-Klasse mithilfe der New-Anweisung mit einem Klassennamen. Eine COM-Klasse, die durch eine Interopassembly repräsentiert wird, ist der einzige Fall, in dem Sie die New-Anweisung mit einer Schnittstelle verwenden können. Sofern Sie die COM-Klasse nicht mit einer Inherits-Anweisung verwenden, können Sie die Schnittstelle genauso wie eine Klasse verwenden. Der folgende Code veranschaulicht das Erstellen eines Command-Objekts in einem Projekt, das einen Verweis auf das COM-Objekt der Microsoft ActiveX Data Objects 2.8-Bibliothek enthält:

Dim cmd As New ADODB.Command

Wenn Sie jedoch die COM-Klasse als Basis für eine abgeleitete Klasse verwenden, müssen Sie die Interopklasse verwenden, die die COM-Klasse repräsentiert, wie im folgenden Code gezeigt:

Class DerivedCommand
    Inherits ADODB.CommandClass
End Class

Hinweis

Interopassemblys implementieren implizit Schnittstellen, die COM-Klassen repräsentieren. Sie sollten nicht versuchen, die Implements-Anweisung zu verwenden, um diese Schnittstellen zu implementieren, da dies zu einem Fehler führt.

Datentypen für Parameter und Rückgabewerte

Im Gegensatz zu Membern von Standardassemblys enthalten Interopassemblys möglicherweise Datentypen, die sich von denen unterscheiden, die in der ursprünglichen Objektdeklaration verwendet werden. Obwohl Interopassemblys COM-Typen implizit in kompatible Common Language Runtime-Typen konvertieren, sollten Sie auf die Datentypen achten, die von beiden Seiten verwendet werden, um Laufzeitfehler zu vermeiden. In COM-Objekten, die in Visual Basic 6.0 und früheren Versionen erstellt wurden, wird bei Werten vom Typ Integer davon ausgegangen, dass der entsprechende .NET Framework-Typ Short ist. Es empfiehlt sich, die Eigenschaften importierter Member vor der Verwendung mithilfe des Objektbrowsers zu untersuchen.

COM-Methoden auf Modulebene

Die meisten COM-Objekte werden verwendet, indem mithilfe des Schlüsselworts New eine Instanz einer COM-Klasse erstellt wird und anschließend Methoden des Objekts aufgerufen werden. Eine Ausnahme von dieser Regel sind COM-Objekte, die die COM-Klassen AppObj oder GlobalMultiUse enthalten. Solche Klassen ähneln Methoden auf Modulebene in Visual Basic .NET-Klassen. Visual Basic 6.0 und frühere Versionen erstellen implizit Instanzen solcher Objekte für Sie, wenn Sie eine ihrer Methoden zum ersten Mal aufrufen. In Visual Basic 6.0 können Sie beispielsweise einen Verweis auf die Microsoft DAO 3.6-Objektbibliothek hinzufügen und die DBEngine-Methode aufrufen, ohne zuerst eine Instanz zu erstellen:

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

Für Visual Basic .NET müssen Sie immer Instanzen von COM-Objekten erstellen, bevor Sie deren Methoden verwenden können. Um diese Methoden in Visual Basic zu verwenden, deklarieren Sie eine Variable der gewünschten Klasse, und verwenden Sie das neue Schlüsselwort, um das Objekt der Objektvariablen zuzuweisen. Das Schlüsselwort Shared kann verwendet werden, wenn Sie sicherstellen möchten, dass nur eine Instanz der Klasse erstellt wird.

' 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

Unbehandelte Fehler in Ereignishandlern

Ein häufiges Interopproblem betrifft Fehler in Ereignishandlern, die von COM-Objekten ausgelöste Ereignisse verarbeiten. Solche Fehler werden ignoriert, es sei denn, Sie führen mithilfe von On Error- oder Try...Catch...Finally-Anweisungen explizit eine Überprüfung auf Fehler durch. Das folgende Beispiel stammt aus einem Visual Basic .NET-Projekt, das einen Verweis auf das COM-Objekt der Microsoft ActiveX Data Objects 2.8-Bibliothek enthält.

' 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 =
    "Provider=Microsoft.Jet.OLEDB.4.0;" &
    "Data Source=C:\NWIND.MDB"
    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

In diesem Beispiel wird wie erwartet ein Fehler ausgelöst. Wenn Sie jedoch dasselbe Beispiel ohne den Try...Catch...Finally-Block ausführen, wird der Fehler ignoriert, so als ob Sie die OnError Resume Next-Anweisung verwendet hätten. Ohne Fehlerbehandlung tritt bei der Division durch 0 unbemerkt ein Fehler auf. Da solche Fehler niemals den Fehler „unbehandelte Ausnahme“ auslösen, ist es wichtig, irgendeine Form der Ausnahmebehandlung in Ereignishandlern zu verwenden, die Ereignisse von COM-Objekten behandeln.

Grundlegendes zu COM-Interopfehlern

Ohne Fehlerbehandlung generieren Interopaufrufe häufig Fehler, die nur wenig Informationen bereitstellen. Verwenden Sie nach Möglichkeit eine strukturierte Fehlerbehandlung, um weitere Informationen zu auftretenden Problemen bereitzustellen. Dies kann besonders hilfreich sein, wenn Sie Anwendungen debuggen. Beispiel:

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

Sie finden Informationen wie die Fehlerbeschreibung, HRESULT und die Quelle von COM-Fehlern, indem Sie den Inhalt des Ausnahmeobjekts untersuchen.

Probleme mit ActiveX-Steuerelementen

Die meisten ActiveX-Steuerelemente, die mit Visual Basic 6.0 arbeiten, funktionieren ohne Probleme mit Visual Basic .NET. Die wichtigsten Ausnahmen sind Containersteuerelemente oder Steuerelemente, die visuell andere Steuerelemente enthalten. Hier sehen Sie einige Beispiele für ältere Steuerelemente, die mit Visual Studio nicht ordnungsgemäß funktionieren:

  • Microsoft Forms 2.0-Framesteuerelement

  • Auf-Ab-Steuerelement, auch bekannt als Drehfeld-Drehsteuerelement

  • Sheridan-Registersteuerelement

Es gibt nur wenige Problemumgehungen für Probleme mit nicht unterstützten ActiveX-Steuerelementen. Sie können vorhandene Steuerelemente zu Visual Studio migrieren, wenn Sie den ursprünglichen Quellcode besitzen. Andernfalls können Sie bei Softwareanbietern nach aktualisierten .NET-kompatiblen Versionen von Steuerelementen fragen, um nicht unterstützte ActiveX-Steuerelemente zu ersetzen.

ByRef-Übergeben von ReadOnly-Eigenschaften von Steuerelementen

Visual Basic .NET löst manchmal COM-Fehler wie „Error 0x800A017F CTL_E_SETNOTSUPPORTED“ aus, wenn Sie ReadOnly-Eigenschaften einiger älterer ActiveX-Steuerelemente als ByRef-Parameter an andere Prozeduren übergeben. Ähnliche Prozeduraufrufe von Visual Basic 6.0 lösen keinen Fehler aus, und die Parameter werden so behandelt, als hätten Sie sie anhand des Werts übergeben. Die Visual Basic .NET-Fehlermeldung gibt an, dass Sie versuchen, eine Eigenschaft zu ändern, die keine Set-Prozedur für Eigenschaften aufweist.

Wenn Sie Zugriff auf die aufgerufene Prozedur haben, können Sie diesen Fehler verhindern, indem Sie das Schlüsselwort ByVal verwenden, um Parameter zu deklarieren, die ReadOnly-Eigenschaften akzeptieren. Beispiel:

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

Wenn Sie keinen Zugriff auf den Quellcode für die aufgerufene Prozedur haben, können Sie erzwingen, dass die Eigenschaft anhand des Werts übergeben wird, indem Sie einen zusätzlichen Satz Klammern um die aufrufende Prozedur hinzufügen. In einem Projekt mit einem Verweis auf das COM-Objekt der Microsoft ActiveX Data Objects 2.8-Bibliothek können Sie beispielsweise Folgendes verwenden:

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

Bereitstellen von Assemblys, die Interop verfügbar machen

Mit dem Bereitstellen von Assemblys, die COM-Schnittstellen verfügbar machen, gehen einige Herausforderungen einher. Beispielsweise tritt ein potenzielles Problem auf, wenn verschiedene Anwendungen auf dieselbe COM-Assembly verweisen. Diese Situation tritt häufig ein, wenn eine neue Version einer Assembly installiert ist und eine andere Anwendung weiterhin die alte Version der Assembly verwendet. Wenn Sie eine Assembly deinstallieren, die eine DLL gemeinsam mit anderen verwendet, kann es passieren, dass diese unbeabsichtigt auch für die anderen Assemblys nicht mehr verfügbar ist.

Um dieses Problem zu vermeiden, sollten Sie gemeinsam genutzte Assemblys im globalen Assemblycache (GAC) installieren und ein MergeModule für die Komponente verwenden. Wenn Sie die Anwendung nicht im GAC installieren können, sollte sie im CommonFilesFolder in einem versionsspezifischen Unterverzeichnis installiert werden.

Nicht gemeinsam genutzte Assemblys sollten sich neben der aufrufenden Anruf im selben Verzeichnis befinden.

Siehe auch