Použití winapp CLI s Flutterem

Úplný funkční příklad najdete v ukázce Flutter v tomto úložišti.

Tato příručka ukazuje, jak pomocí rozhraní příkazového winapp řádku s aplikací Flutter přidat identitu balíčku a zabalit aplikaci jako MSIX.

Identita balíčku je základním konceptem modelu Windows app. Umožňuje vaší aplikaci přistupovat ke konkrétním rozhraním API Windows (jako jsou oznámení, zabezpečení, rozhraní API AI atd.), mají čisté prostředí pro instalaci a odinstalaci a další.

Standardní sestavení Flutter pro Windows nemá identitu balíčku. Tento průvodce ukazuje, jak ho přidat pro ladění a pak ho zabalit pro distribuci.

Předpoklady

  1. Flutter SDK: Nainstalujte flutter podle oficiální příručky.

  2. winapp CLI: Nainstalujte winapp CLI pomocí wingetu (nebo aktualizujte, pokud už je nainstalováno):

    winget install Microsoft.winappcli --source winget
    

1. Vytvoření nové aplikace flutter

Podle pokynů v oficiální dokumentaci flutter vytvořte novou aplikaci a spusťte ji.

Měla by se zobrazit výchozí aplikace čítače Flutter.

2. Aktualizace kódu pro kontrolu identity

Aplikaci aktualizujeme a zkontrolujeme, jestli běží s identitou balíčku. K volání rozhraní API Windows GetCurrentPackageFamilyName použijeme metodu Dart FFI.

Nejprve přidejte ffi balíček:

flutter pub add ffi

Dále nahraďte obsah lib/main.dart následujícím kódem. Tento kód se pokusí načíst identitu aktuálního balíčku pomocí rozhraní API Windows. V případě úspěchu se v uživatelském rozhraní zobrazí název rodiny balíčků; v opačném případě se zobrazí "Není zabaleno".

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),
      ),
    );
  }
}

Spusťte (program) bez přiřazené identity.

Teď sestavte a spusťte aplikaci obvyklým způsobem:

flutter build windows

Spusťte spustitelný soubor přímo (pokud se liší, nahraďte flutter_app názvem projektu):

.\build\windows\x64\runner\Release\flutter_app.exe

Návod

Výstup sestavení je ve složce x64 bez ohledu na architekturu vašeho počítače – to se očekává pro Windows sestavení Flutter.

Měla by se zobrazit aplikace s oranžovým indikátorem Nenabalené. Tím potvrdíte, že standardní spustitelný soubor běží bez jakékoli identity balíčku.

4. Inicializace Project pomocí rozhraní příkazového řádku winapp

Příkaz winapp init nastaví vše, co potřebujete najednou: manifest aplikace, prostředky a volitelné hlavičky Windows App SDK pro vývoj v C++. Manifest definuje identitu vaší aplikace (název, vydavatel, verzi), která Windows používá k udělení přístupu k rozhraní API.

Spusťte následující příkaz a postupujte podle pokynů:

winapp init

Po zobrazení výzvy:

  • Název balíčku: Stisknutím klávesy Enter přijměte výchozí hodnotu (odvozenou z názvu projektu).
  • Publisher název: Stisknutím klávesy Enter přijměte výchozí hodnotu nebo zadejte své jméno.
  • Verze: Stisknutím klávesy Enter přijměte verzi 1.0.0.0.
  • Description: Stisknutím klávesy Enter přijměte výchozí hodnotu (Windows Aplikace).
  • Nastavení sad SDK: Pokud chcete stáhnout Windows App SDK a vygenerovat hlavičky C++ (potřebné pro krok 6), vyberte Stabilní sady SDK.

Tento příkaz:

  • Vytvoření Package.appxmanifest – manifest, který definuje identitu vaší aplikace
  • Vytvoření Assets složky – ikony vyžadované pro balení MSIX a odesílání do Storu
  • Vytvoření složky .winapp s hlavičkami a knihovnami Windows App SDK
  • Vytvoření konfiguračního winapp.yaml souboru pro připnutí verzí sady SDK

