(Print this page)

Embedding a DLL in a .Net application
Published date: Sunday, December 20, 2015
On: Moer and Éric Moreau's web site

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.


(Print this page)