Giovanni Palmiotto (Montreal, Canada) is a Senior Software Developer and independent contractor. He has over 10 years of professional experience developing enterprise-level software applications. He is a Microsoft Certified Technology Specialist (MCTS) as well as a Microsoft Certified Professional (MCP). He specializes in Microsoft .NET and SQL Server 2005 technologies.
The Microsoft .NET Framework offers several mechanisms which facilitate the modernization of legacy code. These mechanisms are collectively known as: Interoperability. In this article, I will attempt to demystify some of the complexities surrounding Interoperability as well as anticipated difficulties. Focus will be given to the COM Interop model while a future article will focus on the Platform Invoke (PInvoke) model.
Interoperability - A Primer
Interoperability, or the process of interacting with unmanaged code, is a necessity for many enterprises struggling to take advantage of modern, distributed, architectures. For the most part, rewriting entire applications is cost-prohibitive as resources have already been invested into developing and testing legacy code. In addition, since many services are only available as COM components, they will still be required during the migration process. For instance, there are certain Windows APIs that have yet to be wrapped for the .NET Framework.
The Common Language Runtime (CLR) offers two mechanisms which allow for interoperation with unmanaged code: Platform Invoke (PInvoke) and COM Interop. The Platform Invoke (PInvoke) model enables managed code to call functions exported from unmanaged type libraries thus allowing .NET code to call traditional DLLs. The COM Interop model enables managed code to interact with COM objects through interfaces, allowing for bidirectional communication between .NET and COM.
Additional information can be found at the following location:
To fully grasp the concept of Interoperability, one must first understand the different code models used: Managed and Unmanaged.
Managed Vs. Unmanaged Code Models
Managed code runs within the context and protection of the .NET CLR and benefits from:
Automatic memory management
Platform-neutrality
Code Access Security (CAS)
Buffer overrun protection
Garbage Collection (GC)
Just-In-Time (JIT) compilation
Scalability and extensibility
Ease of deployment
Cross-language integration
Unmanaged code on the other hand, does not run within the CLR's context and as such, cannot take advantage of any of the aforementioned benefits.
Hint: COM objects, ActiveX components, and Win32 API functions are considered unmanaged code.
Object Lifetime Management - A Mini Primer
The CLR offers a high-speed memory management system appropriately named garbage collection (GC). The garbage collector runs alongside every .NET application and automatically supervises the allocation, usage, and decollation of memory segments. Through various generation-based collection algorithms, the garbage collector detects when an object is no longer needed or can no longer be accessed and disposes of it. The CLR spawns threads which perform the essential clean up of all unused objects and frees up space in the managed memory heap. This type of object lifetime management is called non-deterministic finalization and is in stark comparison with the deterministic, COM object model.
Additional information can be found at the following location:
While it is relatively simple for .NET applications to reference and consume COM objects, it is important to understand what goes on behind the scenes.
Runtime Callable Wrapper (RCW)
The Runtime Callable Wrapper (RCW) acts as a proxy and allows the CLR to expose COM components to .NET clients transparently. Essentially, the RCW object wraps a COM component and makes it appear as though it is a managed component. The complexities of this process are, for the most part, concealed from the developer. It is important to note that there exists a one to one relationship between a RCW proxy class and a COM object, regardless of the number of existing references. The RCW's primary functions include enforcing built-in marshaling rules, converting COM HRESULT's to exceptions and vice versa, as well as component lifetime management. The CLR creates the COM object and a wrapper for that object from metadata derived from a type library.
Designing and Implementing a COM Component in .NET - 3 Step Approach
The following process demonstrates the development of a simple, Visual Basic 6 (VB6) ActiveX DLL server and its implementation in a .NET console application.
Step 1 - Design the COM Component
Start Visual Basic 6.0 and choose the "ActiveX DLL" project type.
Figure 2: New Project (Visual Basic 6.0)
In the "Project Properties" screen, change the "Project Name" to "EmployeeAx".
Figure 3: Project Properties
Rename the default "Class1" class to "Employee" and add the following code:
Option Explicit
Private m_FirstName As String
Private m_LastName As String
Private m_Salary As Currency
Public Event EmployeeUpgraded(ByVal Employee As Employee, ByVal TimeStamp As Date)
Public Enum EmployeeGrade
Administrator = 1
Supervisor = 2
End Enum
Public Property Get CompleteName() As String
CompleteName = m_FirstName & " " & m_LastName
End Property
Public Property Let FirstName(ByVal value As String)
m_FirstName = value
End Property
Public Property Get FirstName() As String
FirstName = m_FirstName
End Property
Public Property Let LastName(ByVal value As String)
m_LastName = value
End Property
Public Property Get LastName() As String
LastName = m_LastName
End Property
Public Property Let Salary(ByVal value As Currency)
m_Salary = value
End Property
Public Property Get Salary() As Currency
Salary = m_Salary
End Property
Public Sub UpgradeEmployee(ByVal Grade As EmployeeGrade)
Select Case Grade
Case Is = EmployeeGrade.Administrator
Salary = Salary + 60000
Case Is = EmployeeGrade.Supervisor
Salary = Salary + 30000
End Select
RaiseEvent EmployeeUpgraded(Me, Now)
End Sub
Compile the "EmployeeAx" ActiveX DLL server to a known location and exit Visual Basic 6.0. Start Visual Studio 2005 and create a Visual Basic .NET console application. Name the application, "EmployeeConsoleApp".
Figure 4: New Project (Visual Studio 2005)
Step 2 - Import the Type Library
There are several methods to import and consume COM components from a .NET application.
Method 1 - Visual Studio 2005 (Automatic)
Figure 5: Add Reference
The simplest way to import a COM component is to let Visual Studio 2005 perform the required tasks automatically:
Choose the "Add Reference" option from the "Project" menu.
Click the "COM" tab.
Choose the desired COM component and click "OK".
In this case, select the "EmployeeAx" COM component.
Visual Studio 2005 will parse the component's embedded type library and will generate an assembly named "Interop.LibraryName.dll" where "LibraryName" is the name of the COM component.
Hint: Unlike .NET components, COM components must be registered in the registry before they can be used. If a COM component does not appear in the "COM" tab, use the RegSvr32.exe command line tool. This will register DLL files as command components in the registry.
Additional information can be found at the following location:
Although Visual Studio 2005 offers a simple way to create metadata for a COM component, there are instances where the Type Library Importer (TlbImp.exe) command line tool is necessary. For instance, TlbImp.exe is necessary when importing a COM component as a strong-name assembly.
TlbImp EmployeeAx.dll /OUT:Interop.EmployeeAx.dll
Additional information can be found at the following location:
Using an imported Interop assembly is similar to developing with regular .NET Framework components.
Step 3 - Implement the Imported Interop Assembly
Add the following code to the Module1.vb module.
Sub Main()
Dim Employee As EmployeeAx.Employee
Employee = New EmployeeAx.Employee
AddHandler Employee.EmployeeUpgraded, AddressOf UpgradedHandler
Employee.FirstName = "John"
Employee.LastName = "Doe"
Employee.Salary = 50000
Console.WriteLine(Employee.CompleteName)
Console.WriteLine("Salary: " & String.Format("{0:c}", Employee.Salary))
Console.WriteLine()
Employee.UpgradeEmployee(EmployeeAx.EmployeeGrade.Supervisor)
Employee.UpgradeEmployee(EmployeeAx.EmployeeGrade.Administrator)
Console.ReadLine()
End Sub
Private Sub UpgradedHandler(ByVal Employee As EmployeeAx.Employee, ByVal TimeStamp As Date)
Console.WriteLine(Employee.CompleteName & " has been upgraded on " & TimeStamp)
Console.WriteLine("New Salary: " & String.Format("{0:c}", Employee .Salary))
Console.WriteLine()
End Sub
Run the application (F5). The output should be similar to the following:
Figure 6: Sample Output
Caveat Developer
One of the fundamental differences COM developers will encounter is the non-deterministic, object lifetime management system of .NET. While every COM component is destroyed as soon as the last variable pointing to it is set to Nothing, managed .NET Framework objects are disposed of by the garbage collection mechanism (See: Garbage Collection - A Mini Primer). This can introduce subtle bugs in an application. For instance, even though references to an imported COM component are set to Nothing, there are no guarantees as to when and how the object will be garbage collected and disposed of. To remedy this situation, the .NET Framework 2.0 offers the FinalReleaseComObject method which tries to release all references to a runtime callable wrapper (RCW). If the reference count returned is zero, then the release was successful.
Hint: It is recommended that the newer, FinalReleaseComObject method be used as a replacement for the ReleaseComObject method used in the .NET Framework 1.1. Using the FinalReleaseComObject method is the equivalent to calling the ReleaseComObject method in a loop until 0 is returned.
The following code snippet demonstrates how to call the FinalReleaseComObject method:
Dim result as Integer = FinalReleaseComObject(Employee)
If result = 0 Then
Console.WriteLine("Employee object successfully released.")
End If
Marshal Class - Quick Tip
Use the IsComObject method to determine whether an object is COM or .NET based.
The following code snippet demonstrates the IsComObject method:
If IsComObject(Employee) Then
Console.WriteLine("The Employee object is a COM component.")
Else
Console.WriteLine("The Employee object is a .NET Framework object.")
End If
Additional information can be found at the following location:
The Interop Forms Toolkit 2.0 is a free add-in that simplifies the process of displaying .NET forms and controls in a Visual Basic 6 application. Instead of upgrading the entire code base, these applications can now be extended one form at a time. The goal is a phased upgrade, with production releases at the end of each iteration containing both Visual Basic 6 and Visual Basic .NET forms running in the same Visual Basic 6 process.
Most importantly, the Toolkit guides you down a migration path that allows you to focus on writing code that adds business value instead of infrastructure and Interop code.
The Microsoft Printer Compatibility Library 1.0 allows projects that were upgraded from Visual Basic 6.0 to easily print without having to re-write your printing logic. By simply adding a reference to the Library, declaring a Printer, and making a few minor syntax changes, your project will be able to print using the Printers collection and Printer object as it did in Visual Basic 6.0.
The PrintForm Component is designed to bring back the ability to easily print a Windows Form. With this new PrintForm component you can once again lay out the Windows Form exactly as you want it and allow your users to print the form as a quick report.
It is virtually impossible to find an enterprise that can claim a harmonized and homogeneous infrastructure; in fact, quite the opposite is true. The nature of enterprise computing today makes interoperability a necessity, not a luxury. In a sense, one can think of interoperability as being one of the more practical approaches to dealing with the heterogeneous nature of the marketplace. The benefits of Interoperability must be assessed and counterbalanced against risk factors. In addition, care must be taken to ensure that differences between the managed and unmanaged code models are thoroughly understood.
I sincerely hope that you have had as much fun reading this article, as I had writing it.