C++ 및 CMake에서 winapp CLI 사용

이 가이드에서는 C++ 애플리케이션에서 CLI를 사용하여 winapp 패키지 ID로 디버그하고 애플리케이션을 MSIX로 패키지하는 방법을 보여 줍니다.

패키지 ID는 Windows app 모델의 핵심 개념입니다. 이를 통해 애플리케이션은 알림, 보안, AI API 등과 같은 특정 Windows API에 액세스하고, 깨끗한 설치/제거 환경 등을 사용할 수 있습니다.

표준 실행 파일(예: 생성된 cmake --build실행 파일)에는 패키지 ID가 없습니다. 이 가이드에서는 디버깅을 위해 추가한 다음 배포용으로 패키지하는 방법을 보여줍니다.

필수 조건

  1. 빌드 도구: CMake에서 지원하는 컴파일러 도구 체인을 사용합니다. 이 예제에서는 Visual Studio 사용합니다. Community Edition을 설치하거나 이미 설치된 경우 업데이트할 수 있습니다.

    winget install --id Microsoft.VisualStudio.Community --source winget --override "--add Microsoft.VisualStudio.Workload.NativeDesktop --includeRecommended --passive --wait"
    

    설치 후 다시 부팅합니다.

  2. CMake: CMake를 설치합니다(또는 이미 설치된 경우 업데이트).

    winget install Kitware.CMake --source winget
    
  3. winapp CLI: winget을 통해 cli를 winapp 설치합니다(또는 이미 설치된 경우 업데이트).

    winget install Microsoft.winappcli --source winget
    

1. 새 C++ 앱 만들기

간단한 C++ 애플리케이션을 만들어 시작합니다. 프로젝트를 위한 새 디렉터리를 만드세요.

mkdir cpp-app
cd cpp-app

main.cpp 기본 "Hello, world!" 프로그램을 사용하여 파일을 만듭니다.

#include <iostream>

int main() {
    std::cout << "Hello, world!" << std::endl;
    return 0;
}

이제 빌드를 설정하기 위해 CMakeLists.txt 파일을 만듭니다.

cmake_minimum_required(VERSION 3.20)
project(cpp-app)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_executable(cpp-app main.cpp)

빌드하고 실행하여 모든 것이 작동하는지 확인합니다.

cmake -B build
cmake --build build --config Debug
.\build\Debug\cpp-app.exe

출력은 "Hello, world!"여야 합니다.

2. ID를 확인하도록 코드 업데이트

패키지 ID를 사용하여 실행되는지 확인하도록 앱을 업데이트합니다. 이렇게 하면 이후 단계에서 ID가 제대로 작동하는지 확인하는 데 도움이 됩니다. Windows 런타임 C++ API를 사용하여 패키지 API에 액세스합니다.

먼저 CMakeLists.txt 끝에 다음 줄을 추가하여 Windows 앱 모델 라이브러리에 연결합니다.

# Link Windows Runtime libraries
target_link_libraries(cpp-app PRIVATE WindowsApp.lib OneCoreUap.lib)

다음으로 전체 내용을 다음 코드로 바꿉니다 main.cpp . 이 코드는 Windows 런타임 API를 사용하여 현재 패키지 ID를 검색하려고 시도합니다. 성공하면 패키지 패밀리 이름이 인쇄됩니다. 그렇지 않으면 "패키지되지 않음"을 출력합니다.

#include <iostream>
#include <windows.h>
#include <appmodel.h>

int main() {
    UINT32 length = 0;
    LONG result = GetCurrentPackageFamilyName(&length, nullptr);
    
    if (result == ERROR_INSUFFICIENT_BUFFER) {
        // We have a package identity
        std::wstring familyName;
        familyName.resize(length);
        
        result = GetCurrentPackageFamilyName(&length, familyName.data());
        
        if (result == ERROR_SUCCESS) {
            std::wcout << L"Package Family Name: " << familyName.c_str() << std::endl;
        } else {
            std::wcout << L"Error retrieving Package Family Name" << std::endl;
        }
    } else {
        // No package identity
        std::cout << "Not packaged" << std::endl;
    }

    return 0;
}

3. ID 없이 실행

