Generating PInvoke code for Win32 apis using a Source Generator
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:
dotnet new winforms
Then, you need to reference the NuGet package Microsoft.Windows.CsWin32
:
dotnet add package Microsoft.Windows.CsWin32 --prerelease
The previous command should add the NuGet package to the csproj
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 import multiple symbols at once.
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:
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!