Search code examples
vb.netwinformsdllactivator

Attach Event handler to a Form created from a dll loaded at run-time


I'm looking for a way to handle close event and fetch one return parameter.
I can't change the way that WinFormApp.dll is being run.

Private Sub ShowWinFormApp() Handles Button1.Click

 ' launching new WinForm app from within another app 

  Dim InvTrack As System.Reflection.Assembly = System.Reflection.Assembly.Load(File.ReadAllBytes("\\NetworkPath\WinFormApp.dll"))
  Dim oType As System.Type
  Dim pControl As System.Object
  Dim Params As New List(Of Object)

  Params.Add(Param1)
  Params.Add(Param2)
  oType = InvTrack.GetType("WinFormApp.FrmMain")
  pControl = Activator.CreateInstance(oType, Params)

     'here i'd like to add handler to catch my event 
  AddHandler pControl.ImDone, AddressOf ReLaunch  
    'this not working, saying that Element `ImDone` is not an event of element `Object`

  Application.Run(pControl)
End Sub

In WinFormApp I tried like this:

Public Class FrmMain
   Public Event ImDone As EventHandler(Of ImDoneEventArgs)

    Public Sub Test() Handles Button1.Click
       RaiseEvent ImDone (Me, New ImDoneEventArgs With {.RetVal = 54})
       Application.Exit()
    End Sub

    Public Class ImDoneEventArgs
        Inherits EventArgs
        Public Property RetVal As Integer
    End Class
End class 

Solution

  • An example, assuming the code you have posted matches the real scenario.

    You cannot use AddHandler when the target event and the Type of its provider are unknown in the current assembly. Activator.CreateInstance() returns an object Type that only exposes its basic methods.

    You can cast the object to its base class, the Form class. This Type is known to both assemblies. Of course, the base class doesn't expose custom events, but you can use call its ShowDialog() method to present the Form.

    You cannot call Application.Run() directly if a message loop is already running. But you don't need to, ShowDialog() already does that for you, in a compatible way.

    To create an event handler for the custom event, you can again use Reflection to get the related EventInfo, associate an EventHandler to the method that is called when the event is raised, then call Delegate.CreateDelegate() passing the [EventInfo].EventHandlerType and the handler's method Target, to generate a compatible delegate.

    Now you can add the event handler to an event of your instance of the FrmMain object, calling [EventInfo].AddEventHandler(), passing the object that provides the event (the FrmMain object) and the event delegate:

    Dim FrmMain As Form = Nothing
    Dim FrmMainEvent As EventInfo = Nothing
    Dim _EventDelegate As [Delegate] = Nothing
    Dim _EventHandler As EventHandler(Of EventArgs) = Sub(s, e) ReLaunch(s, e)
    
    Dim asm As Assembly = Assembly.Load(File.ReadAllBytes("WinFormApp.dll"))
    Dim formType = asm.GetType("WinFormApp.FrmMain")
    If formType Is Nothing Then Throw New Exception("FrmMain Type not found")
    
    Dim instance = Activator.CreateInstance(formType, {Param1, Param2})
    If instance IsNot Nothing Then
        FrmMain = DirectCast(instance, Form)
        FrmMainEvent = instance.GetType().GetEvent("ImDone")
        If FrmMainEvent IsNot Nothing Then
            _EventDelegate = [Delegate].CreateDelegate(
                FrmMainEvent.EventHandlerType, _EventHandler.Target, _EventHandler.Method
             )
            FrmMainEvent.AddEventHandler(instance, _EventDelegate)
        End If
    
        FrmMain.ShowDialog()
    End If
    

    As a note, you could also use an Action delegate Type, which could simplify your code a little.


    When the event is raised, the specified delegate is called.
    You should remove the event handler at this point, calling [EventInfo].RemoveEventHandler(), passing the instance of the object that provided the event (the FrmMain instance you have generated) and the delegate that was associated to the event.

    The EventArgs object is also a custom Type, so you can again retrieve the value of its Properties via Reflection:

    Public Sub ReLaunch(sender As Object, e As EventArgs)
        If FrmMainEvent IsNot Nothing AndAlso _EventDelegate IsNot Nothing Then
            FrmMainEvent.RemoveEventHandler(FrmMain, _EventDelegate)
        End If
        Debug.WriteLine(e.GetType().GetProperty("RetVal").GetValue(e))
    
        _EventDelegate = Nothing
        FrmMainEvent = Nothing
        FrmMain = Nothing
    End Sub