This documentation is archived and is not being maintained.

Troubleshooting Interoperability

When interoperating between COM and the managed code of the .NET platform, you may encounter one or more of the following common issues.

Interop Marshaling

Exporting Fixed-Length Strings to Unmanaged Code

Exporting Inheritance Hierarchies

Use of COM Objects Through Interop Assemblies

Classes Exposed as Both Interfaces and Classes

Creating Instances of a .NET Class

Data Types for Parameters and Return Values

Module level COM Methods

Unhandled Errors in Event Handlers

ActiveX Control Issues

Passing ReadOnly Properties of Controls ByRef

Deploying Assemblies That Expose Interop

Interop Marshaling

At times, you may need to use data types that are not part of the .NET Framework. Interop assemblies handle most of the work for COM objects, but you may need to control the data types used when managed objects are exposed to COM. For example, structures in class libraries must specify the BStr unmanaged type on strings sent to COM objects created by previous versions of Visual Basic. In such cases, you can use the MarshalAs attribute to cause managed types to be exposed as unmanaged types.

Exporting Fixed-Length Strings to Unmanaged Code

In previous versions of Visual Basic, strings are exported to COM objects as sequences of bytes without a null termination character. For compatibility with other languages, Visual Basic .NET includes a termination character when exporting strings. The best way to address this incompatibility is to export strings lacking the termination character as arrays of Byte or Char.

Exporting Inheritance Hierarchies

Managed class hierarchies flatten out when exposed as COM objects. For example, if you define a base class with a member, and then inherit the base class in a derived class that is exposed as a COM object, clients that use the derived class in the COM object will not be able to use the inherited members. Base class members are accessible from COM objects only as instances of a base class, and then only if the base class is also created as a COM object.

Use of COM Objects Through Interop Assemblies

You use interop assemblies almost as though they are managed code replacements for the COM objects they represent. However, because they are wrappers and not actual COM objects, there are some differences between using interop assemblies and standard assemblies. These areas of difference include the exposure of classes, and data types for parameters and return values.

Classes Exposed as Both Interfaces and Classes

Unlike classes in standard assemblies, COM classes are exposed in interop assemblies as both an interface and a class that represents the COM class. The interface's name is identical to that of the COM class. The name of the interop class is the same as that of the original COM class, but with the word "Class" appended. For example, suppose you have a project with a reference to an interop assembly for a COM object. If the COM class is named MyComClass, IntelliSense and the Object Browser show an interface named MyComClass and a class named MyComClassClass.

Creating Instances of a .NET Class

Generally, you create an instance of a .NET class using the New statement with a class name. Having a COM class represented by an interop assembly is the one case in which you can use the New statement with an interface. Unless you are using the COM class with an Inherits statement, you can use the interface the same way as you would a class, as in the following code:

Dim Class1 As New MyComClass()

However, if you are using the COM class as the base for a derived class, you must use the interop class that represents the COM class, as in the following code:

Class DerivedClass
   Inherits MyComClass.AddClassClass
End Class
Note   Interop assemblies implicitly implement interfaces that represent COM classes. You should not attempt to use the implements statement to implement these interfaces or an error will result.

Data Types for Parameters and Return Values

Unlike members of standard assemblies, interop assembly members may have data types that are different than those used in the object's original declaration. Although interop assemblies implicitly convert COM types to compatible common language runtime types, you should pay attention to the data types used by both sides to prevent runtime errors. For example, in COM objects created with Visual Basic 6.0, values of type Integer assume the .NET equivalent type, Short. It is recommended that you use the Object Browser to examine the characteristics of imported members before using them.

Module level COM methods

Most COM objects are used by creating an instance of a COM class using the New keyword and then calling methods of the object. One exception to this rule involves COM objects that contain "AppObj" or "GlobalMultiUse" COM classes. Such classes are similar to module level methods in Visual Basic .NET classes. Visual Basic 6.0 implicitly creates instances of such objects for you the first time you call one of their methods. For example, in Visual Basic 6.0 you can add a reference to the Microsoft DAO 3.6 Object Library and call the DBEngine method without first creating an instance:

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