Můžete otevřít Package.appxmanifest a upravit vlastnosti, jako je zobrazovaný název, vydavatel a možnosti.

5. Ladění pomocí identity

Pokud chcete otestovat funkce, které vyžadují identitu (jako oznámení), aniž by se aplikace plně zabalily, můžete použít winapp run. Tím se zaregistruje volný balíček rozložení (stejně jako skutečná instalace MSIX) a spustí se aplikace v jednom kroku. Pro ladění není potřeba žádný certifikát ani podepisování.

  1. Sestavení aplikace:

    flutter build windows
    
  2. Spusťte s identitou:

    winapp run .\build\windows\x64\runner\Release
    

Návod

winapp run také zaregistruje balíček ve vašem systému. To je důvod, proč se MSIX může při pokusu o jeho instalaci později v kroku 7 zobrazit jako již nainstalovaný. Slouží winapp unregister k vyčištění vývojových balíčků po dokončení.

Teď by se měla zobrazit aplikace se zeleným indikátorem:

Package Family Name: flutterapp.debug_xxxxxxxx

Tím potvrdíte, že vaše aplikace běží s platnou identitou balíčku.

Návod

Pokročilé ladicí pracovní postupy (připojení ladicích programů, nastavení integrovaného vývojového prostředí, ladění po spuštění) najdete v průvodci laděním.

6. Použití Windows App SDK (volitelné)

Pokud jste během winapp init vybrali nastavit sady SDK, máte nyní přístup k hlavičkám Windows App SDK pro C++ ve složce .winapp/include. Protože Windows runner Flutteru je C++, můžete volat rozhraní API Windows App SDK z nativního kódu a zpřístupnit je Dart prostřednictvím kanálu metod. Pokud potřebujete jenom identitu balíčku pro distribuci, můžete přeskočit ke kroku 7.

Pojďme přidat jednoduchý příklad, který zobrazuje verzi aplikace pro Windows Runtime.

Vytvořte nativní plugin

Vytvořit 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_

Vytvořit 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();
}

Aktualizace CMakeLists.txt

Upravte windows/runner/CMakeLists.txt , abyste udělali tři změny. add_executable Vyhledejte blok a přidejte "winapp_sdk_plugin.cpp" ho do seznamu zdrojových souborů:

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"
)

Potom na konec souboru přidejte tyto dva řádky, abyste propojili knihovny WinRT a zahrnuli hlavičky Windows App 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")

Registrace modulu plug-in

V windows/runner/flutter_window.cpp, přidejte include do horní části souboru k ostatním include.

#include "winapp_sdk_plugin.h"

Pak najděte RegisterPlugins volání FlutterWindow::OnCreate() a přidejte RegisterWinAppSdkPlugin na řádek hned za ním:

  RegisterPlugins(flutter_controller_->engine());
  RegisterWinAppSdkPlugin(flutter_controller_->engine());  // <-- add this line

Aktualizace main.dart

Do horní části lib/main.dartpřidejte následující import spolu s existujícími importy:

import 'package:flutter/services.dart';

Přidejte tuto funkci pod existující getPackageFamilyName() funkci (mimo jakoukoli třídu):

/// 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 Do třídy přidejte nové pole vedle existujícího _packageFamilyNamepole:

  late final String? _packageFamilyName;
  String? _runtimeVersion;         // <-- add this line

Aktualizujte initState() tak, aby volalo novou funkci:

  @override
  void initState() {
    super.initState();
    _packageFamilyName = getPackageFamilyName();
    // Fetch the runtime version asynchronously
    getWindowsAppRuntimeVersion().then((version) {
      setState(() {
        _runtimeVersion = version;
      });
    });
  }

