Winapp CLI gebruiken met Flutter

Bekijk het Flutter-voorbeeld in deze repository voor een volledig werkvoorbeeld.

In deze handleiding ziet u hoe u de winapp CLI gebruikt met een Flutter-toepassing om pakketidentiteit toe te voegen en uw app als een MSIX-pakket te verpakken.

Pakketidentiteit is een kernconcept in het Windows app model. Hiermee heeft uw toepassing toegang tot specifieke Windows API's (zoals meldingen, beveiliging, AI-API's, enzovoort), een schone installatie-/verwijderingservaring en meer.

Een standaard Flutter Windows build heeft geen pakketidentiteit. In deze handleiding ziet u hoe u deze toevoegt voor foutopsporing en deze vervolgens inpakt voor distributie.

Vereiste voorwaarden

  1. Flutter SDK: Installeer Flutter volgens de officiële handleiding.

  2. winapp CLI: Installeer de winapp CLI via winget (of werk bij als deze al is geïnstalleerd):

    winget install Microsoft.winappcli --source winget
    

1. Een nieuwe flutter-app maken

Volg de handleiding in de officiële Flutter-documenten om een nieuwe toepassing te maken en uit te voeren.

U zou de standaard Flutter-tellerapp moeten zien.

2. Code bijwerken om identiteit te controleren

We werken de app bij om te controleren of deze wordt uitgevoerd met pakketidentiteit. We gebruiken Dart FFI om de Windows GetCurrentPackageFamilyName API aan te roepen.

Voeg eerst het ffi pakket toe:

flutter pub add ffi

Vervang vervolgens de inhoud van lib/main.dart door de volgende code. Met deze code wordt geprobeerd de huidige pakketidentiteit op te halen met behulp van de Windows-API. Als dit lukt, wordt de familienaam van het pakket weergegeven in de gebruikersinterface; anders wordt 'Niet verpakt' weergegeven.

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. Uitvoeren zonder identiteit

Bouw en voer de app nu zoals gewoonlijk uit:

flutter build windows

Voer het uitvoerbare bestand rechtstreeks uit (vervang flutter_app met je projectnaam indien deze anders is):

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

Aanbeveling

De build-uitvoer bevindt zich in de map x64, ongeacht de architectuur van uw computer. Dit wordt verwacht voor de Windows build van Flutter.

U ziet de app met een oranje indicator 'Niet verpakt'. Hiermee wordt bevestigd dat het standaard uitvoerbare bestand wordt uitgevoerd zonder pakketidentiteit.

4. Initialiseer Project met winapp CLI

Met de opdracht winapp init stelt u alles in wat u nodig hebt: app-manifest, assets en optioneel Windows App SDK headers voor C++-ontwikkeling. Het manifest definieert de identiteit van uw app (naam, uitgever, versie) die Windows gebruikt om API-toegang te verlenen.

Voer de volgende opdracht uit en volg de aanwijzingen:

winapp init

Wanneer u hierom wordt gevraagd:

  • Pakketnaam: Druk op Enter om de standaardwaarde te accepteren (afgeleid van uw projectnaam)
  • Publisher naam: Druk op Enter om de standaardinstelling te accepteren of voer uw naam in
  • Versie: Druk op Enter om 1.0.0.0 te accepteren
  • Description: Druk op Enter om de standaardwaarde (Windows toepassing) te accepteren
  • Setup SDK's: Selecteer 'Stabiele SDK's' om Windows App SDK te downloaden en C++-headers te genereren (nodig voor stap 6)

Met deze opdracht wordt het volgende uitgevoerd:

  • Maken Package.appxmanifest : het manifest waarmee de identiteit van uw app wordt gedefinieerd
  • Map maken Assets : pictogrammen die vereist zijn voor MSIX-pakketten en Store-inzending
  • Een map .winapp maken met Windows App SDK headers en bibliotheken
  • winapp.yaml Een configuratiebestand maken voor het vastmaken van SDK-versies

U kunt openen Package.appxmanifest om eigenschappen zoals de weergavenaam, uitgever en mogelijkheden verder aan te passen.

5. Debuggen met identiteit

Als u functies wilt testen waarvoor identiteit (zoals meldingen) is vereist zonder de app volledig te verpakken, kunt u dit gebruiken winapp run. Hiermee wordt een losse indelingspakket geregistreerd (net als bij een echte MSIX-installatie) en wordt de app in één stap gestart. Er is geen certificaat of ondertekening nodig voor foutopsporing.

  1. Bouw de app:

    flutter build windows
    
  2. Uitvoeren met identiteit:

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

Aanbeveling

winapp run registreert ook het pakket op uw systeem. Daarom kan de MSIX worden weergegeven als 'al geïnstalleerd' wanneer u deze later in stap 7 probeert te installeren. Gebruik winapp unregister om ontwikkelingspakketten op te schonen na voltooiing.

U ziet nu de app met een groene indicator die het volgende weergeeft:

Package Family Name: flutterapp.debug_xxxxxxxx

Hiermee wordt bevestigd dat uw app wordt uitgevoerd met een geldige pakketidentiteit.

Aanbeveling

Zie voor geavanceerde foutopsporingswerkstromen (zoals het koppelen van debuggers, IDE-installatie en het opstarten van debuggen) de handleiding voor foutopsporing.

6. Windows App SDK gebruiken (optioneel)

