Still celebrating National IT Professionals Day with 3 months of free Premium Membership. Use Code ITDAY17

x
?
Solved

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

Posted on 2008-10-23
20
Medium Priority
?
246 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
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 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
Concerto Cloud for Software Providers & ISVs

Can Concerto Cloud Services help you focus on evolving your application offerings, while delivering the best cloud experience to your customers? From DevOps to revenue models and customer support, the answer is yes!

Learn how Concerto can help you.

 

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

Efficient way to get backups off site to Azure

This user guide provides instructions on how to deploy and configure both a StoneFly Scale Out NAS Enterprise Cloud Drive virtual machine and Veeam Cloud Connect in the Microsoft Azure Cloud.

Question has a verified solution.

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

It’s quite interesting for me as I worked with Excel using vb.net for some time. Here are some topics which I know want to share with others whom this might help. First of all if you are working with Excel then you need to Download the Following …
Wouldn’t it be nice if you could test whether an element is contained in an array by using a Contains method just like the one available on List objects? Wouldn’t it be good if you could write code like this? (CODE) In .NET 3.5, this is possible…
This is my first video review of Microsoft Bookings, I will be doing a part two with a bit more information, but wanted to get this out to you folks.
Visualize your data even better in Access queries. Given a date and a value, this lesson shows how to compare that value with the previous value, calculate the difference, and display a circle if the value is the same, an up triangle if it increased…

721 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