Solved

How should a loaded assembly access variables in the application that loads it?

Posted on 2008-10-23
20
206 Views
Last Modified: 2012-05-05
Hi experts

I have an application that loads any modules that it finds in a sub-directory. One of these modules uses a variable that resides in the main application but i want the module to ask for the variable, rather than have the main application tell it, so to speak.

Code included, if it will be of any use.

Any help would be appreciated...

Many thanks in advance.
The module loader...

====================

 

Private Sub LoadModules()

 

	' Declarations

	Dim moddir As String = Application.StartupPath & "\Modules"

	Dim dirinfo As New DirectoryInfo(moddir)

	Dim filelist() As FileInfo = dirinfo.GetFiles("*.dll")

	modcount = filelist.Length

	If modcount > 0 Then

		ReDim moduleobj(modcount)

		Dim modnum As Integer = 0

 

		' Module iteration

		For Each filename As FileInfo In filelist

			Dim AssemblyName As Assembly = Assembly.LoadFile(filename.FullName)

 

			Try

				moduleobj(modnum) = AssemblyName.CreateInstance(String.Format("{0}.{1}", AssemblyName.GetName().Name, "modstart"))

			Catch ex As Exception

				MsgBox(ex.ToString, MsgBoxStyle.Exclamation, "Unknown Module")

			End Try

 

			' Add Menu Item

			Dim modmenuitem As ToolStripMenuItem = moduleobj(modnum).WBCONTROLmenu()

			lmenu.Items.Add(modmenuitem)

			modnum += 1

		Next

 

		' Separator

		Dim sep As New ToolStripSeparator

		lmenu.Items.Add(sep)

 

	End If

End Sub

 

 

 

The module's 'modstart'

=======================

 

 

Imports System.Windows.Forms

Imports System.Drawing

 

Public Class modstart

 

	Public Shared formname As fman

 

	'

	' WBCONTROL Toolstripmenuitem

	'

	Public Function WBCONTROLMenu() As ToolStripMenuItem

 

		' Declarations

		Dim tsitem As New ToolStripMenuItem

 

		' Setup Item

		tsitem.Text = "File Manager"

		tsitem.ForeColor = Color.Black

		tsitem.Image = Global.fmanlib.My.Resources.fman

		AddHandler tsitem.Click, AddressOf FMANStart

 

		' Return

		Return tsitem

 

	End Function

 

	Public Sub FMANStart()

		Dim newform As New fman

		formname = newform

		newform.Show()

	End Sub

 

End Class

Open in new window

0
Comment
Question by:jerute
  • 13
  • 7
20 Comments
 
LVL 19

Expert Comment

by:drichards
Comment Utility
The main app really has to tell it.  The newly loaded assembly cannot reach out until it knows something of the environment into which it has been loaded.  This is typically done by passing in any necessary information in an initialization step after creation.

For instance, you are currently calling WBCONTROLMenu to grab a menu item.  You could create another method to pass in either the desired data or an interface that would allow the loaded assembly code to reach back and get the data.
0
 

Author Comment

by:jerute
Comment Utility
Hi drichards. Thanks for your answer.

My problem is that the main app will have a variety of assemblies to load in, varying from installation to installattion, and has a whole host of information available for these loaded assemblies, so coding for this could be exhaustive.

I have touched upon 'interfaces' in articles across the internet but am a little lost as to how they are used.

Would you be able to give me a small example to allow, say a simple value be taken from then main app? I would appreciate it.

Thanks
0
 
LVL 19

Expert Comment

by:drichards
Comment Utility
The coding WILL be exhaustive no matter what.  Both the main app and the assembly loading the data need to be aware of the data types involved.  Usually you make a separate assembly that contains the common data structures and interfaces.  The main app loads that assembly automatically at startup and then that common assembly is already available to the other assemblies as they are loaded.

For example, you may have the interface shown below in an assembly.  Put it with other common types and reference the assembly from both the main app and your manual-load assemblies.

Then in the main app implement a class that implements the interface something like shown below.

Then add a method in modstart  of each module, similar to what you did with WBCONTROLMenu.  That Function or Sub should take a ModuleInterface parameter.  The module will call the correct GetMyDataX() function to get the data it requires.  Obviously the interaction will be more complicated than this since you said that there is a "whole host" of information available to the assemblies.  You will need to create a set of interfaces, perhaps going in both directions, to manage the interfaction.

I can't give you better code examples without a better idea of the extent of the data needing to pass between the app and the module.

