Dynamically Loading DLLs Over Network at Import Time Like JS
고남현 @gnh1201@hackers.pub
The "DLL Hell" Problem in Windows Apps
When developing Windows (.NET) based applications, the build process results in numerous *.dll files alongside the main *.exe executable file.
For example:
Program.exe # Main executable
a.dll # Compiled library A
b.dll # Compiled library B
c.dll # Compiled library C
...
This pattern creates various compiled library (*.dll) files that support the *.exe file, and these must be included during deployment. This situation is sometimes referred to as "DLL Hell."
While there are existing methods to combine these files into one (Costura.Fody, ILmerge, ILRepack, etc.), the approach introduced here is different.
Merging Assemblies Is Not a Recommended Practice
In the Windows environment, compiled outputs like *.exe and *.dll are called "assemblies." Due to deployment inconveniences, many different methods exist to merge these assemblies into a single file.
Besides using the merging tools mentioned earlier, there are various approaches such as compressing all additional assemblies and embedding them within the project.
However, these merging methods often rely more on developer improvisation rather than ecosystem-agreed practices, which can become an obstacle for future collaboration since only the original developer understands the merging method used.
In fact, Microsoft does not officially provide examples for merging assemblies, and they've even discontinued official support for the ILmerge tool that was previously supported.
This indicates that unless absolutely necessary, it's recommended to maintain assemblies as separate files during deployment.
What If We Change the Data Source?
I decided to avoid merging assemblies into a single file. Instead, I considered an alternative approach, which resulted in dynamically loading assemblies from the network.
In C#, libraries are imported using the following syntax (using statement):
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using System.Windows.Forms;
using WelsonJS.Esent;
In .NET, it's possible to programmatically intervene in the library import process (resolving the using keyword).
For an application that required minimizing additional assembly (*.dll) files, I decided to intervene in the using keyword resolution process and connect the assembly resolution part to a CDN (in this case, Azure Blob Storage was used as the Object Storage).
Now, the path for retrieving *.dll files is the network rather than local storage.
To minimize potential misuse cases when applied, I added a verification process to ensure that only assemblies with valid code signatures can be loaded.
The actual implementation can be found at the link below:
If an assembly already exists in the same directory, it won't be downloaded from the network.
Does This Handle Native (C/C++ Based) Assemblies, Not Just .NET IL Compiled Assemblies?
The implementation in AssemblyLoader.cs (gnh1201/welsonjs) includes support for native (C/C++ based) compiled assemblies.
However, while libraries compiled with .NET IL (like C#) are processed automatically, native libraries must be loaded explicitly.
Example Implementation
The following code should be executed in Main or an equivalent entry point to initiate the loading of additional assemblies:
// load external assemblies
AssemblyLoader.BaseUrl = GetAppConfig("AssemblyBaseUrl");
AssemblyLoader.Logger = _logger;
AssemblyLoader.Register();
AssemblyLoader.LoadNativeModules("MyNativeLib", new Version(1, 0, 0, 0), new[] { "MyNativeLib.dll" });
.NET IL assemblies requested with the using keyword are automatically found and loaded. For native assemblies, you must explicitly specify the assembly information to define which ones should be loaded.
Here are examples of remote network addresses accessed during assembly resolution when the Base URL is https://example.cdn.tld/packages:
- https://example.cdn.tld/packages/managed/MyManagedLib/1.0.0.0/MyManagedLib.dll (.NET IL assembly)
- https://example.cdn.tld/packages/native/MyNativeLib/1.0.0.0/MyNativeLib.dll (native assembly)
The server must support secure communication (https), and all additional assemblies (*.dll) must have valid code signatures.
Do I Still Need to Include Additional Assemblies in Deployment When Using Dynamic Loading Over Network?
Even when implementing dynamic assembly loading over the network, it's still recommended to include additional assemblies (*.dll) during deployment.
This method should be used as a supplementary approach to help resolve library missing errors that occur when users lack understanding of the Windows app deployment process and move only the main executable (.exe) to another computer without the required additional assemblies (.dll).
Of course, using this method means the application can function without including any additional assemblies during deployment since they'll be automatically downloaded from the network.
However, it's important to remember that this is merely a supplementary method and doesn't mean you should unconditionally exclude additional assemblies during deployment.