Als u hebt geselecteerd om de SDK's in te stellen tijdens winapp init, hebt u nu toegang tot Windows App SDK C++-headers in de map .winapp/include. Omdat De Windows runner van Flutter C++ is, kunt u Windows App SDK API's aanroepen vanuit systeemeigen code en deze beschikbaar maken voor Dart via een methodekanaal. Als u alleen pakketidentiteit nodig hebt voor distributie, kunt u doorgaan naar stap 7.

Laten we een eenvoudig voorbeeld toevoegen waarin de Windows-app Runtime-versie wordt weergegeven.

De native invoegtoepassing maken

Maken 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_

Maken 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 bijwerken

Bewerken windows/runner/CMakeLists.txt om drie wijzigingen aan te brengen. Zoek het add_executable blok en voeg deze toe "winapp_sdk_plugin.cpp" aan de lijst met bronbestanden:

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

Voeg vervolgens deze twee regels toe aan het einde van het bestand om WinRT-bibliotheken te koppelen en de Windows App SDK headers op te nemen:

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

De invoegtoepassing registreren

Voeg windows/runner/flutter_window.cpp de include toe boven aan het bestand met de andere includes.

#include "winapp_sdk_plugin.h"

Zoek vervolgens de RegisterPlugins aanroep in FlutterWindow::OnCreate() en voeg RegisterWinAppSdkPlugin toe aan de regel direct erna:

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

Update main.dart

Voeg de volgende import toe aan de bovenkant van lib/main.dart, naast de bestaande imports:

import 'package:flutter/services.dart';

Voeg deze functie toe onder de bestaande getPackageFamilyName() functie (buiten een klasse):

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

Voeg in de _MyHomePageState klasse een nieuw veld toe naast het bestaande _packageFamilyNameveld:

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

Werk initState() bij om de nieuwe functie aan te roepen:

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

Ten slotte geeft u de runtimeversie weer in de build methode. Voeg deze widget toe in de Column child-elementenlijst, direct na de Container die de pakketidentiteit toont:

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

Bouwen en uitvoeren

Bouw de toepassing opnieuw op:

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

U zou nu een uitvoer moeten zien die er als volgt uitziet:

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

De map .winapp/include bevat alle benodigde headers voor Windows App SDK, waaronder:

  • winrt/ - WinRT C++ projectieheaders voor toegang tot Windows Runtime API's
  • Microsoft.UI.*.h - WinUI 3-headers voor moderne UI-onderdelen
  • MddBootstrap.h - Windows App SDK opstarten
  • WindowsAppSDK-VersionInfo.h - Versie-informatie
  • En nog veel meer Windows App SDK onderdelen

Raadpleeg de documentatie Windows App SDK voor geavanceerdere Windows App SDK gebruik.

7. Pakket met MSIX

Zodra u klaar bent om uw app te distribueren, kunt u deze verpakken als een MSIX met hetzelfde manifest.

De pakketmap voorbereiden

Bouw eerst uw toepassing in de releasemodus:

flutter build windows

Maak vervolgens een map met uw releasebestanden:

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

De Build-uitvoer van Flutter Windows bevat het uitvoerbare bestand, flutter_windows.dll en een map data, die allemaal nodig zijn.

Een ontwikkelingscertificaat genereren

Voordat u gaat verpakken, hebt u een ontwikkelingscertificaat nodig voor ondertekening. Genereer er een als u dat nog niet hebt gedaan:

winapp cert generate --if-exists skip

Ondertekenen en inpakken

U kunt nu het volgende inpakken en ondertekenen:

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

Opmerking: De pack opdracht maakt automatisch gebruik van de Package.appxmanifest huidige map en kopieert deze naar de doelmap voordat deze wordt verpakt.

Het certificaat installeren

Voordat u het MSIX-pakket kunt installeren, moet u het ontwikkelingscertificaat op uw computer vertrouwen. Voer deze opdracht uit als beheerder (u hoeft dit slechts één keer per certificaat te doen):

winapp cert install .\devcert.pfx

Installeren en uitvoeren

Aanbeveling

Als u in stap 5 hebt gebruikt winapp run , is het pakket mogelijk al geregistreerd op uw systeem. Gebruik winapp unregister eerst om de ontwikkelingsregistratie te verwijderen en installeer vervolgens het releasepakket.

Installeer het pakket door te dubbelklikken op het gegenereerde .msix bestand of met behulp van PowerShell:

Add-AppxPackage .\flutterapp.msix

Aanbeveling

De MSIX-bestandsnaam bevat de versie en architectuur (bijvoorbeeld flutterapplication1_1.0.0.0_x64.msix). Controleer uw map op de exacte bestandsnaam. Als u na codewijzigingen opnieuw moet verpakken, verhoogt u de Version in uw Package.appxmanifest. Windows vereist een hoger versienummer om een geïnstalleerd pakket bij te werken.

Tips

  1. Zodra u klaar bent voor distributie, kunt u uw MSIX ondertekenen met een certificaat voor ondertekening van programmacode van een certificeringsinstantie, zodat uw gebruikers geen zelfondertekend certificaat hoeven te installeren.
  2. De Vertrouwde ondertekening van Azure-service is een uitstekende manier om uw certificaten veilig te beheren en aanmelding te integreren in uw CI/CD-pijplijn.
  3. De Microsoft Store zal de MSIX voor u ondertekenen, u hoeft deze niet te ondertekenen voordat u hem indient.

Volgende stappen