Introduction
This article shows a way to use events with late bound COM objects. To solve the problem some C# script was used...
Background
There are two common ways in VB.NET to create a late bound object.
-
Using the CreateObject
method.
Dim ie As Object = CreateObject("InternetExplorer.Application")
-
Using the System.Activator.CreateInstance
method.
Dim iet As Type = Type.GetTypeFromProgID("InternetExplorer.Application")
Dim ie As Object = Activator.CreateInstance(iet)
And there are three common ways in VB.NET to call methods and properties on a late bound object.
-
With Option Strict
turned off
ie.Navigate("about:blank")
Dim readyState As Integer = ie.ReadyState
ie.Visible = True
-
Using the Microsoft.VisualBasic.Interaction.CallByName
method.
CallByName(ie, "Navigate", CallType.Method, "about:blank")
readyState = CInt(CallByName(ie, "ReadyState", CallType.Get))
CallByName(ie, "Visible", CallType.Let, True)
-
Using the System.Type.InvokeMember
method.
ie.GetType().InvokeMember("Navigate", BindingFlags.InvokeMethod, Type.DefaultBinder, ie, {"about:blank"})
readyState = CInt(ie.GetType().InvokeMember("ReadyState", BindingFlags.GetProperty, Type.DefaultBinder, ie, Nothing))
ie.GetType().InvokeMember("Visible", BindingFlags.SetProperty, Type.DefaultBinder, ie, {True})
There are some ways discussed on how to use events with late bound objects, see Google search ' Eventhandler for late bound objects). But there isn't any way to add event handlers on late bound COM objects like with the dynamic Type in C#.
-
Working in C#
public delegate void OnQuitDelegate();
static void Main(string[] args)
{
var iet = Type.GetTypeFromProgID("InternetExplorer.Application");
dynamic ie = Activator.CreateInstance(iet);
ie.OnQuit += new OnQuitDelegate(ie_OnQuit);
}
public static void ie_OnQuit()
{
}
-
Not working in VB.NET
Dim iet As Type = Type.GetTypeFromProgID("InternetExplorer.Application")
Dim ie = Activator.CreateInstance(iet)
A first idea was to write a library in C# but the name of the event is hard coded. So that wasn't possible too, except ...It is when compiling C# script at runtime...
-
So first a interface was made to make calls into the compiled C# script.
Public Interface IEventBinder
Sub [AddHandler](ByVal source As Object, ByVal [delegate] As [Delegate])
Sub [RemoveHandler](ByVal source As Object, ByVal [delegate] As [Delegate])
End Interface
-
Then a class was written that implemented the interface with Factory methods that compiled and returned instances of the class/interface. From then on events could be attached to and detached from an object.
Imports Microsoft.CSharp
Imports System.CodeDom
Imports System.CodeDom.Compiler
Imports System.Reflection
Imports System.Text
Public NotInheritable Class EventBinder
Implements IEventBinder
Private _name As String
Private _event As IEventBinder
Private Sub New(ByVal name As String, ByVal [event] As IEventBinder)
Me._name = name
Me._event = [event]
End Sub
Public ReadOnly Property Name As String
Get
Return _name
End Get
End Property
Public Sub [AddHandler](source As Object, [delegate] As System.Delegate) Implements IEventBinder.AddHandler
Me._event.AddHandler(source, [delegate])
End Sub
Public Sub [RemoveHandler](source As Object, [delegate] As System.Delegate) Implements IEventBinder.RemoveHandler
Me._event.RemoveHandler(source, [delegate])
End Sub
Public Shared Function CreateEventBinders(ByVal names() As String) As Dictionary(Of String, EventBinder)
Using provider As CodeDomProvider = New CSharpCodeProvider()
Dim parameters As New CompilerParameters() With {
.GenerateInMemory = True
}
parameters.ReferencedAssemblies.AddRange({
"System.dll",
"System.Core.dll",
Assembly.GetExecutingAssembly().Location,
"Microsoft.CSharp.dll"
})
Dim uid As String = Guid.NewGuid().ToString("N")
Dim code As New StringBuilder()
code.Append(
"using System;" & vbCrLf &
"" & vbCrLf &
"namespace DynamicEvent {" & vbCrLf
)
For Each name As String In names
code.Append(
String.Format(
"public class {0}Event{2} : {1}.IEventBinder" & vbCrLf &
"{{" & vbCrLf &
" public void AddHandler(dynamic source, Delegate del) {{ source.{0} += del; }}" & vbCrLf &
" public void RemoveHandler(dynamic source, Delegate del) {{ source.{0} -= del; }}" & vbCrLf &
"}}" & vbCrLf,
name,
GetType(IEventBinder).Namespace,
uid
)
)
Next
code.Append("}")
Dim codeResult As CompilerResults = provider.CompileAssemblyFromSource(parameters, code.ToString())
If codeResult.Errors.Count = 0 Then
Dim result As New Dictionary(Of String, EventBinder)
For Each name As String In names
Dim codeType As Type = codeResult.CompiledAssembly.GetType(String.Format("DynamicEvent.{0}Event{1}", name, uid))
Dim codeObject As Object = Activator.CreateInstance(codeType)
result.Add(name, New EventBinder(name, DirectCast(codeObject, IEventBinder)))
Next
Return result
Else
Return Nothing
End If
End Using
End Function
Public Shared Function CreateEventBinder(ByVal name As String) As EventBinder
Return CreateEventBinders({name})(name)
End Function
End Class
Using the code
To attach an event now:
-
A delegate declaration is required.
Delegate Sub OnQuitDelegate()
-
A event handler is required.
Sub ie_OnQuit()
' ...
End SUb
-
A instance of the delegate is required.
Dim [delegate] As [Delegate] = New OnQuitDelegate(AddressOf ie_OnQuit)
-
And a instance of EventBinder for the specific event is required.
Dim binder As EventBinder = EventBinder.CreateEventBinder("OnQuit")
-
From then on it's possible to attach, detach events...
Dim ie As Object = CreateObject("InternetExplorer.Application")
binder.AddHandler(ie, [delegate])
binder.RemoveHandler(ie, [delegate])
But it's better for performance to create the interfaces for all events at once. (Not that speed matters, this is VB.NET and has the speed of light...)
- Bind to more then one event:
Dim binders As Dictionary(Of String, EventBinder)
binders = EventBinder.CreateEventBinders({
"BeforeNavigate2",
"DocumentComplete",
"NavigateComplete2",
"OnQuit",
"StatusTextChange"
})
Dim delegates() As [Delegate] = {
New BeforeNavigate2Delegate(AddressOf ie_BeforeNavigate2),
New DocumentCompleteDelegate(AddressOf ie_DocumentComplete),
New NavigateComplete2Delegate(AddressOf ie_NavigateComplete2),
New OnQuitDelegate(AddressOf ie_OnQuit),
New StatusTextChangeDelegate(AddressOf ie_StatusTextChange)
}
For i As Integer = 0 To binders.Count - 1
binders(binders.Keys(i)).AddHandler(ie, delegates(i))
Next
History