docs/architecture/decisions/ADR-003-win32-interop-via-cswin32.md
Accepted
WPF UI requires extensive Win32 API access for features unavailable in standard WPF:
Traditional P/Invoke requires:
Package: Microsoft.Windows.CsWin32 (build-time only, PrivateAssets="all")
Declaration File: src/Wpf.Ui/NativeMethods.txt
CsWin32 generates P/Invoke bindings at compile-time from Win32 metadata.
# DWM Functions
DwmIsCompositionEnabled
DwmSetWindowAttribute
DwmExtendFrameIntoClientArea
S_OK
SetWindowThemeAttribute
DWM_SYSTEMBACKDROP_TYPE
DWM_WINDOW_CORNER_PREFERENCE
DWMWA_COLOR_NONE
WTA_OPTIONS
# Window Management
GetDpiForWindow
GetForegroundWindow
IsWindowVisible
SetWindowRgn
GetWindowRect
GetSystemMetrics
WINDOW_STYLE
# COM Interfaces
ITaskbarList4
TaskbarList
# Wildcard Patterns
WM_*
HT*
CsWin32 automatically generates:
Namespace: Windows.Win32 and Windows.Win32.Foundation
Physical Location: obj/ directory (not committed to source control)
Usage:
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Dwm;
// Generated type-safe P/Invoke
HRESULT result = PInvoke.DwmSetWindowAttribute(
new HWND(windowHandle),
DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE,
&darkMode,
sizeof(BOOL)
);
Purpose: Auto-generated P/Invoke declarations
Characteristics:
Purpose: Safe, validated native API access
Location: src/Wpf.Ui/Interop/
internal static class UnsafeNativeMethods
{
public static unsafe bool ApplyWindowCornerPreference(
IntPtr handle,
WindowCornerPreference cornerPreference)
{
// Validation layer
if (handle == IntPtr.Zero)
return false;
if (!PInvoke.IsWindow(new HWND(handle)))
return false;
// Type conversion
DWM_WINDOW_CORNER_PREFERENCE pvAttribute =
UnsafeReflection.Cast(cornerPreference);
// Native call with exception handling
try
{
HRESULT hr = PInvoke.DwmSetWindowAttribute(
new HWND(handle),
DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE,
&pvAttribute,
(uint)sizeof(DWM_WINDOW_CORNER_PREFERENCE)
);
return hr == HRESULT.S_OK;
}
catch
{
// Graceful degradation for unsupported OS versions
return false;
}
}
}
Responsibilities:
Purpose: Supplement CsWin32 for missing/incorrect signatures
namespace Windows.Win32;
internal static partial class PInvoke
{
// CsWin32 doesn't generate correct SetWindowLongPtr for 32/64-bit
[DllImport("USER32.dll",
ExactSpelling = true,
EntryPoint = "SetWindowLongPtrW",
SetLastError = true)]
internal static extern nint SetWindowLongPtr(
HWND hWnd,
WINDOW_LONG_PTR_INDEX nIndex,
nint dwNewLong
);
}
Purpose: Business logic and feature implementation
Locations:
src/Wpf.Ui/Win32/Utilities.cs - OS version detectionsrc/Wpf.Ui/Appearance/ - Theme managerssrc/Wpf.Ui/Controls/FluentWindow/ - Window chromesrc/Wpf.Ui/Tray/ - System tray managementCharacteristics:
Critical Requirement: All native calls MUST validate handles.
public static bool NativeOperation(IntPtr handle)
{
// Step 1: Null check
if (handle == IntPtr.Zero)
{
return false;
}
// Step 2: Verify window exists
if (!PInvoke.IsWindow(new HWND(handle)))
{
return false;
}
// Step 3: Perform operation
HRESULT hr = PInvoke.SomeWin32Function(new HWND(handle), ...);
return hr == HRESULT.S_OK;
}
Rationale:
Philosophy: Native APIs fail silently across Windows versions. Prefer graceful degradation over exceptions.
try
{
HRESULT hr = PInvoke.DwmSetWindowAttribute(...);
return hr == HRESULT.S_OK;
}
catch (COMException)
{
// API not available on this Windows version
return false;
}
catch
{
// Unexpected failure, degrade gracefully
return false;
}
Suppressed Exceptions:
COMException - COM API failuresWin32Exception - Native API errorsEntryPointNotFoundException - API not available on OS versionDllNotFoundException - DLL not present#if NET5_0_OR_GREATER
// Modern API available
var version = Environment.OSVersion;
#else
// Fallback for .NET Framework
var version = GetVersionFromRegistry();
#endif
// Windows 11+ only features
if (Win32.Utilities.IsOSWindows11OrNewer)
{
UnsafeNativeMethods.ApplyWindowCornerPreference(
handle,
WindowCornerPreference.Round
);
}
// DWM composition required
if (Win32.Utilities.IsCompositionEnabled)
{
UnsafeNativeMethods.ApplyWindowBackdrop(
handle,
WindowBackdropType.Acrylic
);
}
WM_* and HT*)Rejected:
Rejected:
Rejected:
Add to NativeMethods.txt:
DwmGetColorizationColor
Rebuild project (CsWin32 generates code)
Create managed wrapper in UnsafeNativeMethods.cs:
public static bool GetColorizationColor(out Color color)
{
try
{
HRESULT hr = PInvoke.DwmGetColorizationColor(out uint colorValue, out BOOL opaque);
color = Color.FromArgb(...);
return hr == HRESULT.S_OK;
}
catch
{
color = default;
return false;
}
}
Consume from high-level code:
if (UnsafeNativeMethods.GetColorizationColor(out Color color))
{
// Use color
}