Winapp CLI használata a Flutterrel

Egy teljes munkapéldáért tekintse meg az adattár Flutter-mintáját .

Ez az útmutató bemutatja, hogyan használhatja a winapp cli-t egy Flutter-alkalmazással a csomagidentitás hozzáadásához és az alkalmazás MSIX-ként való csomagolásához.

A csomagidentitás a Windows app modell alapvető fogalma. Lehetővé teszi, hogy az alkalmazás hozzáférjen bizonyos Windows API-khoz (például értesítések, biztonság, AI API-k stb.), tiszta telepítési/eltávolítási felülettel rendelkezik, és így tovább.

A standard Flutter Windows buildek nem rendelkeznek csomagidentitással. Ez az útmutató bemutatja, hogyan lehet egy elemet hozzáadni hibakereséshez, majd hogyan csomagolhatja be terjesztés céljából.

Előfeltételek

  1. Flutter SDK: Telepítse a Fluttert a hivatalos útmutatót követve.

  2. winapp parancssori felület: Telepítse a parancssori felületet a winapp wingettel (vagy frissítse, ha már telepítve van):

    winget install Microsoft.winappcli --source winget
    

1. Új Flutter-alkalmazás létrehozása

Kövesse az útmutatót a hivatalos Flutter-dokumentációban egy új alkalmazás létrehozásához és futtatásához.

Az alapértelmezett Flutter-számláló alkalmazásnak kell megjelennie.

2. Kód frissítése az identitás ellenőrzéséhez

Frissítjük az alkalmazást, hogy ellenőrizze, a csomagazonossággal fut-e. A Dart FFI használatával hívjuk meg a Windows GetCurrentPackageFamilyName API-t.

Először adja hozzá a ffi csomagot:

flutter pub add ffi

Ezután cserélje le a lib/main.dart tartalmát az alábbi kóddal. Ez a kód a Windows API használatával próbálja lekérni az aktuális csomagidentitást. Ha sikeres, megjeleníti a csomagcsalád nevét a felhasználói felületen; ellenkező esetben a "Nincs csomagolva" felirat jelenik meg.

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. Futtatás identitás nélkül

Most hozza létre és futtassa az alkalmazást a szokásos módon:

flutter build windows

Futtassa közvetlenül a végrehajtható fájlt (cserélje le flutter_app a projekted nevét, ha eltér):

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

Jótanács

A build kimenete a x64 mappában található, függetlenül a gép architektúrájától – ez a Flutter Windows buildjéhez várható.

Az alkalmazást narancssárga "Nincs csomagolva" jelzővel kell látnia. Ez megerősíti, hogy a standard végrehajtható fájl csomagidentitás nélkül fut.

4. A Project inicializálása winapp parancssori felülettel

A winapp init parancs minden szükséges elemet beállít egy lépésben: alkalmazásjegyzéket, objektumokat és opcionálisan Windows App SDK fejléceket a C++ fejlesztéshez. A jegyzék meghatározza az alkalmazás identitását (név, közzétevő, verzió), amelyet Windows api-hozzáférés biztosításához használ.

Futtassa a következő parancsot, és kövesse az utasításokat:

winapp init

Amikor a rendszer kéri:

  • Csomag neve: Nyomja le az Enter billentyűt az alapértelmezett (a projekt nevéből származtatott) elfogadásához
  • Publisher név: Az Enter billentyűt lenyomva fogadja el az alapértelmezett értéket, vagy adja meg a nevét
  • Verzió: Nyomja le az Enter billentyűt az 1.0.0.0 elfogadásához
  • Description: Nyomja le az Enter billentyűt az alapértelmezett (Windows alkalmazás) elfogadásához
  • Setup SDK-k: Válassza a "Stabil SDK-k" lehetőséget a Windows App SDK letöltéséhez és C++ fejlécek létrehozásához (a 6. lépéshez szükséges)

Ez a parancs a következő lesz:

  • Létrehozza Package.appxmanifest – az alkalmazás identitását meghatározó jegyzék
  • Mappa létrehozása Assets – az MSIX-csomagoláshoz és az Áruház beküldéséhez szükséges ikonok
  • .winapp mappa létrehozása Windows App SDK fejlécekkel és kódtárakkal
  • winapp.yaml Konfigurációs fájl létrehozása SDK-verziók rögzítéséhez

Megnyithatja Package.appxmanifest az olyan tulajdonságok további testreszabásához, mint a megjelenítendő név, a közzétevő és a képességek.

5. Hibakeresés identitással

Az olyan funkciók teszteléséhez, amelyek identitást igényelnek (mint például az értesítések), az alkalmazás teljes csomagolása nélkül használhatja a winapp run eszközt. Ez regisztrál egy laza elrendezési csomagot (csakúgy, mint egy valódi MSIX-telepítés), és egy lépésben elindítja az alkalmazást. A hibakereséshez nincs szükség tanúsítványra vagy aláírásra.

  1. Az alkalmazás létrehozása:

    flutter build windows
    
  2. Futtatás identitással:

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

Jótanács

winapp run a csomagot a rendszeren is regisztrálja. Ezért előfordulhat, hogy az MSIX "már telepítve" állapotban jelenik meg, amikor később megpróbálja telepíteni a 7. lépésben. A fejlesztési csomagok megtisztítására használható winapp unregister , ha elkészült.

Ekkor megjelenik az alkalmazás egy zöld jelzéssel, amely a következőt mutatja:

Package Family Name: flutterapp.debug_xxxxxxxx

Ez megerősíti, hogy az alkalmazás érvényes csomagazonosítóval fut!

Jótanács

A speciális hibakeresési munkafolyamatokat (hibakeresők csatolása, IDE-beállítás, indítási hibakeresés) a hibakeresési útmutatóban találja.