Nakonec v metodě build zobrazte verzi běhového prostředí. Přidejte tento widget do seznamu potomků Column hned za Container widget, který zobrazuje identitu balíčku:

            if (_runtimeVersion != null)
              Padding(
                padding: const EdgeInsets.only(bottom: 16),
                child: Text(
                  'Windows App Runtime: $_runtimeVersion',
                  style: Theme.of(context).textTheme.bodyLarge,
                ),
              ),

Sestavení a spuštění

Znovu sestavte aplikaci:

flutter build windows
winapp run .\build\windows\x64\runner\Release

Teď by se měl zobrazit výstup jako:

Package Family Name: flutterapp.debug_xxxxxxxx
Windows App Runtime: 8000.731.1532.0

Adresář .winapp/include obsahuje všechna potřebná záhlaví pro Windows App SDK, včetně:

  • winrt/ – hlavičky projekce WinRT C++ pro přístup k rozhraním API prostředí Windows Runtime
  • Microsoft.UI.*.h – hlavičky WinUI 3 pro moderní komponenty uživatelského rozhraní
  • MddBootstrap.h – spouštění Windows App SDK
  • WindowsAppSDK-VersionInfo.h - Informace o verzi
  • A mnoho dalších komponent Windows App SDK

Pokud chcete pokročilejší Windows App SDK využití, přečtěte si dokumentaci k Windows App SDK.

7. Balíček s MSIX

Jakmile budete připraveni distribuovat aplikaci, můžete ji zabalit jako MSIX pomocí stejného manifestu.

Příprava adresáře balíčků

Nejprve sestavte aplikaci v režimu vydání:

flutter build windows

Pak vytvořte adresář se soubory vydaných verzí:

mkdir dist
copy .\build\windows\x64\runner\Release\* .\dist\ -Recurse

Výstup sestavení Flutter Windows zahrnuje spustitelný soubor, flutter_windows.dll a složku data – to vše je potřeba.

Vygenerování vývojového certifikátu

Před balením potřebujete vývojový certifikát pro podepisování. Vygenerujte ho, pokud jste to ještě neudělali:

winapp cert generate --if-exists skip

Podepsání a balení

Teď můžete zabalit a podepsat:

winapp pack .\dist --cert .\devcert.pfx

Poznámka: Příkaz pack automaticky použije Package.appxmanifest ze svého aktuálního adresáře a zkopíruje jej do cílové složky před zabalením.

Instalace certifikátu

Než budete moct nainstalovat balíček MSIX, musíte na svém počítači důvěřovat vývojovému certifikátu. Spusťte tento příkaz jako správce (stačí to udělat jenom jednou pro každý certifikát):

winapp cert install .\devcert.pfx

Instalace a spuštění

Návod

Pokud jste tento balíček použili winapp run v kroku 5, možná už je v systému zaregistrovaný. Nejprve winapp unregister odeberte registraci vývojové verze a pak nainstalujte produkční balíček.

Nainstalujte balíček poklikáním na vygenerovaný .msix soubor nebo pomocí PowerShellu:

Add-AppxPackage .\flutterapp.msix

Návod

Název souboru MSIX zahrnuje verzi a architekturu (např flutterapplication1_1.0.0.0_x64.msix. ). Zkontrolujte přesný název souboru v adresáři. Pokud potřebujete po změně kódu znovu zabalit, navyšte Version v Package.appxmanifest – Windows k aktualizaci nainstalovaného balíčku vyžaduje vyšší číslo verze.

Tips

  1. Jakmile budete připraveni k distribuci, můžete podepisovat MSIX certifikátem pro podepisování kódu od certifikační autority, aby uživatelé nemuseli instalovat certifikát podepsaný svým držitelem.
  2. Služba Důvěryhodné podepisování Azure představuje skvělý způsob, jak bezpečně spravovat certifikáty a integrovat přihlašování do kanálu CI/CD.
  3. Microsoft Store za vás podepíše MSIX, před odesláním se nemusíte podepisovat.

Další kroky