The basic idea is just an extension of what you already have done.  You create a new ToolStripMenuItem, set its properties, including an event handler, and then hand it out to the main app.  The main app calls back in via the event handler when the ToolStripMenuItem is clicked.

Now you want to do something similar by providing another method that the app can call.  In this case, the app will provide an interface for the module to call out on.  It is easier for the module to know which method to call on the interface than to have the app interrogate the module to determine what parameters the module might need.  In either case, the app needs to be prepared to provide the necessary data.
... put in a separate project and reference from app and other projects

    Interface ModuleIterface

        Function GetMyData1() As Integer

        Function GetMyData2() As DateTime

    End Interface
 

... create a class in the app that implements the interface

    Class ModuleInitialization

        Implements ModuleIterface
 

        Public Function GetMyData1() As Integer Implements ModuleIterface.GetMyData1

            Return 2

        End Function
 

        Public Function GetMyData2() As Date Implements ModuleIterface.GetMyData2

            Return DateTime.Now

        End Function

    End Class
 
 

...add a function to modstart

    Public Function InitProgram(ByVal iface As ModuleIterface) As Integer

        iface.GetMyData1()

        return 0

    End Function

Open in new window

0
 

Author Comment

by:jerute
Comment Utility
Hi drichards, and thanks again for your involvement.

Well I'm with you thus far, but how do I load the interface assembly? In my snippet below I create a new project and insert the interface code in 'class1' then compile it. I have saved it in the same module folder as the module it refers to and load it with the 'loadinterface' code below.

When I try to implement it, however, it tells me that the object I load it into, is not defined...
Module wbmain
 

' Interface Object

                  Public ifobj As Object
 

         Private Sub LoadInterface()

		' Declarations

		Dim moddir As String = Application.StartupPath & "\Modules"

		Dim AssemblyName As Assembly = Assembly.LoadFile(moddir & "\fmaniface.dll")

		ifobj = AssemblyName.CreateInstance(String.Format("{0}.{1}", AssemblyName.GetName().Name, "class1"))

	End Sub
 

	Class ModuleInitialization

		Implements ifobj
 

		Public Function GetMyData1() As Integer Implements ModuleIterface.GetMyData1

			Return 2

		End Function
 

		Public Function GetMyData2() As Date Implements ModuleIterface.GetMyData2

			Return DateTime.Now

		End Function

	End Class
 

End Module

Open in new window

0
 
LVL 19

Expert Comment

by:drichards
Comment Utility
I don't think you have it quite right, and I left out a step.  The part I left out was that each assembly you are loading should implement a loader class to which you will pass the interface from the app.  This is because modstart is presumably the same for all modules in order to make the dynamic load work.  You'll need another interface inside the dynamic-load assemblies to acrually do the assembly-specific work.  Other than that:

The interface code goes into an interface assembly (dll) as a separate project in Visual Studio.

You put Class ModuleInitialization into your main app and reference the interface assembly.  Since the app uses the interface, the interface assembly will get loaded by the main app.  Then you go ahead and load the other assemblies with Assembly.LoadFile just as you were doing previously.  Since the interface assembly has already been loaded by the app, it will be available for use.
0
 
LVL 19

Expert Comment

by:drichards
Comment Utility
As I read things over, you don't need to add the interface in the module because each module implements a separate modstart class and can do whatever it needs directly.
0
 
LVL 19

Expert Comment

by:drichards
Comment Utility
OK, I'm attaching all the code you need to get going in three posts.  First is the modification to modstart.  I added a module initialization function here (and commented out some code so it would compile on my machine without too much work.  You will need to reference the new VBInterface project (or whatever you decide to name it later) for this to work.
Imports VBInterface

Imports System.Windows.Forms

Imports System.Drawing
 

Public Class modstart

    Public Function InitializeModule(ByVal data As ModuleInitialization) As Integer

        data.GetMyData1()

    End Function
 

    'Public Shared formname As fman
 

    '

    ' WBCONTROL Toolstripmenuitem

    ''

    Public Function WBCONTROLMenu() As ToolStripMenuItem
 

        ' Declarations

        Dim tsitem As New ToolStripMenuItem
 

        ' Setup Item

        tsitem.Text = "File Manager"

        tsitem.ForeColor = Color.Black

        'tsitem.Image = Global.fmanlib.My.Resources.fman

        AddHandler tsitem.Click, AddressOf FMANStart
 

        ' Return

        Return tsitem
 

    End Function
 

    Public Sub FMANStart()

        'Dim newform As New fman

        'formname = newform

        'newform.Show()

    End Sub
 

End Class

