다음을 통해 공유


C를 사용하여 단일 인스턴스 WinUI 앱 만들기#

이 방법에서는 C# 및 Windows 앱 SDK 사용하여 단일 인스턴스 WinUI 3 앱을 만드는 방법을 보여 줍니다. 단일 인스턴스 앱은 한 번에 하나의 앱 인스턴스만 실행할 수 있습니다. WinUI 앱은 기본적으로 다중 인스턴스입니다. 동일한 앱의 여러 인스턴스를 동시에 시작할 수 있습니다. 이를 여러 인스턴스라고 합니다. 그러나 앱의 사용 사례에 따라 단일 인스턴싱을 구현할 수 있습니다. 단일 인스턴스 앱의 두 번째 인스턴스를 시작하려고 하면 첫 번째 인스턴스의 주 창만 대신 활성화됩니다. 이 자습서에서는 WinUI 앱에서 단일 인스턴싱을 구현하는 방법을 보여 줍니다.

이 아티클에서는 다음 방법을 설명합니다.

  • XAML의 생성된 Program 코드 끄기
  • 리디렉션을 Main 위해 사용자 지정된 메서드 정의
  • 앱 배포 후 단일 인스턴싱 테스트

필수 구성 요소

이 자습서에서는 Visual Studio를 사용하고 WinUI 빈 앱 템플릿을 기반으로 빌드합니다. WinUI 개발을 접하는 경우 WinUI 시작의 지침에 따라 설정할 수 있습니다. Visual Studio를 설치하고, WinUI를 사용하여 앱을 개발하도록 구성하고, 최신 버전의 WinUI 및 Windows 앱 SDK 있는지 확인하고, 헬로 월드 프로젝트를 만듭니다.

이렇게 하면 여기로 돌아와서 "헬로 월드" 프로젝트를 단일 인스턴스 앱으로 전환하는 방법을 알아봅니다.

참고 항목

이 방법은 WinUI 3의 Windows 블로그 시리즈에서 앱을 단일 인스턴스로 만들기(3부) 블로그 게시물을 기반으로 합니다. 이러한 문서의 코드는 GitHub에서 사용할 수 있습니다.

자동 생성된 프로그램 코드 사용 안 함

창을 만들기 전에 가능한 한 빨리 리디렉션을 확인해야 합니다. 이렇게 하려면 프로젝트 파일에서 "DISABLE_XAML_GENERATED_MAIN" 기호를 정의해야 합니다. 자동 생성된 프로그램 코드를 사용하지 않도록 설정하려면 다음 단계를 수행합니다.

  1. 솔루션 탐색기 프로젝트 이름을 마우스 오른쪽 단추로 클릭하고 프로젝트 파일 편집을 선택합니다.

  2. 각 구성 및 플랫폼에 대한 DISABLE_XAML_GENERATED_MAIN 기호를 정의합니다. 프로젝트 파일에 다음 XML을 추가합니다.

    <propertygroup condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
      <defineconstants>DISABLE_XAML_GENERATED_MAIN</defineconstants>
    </propertygroup>
    <propertygroup condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
      <defineconstants>DISABLE_XAML_GENERATED_MAIN</defineconstants>
    </propertygroup>
    <propertygroup condition="'$(Configuration)|$(Platform)'=='Release|x86'">
      <defineconstants>DISABLE_XAML_GENERATED_MAIN</defineconstants>
    </propertygroup>
    <propertygroup condition="'$(Configuration)|$(Platform)'=='Release|x64'">
      <defineconstants>DISABLE_XAML_GENERATED_MAIN</defineconstants>
    </propertygroup>
    <propertygroup condition="'$(Configuration)|$(Platform)'=='Debug|arm64'">
      <defineconstants>DISABLE_XAML_GENERATED_MAIN</defineconstants>
    </propertygroup>
    <propertygroup condition="'$(Configuration)|$(Platform)'=='Release|arm64'">
      <defineconstants>DISABLE_XAML_GENERATED_MAIN</defineconstants>
    </propertygroup>
    

