使用 WinUI 3 和 Win32 互操作生成 C# .NET 应用
在本文中,我们逐步演练如何使用平台调用服务 (PInvoke) 生成具有 WinUI 3 和 Win32 互操作功能的基本 C# .NET 应用程序。
先决条件
基本托管 C#/.NET 应用
对于此示例,我们将指定应用窗口的位置和大小,根据相应的 DPI 进行转换和缩放,禁用窗口最小化和最大化按钮,最后查询当前进程以显示当前进程中加载的模块列表。
我们将从初始模板应用程序构建我们的示例应用(请参阅先决条件)。 另请参阅 Visual Studio 中的 WinUI 3 模板。
MainWindow.xaml 文件
凭借 WinUI 3,你可以通过 XAML 标记创建 Window 类的实例。
XAML Window 类已扩展为支持桌面窗口,从而转换为 UWP 和桌面应用模型所使用的每个低级别窗口实现的抽象。 具体而言是适用于 UWP 的 CoreWindow 以及适用于 Win32 的窗口句柄(或 HWND)。
以下代码演示初始模板应用的 MainWindow.xaml 文件,该文件使用 Window 类作为应用的根元素。
<Window
x:Class="WinUI_3_basic_win32_interop.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:WinUI_3_basic_win32_interop"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<Button x:Name="myButton" Click="myButton_Click">Click Me</Button>
</StackPanel>
</Window>
Configuration
若要从 User32.dll 调用 Win32 API,请将开放源代码 PInvoke.User32 NuGet 包添加到 VS 项目中(从 Visual Studio 菜单中,选择“工具”->“NuGet 包管理器”->“管理解决方案的 NuGet 包…”,然后搜索“Pinvoke.User32”)。 有关更多详细信息,请参阅从托管代码调用本机函数。
选择了 PInvoke.User32 的 NuGet 包管理器。通过检查 VS 项目中的 Packages 文件夹来确认安装已成功。
包含 PInvoke.User32 的解决方案资源管理器包。接下来,双击应用程序项目文件(或右键单击并选择“编辑项目文件”)以在文本编辑器中打开该文件,然后确认项目文件现在包含“PInvoke.User32”的 NuGet
PackageReference
。<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>WinExe</OutputType> <TargetFramework>net6.0-windows10.0.19041.0</TargetFramework> <TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion> <RootNamespace>WinUI_3_basic_win32_interop</RootNamespace> <ApplicationManifest>app.manifest</ApplicationManifest> <Platforms>x86;x64;arm64</Platforms> <RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers> <UseWinUI>true</UseWinUI> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.ProjectReunion" Version="0.8.1" /> <PackageReference Include="Microsoft.ProjectReunion.Foundation" Version="0.8.1" /> <PackageReference Include="Microsoft.ProjectReunion.WinUI" Version="0.8.1" /> <PackageReference Include="PInvoke.User32" Version="0.7.104" /> <Manifest Include="$(ApplicationManifest)" /> </ItemGroup> </Project>
代码
在
App.xaml.cs
代码隐藏文件中,我们使用 WindowNative.GetWindowHandle WinRT COM 互操作方法获取窗口的句柄(请参阅检索窗口句柄 (HWND))。此方法从应用的 OnLaunched 处理程序进行调用,如下所示:
/// <summary> /// Invoked when the application is launched normally by the end user. Other entry points /// will be used such as when the application is launched to open a specific file. /// </summary> /// <param name="args">Details about the launch request and process.</param> protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args) { m_window = new MainWindow(); var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(m_window); SetWindowDetails(hwnd, 800, 600); m_window.Activate(); }
我们随后调用
SetWindowDetails
方法,从而传递窗口句柄和首选尺寸。 请记得添加using static PInvoke.User32;
指令。在此方法中:
- 我们调用 GetDpiForWindow 以获取窗口的每英寸点数 (dpi) 值(Win32 使用实际像素,而 WinUI 3 使用有效像素)。 此 dpi 值用于计算缩放因子,并将它应用于为窗口指定的宽度和高度。
- 我们随后调用 SetWindowPos 以指定窗口的所需位置。
- 最后,我们调用 SetWindowLong 以禁用“最小化”和“最大化”按钮 。
private static void SetWindowDetails(IntPtr hwnd, int width, int height) { var dpi = GetDpiForWindow(hwnd); float scalingFactor = (float)dpi / 96; width = (int)(width * scalingFactor); height = (int)(height * scalingFactor); _ = SetWindowPos(hwnd, SpecialWindowHandles.HWND_TOP, 0, 0, width, height, SetWindowPosFlags.SWP_NOMOVE); _ = SetWindowLong(hwnd, WindowLongIndexFlags.GWL_STYLE, (SetWindowLongFlags)(GetWindowLong(hwnd, WindowLongIndexFlags.GWL_STYLE) & ~(int)SetWindowLongFlags.WS_MINIMIZEBOX & ~(int)SetWindowLongFlags.WS_MAXIMIZEBOX)); }
在 MainWindow.xaml 文件中,我们将 ContentDialog 与 ScrollViewer 一起使用,来显示为当前进程加载的所有模块的列表。
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center"> <Button x:Name="myButton" Click="myButton_Click">Display loaded modules</Button> <ContentDialog x:Name="contentDialog" CloseButtonText="Close"> <ScrollViewer> <TextBlock x:Name="cdTextBlock" TextWrapping="Wrap" /> </ScrollViewer> </ContentDialog> </StackPanel>
然后,将
MyButton_Click
事件处理程序替换为以下代码。在这里,我们通过调用 GetCurrentProcess 来获取对当前进程的引用。 我们随后循环访问模块的集合并将每个 ProcessModule 的文件名追加到显示字符串。
private async void myButton_Click(object sender, RoutedEventArgs e) { myButton.Content = "Clicked"; var description = new System.Text.StringBuilder(); var process = System.Diagnostics.Process.GetCurrentProcess(); foreach (System.Diagnostics.ProcessModule module in process.Modules) { description.AppendLine(module.FileName); } cdTextBlock.Text = description.ToString(); await contentDialog.ShowAsync(); }
编译并运行应用。
窗口出现后,选择“显示加载的模块”按钮。
本主题中所述的基本 Win32 互操作应用程序。
总结
在本主题中,我们介绍了如何访问基础窗口实现(本例中为 Win32 和 HWND),以及使用 Win32 API 和 WinRT API。 这演示了如何在创建新 WinUI 3 桌面应用时使用现有桌面应用程序代码。
有关更广泛的示例,请参阅 Windows 应用 SDK 示例 GitHub 存储库中的 AppWindow 库示例。