Embedding a component as a resource in an application simplifies the distribution because you have less resources (files) to distribute.
A couple of months ago, I have shown how to embed a font into a WPF application. But we are not limited to fonts. DLLs from Class library can be embedded as well but it is a bit more complex to use them.
This month, I will show you how to embed a .Net DLL into a .Net application. I believe that this trick can be useful in very specific situation. But for large applications, I would never recommend to use this solution!
This month demo code
This month downloadable demo code is provided in both VB and C#. The solution has been created using Visual Studio 2015 but can be used in older projects as well. It is in the form a Windows application using a Class library but it could be just about any other platform.
Building the Class Library
This is not where we will spend time because there is nothing special here. I will create a very simple class library project just for the sake of embedding it later in our application.
I have added a new VB Class Library to my solution and named this project EmbeddedClassLibrary. In that project, I created a single class named EmbeddedClass. And finally, this class contains a single public method named GetTime. The complex (sigh!) code of the whole class reads like this:
Option Strict On Public Class EmbeddedClass Public Function GetTime() As DateTime Return Now End Function End Class
As you can see, there is nothing special, no attributes, no special declaration. Just a plain-old class in a class library.
Now that you have a project, build it to get your .DLL file ready to embed.
Creating the client
A complex class library would require a complex UI. But since I have a very simple class library, I will also have a very simple UI.
So I created a simple form with a button (btnGetTime) to call the method and a label (lblTime) to show the result of the called method.
Figure 1: The simple UI
Embedding the component
Exactly like we did with the font in the previous article, we need to embed the component into our client project.
Add a folder named Resources to your client project. To add my DLL to this new folder, I have opened the bin folder of my class library in Windows Explorer and dragged the DLL from Windows Explorer directly into the Resources folder in my client project.
Once you have your component in your resources folder, check the properties of your component to ensure that the “Copy to Output Directory” property is set to “Do not copy” (otherwise if defeats the purpose!) and that the “Build Action” property is set to “Embedded Resource”.
Figure 2: Showing the properties of the embedded component
Adding a Reference?
Now this is a step you might find funny. You need to add a reference to your component like you have always done before. Why? Just to be able to use early binding and get Intellisense. Don’t worry, we won’t copy this reference to clients, I promise. You can even set the “Copy Local” property to False if you want.
There would be ways of using Reflection to use your object without adding a reference but this is not the topic of this month.
The tough job
The complex part in this whole article is to load the component into a usable object.
Add a class named EmbeddedAssembly to your project and paste this code in there:
Option Strict On Imports System.IO Imports System.Reflection Imports System.Security.Cryptography Public Class EmbeddedAssembly Private Shared mdicAssemblies As Dictionary(Of String, Assembly) = Nothing Public Shared Sub Load(ByVal pResourceString As String, ByVal pFileName As String) Console.WriteLine("Attempting to load the following embedded resource {0} ({1}).", pFileName, pResourceString) If mdicAssemblies Is Nothing Then mdicAssemblies = New Dictionary(Of String, Assembly)(StringComparer.OrdinalIgnoreCase) End If Dim arrBuffer As Byte() Dim objAssembly As Assembly Dim objCurrentAssembly = Assembly.GetExecutingAssembly() For Each name As String In objCurrentAssembly.GetManifestResourceNames() Console.WriteLine("Embedded resource {0} of '{1}'.", name, objCurrentAssembly.GetName()) Next Using objStream = objCurrentAssembly.GetManifestResourceStream(pResourceString) ' Either the file does not exist or it is not mark as an embedded resource. If objStream Is Nothing Then Console.WriteLine("WARNING!!! Embedded resource {0} ({1}) was not found in '{2}'.", pFileName, pResourceString, objCurrentAssembly.GetName()) Return End If Console.WriteLine("Found embedded resource {0} ({1}) in '{2}'.", pFileName, pResourceString, objCurrentAssembly.GetName()) ' Get byte[] from the file from embedded resource arrBuffer = New Byte(CInt(objStream.Length) - 1) {} objStream.Read(arrBuffer, 0, CInt(objStream.Length)) Try Console.WriteLine("Loading embedded resource {0} ({1}) from '{2}'.", pFileName, pResourceString, objCurrentAssembly.GetName()) objAssembly = Assembly.Load(arrBuffer) ' Add the assembly/dll into dictionary Console.WriteLine("Adding embedded resource {0} ({1}) to local dictionary '{2}'.", pFileName, pResourceString, objCurrentAssembly.GetName()) mdicAssemblies.Add(objAssembly.FullName, objAssembly) Return ' Purposely do nothing. An unmanaged dll or assembly cannot be loaded directly from byte[]. Let the process fall through for next part Catch ' Eat the exception End Try End Using Dim blnIsFileOk As Boolean Dim strTempFile As String Using sha1 = New SHA1CryptoServiceProvider() ' Get the hash value from embedded DLL/assembly Dim strHash As String = BitConverter.ToString(sha1.ComputeHash(arrBuffer)).Replace("-", String.Empty) ' Define the temporary storage location of the DLL/assembly strTempFile = String.Format("{0}{1}", Path.GetTempPath(), pFileName) ' Determines whether the DLL/assembly is existed or not If File.Exists(strTempFile) Then ' Get the hash value of the existed file Dim arrTempBuffer As Byte() = File.ReadAllBytes(strTempFile) Dim strTempHash As String = BitConverter.ToString(sha1.ComputeHash(arrTempBuffer)).Replace("-", String.Empty) ' Compare the existed DLL/assembly with the Embedded DLL/assembly blnIsFileOk = (strHash = strTempHash) Else blnIsFileOk = False End If End Using Try ' Create the file on disk If Not blnIsFileOk Then File.WriteAllBytes(strTempFile, arrBuffer) End If ' Load it into memory Console.WriteLine("Loading resource {0} from the disk.", strTempFile) objAssembly = Assembly.LoadFile(strTempFile) ' Add the loaded DLL/assembly into dictionary Console.WriteLine("Adding resource {0} to local dictionary '{1}'.", strTempFile, objCurrentAssembly.GetName()) mdicAssemblies.Add(objAssembly.FullName, objAssembly) Catch ex As Exception Console.WriteLine("Error loading resource {0} ({1}).", pFileName, pResourceString) End Try End Sub Public Shared Function [Get](ByVal pAssemblyFullName As String) As Assembly Console.WriteLine("Attempting to retrieve resource '{0}' from local dictionary.", pAssemblyFullName) If mdicAssemblies Is Nothing OrElse mdicAssemblies.Count = 0 Then Console.WriteLine("WARNING!!! There are no resources loaded in the local dictionary.") Return Nothing End If If mdicAssemblies.ContainsKey(pAssemblyFullName) Then Console.WriteLine("Found '{0}' in the local dictionary.", pAssemblyFullName) Return mdicAssemblies(pAssemblyFullName) End If Return Nothing ' Don't throw Exception if the dictionary does not contain the requested assembly. ' This is because the event of AssemblyResolve will be raised for every ' Embedded Resources (such as pictures) of the projects. ' Those resources wil not be loaded by this class and will not exist in dictionary. End Function End Class
This code really does the heavy lifting. We will call this method in the next section.
This is code was written by one of my fellow experts of Experts Exchange having the expert’s name of it_saige.
Loading the assembly from the resources
We now have all the parts to load the assembly from resources. This need to be done only once. For exemple in my demo application, I have done it in the Form’s load event using this code:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load EmbeddedAssembly.Load("DemoEmbeddingVB.EmbeddedClassLibrary.dll", "EmbeddedClassLibrary.dll") AddHandler AppDomain.CurrentDomain.AssemblyResolve, AddressOf OnAssemblyResolve End Sub
This snippet does 2 things. The first one is to load the assembly from the resources. The tough part here is being able to find out the correct arguments to pass to the Load method. The first argument is the namespace of the client application (yes the namespace of the client application - DemoEmbeddingVB in my demo) followed by the name of the resource. The second one, is the name of the resource again.
The second line of code in this snippet is to register the AssemblyResolve event. This event will be raised only when the assembly from the resource will be loaded in memory.
Private Function OnAssemblyResolve(sender As Object, args As ResolveEventArgs) As Assembly Dim result As Assembly Try result = EmbeddedAssembly.Get(args.Name) Catch ex As Exception Console.WriteLine("Cannot resolve assembly for - {0}", args.Name) result = Nothing End Try Return result End Function
This code will effectively load the assembly from the resources.
Namespace for C# readers
C# readers will need to provide the first parameter in a format a bit different than VB developers. In C#, we need to specify the name of the resource’s folder. The code needs to look like this:
EmbeddedAssembly.Load("DemoEmbeddingCS.Resources.EmbeddedClassLibrary.dll", "EmbeddedClassLibrary.dll");
You see the difference? The name of the folder (Resources) has been provided in the name of the component to load. So if you named your folder anything else than Resources, you will need to adjust this line.
Using the assembly
We are now finally ready use the embedded component. Do you remember completely at the beginning of this article, our simple class had a single method named GetTime? It is now the time to call it.
On my simple UI (see figure 1), I have a button to call that method.
Private Sub btnGetTime_Click(sender As Object, e As EventArgs) Handles btnGetTime.Click dim objTest as new EmbeddedClassLibrary.EmbeddedClass lblTime.Text = objTest.GetTime().ToLongTimeString() End Sub
There is nothing special here. We just create an instance of the class, and call its method. If the component was properly loaded, it will just work.
If you did anything wrong (most surely incorrectly provided the namespace and assembly name), you will end up with a FileNotFoundException as shown in figure 3.
Figure 3: Typical error in this scenario
Debugging
Some magic happens here. You can still fully debug the component like if it was used the regular way.
Even if the component is loaded at runtime, don’t forget that we have added a reference to the assembly and this is why we are able to fully debug it.
Testing the application
Now that we have done all this, we want to make sure that it will still be working when we will ship the .exe of our application without the .dll.
Open the build folder and copy the .exe only to a USB stick if you want, go on another computer and execute your application from the USB stick. If this other computer has the .Net Framework installed, your simple application will work without having to copy the .dll because it is embedded as a resource into the .exe file.
References
If you are a member of Experts Exchange and would like to see the original posts of it_saige, you need to go here for the VB version or here for the C# version.
Notice that if you are not a member of Experts Exchange, chances are that you won’t see part of content.
Conclusion
Like I told you in the introduction, I believe that this trick can be useful in some circumstances but I don’t believe in it for large solution.