Generating PInvoke code for Win32 apis using a Source Generator

  • Gérald Barré

Writing PInvoke code is not trivial. Most of the time you need to find the method signature from the documentation or the header files. This takes time and is error prone. Also, you can't find a NuGet package that wraps all Win32 methods because the number of methods, constants and structures is huge. Hopefully, Microsoft has released a Roslyn source generator to generate PInvoke for Win32 apis.

In this post, I'll build a winforms application that displays the live thumbnail of an existing application. This code uses methods from DwmApi.

First, you need to create a winforms project:

Shell
dotnet new winforms

Then, you need to reference the NuGet package Microsoft.Windows.CsWin32:

Shell
dotnet add package Microsoft.Windows.CsWin32 --prerelease

The previous command should add the NuGet package to the csproj file:

csproj (MSBuild project file)
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net6.0-windows</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Windows.CsWin32" Version="0.2.10-beta">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
  </ItemGroup>

</Project>

Then, you need to create a new file named NativeMethods.txt at the root of the project. This file contains the list of methods and constants you want to use. Note that you can use the * symbols to imports multiple symbols at once.

NativeMethods.txt
DwmRegisterThumbnail
DwmUpdateThumbnailProperties
DWM_TNP_*

If you compile the application, you should see the generated files in the solution explorer:

You can now use the generated code in your application:

Form1.cs (C#)
public partial class Form1 : Form
{
    private nint _thumbnail;

    public Form1()
    {
        InitializeComponent();
        Load += Form1_Load;
        Resize += Form1_Resize;
    }

    private void Form1_Resize(object? sender, EventArgs e)
    {
        UpdateThumbnail();
    }

    private void Form1_Load(object? sender, EventArgs e)
    {
        var mainWindowHandle = Process.GetProcessesByName("devenv")[0].MainWindowHandle;

        // Register the thumbnail
        // Convert IntPtr to HWND using the explicit converter
        var hresult = PInvoke.DwmRegisterThumbnail((HWND)this.Handle, (HWND)mainWindowHandle, out var thumbnailId);

        // HWND contains properties to check the result (Succeeded, Failed)
        if (hresult.Succeeded)
        {
            _thumbnail = thumbnailId;
            UpdateThumbnail();
        }
    }

    private void UpdateThumbnail()
    {
        // Specify the destination rectangle size
        RECT dest = new()
        {
            left = 0,
            top = 0,
            bottom = Height,
            right = Width,
        };

        // Set the thumbnail properties for use
        DWM_THUMBNAIL_PROPERTIES dskThumbProps = new()
        {
            dwFlags = PInvoke.DWM_TNP_SOURCECLIENTAREAONLY | PInvoke.DWM_TNP_VISIBLE | PInvoke.DWM_TNP_OPACITY | PInvoke.DWM_TNP_RECTDESTINATION,
            fSourceClientAreaOnly = false,
            fVisible = true,
            opacity = 255,
            rcDestination = dest,
        };

        // Display the thumbnail
        var hr = PInvoke.DwmUpdateThumbnailProperties(_thumbnail, in dskThumbProps);
        if (hr.Failed)
        {
            // todo
        }
    }
}

When you start the application, you can see the live thumbnail of the Visual Studio application

#Additional resources

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?Buy Me A Coffee💖 Sponsor on GitHub