Link to home
Start Free TrialLog in
Avatar of bhermer
bhermer

asked on

Add an event handler to a loaded dll from outside project

Hi,

I have an application that loads plugins which are external dll's within my exe directory using

            Dim A As Assembly = Assembly.LoadFrom(fFile.FullName)
            Dim Plugin As Object = A.CreateInstance(fFile.Name.ToString.Replace(fFile.Extension, "") & ".SuperClass")


where fFile.Fullname is the path to my DLL.

Within this dll I know it has a public event called RaiseMessage, and within my application I want to be able to handle this event when the dll raises the event. the event in the DLL is declared such


Public Event RaiseMessage(ByVal hEventMessages As Hashtable)


What I have tried are the following to try and add a handler to this event, but it hasnt worked, so any ideas would be great:


-------------------------
Using the method of the object (of which my project is unaware):

     AddHandler Plugin.RaiseError, AddressOf RaiseMessage

Error:
RaiseError is not an event of Object

-----
AND
-----

Using reflection:

    Dim tModule As Type = Plugin.GetType()
    Dim mi As MethodInfo = tModule.GetMethod("RaiseMessage", BindingFlags.Public Or BindingFlags.Instance)

    AddHandler Plugin.GetType.GetMethod("RaiseMessage"), AddressOf RaiseMessage

Error:

'AddHandler' or 'RemoveHandler' statement event operand must be a dot-qualified expression or a simple name.



Avatar of Jason Evans
Jason Evans
Flag of United Kingdom of Great Britain and Northern Ireland image

Hi there.
I'm not sure how to help with this question, but I can recommend looking at this article:
http://damianblog.com/2008/07/02/net-code-injection/
This guy shows how to write .NET code to inject into another .NET  process, and one of things he does is subscribe to an event in the other process, similar to what you want to do. It looks like he's using a DynamicMethod object to do this.
I'm not sure how much this will help you, but it might give you an idea.
Cheers.
Jas.
Avatar of bhermer
bhermer

ASKER

thanks for that, I understand the gist of what he is doing there, but it seems way over the top for what should be a simple thing to do (that doesnt mean it is possble to do simply ;)

I will leave the Q open on the hopes someone knows an easier method.
Code injection should not be necessary, actually, Emit techniques are not necessary at all. Though I haven't got a fully working sample yet, but the basic idea is explained in this thread, through Reflection: http://www.pcreview.co.uk/forums/thread-3723456.php

More background information and source code can be found here: http://www.codeproject.com/KB/cs/WeakEvents.aspx. Of interest to you is the Solution 3: SmartWeakEvent, but you'll have to view the source for how he does that (explanation on the page is rather short).
Avatar of bhermer

ASKER

Hi Abel,

In the first link, from what it looks like, he is pointing an already existing method to his own custom method?

I am presuming tx is a textbox and he is extracting a methog called tx.Name + "_MouseClick" and assigning that methid to the mousclick event which is always attached to all textbox's,

 I need to add a handler to a method that wouldn't appear on intellisense as my class (dll) is typed as an object. I could, I think, using that code, override a public standard method of the object class, but that sounds like a bodge.

For the second link, it went over my head a little when scan reading, so I will have a better look at report back here.

Thanks
If your DLL is a .NET assembly, which I assume it is. then you can retrieve any handler, hidden or not, using reflection. Which is actually what that link shows:

MethodInfo mi = t.GetMethod(sMethodName);

where sMethodName is the name of your event. Intelisense is not of an issue here, because you are using reflection. And reflection is the technique of finding methods / events / properties / fields which are unknown to the compiler beforehand.
Avatar of bhermer

ASKER

Hi Abel,

I am already using reflection to call a method called LoadControl within my dll (which is NET, and written by me)

From my main application, I have no problems calling methids within my dll, the problem I have is getting my dll to talk back to my main application

Effectivly the dll is a user control that can be loaded into a control panel in my app, I need a way for that dll (user control) to raise events within the main application.

Normally, if I have the dll as a class file within my project I can use

AddHandler Object.MethodName(), AddressOf MyApp.MethodName()

so when the dll raises an event called MethodName() the event is fired in the main application called MethodName()


he problem is, using reflection, you cannot add an event handler to a method as stated in my original Q:


    Dim tModule As Type = Plugin.GetType()
    Dim mi As MethodInfo = tModule.GetMethod("RaiseMessage", BindingFlags.Public Or BindingFlags.Instance)

    AddHandler Plugin.GetType.GetMethod("RaiseMessage"), AddressOf RaiseMessage