Open in new window

0
 
LVL 19

Expert Comment

by:drichards
Comment Utility
Create a new project called VBInterface and put this code into the vb file:
Public Interface ModuleInitialization

    Function GetMyData1() As Integer

    Function GetMyData2() As DateTime

End Interface

Open in new window

0
 
LVL 19

Expert Comment

by:drichards
Comment Utility
Now in your main app I added the class that performs the module initializattion and in the LoadModules function, I added the call to initialize the module using the ModuleInitialization interface.  To deploy this solution, put VBInterface.dll into the folder with the main app,  To show that this works, make sure there is not a copy of VBInterface.dll in the Module folder.  It should only be in the folder with the main executable.
    Class MyModuleInitialization

        Implements ModuleInitialization
 

        Public Function GetMyData1() As Integer Implements ModuleInitialization.GetMyData1

            Return 2

        End Function
 

        Public Function GetMyData2() As Date Implements ModuleInitialization.GetMyData2

            Return DateTime.Now

        End Function

    End Class
 

    Private Sub LoadModules()

        ' Declarations

        Dim moddir As String = Application.StartupPath & "\Modules"

        Dim dirinfo As New DirectoryInfo(moddir)

        Dim filelist() As FileInfo = dirinfo.GetFiles("*.dll")

        Dim modcount As Integer = filelist.Length

        If modcount > 0 Then

            Dim moduleobj(modcount) As Object

            Dim modnum As Integer = 0
 

            ' Module iteration

            For Each filename As FileInfo In filelist

                Dim AssemblyName As Assembly = Assembly.LoadFile(filename.FullName)
 

                Try

                    moduleobj(modnum) = AssemblyName.CreateInstance(String.Format("{0}.{1}", AssemblyName.GetName().Name, "modstart"))

                Catch ex As Exception

                    MsgBox(ex.ToString, MsgBoxStyle.Exclamation, "Unknown Module")

                End Try
 

                'Initialize Module (NEW!!!)

                Dim initClass As MyModuleInitialization = New MyModuleInitialization()

                moduleobj(modnum).InitializeModule(initClass)
 

                ' Add Menu Item

                Dim modmenuitem As ToolStripMenuItem = moduleobj(modnum).WBCONTROLmenu()

                'lmenu.Items.Add(modmenuitem)

                modnum += 1

            Next
 

            ' Separator

            Dim sep As New ToolStripSeparator

            'lmenu.Items.Add(sep)
 

        End If

    End Sub

Open in new window

0
 

Author Comment

by:jerute
Comment Utility
Well that's cleared up one of my problems. I was trying to understand how my loaded assembly should reference the interface assembly, and had assumed from what you had written that you meant it should do so at runtime. Now I see it needed to do so at compile time, so all OK there!

But, going back to my post above, when the main app references the interface assembly at runtime and tries to implement the object, it tells me that it doesn't exist. Obviously I cannot load it the same way I load the main modules.

How should I do this? In the following code intellisense is telling me that ifobj is undefined...
Module wbmain

 

         ' Interface Object

         Public ifobj As Object

 

         Private Sub LoadInterface()

		' Declarations

		Dim moddir As String = Application.StartupPath & "\Modules"

		Dim AssemblyName As Assembly = Assembly.LoadFile(moddir & "\fmaniface.dll")

		ifobj = AssemblyName.CreateInstance(String.Format("{0}.{1}", AssemblyName.GetName().Name, "class1"))

	End Sub

 

	Class ModuleInit

		Implements ifobj

 

		Public Function GetMyData1() As Integer Implements ModuleIterface.GetMyData1

			Return 2

		End Function

 

		Public Function GetMyData2() As Date Implements ModuleIterface.GetMyData2

			Return DateTime.Now

		End Function

	End Class

 

End Module

Open in new window

0
How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

 
LVL 19

Expert Comment

by:drichards
Comment Utility
You don't load the interface assembly.  If you follow my code examples closely, you will see that ModuleInit (I called it MyModuleInitialization) is in the main app project.  The main app then picks up the interface assembly automatically at runtime (that's why they need to be in the same folder).  The when you dynamically load the other modules, the interface assembly is already loaded into the app and no further work is required.

Go back to my next-to-last code post and you'll see that the ONLY thing in the interface assembly is the Interface itself.  There is NO implementation in the interface assembly.  If you have data types that will be common to both the main app and the loaded modules, they should go in the interface assembly as well.
0
 

Author Comment

by:jerute
Comment Utility
I'm afraid, drichards, it is you who doesn't understand me, and you haven't explained yourself adequately. Allow me to quote you...