DISABLE_XAML_GENERATED_MAIN 기호를 추가하면 프로젝트에 대해 자동으로 생성된 프로그램 코드가 비활성화됩니다.

Main 메서드를 사용하여 Program 클래스 정의

기본 Main 메서드를 실행하는 대신 사용자 지정된 Program.cs 파일을 만들어야 합니다. Program 클래스에 추가된 코드를 통해 앱은 WinUI 앱의 기본 동작이 아닌 리디렉션을 확인할 수 있습니다.

  1. 솔루션 탐색기 이동하고 프로젝트 이름을 마우스 오른쪽 단추로 클릭한 다음 추가 | 선택 클래스입니다.

  2. 새 클래스 Program.cs 의 이름을 지정하고 추가를 선택합니다.

  3. Program 클래스에 다음 네임스페이스를 추가하여 기존 네임스페이스를 바꿉니다.

    using System;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    using System.Threading;
    using System.Threading.Tasks;
    using Microsoft.UI.Dispatching;
    using Microsoft.UI.Xaml;
    using Microsoft.Windows.AppLifecycle;
    
  4. Program 클래스를 다음으로 바꿉다.

    public class Program
    {
        [STAThread]
        static int Main(string[] args)
        {
            WinRT.ComWrappersSupport.InitializeComWrappers();
            bool isRedirect = DecideRedirection();
    
            if (!isRedirect)
            {
                Application.Start((p) =>
                {
                    var context = new DispatcherQueueSynchronizationContext(
                        DispatcherQueue.GetForCurrentThread());
                    SynchronizationContext.SetSynchronizationContext(context);
                    _ = new App();
                });
            }
    
            return 0;
        }
    }
    

    Main 메서드는 다음으로 정의할 DecideRedirection을 호출한 후 앱이 첫 번째 인스턴스로 리디렉션할지 또는 새 인스턴스를 시작해야 하는지를 결정합니다.

  5. Main 메서드 아래에 DecideRedirection 메서드를 정의합니다.

    private static bool DecideRedirection()
    {
        bool isRedirect = false;
        AppActivationArguments args = AppInstance.GetCurrent().GetActivatedEventArgs();
        ExtendedActivationKind kind = args.Kind;
        AppInstance keyInstance = AppInstance.FindOrRegisterForKey("MySingleInstanceApp");
    
        if (keyInstance.IsCurrent)
        {
            keyInstance.Activated += OnActivated;
        }
        else
        {
            isRedirect = true;
            RedirectActivationTo(args, keyInstance);
        }
    
        return isRedirect;
    }
    

    DecideRedirection 은 앱 인스턴스를 나타내는 고유 키를 등록하여 앱이 등록되었는지 여부를 결정합니다. 키 등록 결과에 따라 실행 중인 앱의 현재 인스턴스가 있는지 확인할 수 있습니다. 결정 후 메서드는 앱이 새 인스턴스를 계속 시작하도록 리디렉션할지 아니면 허용할지 여부를 알 수 있습니다. 리디렉션이 필요한 경우 RedirectActivationTo 메서드가 호출됩니다.

  6. 다음으로, 필요한 DllImport 문과 함께 DecideRedirection 메서드 아래에 RedirectActivationTo 메서드를 만들어 보겠습니다. Program 클래스에 다음 코드를 추가합니다.

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
    private static extern IntPtr CreateEvent(
        IntPtr lpEventAttributes, bool bManualReset,
        bool bInitialState, string lpName);
    
    [DllImport("kernel32.dll")]
    private static extern bool SetEvent(IntPtr hEvent);
    
    [DllImport("ole32.dll")]
    private static extern uint CoWaitForMultipleObjects(
        uint dwFlags, uint dwMilliseconds, ulong nHandles,
        IntPtr[] pHandles, out uint dwIndex);
    
    [DllImport("user32.dll")]
    static extern bool SetForegroundWindow(IntPtr hWnd);
    
    private static IntPtr redirectEventHandle = IntPtr.Zero;
    
    // Do the redirection on another thread, and use a non-blocking
    // wait method to wait for the redirection to complete.
    public static void RedirectActivationTo(AppActivationArguments args,
                                            AppInstance keyInstance)
    {
        redirectEventHandle = CreateEvent(IntPtr.Zero, true, false, null);
        Task.Run(() =>
        {
            keyInstance.RedirectActivationToAsync(args).AsTask().Wait();
            SetEvent(redirectEventHandle);
        });
    
        uint CWMO_DEFAULT = 0;
        uint INFINITE = 0xFFFFFFFF;
        _ = CoWaitForMultipleObjects(
           CWMO_DEFAULT, INFINITE, 1,
           [redirectEventHandle], out uint handleIndex);
    
        // Bring the window to the foreground
        Process process = Process.GetProcessById((int)keyInstance.ProcessId);
        SetForegroundWindow(process.MainWindowHandle);
    }
    

    RedirectActivationTo 메서드는 활성화를 앱의 첫 번째 인스턴스로 리디렉션합니다. 이벤트 핸들을 만들고, 활성화를 리디렉션하기 위해 새 스레드를 시작하고, 리디렉션이 완료되기를 기다립니다. 리디렉션이 완료되면 메서드는 창을 포그라운드로 가져옵니다.

  7. 마지막으로 DecideRedirection 메서드 아래에 OnActivated 도우미 메서드를 정의합니다.

    private static void OnActivated(object sender, AppActivationArguments args)
    {
        ExtendedActivationKind kind = args.Kind;
    }
    