6. A Windows App SDK használata (nem kötelező)

Ha a winapp init során az SDK-k beállítását választotta, most már hozzáférhet Windows App SDK C++ fejlécekhez a .winapp/include mappában. Mivel a Flutter Windows futója C++, meghívhatja Windows App SDK API-kat natív kódból, és egy metóduscsatornán keresztül elérhetővé teheti őket a Dartnak. Ha csak csomagidentitásra van szüksége a terjesztéshez, ugorjon a 7. lépésre.

Vegyünk fel egy egyszerű példát, amely megjeleníti a Windows-alkalmazás Futtatókörnyezet verzióját.

A natív beépülő modul létrehozása

Hozzon létre 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_

Hozzon létre 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 frissítése

Szerkessze a windows/runner/CMakeLists.txt-et három változtatás elvégzéséhez. Keresse meg a add_executable blokkot, és adja hozzá "winapp_sdk_plugin.cpp" a forrásfájllistához:

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

Ezután adja hozzá ezt a két sort a fájl végéhez a WinRT-kódtárak csatolásához és a Windows App SDK fejlécek hozzáadásához:

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

A beépülő modul regisztrálása

Adja hozzá az windows/runner/flutter_window.cpp include-ot a fájl tetején a többi include-dal együtt.

#include "winapp_sdk_plugin.h"

Ezután keresse meg a RegisterPlugins hívást FlutterWindow::OnCreate() és adja hozzá RegisterWinAppSdkPlugin a közvetlenül utána következő sorba.

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

A main.dart frissítése

Adja hozzá a következő importálást a meglévők mellé a lib/main.dart tetejére:

import 'package:flutter/services.dart';

Adja hozzá ezt a függvényt a meglévő getPackageFamilyName() függvény alá (bármely osztályon kívül):

/// 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;
  }
}

Az osztályban _MyHomePageState adjon hozzá egy új mezőt a meglévőhöz _packageFamilyName:

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

Frissítsen initState() az új függvény meghívásához:

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

Végül jelenítse meg a futtatókörnyezet verzióját a build metódusban. Adja hozzá ezt a widgetet a Column gyermeklistában közvetlenül a Container csomag identitását megjelenítő elem után:

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

Építés és futtatás

Az alkalmazás újraépítése:

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

Most a következő kimenetnek kell megjelennie:

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

A .winapp/include könyvtár tartalmazza a Windows App SDK összes szükséges fejlécét, beleértve a következőket:

  • winrt/ – WinRT C++ kivetítési fejlécek Windows-futtatókörnyezet API-k eléréséhez
  • Microsoft.UI.*.h – WinUI 3 fejlécek modern felhasználói felületi összetevőkhöz
  • MddBootstrap.h – Windows App SDK rendszerindítás
  • WindowsAppSDK-VersionInfo.h – Verzióadatok
  • És még sok más Windows App SDK összetevő

A fejlettebb Windows App SDK használatért tekintse meg a Windows App SDK dokumentációját.

7. MSIX csomagolás

Miután készen áll az alkalmazás terjesztésére, ugyanazzal a jegyzékfájllal MSIX-ként csomagolhatja be.

A csomagkönyvtár előkészítése

Először hozza létre az alkalmazást kiadási módban:

flutter build windows

Ezután hozzon létre egy könyvtárat a kiadási fájlokkal:

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

A Flutter Windows build kimenete tartalmazza a végrehajtható, a flutter_windows.dll és a data mappát – mindegyikre szükség van.

Fejlesztési tanúsítvány létrehozása

A csomagolás előtt fejlesztési tanúsítványra van szüksége az aláíráshoz. Hozzon létre egyet, ha még nem tette meg:

winapp cert generate --if-exists skip

Aláírás és csomagolás

Most már csomagolhatja és aláírhatja a következőt:

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

Megjegyzés: A pack parancs automatikusan az Package.appxmanifest aktuális könyvtárból származó fájlt használja, és a csomagolás előtt átmásolja a célmappába.

A tanúsítvány telepítése

Az MSIX-csomag telepítése előtt meg kell bíznia a fejlesztési tanúsítványban a számítógépen. Futtassa ezt a parancsot rendszergazdaként (ezt tanúsítványonként csak egyszer kell elvégeznie):

winapp cert install .\devcert.pfx

Telepítés és futtatás

Jótanács

Ha az 5. lépésben használta winapp run , előfordulhat, hogy a csomag már regisztrálva van a rendszeren. Először winapp unregister távolítsa el a fejlesztési regisztrációt, majd telepítse a kiadási csomagot.

Telepítse a csomagot a létrehozott .msix fájlra duplán kattintva vagy a PowerShell használatával:

Add-AppxPackage .\flutterapp.msix

Jótanács

Az MSIX fájlnév tartalmazza a verziót és az architektúrát (pl. flutterapplication1_1.0.0.0_x64.msix). Ellenőrizze a könyvtárban a pontos fájlnevet. Ha a kódmódosítások után újra kell csomagolnia, növelje a Version értékét a Package.appxmanifest-ben – a Windows magasabb verziószámot igényel egy telepített csomag frissítéséhez.

Tips

  1. Miután készen áll a terjesztésre, aláírhatja az MSIX-et egy hitelesítésszolgáltató kódaláíró tanúsítványával, hogy a felhasználóknak ne kelljen önaláírt tanúsítványt telepíteniük.
  2. A Megbízható Azure-aláírás szolgáltatással biztonságosan kezelheti tanúsítványait, és integrálhatja a CI/CD-folyamatba való bejelentkezést.
  3. A Microsoft Store aláírja Önnek az MSIX-et, a beküldés előtt nem kell aláírnia.

Következő lépések