Generating C# bindings for native Windows libraries 

When writing system applications in C# we often need to interact with the system APIs directly. And it has always been a challenge to write proper PInvoke signatures. However, with the introduction of the Windows metadata project and later, cswin32, things changed significantly. In this post, I will walk you through the steps required to generate C# bindings for a sample native library. I picked Detours, because I needed it for withdll, my new tool inspired by the withdll example from the Detours repository. The post by Rafael Rivera describing how to create bindings for Rust language helped me tremendously in writing this post (and bindings 😊). 

Creating Windows metadata 

Preparing the metadata project 

Before we could see the generated C# code, we need to build a Windows metadata (winmd) file. Rafael describes the steps in details, so I will take a shortcut here and show you the generate.proj for the detours library: 

<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.Windows.WinmdGenerator/0.55.45-preview">
    <PropertyGroup Label="Globals">
        <OutputWinmd>winmd/detours.winmd</OutputWinmd>
        <WinmdVersion>0.1.0.0</WinmdVersion>
    </PropertyGroup>

    <ItemGroup>
        <Headers Include="../detours/include/detours.h" />

        <ImportLibs Include="../detours-dll/bin.x64$(BuildConfig)/detours.lib">
            <StaticLibs>detours=detours</StaticLibs>
        </ImportLibs>

        <Partition Include="main.cpp">
            <TraverseFiles>@(Headers)</TraverseFiles>
            <Namespace>Microsoft.Detours</Namespace>
            <ExcludeFromCrossarch>true</ExcludeFromCrossarch>
        </Partition> 
    </ItemGroup>
</Project>

My folder structure looks as follows: 

I also needed to add the ImportLibs item as my library is not in the folders searched normally by the MSVC compiler. Additionally, the output of the detours build is a static library (detours.lib) that we can link with our project. Theoretically, we can point Windows Metadata Generator to static libraries using the StaticLibs tag. However, I did not manage to make it work without creating an additional shared library. There is an old issue in the win32metadata project about importing static libraries directly, but it was never resolved. I noticed though that the generated methods have StaticLibraryAttribute attached to them. Still, I’m unsure what its purpose is. 

Building a wrapping DLL for the static library

Fortunately, creating a shared library for a static library is a straightforward process. You need a cpp file, for example: 

#include <windows.h>

BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved) {
    UNREFERENCED_PARAMETER(lpReserved);

    switch (ul_reason_for_call) {
    case DLL_PROCESS_ATTACH:
        ::DisableThreadLibraryCalls(hModule);
        break;
    case DLL_PROCESS_DETACH:
        break;
    default:
        break;
    }
    return TRUE;
}

And a module-definition file (detours.def) that will list the exported methods, for example: 

LIBRARY detours
EXPORTS
	DetourCreateProcessWithDllExW
	DetourCreateProcessWithDllsW

Then you need to compile your DLL, for example: 

cl.exe /I "..\detours\include" /nologo /LD /TP /DUNICODE /DWIN32 /D_WINDOWS /EHsc /W4 /WX /Zi /O2 /Ob1 /DNDEBUG -std:c++latest detours.cpp /link /DEF:detours.def ..\detours\lib.X64\detours.lib

Now, we may point ImportLibs to the detours-dll folder and try to build the detours.winmd file.

Building the metadata project 

This step should be as straightforward as running the dotnet build command. Sometimes, however, you may run into problems with the parsers. For detours, for example, I needed to remove a section from the detours header. Finally, the build was successful and I could verify in ILSpy that the detours.winmd file contains all the methods exported by my detours.dll: 

Generating and using bindings from the metadata project 

With the metadata file ready, it’s time to use it in our C# project. Firstly, we will install the cswin32 package that imports Win32 metadata and allows us to define which types and methods we want to import through the NativeMethods.txt file. Cswin32 by default understands only names defined in the Win32 metadata project. However, thanks to the ProjectionMetadataWinmd tag we can easily make it process our custom metadata files as well! 

<ItemGroup>
    <ProjectionMetadataWinmd Include="../detours-meta/winmd/*.winmd" />
</ItemGroup>

Now, we may reference the names that we want to import in the NativeMethods.txt and, finally, use it in our code: 

// NativeMethods.txt

// Windows
CloseHandle
DebugActiveProcessStop
// ...

// Detours
DetourCreateProcessWithDllsW
// ...
// An example C# code using the DetourCreateProcessWithDlls function

using PInvokeDetours = Microsoft.Detours.PInvoke;
using PInvokeWin32 = Windows.Win32.PInvoke;

var pcstrs = dllPaths.Select(path => new PCSTR((byte*)Marshal.StringToHGlobalAnsi(path))).ToArray();
try
{
    if (!PInvokeDetours.DetourCreateProcessWithDlls(null, ref cmdline, null, null, false,
        createFlags, null, null, startupInfo, out var processInfo,
        pcstrs, null))
    {
        throw new Win32Exception();
    }

    PInvokeWin32.CloseHandle(processInfo.hThread);
    PInvokeWin32.CloseHandle(processInfo.hProcess);

    if (debug)
    {
        PInvokeWin32.DebugActiveProcessStop(processInfo.dwProcessId);
    }
}
finally
{
    Array.ForEach(pcstrs, pcstr => Marshal.FreeHGlobal((nint)pcstr.Value));
}

The NativeAOT compilation gives us also an option to statically link the detours library:

<ItemGroup>    
    <DirectPInvoke Include="detours" />
    <NativeLibrary Include="../detours/lib.X64/detours.lib" />
</ItemGroup>

It’s fantastic that we can have all the native imports in one place. If you would like to examine a working project that uses the presented detours bindings, please check the withdll repository. You may use it to, for example, inject detours DLLs that hook Win API functions and trace them. I describe this usage scenario in a guide at wtrace.net

3 thoughts on “Generating C# bindings for native Windows libraries 

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.