A cross-platform .NET Standard 2.1 library for dynamically loading and unloading native (unmanaged) and managed assemblies at runtime. It provides a flexible alternative to traditional P/Invoke by enabling dynamic declaration of interop methods and runtime type inspection across Linux, macOS, and Windows.
Working with native libraries in .NET often requires:
- Hardcoding platform-specific paths and extensions.
- Static P/Invoke declarations that are brittle and inflexible.
- Limited control over unloading assemblies and reclaiming memory.
NetTools.AssemblyLoader solves these problems by:
- Providing a unified
INativeAssemblyLoaderinterface with built-in loaders for Linux, macOS, and Windows. - Offering a
DynamicAssemblyclass to declare and invoke P/Invoke methods at runtime. - Supporting dynamic managed assembly loading/unloading via
ManagedTypeWrapperand a customCollectableAssemblyLoadContext. - Including
RuntimePlatformhelpers to detect OS and CPU architecture at runtime.
This makes it easier to build cross-platform .NET applications that integrate with native libraries, while keeping memory usage predictable and 'hopefully' avoiding leaks.
In addition, it provides a more flexible and dynamic approach to interop compared to traditional static P/Invoke declarations. For example, if in Windows you were to require functionality of native GDI+ (Gdiplus.dll), you could dynamically load the library and declare the necessary methods at runtime, without hardcoding paths or relying on static declarations. Meaning that functionality can easily be managed and replaced on other operating systems and CPU architectures.
-
Dynamic native assembly loading
Load unmanaged libraries at runtime with platform-specific loaders. -
Dynamic P/Invoke declaration
Define and invoke native methods dynamically usingDynamicAssembly. -
Managed assembly loading/unloading
Inspect and invoke managed types dynamically withManagedTypeWrapper. -
Cross-platform support
Built-in loaders for Linux, macOS, and Windows. -
Runtime detection utilities
Identify the current OS and CPU architecture withRuntimePlatform.
To use NetTools.AssemblyLoader in your project, simply add the library via NuGet Package Manager:
Install-Package NetTools.AssemblyLoaderHere's a few basic demonstrations of how to use NetTools.AssemblyLoader:
This snippet demonstrates how to use the RuntimePlatform helper to identify the current operating system and CPU architecture at runtime. It retrieves both an enum value (platform) and a string representation (runtimeString), making it easy to branch logic or construct platform‑specific paths in cross‑platform applications.
using NetTools.AssemblyLoader;
var platform = RuntimePlatform.GetRuntimePlatform();
var runtimeString = RuntimePlatform.GetRuntimeString();
Console.WriteLine($"Running on: {platform} ({runtimeString})");This example shows how to use the ManagedTypeWrapper to load and interact with managed assemblies at runtime. It demonstrates retrieving all types from a given assembly, wrapping a specific type for inspection, extracting metadata such as assembly name and version, and dynamically invoking methods without static references. This enables flexible runtime discovery and execution of managed code across different assemblies.
using NetTools.AssemblyLoader.Managed;
// Load types from a managed assembly (this example loads itself as a dynamic library, replace with any managed library path and known types).
var types = ManagedTypeWrapper.GetTypes("NetTools.AssemblyLoader.dll");
Console.WriteLine($"Found {types.Count()} types.");
// Wrap a specific type.
using var wrapper = new ManagedTypeWrapper("NetTools.AssemblyLoader.dll", "NetTools.AssemblyLoader.RuntimePlatform");
// Inspect type metadata.
wrapper.GetAssemblyName(out var name, out var version);
wrapper.GetTypeName(out var typeName, out var fullName);
Console.WriteLine($"Loaded {name} v{version}, type: {fullName}");
// Invoke a method dynamically. You can invoke Methods with arguments, and Fields and Properties dynamically using the Invoke methods within ManagedTypeWrapper.
var runtimeString = wrapper.Invoke("GetRuntimeString");
Console.WriteLine($"Runtime string: {runtimeString}");This example demonstrates how to use the DynamicAssembly class to define and invoke native methods at runtime without static P/Invoke declarations. By dynamically binding to functions in unmanaged libraries (such as user32.dll), you can call native APIs on demand, customize signatures, and manage interop more flexibly across platforms. This approach avoids compile‑time bindings and enables dynamic interop scenarios where native methods are discovered or loaded at runtime.
using NetTools.AssemblyLoader.Dynamic;
// Define a dynamic P/Invoke for user32.dll MessageBox
using var dynamicAssembly = new DynamicAssembly(
new PInvokeMethod("user32.dll", "ShowMessageBox", "MessageBoxA",
new PInvokeType<int>(),
new PInvokeType[] {
new PInvokeType<IntPtr>(),
new PInvokeType<string>(),
new PInvokeType<string>(),
new PInvokeType<uint>()
})
);
// Invoke the method
var result = dynamicAssembly.Invoke("ShowMessageBox",
IntPtr.Zero,
"Hello from dynamic P/Invoke!",
"DynamicAssembly Example",
(uint)0);
Console.WriteLine($"MessageBox returned: {result}");This example illustrates how to load unmanaged libraries at runtime using the platform‑specific INativeAssemblyLoader implementations. By selecting the appropriate loader for Windows, Linux, or macOS, you can dynamically resolve and load native dependencies without hardcoding platform details. The Load method returns a result indicating success or failure, allowing you to handle errors gracefully and ensure your application works consistently across different operating systems.
using NetTools.AssemblyLoader.Native.AssemblyLoaders;
var libraryPath = "path/to/native/library";
using var loader = new WindowsAssemblyLoader();
// Or LinuxAssemblyLoader / MacOsAssemblyLoader depending on runtime
// platform detection (see above example).
var result = loader.Load(libraryPath);
if (!result.Success)
Console.WriteLine($"Failed to load: {result.Error}");
else
Console.WriteLine($"Successfully loaded {libraryPath}");| Class/Interface | Namespace | Description |
|---|---|---|
| INativeAssemblyLoader | NetTools.AssemblyLoader | Interface for native assembly loaders. |
| LinuxAssemblyLoader | NetTools.AssemblyLoader.Native.AssemblyLoaders | Loader for Linux native libraries. |
| MacOsAssemblyLoader | NetTools.AssemblyLoader.Native.AssemblyLoaders | Loader for macOS native libraries. |
| WindowsAssemblyLoader | NetTools.AssemblyLoader.Native.AssemblyLoaders | Loader for Windows native libraries. |
| DynamicAssembly | NetTools.AssemblyLoader.Dynamic | Define and invoke P/Invoke methods dynamically. |
| ManagedTypeWrapper | NetTools.AssemblyLoader.Managed | Load and interact with managed types dynamically. |
| RuntimePlatform | NetTools.AssemblyLoader | Detect OS and CPU architecture at runtime. |
Explore the NetTools.AssemblyLoader.TestProject/Program.cs.
For more detailed guidance and examples, visit our repository wiki or our website (coming soon).
Full documentation coming soon!
We welcome contributions! To get involved:
- Fork NetTools.AssemblyLoader, make improvements, and submit a pull request.
- Code will be reviewed upon submission.
- Join discussions via the issues board.
This project is licensed under the MIT License, allowing unrestricted use, modification, and distribution. If you use NetTools.AssemblyLoader in your own project, we’d love to hear about your experience, and possibly feature you on our website!