"You don't load the interface assembly. If you follow... app project. The main app project then picks up the interface assembly automatically at runtime."

How does the main app pick up the interface assembly at runtime if it doesn't load it?

Also, the code in my last post IS in my main app. As I stated, I am unclear how to implement the interface. Are you saying that the interface class library needs to be added as a reference to the main app at compile time, just like it does to the assembly it is loading? If so, that defeats the object...

Would it help if I attached an example of my project to demonstrate how it is setup?

Many thanks again
0
 
LVL 19

Expert Comment

by:drichards
Comment Utility
Yes, I see that the code you posted is in the main app.  Sorry for missing that before, but what is class1?  You are trying to load modstart from the assembly and that should not change.

> Are you saying that the interface class library needs to be added as a reference to the main
> app at compile time, just like it does to the assembly it is loading? If so, that defeats the object...

Yes, that is what I am saying, and no, it doesn't defeat the purpose.  The purpose is to define an interface that allows the two parts of your project to communicate.  This should live in an assembly that is referenced by both the main app and the assmeblies you want to load dynamically.  Thet only thing that lives in that common assembly is the definition of the interface and any data types that are needed by the main app and the other assembly.  For this test there aren't any data types since we're just  using CLR types in the interface.

The common assembly is loaded automatically (which is what I meant when I said YOU don't have to load it) and will be available to any assembly you later load dynamically via Assembly.LoadFile.

Your problem is that you do not properly define the interface.  You have a line:

         Public ifobj As Object

in your main code that should not be there.  Then when you define the class to implement the interface:

      Class ModuleInit
            Implements ifobj

you should not use ifobj.  Rather, you need to say "Implements ModuleInterface" and reference the project that defines ModuleInterface.  The code in that project should just be:

Public Interface ModuleInterface
    Function GetMyData1() As Integer
    Function GetMyData2() As DateTime
End Interface

which is probably what you've got there (you didn't say).  Then everything should work.
0
 
LVL 19

Expert Comment

by:drichards
Comment Utility
Any progress here?
0
 

Author Comment

by:jerute
Comment Utility
Ok, well some good news. I get the gist of how the interface works in this respect. That's a marked improvement in a few days ago and I thank you for that, and for your patience.

I have included the code as instructed, though the names have changed to match the environment. I am stuck at the moment because of a design time error from the IDE intellisense. In the main application, in the 'FileManagerInterface' Class, the line

... implements fmaninterface...

generates the following error:

Class 'FileManagerInterface' must implement 'Function GetDefDir() As String' for interface 'fmanaif.modinterface.fmaninterface'

...and as I stare at my code I sort of thought I had.
fmanaif.dll - The interface library referenced by both fmanlib.dll and wbcontrol.exe

====================================================================================
 

Public Class Modinterface

    Public Interface fmaninterface

    Function GetDefDir() As String

    End Interface

End Class
 
 
 
 
 

fmanlib.dll - The Add-in module

===============================
 

Imports System.Windows.Forms

Imports System.Drawing

Imports fmanaif.modinterface
 

Public Class modstart
 

	Public Function Startup(ByVal data As fmaninterface) As Integer

		data.GetDefDir()

	End Function
 

	Public Function WBCONTROLMenu() As ToolStripMenuItem
 

		Dim tsitem As New ToolStripMenuItem

		tsitem.Text = "File Manager"

		tsitem.ForeColor = Color.Black

		tsitem.Image = Global.fmanlib.My.Resources.fman

		AddHandler tsitem.Click, AddressOf LoadForm

		Return tsitem
 

	End Function
 

	Public Sub LoadForm()

		Dim newform As New fman

		newform.Show()

	End Sub
 

End Class
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

wbcontrol.exe - The main application

====================================
 

Imports System.IO.File

Imports System.IO.Directory

Imports System.Reflection

Imports System.IO

Imports Microsoft.Office.Interop

Imports fmanaif.modinterface
 

Public Class wbmain
 

    Private Sub LoadModules()
 

    ...
 

    Dim initClass As FileManagerInterface = New FileManagerInterface()

    moduleobj(modnum).Startup(initClass)

    ...
 

    End Sub
 

End Class
 

Public Class FileManagerInterface

    Implements fmaninterface
 

    Public Function GetDefDir() As String

    Return defdir

    End Function

End Class

Open in new window

0
 
LVL 19

Expert Comment

by:drichards
Comment Utility
Oh so close.  You need to add "Implements fmaninterface.GetDefDir" after "Public Function GetDefDir() As String":

