전체 작업 예제를 보려면 이 리포지토리의 Flutter 샘플을 확인하세요.
이 가이드에서는 Flutter 애플리케이션에서 CLI를 사용하여 winapp 패키지 ID를 추가하고 앱을 MSIX로 패키지하는 방법을 보여 줍니다.
패키지 ID는 Windows app 모델의 핵심 개념입니다. 이를 통해 애플리케이션은 알림, 보안, AI API 등과 같은 특정 Windows API에 액세스하고, 깨끗한 설치/제거 환경 등을 사용할 수 있습니다.
표준 Flutter Windows 빌드에는 패키지 ID가 없습니다. 이 가이드에서는 디버깅을 위해 추가한 다음 배포용으로 패키지하는 방법을 보여줍니다.
필수 조건
Flutter SDK: 공식 가이드에 따라 Flutter를 설치합니다.
winapp CLI: winget을 통해 CLI를
winapp설치하거나 이미 설치된 경우 업데이트합니다.winget install Microsoft.winappcli --source winget
1. 새 Flutter 앱 만들기
공식 Flutter 문서의 가이드에 따라 새 애플리케이션을 만들고 실행합니다.
기본 Flutter 카운터 앱이 표시됩니다.
2. ID를 확인하도록 코드 업데이트
패키지 ID를 사용하여 실행되는지 확인하도록 앱을 업데이트합니다. 다트 FFI를 사용하여 Windows GetCurrentPackageFamilyName API를 호출합니다.
먼저 패키지를 추가합니다.ffi
flutter pub add ffi
다음으로, 내용을 다음 코드로 바꿉니다 lib/main.dart . 이 코드는 Windows API를 사용하여 현재 패키지 ID를 검색하려고 시도합니다. 성공하면 UI에 패키지 패밀리 이름이 표시됩니다. 그렇지 않으면 "패키지되지 않음"이 표시됩니다.
import 'dart:ffi';
import 'dart:io' show Platform;
import 'package:ffi/ffi.dart';
import 'package:flutter/material.dart';
/// Returns the Package Family Name if running with package identity, or null.
String? getPackageFamilyName() {
if (!Platform.isWindows) return null;
final kernel32 = DynamicLibrary.open('kernel32.dll');
final getCurrentPackageFamilyName = kernel32.lookupFunction<
Int32 Function(Pointer<Uint32>, Pointer<Uint16>),
int Function(
Pointer<Uint32>, Pointer<Uint16>)>('GetCurrentPackageFamilyName');
final length = calloc<Uint32>();
try {
// First call to get required buffer length
final result =
getCurrentPackageFamilyName(length, Pointer<Uint16>.fromAddress(0));
if (result != 122) return null; // ERROR_INSUFFICIENT_BUFFER = 122
// Second call with buffer to get the name
final namePtr = calloc<Uint16>(length.value);
try {
final result2 = getCurrentPackageFamilyName(length, namePtr);
if (result2 == 0) {
return namePtr.cast<Utf16>().toDartString(); // ERROR_SUCCESS = 0
}
return null;
} finally {
calloc.free(namePtr);
}
} finally {
calloc.free(length);
}
}
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
late final String? _packageFamilyName;
@override
void initState() {
super.initState();
_packageFamilyName = getPackageFamilyName();
}
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(16),
margin: const EdgeInsets.only(bottom: 24),
decoration: BoxDecoration(
color: _packageFamilyName != null
? Colors.green.shade50
: Colors.orange.shade50,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: _packageFamilyName != null
? Colors.green
: Colors.orange,
),
),
child: Text(
_packageFamilyName != null
? 'Package Family Name:\n$_packageFamilyName'
: 'Not packaged',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyLarge,
),
),
const Text('You have pushed the button this many times:'),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
3. ID 없이 실행
이제 평소와 같이 앱을 빌드하고 실행합니다.
flutter build windows
실행 파일을 직접 실행합니다(다른 경우 프로젝트 이름으로 바꾸기 flutter_app ).
.\build\windows\x64\runner\Release\flutter_app.exe
팁 (조언)
빌드 출력은 컴퓨터의 아키텍처에 관계없이 x64 폴더에 있습니다. 이는 Flutter의 Windows 빌드에 필요합니다.
주황색 "패키지되지 않음" 표시기가 있는 앱이 표시됩니다. 그러면 패키지 ID 없이 표준 실행 파일이 실행 중임을 확인합니다.
4. winapp CLI를 사용하여 Project 초기화
winapp init 명령은 앱 매니페스트, 자산, 그리고 필요에 따라 C++ 개발을 위한 Windows 앱 SDK 헤더까지 한 번에 필요한 모든 것을 설정합니다. 매니페스트는 Windows API 액세스 권한을 부여하는 데 사용하는 앱의 ID(이름, 게시자, 버전)를 정의합니다.
다음 명령을 실행하고 프롬프트를 따릅니다.
winapp init
프롬프트가 표시되면
- 패키지 이름: Enter 키를 눌러 기본값을 적용합니다(프로젝트 이름에서 파생됨).
- Publisher 이름: Enter 키를 눌러 기본값을 적용하거나 이름을 입력합니다.
- 버전: Enter 키를 눌러 1.0.0.0 허용
- 설명: Enter 키를 눌러 기본값(Windows 애플리케이션)을 적용합니다.
- SDK 설정: "안정적인 SDK"를 선택하여 Windows 앱 SDK 다운로드하고 C++ 헤더를 생성합니다(6단계에 필요).
이 명령은 다음을 수행합니다.
- 만들기
Package.appxmanifest- 앱의 ID를 정의하는 매니페스트 - 폴더 만들기
Assets- MSIX 패키징 및 스토어 제출에 필요한 아이콘 - Windows 앱 SDK 헤더 및 라이브러리를 사용하여
.winapp폴더 만들기 -
winapp.yamlSDK 버전을 고정하기 위한 구성 파일 만들기
열 Package.appxmanifest는 표시 이름, 게시자 및 기능과 같은 속성을 추가로 사용자 지정하는 데 사용할 수 있습니다.
5. ID를 사용하여 디버그
앱을 완전히 패키징하지 않고 ID(예: 알림)가 필요한 기능을 테스트하려면 .winapp run 실제 MSIX 설치와 마찬가지로 느슨한 레이아웃 패키지를 등록하고 한 단계에서 앱을 시작합니다. 디버깅에 인증서 또는 서명이 필요하지 않습니다.
앱을 빌드합니다.
flutter build windowsID를 사용하여 실행:
winapp run .\build\windows\x64\runner\Release
팁 (조언)
winapp run 또한 시스템에 패키지를 등록합니다. 이 때문에 MSIX는 7단계의 뒷부분에서 설치하려고 할 때 "이미 설치됨"으로 표시될 수 있습니다. 완료되면 개발 패키지를 정리하는 데 사용합니다 winapp unregister .
이제 녹색 표시기가 표시된 앱이 표시됩니다.
Package Family Name: flutterapp.debug_xxxxxxxx
그러면 앱이 유효한 패키지 ID로 실행되고 있는지 확인합니다.
팁 (조언)
고급 디버깅 워크플로(디버거 연결, IDE 설정, 시작 디버깅)는 디버깅 가이드를 참조하세요.
6. Windows 앱 SDK 사용(선택 사항)
winapp init 중에 SDK를 설정하도록 선택한 경우 이제 .winapp/include 폴더의 Windows 앱 SDK C++ 헤더에 액세스할 수 있습니다. Flutter의 Windows 실행기는 C++이므로 네이티브 코드에서 Windows 앱 SDK API를 호출하고 메서드 채널을 통해 Dart에 노출할 수 있습니다. 배포에 패키지 ID만 필요한 경우 7단계로 건너뛸 수 있습니다.
Windows 앱 런타임 버전을 표시하는 간단한 예제를 추가해 보겠습니다.
네이티브 플러그 인 만들기
windows/runner/winapp_sdk_plugin.h을 만듭니다.
#ifndef RUNNER_WINAPP_SDK_PLUGIN_H_
#define RUNNER_WINAPP_SDK_PLUGIN_H_
#include <flutter/flutter_engine.h>
// Registers a method channel for querying Windows App SDK info.
void RegisterWinAppSdkPlugin(flutter::FlutterEngine* engine);
#endif // RUNNER_WINAPP_SDK_PLUGIN_H_
windows/runner/winapp_sdk_plugin.cpp을 만듭니다.
#include "winapp_sdk_plugin.h"
#include <flutter/method_channel.h>
#include <flutter/standard_method_codec.h>
#include <winrt/Microsoft.Windows.ApplicationModel.WindowsAppRuntime.h>
#include <string>
void RegisterWinAppSdkPlugin(flutter::FlutterEngine* engine) {
auto channel = std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
engine->messenger(), "com.example/winapp_sdk",
&flutter::StandardMethodCodec::GetInstance());
channel->SetMethodCallHandler(
[](const flutter::MethodCall<flutter::EncodableValue>& call,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
if (call.method_name() == "getRuntimeVersion") {
try {
// Flutter already initializes COM in main.cpp, so we skip
// winrt::init_apartment() here — the apartment is already set up.
auto version = winrt::Microsoft::Windows::ApplicationModel::
WindowsAppRuntime::RuntimeInfo::AsString();
std::string versionStr = winrt::to_string(version);
result->Success(flutter::EncodableValue(versionStr));
} catch (const winrt::hresult_error& e) {
result->Error("WINRT_ERROR", winrt::to_string(e.message()));
} catch (...) {
result->Error("UNKNOWN_ERROR",
"Failed to get Windows App Runtime version");
}
} else {
result->NotImplemented();
}
});
// prevent channel destruction by releasing ownership
channel.release();
}
CMakeLists.txt 업데이트하기
편집 windows/runner/CMakeLists.txt 하여 세 가지 변경 내용을 만듭니다.
add_executable 블록을 찾아 원본 파일 목록에 추가 "winapp_sdk_plugin.cpp" 합니다.
add_executable(${BINARY_NAME} WIN32
"flutter_window.cpp"
"main.cpp"
"utils.cpp"
"win32_window.cpp"
"winapp_sdk_plugin.cpp" # <-- add this line
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
"Runner.rc"
"runner.exe.manifest"
)
그런 다음, 파일 끝에 다음 두 줄을 추가하여 WinRT 라이브러리를 연결하고 Windows 앱 SDK 헤더를 포함합니다.
# Link Windows Runtime libraries for WinRT
target_link_libraries(${BINARY_NAME} PRIVATE "WindowsApp.lib")
# Windows App SDK headers from winapp CLI
target_include_directories(${BINARY_NAME} PRIVATE
"${CMAKE_SOURCE_DIR}/../.winapp/include")
플러그 인 등록
에서 windows/runner/flutter_window.cpp파일 맨 위에 include를 추가하고 다른 포함 항목을 추가합니다.
#include "winapp_sdk_plugin.h"
그런 다음 RegisterPlugins 호출을 FlutterWindow::OnCreate()에서 찾아서 그 다음 줄에 RegisterWinAppSdkPlugin를 추가합니다.
RegisterPlugins(flutter_controller_->engine());
RegisterWinAppSdkPlugin(flutter_controller_->engine()); // <-- add this line
main.dart 업데이트
다음 가져오기를 lib/main.dart 맨 위에 추가하고, 기존 가져오기와 함께 사용하십시오.
import 'package:flutter/services.dart';
기존 함수 아래에 이 함수를 getPackageFamilyName() 추가합니다(클래스 외부).
/// Queries the Windows App Runtime version via a native method channel.
Future<String?> getWindowsAppRuntimeVersion() async {
if (!Platform.isWindows) return null;
try {
const channel = MethodChannel('com.example/winapp_sdk');
final version = await channel.invokeMethod<String>('getRuntimeVersion');
return version;
} catch (_) {
return null;
}
}
클래스에서 _MyHomePageState 기존 필드 옆에 새 필드를 추가합니다._packageFamilyName
late final String? _packageFamilyName;
String? _runtimeVersion; // <-- add this line
새로운 함수를 호출하도록 initState()을 업데이트합니다.
@override
void initState() {
super.initState();
_packageFamilyName = getPackageFamilyName();
// Fetch the runtime version asynchronously
getWindowsAppRuntimeVersion().then((version) {
setState(() {
_runtimeVersion = version;
});
});
}
마지막으로 메서드에 런타임 버전을 표시합니다 build .
Column의 자식 목록에서 패키지 ID를 표시하는 Container 바로 뒤에 이 위젯을 추가합니다.
if (_runtimeVersion != null)
Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Text(
'Windows App Runtime: $_runtimeVersion',
style: Theme.of(context).textTheme.bodyLarge,
),
),
빌드 및 실행
애플리케이션을 다시 빌드합니다.
flutter build windows
winapp run .\build\windows\x64\runner\Release
이제 다음과 같은 출력이 표시됩니다.
Package Family Name: flutterapp.debug_xxxxxxxx
Windows App Runtime: 8000.731.1532.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. MSIX를 사용한 패키지
앱을 배포할 준비가 되면 동일한 매니페스트를 사용하여 MSIX로 패키지할 수 있습니다.
패키지 디렉터리 준비
먼저 릴리스 모드에서 애플리케이션을 빌드합니다.
flutter build windows
그런 다음 릴리스 파일을 사용하여 디렉터리를 만듭니다.
mkdir dist
copy .\build\windows\x64\runner\Release\* .\dist\ -Recurse
Flutter Windows 빌드 출력에는 실행 파일 flutter_windows.dll 및 data 폴더가 모두 필요합니다.
개발 인증서 생성
패키징하기 전에 서명을 위한 개발 인증서가 필요합니다. 아직 생성하지 않은 경우 생성합니다.
winapp cert generate --if-exists skip
서명 및 패키징
이제 패키지를 만들고 서명할 수 있습니다.
winapp pack .\dist --cert .\devcert.pfx
참고: 이
pack명령은 현재 디렉터리에서 자동으로Package.appxmanifest을 사용하고 나서, 이를 대상 폴더에 복사한 후 패키징합니다.
인증서 설치
MSIX 패키지를 설치하려면 컴퓨터에서 개발 인증서를 신뢰해야 합니다. 관리자 권한으로 이 명령을 실행합니다(인증서당 한 번만 수행하면 됨).
winapp cert install .\devcert.pfx
설치 및 실행
팁 (조언)
5단계에서 사용한 winapp run 경우 패키지가 시스템에 이미 등록되어 있을 수 있습니다.
winapp unregister 먼저 개발 등록을 제거한 다음 릴리스 패키지를 설치합니다.
생성된 .msix 파일을 두 번 클릭하거나 PowerShell을 사용하여 패키지를 설치합니다.
Add-AppxPackage .\flutterapp.msix
팁 (조언)
MSIX 파일 이름에는 버전 및 아키텍처(예: flutterapplication1_1.0.0.0_x64.msix)가 포함됩니다. 디렉터리에서 정확한 파일 이름을 확인합니다. 코드 변경 후 패키지를 다시 만들어야 하는 경우, Version 내부의 Package.appxmanifest를 증가시켜야 합니다. Windows에서는 설치된 패키지를 업데이트하기 위해 더 높은 버전 번호가 필요합니다.
팁
- 배포할 준비가 되면 사용자가 자체 서명된 인증서를 설치할 필요가 없도록 인증 기관의 코드 서명 인증서로 MSIX에 서명할 수 있습니다.
- Azure 신뢰할 수 있는 서명 서비스는 인증서를 안전하게 관리하고 CI/CD 파이프라인에 로그인을 통합하는 좋은 방법입니다.
- Microsoft Store 제출 전에 서명할 필요 없이 MSIX에 서명합니다.
다음 단계
winget을 통한 배포 : MSIX를Windows 패키지 관리자 커뮤니티 리포지토리 -
Microsoft Store에 게시:
winapp store를 사용하여 패키지를 제출하십시오. -
CI/CD 설정:
setup-WinAppCliGitHub 작업을 사용하여 파이프라인에서 패키징 자동화 - Explore Windows API들: 패키지 ID로, 이제 알림, 기기 내 AI 및 기타 ID에 의존하는 API을 사용할 수 있습니다.
Windows developer