When a COM client calls a DotNet object, the DotNet framework will create a COM callable wrapper (CCW). COM clients use the CCW as a proxy for the managed object.
Their primary purpose of CCW is to marshal calls between managed and unmanaged code and also to manage the object identity and object lifetime of the managed objects they wrap.
The primary goal of this article is to create a demo project to show how to call a DotNet component from COM client and to implement events raised by the DotNet component.
The example uses a DotNet component and COM client extending the functionality of the DotNet component using delegation.
This is a follow on article and we will be using the same demo project the only difference would be the client now is COM using DotNet component.
Step 1
Lets start by creating a Strong Name for the DotNet component we would be creating in next steps. Strong name is a unique name created by hashing a 128-bit encryption key against the name of the Assembly (ComInterOp in our case). The strong name is created using SN.exe, that would create ComInterOp.snk file, which we would use while creating the DotNet Assembly.
The command line instruction to create a strong name sn k ComInterOp.snk
Step 2
Now lets create a DotNet Assembly. Our assembly has only one class CEmp that exposes properties (FirstName, LastName, DOB) and raises an event (Senior). Raising an event from DotNet Component and consuming it in COM component is done by defining event sink interface in managed code and then apply the ComSourceInterfacesAttribute to connect the event sink interface to the managed class.
The command line instruction to create an assembly using the strong name (for vbc switch info see MSDN) vbc /out:ComInterOp.dll /t:library /keyfile:ComInterOp.snk CEmp.vb
VB.Net CEmp class code
Imports System
Imports Microsoft.VisualBasic
Imports System.Runtime.InteropServices
<InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)> _
Public Interface evtSenior
Sub Senior()
End Interface
<ComSourceInterfacesAttribute("evtSenior")> _
Public Class CEmp
Private mstrFirstName As String
Private mstrLastName As String
Private mdtDOB As Date
Public Event Senior()
Public Property FirstName() As String
Get
FirstName = mstrFirstName
End Get
Set(ByVal Value As String)
mstrFirstName = Value
End Set
End Property
Public Property LastName() As String
Get
LastName = mstrLastName
End Get
Set(ByVal Value As String)
mstrLastName = Value
End Set
End Property
Public Property DOB() As Date
Get
DOB = mdtDOB
End Get
Set(ByVal Value As Date)
mdtDOB = Value
If DateDiff(DateInterval.Year, Value, Now) > 60 Then
RaiseEvent Senior()
End If
End Set
End Property
End Class
Step 3
Once the assembly is created we have to create a Type library so the COM client can use the Assembly seamlessly. We can package an assembly for COM by following methods.
Type Library Exporter
Using the Type Library Exporter (Tlbexp.exe) the classes and interfaces contained in an assembly are converted into to a COM type library. Once the TypeLib is created COM clients can create an instance of the .NET class and call the methods of that object, just as if it were a COM object.
TypeLibConverter Class
The TypeLibConverter Class of the System.Runtime.InteropServices namespace provides methods to convert an assembly to a TypeLib. This API produces same output as Tlbexp.exe
Assembly Registration Tool
The Assembly Registration Tool (Regasm.exe), reads the metadata within an assembly and adds the necessary entries to the registry, which allows COM clients to create DotNet Framework classes transparently. The Assembly Registration tool can generate and register a type library when you apply the /tlb: option. COM clients require that type libraries be installed in the Windows registry. Without this option, Regasm.exe only registers the types in an assembly, not the type library. Registering the types in an assembly and registering the type library are distinct activities.
The DotNet Services Installation Tool (Regsvcs.exe) (see MSDN for more info)
In our example we will use the RegAsm.exe to create TypeLib from the classes and interfaces definitions in our ComInterOp.dll assembly.
The command line instruction to create and register ComINterOp.tlb is
regasm ComInterOp.dll /tlb:ComInterOp.tlb
Step 4
Now the DotNet component (ComInterOp.dll) should be installs in the GAC (global assembly cache) to work with the COM code. The command line instruction to install ComINterOp.dll in GAC is Gacutil -i ComInterOp.dll
Step 5
To use the DotNet component, as it was a COM component, add reference to the TypeLib from the COM component project created in the previous steps and code against it by creating CEmp object and delegation the calls to this object.
The COM Client has two classes CEmp and CEmps, CEmp is a wrapper over our DotNet components CEmp and exposes FirstName, LastName and IsSenior properties. The FirstName, LastName properties just delegates to the properties of DotNets CEmp but IsSenior uses the event raised by the DotNet component to set its value. The CEmps class is a collection of CEmps and exposes methods to test our code.
COM Component
'Class Emps
Option Explicit
Private Emps As Scripting.Dictionary
Private Sub Class_Initialize()
Emps = New Scripting.Dictionary
Dim objEmp As CEmp
objEmp = New CEmp
objEmp.InitMe("John", "Doe", "01/01/1970")
Emps.Add(0, objEmp)
objEmp = New CEmp
objEmp.InitMe("Mike", "Edwards", "01/01/1941")
Emps.Add(1, objEmp)
objEmp = New CEmp
objEmp.InitMe("Debra", "Bunn", "01/01/1930")
Emps.Add(2, objEmp)
End Sub
Public Function PrintEmps() As String
PrintEmps = PrintBool(True) & PrintBool(False)
End Function
Public Function PrintBool(ByVal xblnSeniors As Boolean) As String
Dim intCount As Integer
Dim objEmp As CEmp
Dim strPrint As String
For intCount = 0 To Emps.Count - 1
objEmp = Emps(intCount)
If xblnSeniors = objEmp.IsSenior Then
strPrint = strPrint & PrintEmp(objEmp) & Chr(13)
End If
Next intCount
PrintBool = strPrint
End Function
Private Function PrintEmp(ByVal xobjEmp As CEmp) As String
Dim strPrint As String
strPrint = xobjEmp.FirstName & Chr(9) & xobjEmp.LastName
PrintEmp = strPrint
End Function
'End Class Emps
'Class Emp
Option Explicit
Private mblnIsSenior As Boolean
Private WithEvents mobjEmp As ComInterOp.CEmp
Public Sub InitMe(ByVal xstrFName As String, ByVal xstrLName As String, ByVal xdtDOB As Date)
mobjEmp = New ComInterOp.CEmp
With mobjEmp
.FirstName = xstrFName
.LastName = xstrLName
.DOB = xdtDOB
End With
End Sub
Public Property Get FirstName() As String
FirstName = mobjEmp.FirstName
End Property
Public Property Get LastName() As String
LastName = mobjEmp.LastName
End Property
Public Property Get IsSenior() As Boolean
IsSenior = mblnIsSenior
End Property
Private Sub mobjEmp_Senior()
mblnIsSenior = True
End Sub
'End Class Emp