The Problem
It was decided by the powers that we take an existing in-house application and release it externally. This application used a proprietary class library, which could not to be included. However, the application would need the same functionality in a new class library when released externally. Furthermore, the existing in-house class library was scheduled to be re-architected in the near future.
The challenge was to maintain a single code base for the application to support both the existing and the new DLLs, and be flexible enough to support the yet-to-be-designed rewrite of the existing DLL. This seemed a perfect scenario to use the System.Reflection
namespace and the GetInterface()
method. This would allow me to create swappable class libraries for each implementation option, and access them in a type safe manner.
The Solution
The plan was to create an interface to describe the properties, methods, and events that will be exposed from the external class library. Any external dynamic linked library must implement this interface. We will then create a helper class library implementing the same interface. The helper class library will contain all the logic needed to load an external DLL implementing the interface. The main application will simply link to the helper class library, and remain oblivious to the logic needed to link specific external class libraries.
We will use VB.NET 2005 to create a solution of several fairly generic projects. These projects will serve no useful purpose other than to demonstrate this concept. The first thing we need to do is create an interface that describes our swappable library. We will create a class library project named InterfaceDLL. This project contains a single interface named ILibrary
.
Public Interface ILibrary
Function GetName() As String
End Interface
This interface states that any DLL to which our application intends to link must expose a GetName()
function that returns a String
data type. It is important to notice that we created a separate project for the interface. This is because the fully qualified name of the interface includes the project namespace. When other projects refer to this interface, they will refer to <InterfaceDLL.>ILibrary
. If we just linked the interface across projects, it would still belong to the individual projects, and thus, not be the same interface.
Next, we will create the first class library to use this interface. This will be the RedDLL project. It will contain the following code.
Imports InterfaceDLL
Public Class clsMe
Implements ILibrary
Public Function GetName() As String Implements ILibrary.GetName
Return "RedDLL"
End Function
End Class
This code imports the InterfaceDLL
namespace, and instructs this class to implement the ILibrary
interface. All we are required to implement is the GetName()
function. Here, we are returning the library name, “RedDLL”. We will create a nearly duplicate BlueDLL project, using the same interface. In our test case, it is the same code, with the exception of returning “BlueDLL”. It is included in the sample solution.
Next, we will create the helper class library project, in this case, ColorDLL. This project contains all the .NET magic for this example. First, just like the previous two class libraries, this one imports the InterfaceDLL
, and exposes the GetName()
function. But, this function is a little different from the others. This one makes use of the System.Reflection
namespace that we have imported. We can use LoadFrom()
from that namespace to get the assembly from a given class library (specified, in this case, in the app.config). We will then wade through the assembly, looking for an object that implements the ILibrary
interface. When we find it, we call its GetName()
function.
Imports InterfaceDLL
Imports System.Reflection
Public Class clsMe
Implements ILibrary
Private strColorDLL As String = _
System.Configuration.ConfigurationSettings.AppSettings("ColorDLL")
Public Function GetName() As String Implements ILibrary.GetName
Dim objAssembly As Reflection.Assembly
Dim clsColor As ILibrary = Nothing
Dim strColorDLLPath As String = _
System.IO.Path.Combine(My.Application.Info.DirectoryPath, strColorDLL)
Dim objTypes() As Type
Dim objFound As Type
Dim strRV As String = String.Empty
objAssembly = Reflection.Assembly.LoadFrom(strColorDLLPath)
objTypes = objAssembly.GetTypes
For Each objItem As Type In objTypes
objFound = objItem.GetInterface("ILibrary")
If objFound IsNot Nothing Then
clsColor = DirectCast(objAssembly.CreateInstance(objItem.FullName), _
ILibrary)
Exit For
End If
Next
If clsColor IsNot Nothing Then
strRV = clsColor.GetName
End If
GetName = strRV
End Function
End Class
For each project containing a class importing the ILibrary
interface, you will need to add a project reference to the InterfaceDLL project. This is done by opening the References tab in My Project, and clicking the Add button. Open the Projects tab in the Add Reference form, and find the InterfaceDLL project.
Finally, we will create the main application project, in this case, creatively named MainExe. For this example, we will just drop the code into the Load
event for Form1
.
Public Class Form1
Dim clsColor As New ColorDLL.clsMe
Private Sub Form1_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
Me.lblLibrary.Text = clsColor.GetName
End Sub
End Class
Form1_Load()
simply calls the GetName()
function from the helper class. MainExe knows nothing of the internals of RedDLL or BlueDLL, and doesn’t care. As long as RedDLL and BlueDLL implement the ILibrary
interface, the helper library will quietly call them.
Now, a couple things are happening behind the scenes here. First, when the developer builds the MainExe project, a reference will have to be added to the appropriate RedDLL or BlueDLL project or DLL, as well as a reference to the helper class. Additionally, we will add a line to the app.config to specify which DLL will be loaded by the helper class library. Remember, the app.config key is referenced in the helper class library, ColorDLL. It is not referenced in the main application.
<appSettings>
-->
<add key="ColorDLL" value="BlueDLL.dll"/>
</appSettings>
Conclusion
Now, using this method, you can create a system for plugging in interchangeable libraries, without having multiple versions of your main application. You are also ready for future libraries that implement the same interface. Finally, you do not have to stray from your type safe development.