65.9K
CodeProject is changing. Read more.
Home

Dynamic Events in VB.NET

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.52/5 (8 votes)

Mar 18, 2013

CPOL

2 min read

viewsIcon

39584

downloadIcon

733

How to handle dynamic, late binding, events for COM objects in VB.NET.

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

    ' call a method
    ie.Navigate("about:blank")
    ' read a property
    Dim readyState As Integer = ie.ReadyState
    ' write a property
    ie.Visible = True
  • Using the Microsoft.VisualBasic.Interaction.CallByName method.

    ' Call a method 
    CallByName(ie, "Navigate", CallType.Method, "about:blank")
    ' Read a property
    readyState = CInt(CallByName(ie, "ReadyState", CallType.Get))
    ' Write a property
    CallByName(ie, "Visible", CallType.Let, True)
  • Using the System.Type.InvokeMember method.

    ' Call a method 
    ie.GetType().InvokeMember("Navigate", BindingFlags.InvokeMethod, Type.DefaultBinder, ie, {"about:blank"})
    ' Read a property
    readyState = CInt(ie.GetType().InvokeMember("ReadyState", BindingFlags.GetProperty, Type.DefaultBinder, ie, Nothing))
    ' Write a property
    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")
    ' No dynamic type...
    Dim ie = Activator.CreateInstance(iet)
    ' Not working...
    ' AddHandler ie.OnQuit, AddressOf ie_OnQuit
    ' Not working also...
    'ie.OnQuit += New OnQuitDelegate(AddressOf ie_OnQuit)

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
                    ' todo: Compiler errors...
                    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:
    ' Create event binders
    Dim binders As Dictionary(Of String, EventBinder)
    binders = EventBinder.CreateEventBinders({
        "BeforeNavigate2",
        "DocumentComplete",
        "NavigateComplete2",
        "OnQuit",
        "StatusTextChange"
    })
    
    ' Create event handlers
    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)
    }
    
    ' Bondage...
    For i As Integer = 0 To binders.Count - 1
        binders(binders.Keys(i)).AddHandler(ie, delegates(i))
    Next

History

  • 18/03/2013: First draft.