Troubleshooting Interoperability
When interoperating between COM and the managed code of the .NET Framework, you may encounter one or more of the following common issues.
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 Visual Basic 6.0 and earlier versions. In such cases, you can use the MarshalAsAttribute attribute to cause managed types to be exposed as unmanaged types.
Exporting Fixed-Length Strings to Unmanaged Code
In Visual Basic 6.0 and earlier versions, strings are exported to COM objects as sequences of bytes without a null termination character. For compatibility with other languages, Visual Basic 2005 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 Framework Class
Generally, you create an instance of a .NET Framework 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. The following code demonstrates how to create a Command
object in a project that has a reference to the Microsoft ActiveX Data Objects 2.8 Library COM object:
Dim cmd As New ADODB.Command
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 DerivedCommand
Inherits ADODB.CommandClass
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 and earlier versions, values of type Integer assume the .NET Framework 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 2005 classes. Visual Basic 6.0 and earlier versions implicitly create 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 2005 requires that you always create instances of COM objects before you can use their methods. To use these methods in Visual Basic 2005, 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.
' 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
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 2005 project that has a reference to the Microsoft ActiveX Data Objects 2.8 Library COM object.
' 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
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 2005 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 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 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 2005 sometimes raises COM errors such as "Error 0x800A017F CTL_E_SETNOTSUPPORTED" when you 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 2005 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, in a project that has a reference to the Microsoft ActiveX Data Objects 2.8 Library COM object, you can use:
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
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
Tasks
Walkthrough: Implementing Inheritance with COM Objects
Reference
Type Library Importer (Tlbimp.exe)
Type Library Exporter (Tlbexp.exe)
Inherits Statement
MarshalAsAttribute
Concepts
Data Type Changes for Visual Basic 6.0 Users
Introduction to Merge Modules
Merge Module Projects
Global Assembly Cache