Friday, December 4, 2009

COM and the Run-time Object Table (ROT)

You would think that it would be relatively easy for one managed code assembly to get a reference to another managed code assembly at runtime that might be running under a different application domain, but it's not. In order to do that you have to do several things:
  1. Make the server .Net Assembly Com-visible and Com-Compatible
  2. Register the server assembly in the RunTime Object Table (ROT) so that the client assembly can find it at run time

The technique for exposing a .Net assembly to COM is not too difficult and is covered in a number of locations on the web.  The task of registering a COM object in the ROT and retrieving a reference to that COM object from another application is a bit tricky.  Here’s some code that I’ve found works for that:

Option Strict On
Option Explicit On

Imports System.Runtime.InteropServices
Imports System.Runtime.InteropServices.ComTypes
Imports System.Runtime.InteropServices.Marshal

Public Class ROTHook

Private Const ROTFLAGS_REGISTRATIONKEEPSALIVE As Integer = 1
Private Const ROTFLAGS_ALLOWANYCLIENT As Integer = 2

<DllImport("ole32.dll", ExactSpelling:=True, PreserveSig:=False)> _
Private Shared Function GetRunningObjectTable(ByVal reserved As Int32) As IRunningObjectTable
End Function

<DllImport("ole32.dll", CharSet:=CharSet.Unicode, ExactSpelling:=True, PreserveSig:=False)> _
Private Shared Function CreateItemMoniker(ByVal lpszDelim As String, ByVal lpszItem As String) As IMoniker
End Function

<DllImport("ole32.dll", ExactSpelling:=True, PreserveSig:=False)> _
Private Shared Function CreateBindCtx(ByVal reserved As Integer) As IBindCtx
End Function

Public Shared Function AddToROT(ByVal obj As Object, ByVal classID As String) As Integer
'----------------------------------------------------------------------------------
' AddToROT
'
' Abstract - Adds a Reference to This Object to the Runtime Object Table
'
' Parameters
'               obj Object to register
'               classID Class ID of object to register
'
' Return Value  cookie to revoke ROT registration
'----------------------------------------------------------------------------------
Dim cookieValue As Integer
Dim rot As IRunningObjectTable = Nothing
Dim moniker As IMoniker = Nothing

Try
'---- Get the ROT -------------------------------------------------------------
rot = GetRunningObjectTable(0)

'--- Create a moniker ---------------------------------------------------------
Dim objName As String = "{" + classID + "}"
moniker = CreateItemMoniker("!", objName)

'--- Registers the object in the running object table -------------------------
cookieValue = rot.Register(ROTFLAGS_REGISTRATIONKEEPSALIVE, obj, moniker)

Return cookieValue

Catch Exc As Exception
Throw Exc
Finally

'--- Releases the COM objects -----------------------------------------------
If Not (moniker Is Nothing) Then
Try
While (Marshal.ReleaseComObject(moniker) > 0)
End While
Catch ex As Exception
Exit Try
End Try
End If
If Not (rot Is Nothing) Then
Try
While (Marshal.ReleaseComObject(rot) > 0)
End While
Catch ex As Exception
Exit Try
End Try
End If
End Try

End Function

Public Shared Function GetObjectByClassName(ByVal objClassName As String) As Object
Return GetActiveObject("!" + objClassName)
End Function

Public Shared Function GetObjectByClassID(ByVal objClassID As String) As Object
Return GetActiveObject("!{" + objClassID + "}")
End Function

Public Shared Function GetActiveObject(ByVal objName As String) _
As Object
'----------------------------------------------------------------------------------
' GetActiveObject
'
' Abstract - Gets an Instance of this Object from the Runtime Object Table
'
' Parameters
'
' Return Value  Object found or Nothing if Not Found
'----------------------------------------------------------------------------------
Try

Dim ROTObject As Object = Nothing
Dim runningObjectTable As IRunningObjectTable
Dim monikerEnumerator As IEnumMoniker = Nothing
Dim monikers(1) As IMoniker

runningObjectTable = GetRunningObjectTable(0)
runningObjectTable.EnumRunning(monikerEnumerator)
monikerEnumerator.Reset()

Dim numFetched As IntPtr = New IntPtr()
While (monikerEnumerator.Next(1, monikers, numFetched) = 0)
Dim ctx As IBindCtx
ctx = CreateBindCtx(0)

Dim runningObjectName As String = ""
monikers(0).GetDisplayName(ctx, Nothing, runningObjectName)
runningObjectName = runningObjectName.ToUpper
If (runningObjectName.StartsWith(objName.ToUpper)) Then
Dim runningObjectVal As Object = Nothing
runningObjectTable.GetObject(monikers(0), runningObjectVal)
ROTObject = CType(runningObjectVal, Object)
Return ROTObject
End If
End While

Return ROTObject

Catch Exc As Exception
Throw Exc
End Try

End Function

Public Shared Sub RemoveFromROT(ByVal myCookie As Integer)
'----------------------------------------------------------------------------------
' RemoveFromROT
'
' Abstract - Removes a Reference to This Object From the Runtime Object Table
'
' Parameters
'
' Return Value  
'----------------------------------------------------------------------------------

If (myCookie = 0) Then Exit Sub
Dim rot As IRunningObjectTable = Nothing

Try

'--- Get the running object table and revoke the cookie ----------------
rot = GetRunningObjectTable(0)
rot.Revoke(myCookie)
myCookie = 0

Catch Exc As Exception
Throw Exc
Finally
If Not (rot Is Nothing) Then
While (Marshal.ReleaseComObject(rot) > 0)
End While
End If
End Try

End Sub

End Class

You can use the “AddToROT” method to register an object in the ROT by classID – that method will return a cookie that can be used later to remove the object.  The “GetObjectByClassID” can be used to retrieve the object from another process.

Chuck

0 comments:

Post a Comment