Public Function GetDefDir() As String Implements fmaninterface.GetDefDir

That should do it.

Also, you should get rid of the "Public Class Modinterface" around the fmaninterface definition in the interface dll and just use "Imports fmanaif" in the other projects.  It's not necessary and just adds extra indirection.



0
 
LVL 19

Expert Comment

by:drichards
Comment Utility
Just to be clear, that last comment is to change the function defiinition inside the FileManagerInterface class.  It will look like this when done:

Public Class FileManagerInterface
    Implements fmaninterface
 
    Public Function GetDefDir() As String Implements fmaninterface.GetDefDir
    Return defdir ' I assume you need to declare defdir here as well
    End Function
End Class
0
 

Author Comment

by:jerute
Comment Utility
Ok, Got it working. Thanks again and I'll issue your points shortly. However, I have one issue with this. I was looking for a method whereby the loaded assembly could question the main app without assistance. I was under the impression that interfaces allowed this, but with this method I see they are not totally independant.

The 'initclass' variable must be defined in the main app and passed to the loaded assembly BEFORE the assembly can ask for the variable state. Bit of an inconvenience. Can I store the initclass variable in the loaded assembly and re-use it anytime I want to question the main app? If not, then my loaded assembly still relies on the main app in this respect.

Thanks again.
0
 
LVL 19

Accepted Solution

by:
drichards earned 500 total points
Comment Utility
Now that you've got the basic design implemented and working, you can explore some more advanced options:

1) Yes, you can store the interface in the loaded assemblies and use it any time you want to reach out into the app.

2) You can have a (other) interface that the modules give to the app so the app can call into the modules.  the Startup method could return an interface from the modules or you could put a method on fmaninterface something like "fmaninterface.GetInterface()" which could return an interface for the app to use.  The disadvantage of this is that the app has to manage which interface reference goes with which module.

3) You can add events/delegates to fmaninterface so the modules can notify the app when something interesting happens.

As I say, now that you've got it working, you can explore some more advanced uses if you need.  Please feel free to ask more questions if you are interested in additional features.

As for your previous comment:

>> a method whereby the loaded assembly could question the main app without assistance
This does not exist.  For one piece of code to call another, it needs to know how, and that needs to be built in at compile time.  Dynamic loading/reflection is still compile time knowledge since you had to know what classes to create and what methods ot call.  You are just defering the dependency from link time to run time.

Reflection (like you did for the app to call into the module inthe frst place) still puts a tight dependency between the module and the app, though.  Changes to the app structure will break the system.  The interface approach frees you from any dependence on implementation other than that the app must implement the interface methods somewhere, and somewhere is not restricted.  

You could define a specific class/classes to use in reflection, but that's functionally equivalent to defining an interface and far less transparent.  The interface approach gives you a well-defined interaction and lets someone joining your project easily see the couling between the app and the modules.
0
 

Author Closing Comment

by:jerute
Comment Utility
Thanks again for your input drichards. I clearly haven't been the best student, mainly because, being self-taught, I'm not clued up on much of the terminology used and trying to get to grips with interfaces has been a headache. But I got there with your help.

I'm certainly interested in one of the points you raised:

"...You can add events/delegates to maninterface so that the modules can notify the app when something interesting happens..."

This will certainly be an avenue I must examine at some stage as it would provide the means address another need of the module: to refresh a form in the main app. The use of an event here would be ideal.

As for delegates, they are another function that I have struggled with. I have come across them while dealing with threading, and they left me feeling a bit cold too.

Thanks again for your help.

Jerute
0

Featured Post

How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

Join & Write a Comment

For those of you who don't follow the news, or just happen to live under rocks, Microsoft Research released a beta SDK (http://www.microsoft.com/en-us/download/details.aspx?id=27876) for the Xbox 360 Kinect. If you don't know what a Kinect is (http:…
The ECB site provides FX rates for major currencies since its inception in 1999 in the form of an XML feed. The files have the following format (reducted for brevity) (CODE) There are three files available HERE (http://www.ecb.europa.eu/stats/exch…
This video gives you a great overview about bandwidth monitoring with SNMP and WMI with our network monitoring solution PRTG Network Monitor (https://www.paessler.com/prtg). If you're looking for how to monitor bandwidth using netflow or packet s…
This demo shows you how to set up the containerized NetScaler CPX with NetScaler Management and Analytics System in a non-routable Mesos/Marathon environment for use with Micro-Services applications.

771 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

11 Experts available now in Live!

Get 1:1 Help Now