Loading DLLs Dynamically Over Network at Import Time Like JS
고남현 @gnh1201@hackers.pub
The "DLL Hell" Problem in Windows Applications
When developing Windows (.NET) 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 (Costura.Fody, ILmerge, ILRepack, etc.), the approach introduced here is different.
Merging Assemblies Is Not a Recommended Practice
In Windows environments, compiled outputs like *.exe and *.dll files are called "assemblies." Due to deployment inconveniences, many 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 than on ecosystem-agreed practices, which can create obstacles 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: dynamically loading assemblies from the network.
In C#, libraries are imported using the following syntax (using statements):
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 requiring minimal additional assemblies (*.dll), I decided to intervene in the using keyword resolution process by connecting a CDN (in this case, Azure Blob Storage as an Object Storage) to the assembly resolution component.
This means the path for retrieving *.dll files is now the network rather than local storage.
To minimize potential misuse cases, 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 Work for Native (C/C++) Assemblies, Not Just .NET IL Compiled Assemblies?
The implementation in AssemblyLoader.cs (gnh1201/welsonjs) includes support for native (C/C++) 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 the Main method 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 Assembly 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 additional assemblies during deployment since they'll be automatically downloaded from the network.
However, it's important to remember that this is meant to be a supplementary method, not an indication that you should never include additional assemblies during deployment.