Go Premium for a chance to win a PS4. Enter to Win

x
?
Solved

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

Posted on 2008-10-23
20
Medium Priority
?
249 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
ID: 22793427
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
ID: 22793920
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
ID: 22794119
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
Veeam Task Manager for Hyper-V

Task Manager for Hyper-V provides critical information that allows you to monitor Hyper-V performance by displaying real-time views of CPU and memory at the individual VM-level, so you can quickly identify which VMs are using host resources.

 

Author Comment

by:jerute
ID: 22794468
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
ID: 22796406
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
ID: 22796596
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
ID: 22796846
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
ID: 22796859
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
ID: 22796944
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
ID: 22797080
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
 
LVL 19

Expert Comment

by:drichards
ID: 22800571
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
ID: 22801923
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
ID: 22803753
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
ID: 22819044
Any progress here?
0
 

Author Comment

by:jerute
ID: 22826041
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
ID: 22827232
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
ID: 22827251
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
ID: 22829014
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 2000 total points
ID: 22830724
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
ID: 31511178
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

What does it mean to be "Always On"?

Is your cloud always on? With an Always On cloud you won't have to worry about downtime for maintenance or software application code updates, ensuring that your bottom line isn't affected.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

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…
A long time ago (May 2011), I have written an article showing you how to create a DLL using Visual Studio 2005 to be hosted in SQL Server 2005. That was valid at that time and it is still valid if you are still using these versions. You can still re…
This course is ideal for IT System Administrators working with VMware vSphere and its associated products in their company infrastructure. This course teaches you how to install and maintain this virtualization technology to store data, prevent vuln…
This video shows how to quickly and easily deploy an email signature for all users in Office 365 and prevent it from being added to replies and forwards. (the resulting signature is applied on the server level in Exchange Online) The email signat…
Suggested Courses

885 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