Microsoft .NET has an interesting and very useful concept
called Reflection. Since your compiled programs in .NET are compiled to IL and
not machine code, it is very easy to query an EXE or DLL for the information it
exposes in the form of classes, methods and properties, etc. In this article,
I will show you how to create a very simple and easy method for "plug-in"
type architectures.
Our project will consist of 2 DLL's and one EXE. Our EXE
will show a ListBox and fill it with any of the strings of information it finds
in compatible classes. When you double click on the box, it will dynamically
call the corresponding class that placed the string in the box. Our regular
class will have one string and our plug-in class will have another, if the EXE
finds the DLL in its directory.
As with all good architectures, we will start with an
interface. The interface defined below will be the contract between our main
EXE and any plug-in DLLs that exist. We put the interface in its own DLL so
other people can use it. It is typical to have a DLL that is specifically for
your interfaces. This allows any other projects to get to them easily. You
can also have more control over the distribution of it when others want to add
plug-ins to your application. Ours is simple for this demo, but it can be as
complex as you need it to be. Here is its definition:
Listing 1
Public Interface IShowInListBox
Property TextValue() As String
Sub SayHello()
End Interface
It has a single property and a sub. The property will
return the line of text that we want to show in the ListBox. The sub will be
the code that gets fired when we double click on the item in the list box. Any
class that wants to be recognized by our main EXE as a plug-in will have to
implement this interface.
We will now look at our two classes that will implement this
interface. One class is in our main EXE. This one will function as the "standard
functionality" of our app. The other class is in our second DLL. This
one will function as our plug-in class. Here are the definitions.
First the standard one:
Listing 2
Imports System.Windows.Forms
Public Class MyListItem
Implements ReflectionInterfaces.IShowInListBox
Sub SayHello() Implements ReflectionInterfaces.IShowInListBox.SayHello
MessageBox.Show("Hello from My Exe!")
End Sub
Property IShowInListBox_TextValue() As String _
ImplementsReflectionInterfaces.IShowInListBox.TextValue
Get
Return "ListBox Item From Exe"
End Get
Set(ByVal Value As String)
End Set
End Property
End Class
Here is the plug-in class.
Listing 3
Imports System.Windows.Forms
Public Class DllListItem
Implements ReflectionInterfaces.IShowInListBox
Sub SayHello() ImplementsReflectionInterfaces.IShowInListBox.SayHello
MessageBox.Show("Hello from a DLLClass!")
End Sub
Property IShowInListBox_TextValue() As String _
ImplementsReflectionInterfaces.IShowInListBox.TextValue
Get
Return "ListBox Item From a DLL Class"
End Get
Set(ByVal Value As String)
End Set
End Property
End Class
You will notice that they are almost exactly the same. The
only differences being their names, what they show in the message box and the
strings they return to show in the list box. Notice that both of the classes implement
ReflectionInterfaces.IShowInListBox. ReflectionInterfaces is the name of the
namespace I put our interface in.
Now we will examine the main working code in our EXE. It
will load our standard class in the ListBox and then search for classes in our
plug-in DLL that implement our interface and load them. To show that it picks
up only classes that implement IshowInListBox, I created another class that
does not implement it and placed it in the DLL. Its definition is very simple
and is as follows:
Listing 4
Public Class NonListItem
End Class
And now our main EXE:
Listing 5
Imports System.Reflection
Imports ReflectionInterfaces
Private Sub Form1_Load(ByVal sender AsSystem.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim myCls As MyListItem = New MyListItem
lstListBox1.DisplayMember = "TextValue"
lstListBox1.Items.Add(myCls)
LoadFromFoundDll()
End Sub
Private Sub LoadFromFoundDll()
Dim assemblyObj As Reflection.Assembly = _
Reflection.Assembly.LoadFrom("C:\VBNetProjects\ReflectionArticle\bin\ReflectionDll.Dll")
Dim types() As Type
Dim FoundInterface As Type
Dim o As Object
types = assemblyObj.GetTypes
For Each tp As Type In types
Try
FoundInterface =tp.GetInterface("IShowInListBox")
If Not FoundInterface Is Nothing Then
o = assemblyObj.CreateInstance(tp.FullName)
Dim oLst As IShowInListBox
oLst = DirectCast(o, IShowInListBox)
lstListBox1.Items.Add(oLst)
End If
Catch
Finally
FoundInterface = Nothing
End Try
Next
End Sub
Private Sub lstListBox1_DoubleClick(ByVal sender AsObject, ByVal e As System.EventArgs) _
Handles lstListBox1.DoubleClick
Dim oLst As IShowInListBox
Dim Lst As ListBox
Lst = DirectCast(sender, ListBox)
oLst = DirectCast(Lst.SelectedItem,IShowInListBox)
oLst.SayHello()
End Sub
It is a single form with a ListBox on it. I have added a
reference to my interface DLL and the corresponding imports for it and also for
System.Reflection to get our reflection classes we will use. Now I will
explain the high points of the code.
If we start in the form load event, you see that I am
setting up a new instance of my standard class and adding it to the list box. The
key thing to notice here is that I am adding the entire object and not just the
text from its property. This is one of .NET's strong points. I can assign
anything to the list box, even whole objects, and pull them out later. I also
can tell the list box to display the TextValue property of any object that is
loaded into it. How do you think it displays text of object that it only knows
the name of the property of? If you said reflection you would be right! It
uses it internally to dynamically call the property you specify. After loading
the standard class we then call a function that will load our plug-in class.
In our LoadFromFoundDll we hard code the name of the DLL for
simplicity. You could change this to search the application’s path for all
DLL’s that have classes in them that implement our interface or have it look in
the registry for the paths and names of the DLL’s it is supposed to load to
make it more useful.
First we want to load our DLL in to memory using the
assembly class. After it is loaded, we can query the DLL for all its classes,
which we do by asking for its types and loading them into an array. We then
loop through the array and query it to see if it supports our interface. If it
does, we create an object of the class and load it into our list box.
Now for our last function, the double click event of the ListBox.
In this function, we want to dynamically call the correct object when the user
double clicks on it. Since we have the interface as our contract, it is very
simple to convert the returned object form the list box into an object of our
interface and run the correct subroutine.
Summary
As you can see, creating a plug-in architecture in .NET is
much easier than it used to be in VB6. Reflection allows you to create a
robust dynamic application that your end users will appreciate. It also allows
you to control what end functionality the user gets and allows for easier
partial patching of your system when you find bugs in specific plug-ins. In a
later article, we will examine how to read all information from an assembly in
.NET.