Invoking an unmanaged 32bit library out of a 64bit process

28.09.2015 Programming .NET C# Legacy Wrapper

When loading unmanaged / native libraries from managed .NET code, the normal way is to use the “Platform Invoke (P/Invoke)” mechanism.

1[DllImport("shell32.dll", CallingConvention = CallingConvention.Winapi)]
2static extern int DllGetVersion(ref DLLVERSIONINFO pdvi);

The problem

P/Invoke has an annoying limitation: You can not load an x86 (32bit) library into a 64bit process and vice versa. This is especially problematic when your application is compiled with the “AnyCPU”-flag - which lets the .NET runtime decide which architecture to use at runtime - and there is only one 32bit version of a specific DLL. If recompiling the library against a different architecture is not an option, you have to find another solution.

The idea

Since we can’t load 32bit code into our 64bit process, the idea is to create a separate executable for this task. It would somehow load a library, invoke a specific function and pass the results back to the caller. So this is what the Legacy Wrapper (Link points to GitHub) does. See this example:

 1// Define delegate matching API  function
 2private delegate int GetSystemMetrics(int index);
 3
 4// Create new WrapperClient
 5// Remember to ensure a call to the Dispose()-Method!
 6using (var client = new WrapperClient())
 7{
 8    // Make calls providing library name, function name, and parameters
 9    int x = (int)client.Invoke<GetSystemMetrics>("User32.dll", "GetSystemMetrics", new object[] { 0 });
10    int y = (int)client.Invoke<GetSystemMetrics>("User32.dll", "GetSystemMetrics", new object[] { 1 });
11}

Internals

How does this work? The Legacy Wrapper consists of an executable (the “server”) and a class library (the “client”). With creating a new WrapperClient instance, the library would at first create a random token and pass it as an argument when starting the “server” executable.

1string token = Guid.NewGuid().ToString();
2
3// Pass token to child process
4Process.Start("LegacyWrapper", token);

The executable will use this token to create a new named pipe and wait for incoming connections:

 1static void Main(string[] args)
 2{
 3    if (args.Length < 1) return;
 4
 5    string token = args[0];
 6
 7    // Create new named pipe with token from client
 8    using (var pipe = new NamedPipeServerStream(token, PipeDirection.InOut, 1, PipeTransmissionMode.Message))
 9    {
10        pipe.WaitForConnection();

Now, the client will send binary serialized information about the desired library to the server:

 1var info = new CallData
 2{
 3    Library = library,
 4    ProcedureName = function,
 5    Parameters = args,
 6    Delegate = typeof(T),
 7};
 8var formatter = new BinaryFormatter();
 9// Write request to server
10formatter.Serialize(_pipe, info);

If the called DLL function returns any result, the server will eventually write it to the pipe - after that, the Invoke<T>() method returns. If an exception occurs, it will also be serialized and re-thrown client side.

Security considerations

Named pipes are by default public and visible to anyone who knows its name. The following screenshot was taken using Process Explorer:

Named Pipe Listing

This includes read / write access to all visible pipes. So at the moment you should NOT use the Legacy Wrapper for cryptographic / security purposes, as anyone could connect to the open pipe and read your keys, ciphertext etc.

Further reading

Check out the GitHub repository for more technical details. Feel free to submit any suggestions/issues and contribute to the project.

Related posts

... some things you might also be interested in

LegacyWrapper 3.0 released
02.02.2019 | Programming | .NET C# Legacy Wrapper
LegacyWrapper 2.1 is out!
20.08.2017 | Programming | .NET C# Legacy Wrapper
UWP-Apps - First Steps [Part 1]
22.03.2016 | Programming | .NET C# UWP WPF