As to why I need this, others will be writinging user controls to plug into this app, and we dont want to re-compile the application every time we want to update or add a user control, we just load all available controls from a dll directory

I think you are mixing a few things up. The code in that example page shows to do just that. All you need is, of course, the event handler (which is in your current calling application) and then you register that to the dynamically loaded event.

I'll try to workout a simple example that suits your situation, but that may take some time....
Avatar of bhermer

ASKER

Hi Abel,  Any luck working out a solution to this?
Yes, actually. It wasn't easy to find out, and I wasn't completely correct with my assumptions, apparently, you do need some extra steps here to get it right.

The event handler, according to Microsoft, must be static (Shared in VB) and I haven't found a way to do this differently, as of yet. Probably the easiest work around is to add a reference to your current object and send that through the event. But those are implementation details, I am sure you'll manage.

Test scenario
Below is a button_click event on a form that I used to start a timer object that I created. The timer object is below as well and is called OneOffTimer (and uses a Timer, surprise, surprise). It has one event, OnTimeOut. The timer starts when the method SetTimeOutAndStart is called with the number of seconds.

Subscribing to an event using reflection
In the form button_click handler there's the core of this technique. I put comments in there to help you out understanding what's going on. The main point is creating a delegate (an existing normal delegate won't work, because it is bound to the form) and to lookup the AddMethod of the event. The event itself is found using GetEvent.

All these steps on themselves are pretty basic, but putting it together in the right order can be daunting. I had quite some struggles with the delegates, until I found out that they had to be static (Shared).

-- Abel --

' demonstation of calling an event dynamically '
Private WithEvents myOneOffTimer As New OneOffTimer
Private Sub Q_24335269_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Q_24335269.Click
 
    ' Find the event and its AddMethod '
    Dim infoTimeOutEvt As EventInfo = myOneOffTimer.GetType().GetEvent("OnTimeOut")
    Dim infoTimeOutEvtAddMethod As MethodInfo = infoTimeOutEvt.GetAddMethod()
    'Dim concreteTimeOutDelegate As OnTimeOutDelegate = New OnTimeOutDelegate(AddressOf OnTimeOutHandler)
 
    ' Find the handler that we want to use to receive the events '
    Dim infoTimeoutHandler As MethodInfo = _
    Me.GetType().GetMethod("OnTimeOutHandler", _
    BindingFlags.NonPublic Or BindingFlags.Static)
 
 
    ' Create a delegate for our handler '
    Dim delegType As Type = infoTimeOutEvt.EventHandlerType
    Dim myDelegate As [Delegate] = [Delegate].CreateDelegate(delegType, infoTimeoutHandler)
 
    ' Add the event handler to the event chain of OnTimeOut '
    infoTimeOutEvtAddMethod.Invoke(myOneOffTimer, New Object() {myDelegate})
 
    ' set the timeout to raise the event after some seconds'
    myOneOffTimer.SetTimeOutAndStart(3)
 
    '
    'AddHandler myOneOffTimer.OnTimeOut, AddressOf OnTimeOutHandler
End Sub
 
' this is the callback for the event that will be called if the event is raised '
Private Shared Sub OnTimeOutHandler()
    Debug.WriteLine("timeout expired")
End Sub
 
 
 
' just some object that raises an event '
 
Public Class OneOffTimer
    Private _timer As Timer
    Public Sub New()
        _timer = New Timer(AddressOf HandleTimeOut)
    End Sub
 
    Public Sub SetTimeOutAndStart(ByVal timeOutValue As Integer)
        _timer.Change(timeOutValue * 1000, Timeout.Infinite)
    End Sub
 
    Private Sub HandleTimeOut(ByVal state As Object)
        _timer.Dispose()
        RaiseEvent OnTimeOut()
    End Sub
 
    Public Event OnTimeOut()
End Class

Open in new window

ASKER CERTIFIED SOLUTION
Avatar of abel
abel
Flag of Netherlands image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of bhermer

ASKER

thanks abel, will test tomorrow, thanks for your effort
Avatar of bhermer

ASKER

Thanks Abel, sorry for delay, back on this today, managed to make good progress with your code
(from grading comment)
> managed to make good progress with your code

glad to hear that. It was a bit of work to get it all together, but a nice learning experience too.