This is a simple proof of concept for dynamically mapping library exports to delegates using the dynamic type.
using (dynamic kernel32 = new Kernel32())
{
kernel32.Beep(2000, 100);
var currentThreadId = kernel32.GetCurrentThreadId<uint>();
Console.WriteLine($"Current thread ID is {currentThreadId}.");
}using (dynamic user32 = new User32())
{
var monitorCount = user32.GetSystemMetrics<int>(0x50);
var (x, y) = (user32.GetSystemMetrics<int>(0), user32.GetSystemMetrics<int>(1));
var message = $"You have {monitorCount} monitors.\n"
+ $"Your primary monitor's resolution is {x}x{y}.";
// Null arguments are replaced with null pointers.
user32.MessageBoxA(null, message, "Important Info";, 0x40);
}using (dynamic winmm = new DynamicLibrary("winmm"))
{
const string fileName = "bgm001.wav"; // Huh? You don't know, pal?
winmm.PlaySoundA(fileName, null, 0x1 | 0x8 | 0x20000);
}The only important type in this project is the DynamicLibrary type - a native library handle wrapper that implements DynamicObject. It overrides TryInvokeMember which creates delegates from the provided arguments as necessary to be used with Marshal.GetDelegateForFunctionPointer and the library export that matches the invoked method's name.
Types such as User32 and Kernel32 are nothing special, they are implementations of the above explained type with the respective library names passed to its constructor.