이제 평소와 같이 앱을 다시 빌드하고 실행합니다.

cmake --build build --config Debug
.\build\Debug\cpp-app.exe

"패키지되지 않음" 출력이 표시됩니다. 그러면 패키지 ID 없이 표준 실행 파일이 실행 중임을 확인합니다.

4. winapp CLI를 사용하여 Project 초기화

winapp init 명령은 앱 매니페스트, 자산, 그리고 필요에 따라 C++ 개발을 위한 Windows 앱 SDK 헤더까지 한 번에 필요한 모든 것을 설정합니다.

다음 명령을 실행하고 프롬프트를 따릅니다.

winapp init .

프롬프트가 표시되면

  • 패키지 이름: Enter 키를 눌러 기본값 적용(cpp-app)
  • Publisher 이름: Enter 키를 눌러 기본값을 적용하거나 이름을 입력합니다.
  • 버전: Enter 키를 눌러 1.0.0.0 허용
  • 진입점: Enter 키를 눌러 기본값(cpp-app.exe)을 적용합니다.
  • SDK 설정: "안정적인 SDK"를 선택하여 Windows 앱 SDK 다운로드하고 C++ 헤더를 생성합니다.

이 명령은 다음을 수행합니다.

  • 만들기 Package.appxmanifest - 앱의 ID를 정의하는 매니페스트
  • 폴더 만들기 Assets - MSIX 패키징 및 스토어 제출에 필요한 아이콘
  • Windows 앱 SDK 헤더 및 라이브러리를 사용하여 .winapp 폴더 만들기
  • winapp.yaml SDK 버전을 고정하기 위한 구성 파일 만들기

Package.appxmanifest는 표시 이름, 게시자 및 기능과 같은 속성을 추가로 사용자 지정하는 데 사용할 수 있습니다.

실행 별칭 추가(콘솔 앱용)

실행 별칭을 사용하면 사용자가 모든 터미널(예: cpp-app)에서 이름으로 앱을 실행할 수 있습니다. 개발 중에 새 창을 여는 대신 콘솔 출력을 현재 터미널에 유지할 수 있도록 winapp run --with-alias 합니다.

다음을 자동으로 추가할 수 있습니다.

winapp manifest add-alias

또는 수동으로: Package.appxmanifest을(를) 열고 uap5 네임스페이스가 누락된 경우 태그 <Package>에 추가한 다음, <Applications><Application><Extensions>... 내에 확장을 추가합니다.

<Package
  ...
  xmlns:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10"
+ xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5"
  IgnorableNamespaces="uap uap2 uap3 rescap desktop desktop6 uap10">

  ...
  <Applications>
    <Application ...>
      ...
+     <Extensions>
+       <uap5:Extension Category="windows.appExecutionAlias">
+         <uap5:AppExecutionAlias>
+           <uap5:ExecutionAlias Alias="cpp-app.exe" />
+         </uap5:AppExecutionAlias>
+       </uap5:Extension>
+     </Extensions>
    </Application>
  </Applications>
</Package>

5. ID를 사용하여 디버그

앱을 완전히 패키징하지 않고 ID(예: 알림)가 필요한 기능을 테스트하려면 .winapp run 실제 MSIX 설치와 마찬가지로 느슨한 레이아웃 패키지를 등록하고 한 단계에서 앱을 시작합니다. 디버깅에 인증서 또는 서명이 필요하지 않습니다.

  1. 실행 파일을 빌드합니다.

    cmake --build build --config Debug
    
  2. ID를 사용하여 실행:

    winapp run .\build\Debug --with-alias
    

콘솔 출력이 현재 터미널에 유지되도록 플래그는 --with-alias 실행 별칭을 통해 앱을 시작합니다. 이렇게 하려면 4단계에서 추가한 것이 필요합니다 uap5:ExecutionAlias .

팁 (조언)

winapp run 또한 시스템에 패키지를 등록합니다. 따라서 나중에 8단계에서 설치하려고 할 때 MSIX가 "이미 설치됨"으로 표시될 수 있습니다. 완료되면 개발 패키지를 정리하는 데 사용합니다 winapp unregister .