앱 배포를 통한 단일 인스턴싱 테스트

지금까지 Visual Studio 내에서 디버깅하여 앱을 테스트해 왔습니다. 그러나 한 번에 하나의 디버거만 실행할 수 있습니다. 이 제한으로 인해 동일한 프로젝트를 동시에 두 번 디버그할 수 없으므로 앱이 단일 인스턴스인지 여부를 알 수 없습니다. 정확한 테스트를 위해 로컬 Windows 클라이언트에 애플리케이션을 배포합니다. 배포 후 Windows에 설치된 모든 앱과 마찬가지로 데스크톱에서 앱을 시작할 수 있습니다.

  1. 솔루션 탐색기 이동하고 프로젝트 이름을 마우스 오른쪽 단추로 클릭한 다음 배포를 선택합니다.

  2. 시작 메뉴 열고 검색 필드를 클릭합니다.

  3. 검색 필드에 앱의 이름을 입력합니다.

  4. 검색 결과에서 앱 아이콘을 클릭하여 앱을 시작합니다.

    참고 항목

    릴리스 모드에서 앱 크래시가 발생하는 경우 Windows 앱 SDK 잘려진 앱에 몇 가지 알려진 문제가 있습니다. 프로젝트 파일의 모든 빌드 구성에 대해 PublishTrimmed 속성을 false설정하여 프로젝트에서 .pubxml 트리밍을 사용하지 않도록 설정할 수 있습니다. 자세한 내용은 GitHub에서 이 문제를 참조하세요.

  5. 2~4단계를 반복하여 동일한 앱을 다시 시작하고 다른 인스턴스가 열리는지 확인합니다. 앱이 단일 인스턴스인 경우 새 인스턴스를 여는 대신 첫 번째 인스턴스가 활성화됩니다.

    필요에 따라 일부 로깅 코드를 OnActivated 메서드에 추가하여 기존 인스턴스가 활성화되었는지 확인할 수 있습니다. WinUI 앱에 ILogger 구현추가하는 데 도움이 되도록 Copilot에 문의하세요.

요약

여기서 다루는 모든 코드는 GitHub있으며 원래 Windows 블로그 시리즈의 여러 단계에 대한 분기가 있습니다. 이 방법 관련 코드는 단일 인스턴싱 분기를 참조하세요. main 분기가 가장 포괄적입니다. 다른 분기는 앱 아키텍처가 어떻게 발전했는지 보여 줍니다.

앱 수명 주기 API를 사용하여 앱 인스턴스화

앱을 단일 인스턴스로 만들기(3부)

GitHub의 WinAppSDK-DrumPad 샘플