Visual Basic .NET requires that you always create instances of COM objects before you can use their methods. To use these methods in Visual Basic .NET, declare a variable of the desired class and use the new keyword to assign the object to the object variable. The Shared keyword can be used when you want to make sure that only one instance of the class is created.

' Declarations 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 & ";")
      Next
      Debug.WriteLine("")
      ' Close the recordset
      rst.Close()
   End Sub

Unhandled Errors in Event Handlers

One common interop problem involves errors in event handlers that handle events raised by COM objects. Such errors are ignored unless you specifically check for errors using On Error or Try...Catch...Finally statements. For example, the following example is from a Visual Basic .NET project that has a reference to the ADODB COM object.

' To use this example, add a reference to ADODB 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 = 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

This example raises an error as expected. However, if you try the same example without the Try...Catch...Finally block, the error is ignored as if you used the OnError Resume Next statement. Without error handling, the division by zero silently fails. Because such errors never raise unhandled exception errors, it is vital that you use some form of exception handling in event handlers that handle events from COM objects.

Understanding COM interop errors

Without error handling, interop calls often generate errors that provide little information. Whenever possible, use structured error handling to provide more information about problems when they occur. This can be particularly helpful when debugging applications For example:

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

You can find out information such as the error description, HRESULT, and the source of COM errors by examining the contents of the exception object.

ActiveX Control Issues

Most ActiveX controls that work with Visual Basic 6.0 work with Visual Basic .NET without trouble. The main exceptions are container controls, or controls that visually contain other controls. Some examples of older controls that do not work correctly with Visual Studio .NET are:

  • Microsoft Forms 2.0 Frame control
  • Up-Down control, also known as the spin control
  • Sheridan Tab Control

There are only a few workarounds for unsupported ActiveX control problems. You can migrate existing controls to Visual Studio .NET if you own the original source code. Otherwise, you can check with software vendors for updated .NET compatible versions of controls to replace unsupported ActiveX controls.

Passing ReadOnly Properties of Controls ByRef

Visual Basic .NET sometimes raises COM errors such as "Error 0x800A017F CTL_E_SETNOTSUPPORTED" when you to pass ReadOnly properties of some older ActiveX controls as ByRef parameters to other procedures. Similar procedure calls from Visual Basic 6.0 do not raise an error, and the parameters are treated as if you passed them by value. The error message you see in Visual Basic .NET is the COM object reporting that you are attempting to change a property that does not have a property Set procedure.

If you have access to the procedure being called, you can prevent this error by using the ByVal keyword to declare parameters that accept ReadOnly properties. For example:

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

If you do not have access to the source code for the procedure being called, you can force the property to be passed by value by adding an extra set of brackets around the calling procedure. For example:

Sub PassByVal ()
' The extra set of parens around the arguments
' forces them to be passed by value.
   ProcessParams((Me.AxListView1.ListItems))
End Sub

Deploying Assemblies That Expose Interop

Deploying assemblies that expose COM interfaces presents some unique challenges. For example, a potential problem arises when separate applications reference the same COM assembly. This situation is common when a new version of an assembly is installed and another application is still using the old version of the assembly. Uninstalling any assembly that shares a dll can unintentionally make it unavailable to the other assemblies.

To avoid this problem, you should install shared assemblies to the Global Assembly Cache (GAC) and use a MergeModule for the component. If you are unable to install the application in the GAC, then it should be installed to CommonFilesFolder in a version-specific subdirectory.

Assemblies that are not shared should be placed side by side in the directory with the calling application.

See Also

COM Interop | MarshalAsAttribute Class | Interop Marshaling | Data Type Changes in Visual Basic | Type Library Importer (Tlbimp.exe) | Type Library Exporter (Tlbexp.exe) | Walkthrough: Implementing Inheritance with COM Objects | Inherits Statement | About Merge Modules | Merge Module Projects | Global Assembly Cache

Show: