WinUI 3 및 Win32 interop을 사용하여 C# .NET 앱 빌드

이 문서에서는 PInvoke(플랫폼 호출 서비스)를 사용하여 WinUI 3 및 Win32 interop 기능이 있는 기본 C# .NET 애플리케이션을 빌드하는 방법을 단계별로 안내합니다.

전제 조건

  1. Windows 앱 SDK용 설치 도구에서 설명한 대로 개발 환경을 설정합니다.
  2. 첫 번째 WinUI 3 프로젝트 만들기의 단계에 따라 구성을 테스트합니다.

기본 관리형 C#/.NET 앱

이 예에서는 앱 창의 위치와 크기를 지정하고, 적절한 DPI에 맞게 변환 및 크기 조정하며, 창 최소화 및 최대화 단추를 사용하지 않도록 설정하고, 마지막으로 현재 프로세스를 쿼리하여 현재 프로세스에서 로드된 모듈의 목록을 표시합니다.

여기서는 템플릿 애플리케이션에서 예제 앱을 빌드합니다(필수 구성 요소 참조). Visual Studio의 WinUI 3 템플릿도 참조하세요.

MainWindow.xaml 파일

WinUI 3을 사용하면 XAML 태그에서 Window 클래스의 인스턴스를 만들 수 있습니다.

XAML Window 클래스는 데스크톱 창을 지원하도록 확장되어 UWP 및 데스크톱 앱 모델에서 사용하는 각 하위 수준 창 구현의 추상화로 전환되었습니다. 특히 UWP용 CoreWindow 및 Win32용 창 핸들(또는 HWND)이 있습니다.

다음 코드에서는 Window 클래스를 앱의 루트 요소로 사용하는 초기 템플릿 앱의 MainWindow.xaml 파일을 보여 줍니다.

<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

  1. User32.dll에 공개된 Win32 API를 호출하려면 PInvoke.User32 NuGet 패키지를 VS 프로젝트에 추가합니다(Visual Studio 메뉴에서 도구 -> NuGet 패키지 관리자 -> 솔루션용 NuGet 패키지 관리...를 차례로 선택하고 "Pinvoke.User32"를 검색함). 자세한 내용은 관리 코드에서 네이티브 함수 호출을 참조하세요.

    Screenshot of the Visual Studio NuGet Package Manager with PInvoke.User32 selected.
    PInvoke.User32가 선택된 NuGet 패키지 관리자.

    VS 프로젝트의 Packages 폴더를 확인하여 설치가 성공했는지 확인합니다.

    Screenshot of the Visual Studio Solution Explorer Packages with PInvoke.User32.
    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>
    

코드

  1. App.xaml.cs 코드 숨김 파일에서 WindowNative.GetWindowHandle WinRT COM interop 메서드를 사용하여 Window에 대한 핸들을 가져옵니다(창 핸들(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();
    }
    
  2. 그런 다음, 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));
    }
    
  3. MainWindow.xaml 파일에서 ScrollViewer와 함께 ContentDialog를 사용하여 현재 프로세스에 대해 로드된 모든 모듈의 목록을 표시합니다.

    <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>
    
  4. 그런 다음, MyButton_Click 이벤트 처리기를 다음 코드로 바꿉니다.

    여기서 GetCurrentProcess를 호출하여 현재 프로세스에 대한 참조를 가져옵니다. 그런 다음, Modules 컬렉션을 반복하고, 각 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();
    }
    
  5. 앱을 컴파일하고 실행합니다.

  6. 창이 표시되면 "로드된 모듈 표시" 단추를 선택합니다.

    Screenshot of the basic Win32 interop application described in this topic.
    이 문서에서 설명하는 기본 Win32 interop 애플리케이션입니다.

요약

이 문서에서는 기본 창 구현(이 경우 Win32 및 HWND)에 액세스하고 Win32 API와 함께 WinRT API를 사용하는 방법에 대해 설명했습니다. 여기서는 WinUI 3 데스크톱 앱을 만들 때 기존 데스크톱 애플리케이션 코드를 사용하는 방법을 보여 줍니다.

더 광범위한 샘플은 Windows 앱 SDK 샘플 GitHub 리포지토리의 AppWindow 갤러리 샘플을 참조하세요.

참고 항목