이제 다음과 유사한 출력이 표시됩니다.

Package Family Name: cpp-app_12345abcde

그러면 앱이 유효한 패키지 ID로 실행되고 있는지 확인합니다.

대안: 스파스 패키지 ID

특히 스파스 패키지 동작(파일을 복사하지 않는 ID)이 필요한 경우 다음을 대신 사용할 create-debug-identity 수 있습니다.

winapp create-debug-identity .\build\Debug\cpp-app.exe
.\build\Debug\cpp-app.exe

팁 (조언)

고급 디버깅 워크플로(디버거 연결, IDE 설정, 시작 디버깅)는 디버깅 가이드를 참조하세요.

6. Windows 앱 SDK 사용(선택 사항)

winapp init 중에 SDK를 설정하도록 선택한 경우 이제 .winapp/include 폴더의 Windows 앱 SDK 헤더에 액세스할 수 있습니다. 이를 통해 알림, 창 창, 디바이스 내 AI 등과 같은 최신 Windows API에 액세스할 수 있습니다. 배포에 패키지 ID만 필요한 경우 7단계로 건너뛸 수 있습니다.

Windows 앱 런타임 버전을 인쇄하는 간단한 예제를 추가해 보겠습니다.

CMakeLists.txt 업데이트하기

Windows 앱 SDK 헤더를 포함하려면 CMakeLists.txt 끝에 다음 줄을 추가합니다.

# Add Windows App SDK include directory
target_include_directories(cpp-app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/.winapp/include)

메인.cpp 업데이트

Windows 앱 런타임 API를 사용하도록 main.cpp 전체 콘텐츠를 바꿉니다.

#include <iostream>
#include <windows.h>
#include <appmodel.h>
#include <winrt/Microsoft.Windows.ApplicationModel.WindowsAppRuntime.h>

int main() {
    // Initialize WinRT
    winrt::init_apartment();
    
    UINT32 length = 0;
    LONG result = GetCurrentPackageFamilyName(&length, nullptr);
    
    if (result == ERROR_INSUFFICIENT_BUFFER) {
        // We have a package identity
        std::wstring familyName;
        familyName.resize(length);
        
        result = GetCurrentPackageFamilyName(&length, familyName.data());
        
        if (result == ERROR_SUCCESS) {
            std::wcout << L"Package Family Name: " << familyName.c_str() << std::endl;
            
            // Get Windows App Runtime version using the API
            auto runtimeVersion = winrt::Microsoft::Windows::ApplicationModel::WindowsAppRuntime::RuntimeInfo::AsString();
            std::wcout << L"Windows App Runtime Version: " << runtimeVersion.c_str() << std::endl;
        } else {
            std::wcout << L"Error retrieving Package Family Name" << std::endl;
        }
    } else {
        std::cout << "Not packaged" << std::endl;
    }
    
    return 0;
}

빌드 및 실행

Windows 앱 SDK 헤더를 사용하여 애플리케이션을 다시 빌드합니다.

cmake --build build --config Debug
winapp run .\build\Debug --with-alias

이제 다음과 같은 출력이 표시됩니다.

Package Family Name: cpp-app_12345abcde
Windows App Runtime Version: 1.8-stable (1.8.0)

.winapp/include 디렉터리에는 다음을 포함하여 Windows 앱 SDK 필요한 모든 헤더가 포함됩니다.

  • winrt/ - Windows 런타임 API에 액세스하기 위한 WinRT C++ 프로젝션 헤더
  • Microsoft.UI.*.h - 최신 UI 구성 요소에 대한 WinUI 3 헤더
  • MddBootstrap.h - Windows 앱 SDK 부트스트랩
  • WindowsAppSDK-VersionInfo.h - 버전 정보
  • 그리고 더 많은 Windows 앱 SDK 구성 요소

고급 Windows 앱 SDK 사용은 Windows 앱 SDK 설명서 참조하세요.

7. 필요한 경우 헤더 복원

.winapp 폴더는 .gitignorewinapp init 의해 자동으로 추가되므로 소스 제어에 저장되지 않습니다. 다른 사용자가 프로젝트를 복제하는 경우 빌드하기 전에 이러한 파일을 복원해야 합니다.

수동 설정

리포지토리를 복제한 후 다음 두 명령을 실행합니다.

# Restore Windows App SDK headers
winapp restore

# Generate development certificate (optional - only if planning to package the app and sideload)
winapp cert generate --if-exists skip

그런 다음, cmake -B buildcmake --build build --config Debug을(를) 사용하여 정상적으로 빌드하고 실행할 수 있습니다.

CMake를 사용하여 자동화된 설치

또는 CMakeLists.txt에 설정 논리를 추가하여 이를 자동화할 수 있습니다. 다음은 자동화, 적절한 링크 및 최소 C++20 표준이 적용된 CMakeLists.txt입니다.

cmake_minimum_required(VERSION 3.20)
project(cpp-app)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Download winapp CLI if not available in PATH
find_program(WINAPP_CLI winapp)
if(NOT WINAPP_CLI)
    set(WINAPP_DIR "${CMAKE_CURRENT_SOURCE_DIR}/.winapp-tools")
    set(WINAPP_CLI "${WINAPP_DIR}/winapp.exe")
    
    if(NOT EXISTS "${WINAPP_CLI}")
        message(STATUS "Downloading winapp CLI...")
        
        # Determine architecture
        if(CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|aarch64")
            set(WINAPP_ARCH "arm64")
        else()
            set(WINAPP_ARCH "x64")
        endif()
        
        # Download and extract
        set(WINAPP_ZIP "${CMAKE_CURRENT_BINARY_DIR}/winappcli.zip")
        file(DOWNLOAD 
            "https://github.com/microsoft/WinAppCli/releases/latest/download/winappcli-${WINAPP_ARCH}.zip"
            "${WINAPP_ZIP}"
            SHOW_PROGRESS
        )
        
        file(ARCHIVE_EXTRACT INPUT "${WINAPP_ZIP}" DESTINATION "${WINAPP_DIR}")
        file(REMOVE "${WINAPP_ZIP}")
        message(STATUS "winapp CLI downloaded to ${WINAPP_DIR}")
    endif()
endif()

# Automatically restore Windows App SDK headers and generate certificate if needed
# This runs once during CMake configuration, not on every build
if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.winapp/include")
    message(STATUS "Restoring Windows App SDK headers...")
    execute_process(
        COMMAND "${WINAPP_CLI}" restore
        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
        RESULT_VARIABLE RESTORE_RESULT
    )
    if(NOT RESTORE_RESULT EQUAL 0)
        message(WARNING "Failed to restore Windows App SDK. Run 'winapp restore' manually.")
    endif()
endif()

if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/devcert.pfx")
    message(STATUS "Generating development certificate...")
    execute_process(
        COMMAND "${WINAPP_CLI}" cert generate --if-exists skip
        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
        RESULT_VARIABLE CERT_RESULT
    )
    if(NOT CERT_RESULT EQUAL 0)
        message(WARNING "Failed to generate certificate. Run 'winapp cert generate' manually.")
    endif()
endif()

add_executable(cpp-app main.cpp)

# Link Windows Runtime libraries
target_link_libraries(cpp-app PRIVATE WindowsApp.lib OneCoreUap.lib)

# Add Windows App SDK include directory
target_include_directories(cpp-app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/.winapp/include)

이 설정을 사용하면 다음을 수행합니다.

  • 누군가가 리포지토리를 복제하고 실행 cmake -B build하면 PATH에서 찾을 수 없으면 winapp이 자동으로 다운로드됩니다.
  • Windows 앱 SDK 헤더 및 인증서가 자동으로 복원됩니다.
  • 명령이 구성 중에 한 번만 실행됩니다(모든 빌드에서는 아님). 파일이 이미 있는지 확인하기 때문입니다.
  • 명령이 실패하면 CMake에서 수동으로 실행하는 지침이 포함된 경고를 표시합니다.
  • 다운로드한 winapp이 .winapp-tools/에 저장됩니다(필요한 경우 .gitignore에 추가).

8. MSIX를 사용한 패키지

앱을 배포할 준비가 되면 동일한 매니페스트를 사용하여 MSIX로 패키지할 수 있습니다. MSIX는 클린 설치/제거, 자동 업데이트 및 신뢰할 수 있는 설치 환경을 제공합니다.

패키지 디렉터리 준비

먼저 최적의 성능을 위해 릴리스 모드에서 애플리케이션을 빌드합니다.

cmake --build build --config Release

그런 다음 배포에 필요한 파일만 있는 디렉터리를 만들고 릴리스 실행 파일을 복사합니다.

mkdir dist
copy .\build\Release\cpp-app.exe .\dist\

개발 인증서 생성

MSIX 패키지에 서명해야 합니다. 로컬 테스트의 경우 자체 서명된 개발 인증서를 생성합니다.

winapp cert generate --if-exists skip

팁 (조언)

인증서의 발행자는 Package.appxmanifest에서 Publisher와 일치해야 합니다. 이 cert generate 명령은 매니페스트에서 자동으로 읽습니다.

서명 및 패키징

이제 패키지를 만들고 서명할 수 있습니다.

# package and sign the app with the generated certificate
winapp pack .\dist --cert .\devcert.pfx 

팁 (조언)

pack 명령은 현재 디렉터리의 Package.appxmanifest를 자동으로 사용하고 패키징하기 전에 대상 폴더에 복사합니다. 생성된 .msix 파일은 현재 디렉터리에 있습니다.

인증서 설치

MSIX 패키지를 설치하려면 컴퓨터에서 개발 인증서를 신뢰해야 합니다. 관리자 권한으로 이 명령을 실행합니다(인증서당 한 번만 수행하면 됨).

winapp cert install .\devcert.pfx

설치 및 실행

팁 (조언)

5단계에서 사용한 winapp run 경우 패키지가 시스템에 이미 등록되어 있을 수 있습니다. winapp unregister 먼저 개발 등록을 제거한 다음 릴리스 패키지를 설치합니다.

winapp pack 명령은 프로젝트 루트 디렉터리에 MSIX 파일을 생성합니다. 생성된 .msix 파일을 두 번 클릭하거나 PowerShell을 사용하여 패키지를 설치합니다.

Add-AppxPackage .\cpp-app_1.0.0.0_x64.msix

팁 (조언)

MSIX 파일 이름에는 버전 및 아키텍처(예: cpp-app_1.0.0.0_arm64.msix)가 포함됩니다. 디렉터리에서 정확한 파일 이름을 확인합니다.

이제 다음을 입력하여 터미널의 어디에서나 앱을 실행할 수 있습니다.

cpp-app

"패키지 패밀리 이름" 출력이 표시되면, 설치되어 있고 식별자로 실행 중임을 확인할 수 있습니다.

팁 (조언)

앱을 다시 패키지해야 하는 경우(예: 코드 변경 후), winapp pack를 다시 실행하기 전에 Package.appxmanifest을 증가시키십시오. Windows 설치된 패키지를 업데이트하려면 더 높은 버전 번호가 필요합니다.

  1. 배포할 준비가 되면 사용자가 자체 서명된 인증서를 설치할 필요가 없도록 인증 기관의 코드 서명 인증서로 MSIX에 서명할 수 있습니다.
  2. Azure 신뢰할 수 있는 서명 서비스는 인증서를 안전하게 관리하고 CI/CD 파이프라인에 로그인을 통합하는 좋은 방법입니다.
  3. Microsoft Store 제출 전에 서명할 필요 없이 MSIX에 서명합니다.
  4. 지원하는 각 아키텍처(x64, Arm64)에 대해 하나씩 여러 MSIX 패키지를 만들어야 할 수 있습니다. 적절한 생성기 및 아키텍처 플래그를 사용하여 CMake를 구성합니다.

다음 단계

  • winget을 통한 배포: MSIX를 Windows 패키지 관리자 커뮤니티 리포지토리
  • Microsoft Store에 게시: winapp store를 사용하여 패키지를 제출하십시오.
  • CI/CD 설정: setup-WinAppCli GitHub 작업을 사용하여 파이프라인에서 패키징 자동화
  • Explore Windows API들: 패키지 ID로, 이제 알림, 기기 내 AI 및 기타 ID에 의존하는 API을 사용할 수 있습니다.