Verwenden der winapp CLI mit Flutter

Ein vollständiges Arbeitsbeispiel finden Sie im Beispiel "Flutter " in diesem Repository.

In diesem Leitfaden wird veranschaulicht, wie Sie die winapp CLI mit einer Flutter-Anwendung verwenden, um die Paketidentität hinzuzufügen und Ihre App als MSIX zu verpacken.

Die Paketidentität ist ein Kernkonzept im Windows app-Modell. Sie ermöglicht Ihrer Anwendung den Zugriff auf bestimmte Windows-APIs (z. B. Benachrichtigungen, Sicherheit, AI-APIs usw.), eine Benutzeroberfläche für die Neuinstallation/Deinstallation und vieles mehr.

Ein Standard-Flutter-Windows Build besitzt keine Paketidentität. In diesem Handbuch wird gezeigt, wie Sie es zum Debuggen hinzufügen und dann für die Verteilung verpacken.

Voraussetzungen

  1. Flutter SDK: Installieren Sie Flutter nach dem offiziellen Leitfaden.

  2. winapp CLI: Installieren Sie die winapp CLI über winget (oder aktualisieren Sie, falls bereits installiert):

    winget install Microsoft.winappcli --source winget
    

1. Erstellen einer neuen Fluter-App

Folgen Sie dem Leitfaden in den offiziellen Flutter-Dokumenten, um eine neue Anwendung zu erstellen und auszuführen.

Die Standardmäßige Flutterzähler-App sollte angezeigt werden.

2. Aktualisieren von Code zum Überprüfen der Identität

Wir aktualisieren die App, um zu überprüfen, ob sie mit Paketidentität ausgeführt wird. Wir verwenden Dart FFI, um die Windows GetCurrentPackageFamilyName-API aufzurufen.

Fügen Sie zuerst das ffi Paket hinzu:

flutter pub add ffi

Ersetzen Sie als Nächstes den Inhalt von lib/main.dart durch den folgenden Code. Dieser Code versucht, die aktuelle Paketidentität mithilfe der Windows-API abzurufen. Wenn dies erfolgreich ist, wird der Paketfamilienname in der Benutzeroberfläche angezeigt. andernfalls wird "Nicht verpackt" angezeigt.

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. Ohne Identität ausführen

Erstellen Sie nun die App wie gewohnt, und führen Sie sie aus:

flutter build windows

Führen Sie die ausführbare Datei direkt aus (ersetzen Sie flutter_app durch Ihren Projektnamen, falls dieser abweicht):

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

Tipp

Die Buildausgabe befindet sich unabhängig von der Architektur Ihres Computers im Ordner x64 – dies wird für den Windows Build von Flutter erwartet.

Die App sollte mit einem orangefarbenen Indikator "Nicht verpackt" angezeigt werden. Dadurch wird bestätigt, dass die standardmäßige ausführbare Datei ohne Paketidentität ausgeführt wird.

4. Projekt mit winapp CLI initialisieren

Der Befehl winapp init richtet alles ein, was Sie benötigen: App-Manifest, Ressourcen und optional Windows App SDK Header für die C++-Entwicklung. Das Manifest definiert die Identität Ihrer App (Name, Herausgeber, Version), die Windows verwendet, um API-Zugriff zu gewähren.

Führen Sie den folgenden Befehl aus, und folgen Sie den Eingabeaufforderungen:

winapp init

Wenn Sie dazu aufgefordert werden:

  • Paketname: Drücken Sie die EINGABETASTE, um die Standardeinstellung zu übernehmen (abgeleitet vom Projektnamen)
  • Publisher Name: Drücken Sie die EINGABETASTE, um die Standardeinstellung zu akzeptieren oder Ihren Namen einzugeben.
  • Version: Drücken Sie die EINGABETASTE, um 1.0.0.0 zu akzeptieren.
  • Description: Drücken Sie die EINGABETASTE, um die Standardeinstellung zu übernehmen (Windows Anwendung)
  • Setup-SDKs: Wählen Sie "Stable SDKs" aus, um Windows App SDK herunterzuladen und C++-Header zu generieren (erforderlich für Schritt 6)

Dieser Befehl bewirkt Folgendes:

  • Erstellen Package.appxmanifest – das Manifest, das die Identität Ihrer App definiert
  • Erstellen eines Ordners Assets – Symbole, die für die MSIX-Verpackung und die Store-Übermittlung erforderlich sind
  • Erstellen eines ordners .winapp mit Windows App SDK Headern und Bibliotheken
  • Erstellen Sie eine winapp.yaml Konfigurationsdatei zum Festlegen von SDK-Versionen

Sie können Package.appxmanifest öffnen, um Eigenschaften wie den Anzeigenamen, den Herausgeber und die Funktionen weiter anzupassen.

5. Debuggen mit Identität

Um Features zu testen, die Identität erfordern (z. B. Benachrichtigungen), ohne die App vollständig zu verpacken, können Sie verwenden winapp run. Dadurch wird ein loses Layoutpaket (genau wie eine echte MSIX-Installation) registriert und die App in einem Schritt gestartet. Für das Debuggen ist kein Zertifikat oder keine Signatur erforderlich.

  1. Erstellen Sie die App:

    flutter build windows
    
  2. Ausführen mit Identität:

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

Tipp

winapp run registriert außerdem das Paket auf Ihrem System. Aus diesem Grund wird msIX möglicherweise als "bereits installiert" angezeigt, wenn Sie versuchen, es später in Schritt 7 zu installieren. Verwenden Sie winapp unregister, um Entwicklungspakete zu bereinigen, wenn Sie fertig sind.

Nun sollte die App mit einem grünen Indikator angezeigt werden:

Package Family Name: flutterapp.debug_xxxxxxxx

Dadurch wird bestätigt, dass Ihre App mit einer gültigen Paketidentität ausgeführt wird!

Tipp

Erweiterte Debugworkflows (Anfügen von Debuggern, IDE-Setup, Startdebugging) finden Sie im Debughandbuch.

6. Verwenden von Windows App SDK (Optional)

Wenn Sie die SDKs während winapp init einrichten möchten, haben Sie jetzt Zugriff auf Windows App SDK C++-Header im Ordner .winapp/include. Da der Windows-Runner von Flutter in C++ geschrieben ist, können Sie Windows-App-SDK-APIs aus nativem Code aufrufen und sie über einen Methodenkanal für Dart verfügbar machen. Wenn Sie nur die Paketidentität für die Verteilung benötigen, können Sie mit Schritt 7 fortfahren.

Fügen wir ein einfaches Beispiel hinzu, in dem die Windows-App Runtime-Version angezeigt wird.

Erstellen des nativen Plug-Ins

Erstellen Sie 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_

Erstellen Sie 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();
}

Aktualisieren von CMakeLists.txt

Bearbeiten Sie windows/runner/CMakeLists.txt, um drei Änderungen vorzunehmen. Suchen Sie den add_executable Block und fügen Sie ihn der Quelldateiliste "winapp_sdk_plugin.cpp" hinzu.

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

Fügen Sie dann diese beiden Zeilen am Ende der Datei hinzu, um WinRT-Bibliotheken zu verknüpfen und die Windows App SDK Header einzuschließen:

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

Registrieren des Plug-Ins

In windows/runner/flutter_window.cpp fügen Sie die Include-Anweisung am Anfang der Datei zusammen mit den anderen Include-Anweisungen hinzu.

#include "winapp_sdk_plugin.h"

Suchen Sie dann den RegisterPlugins-Aufruf in FlutterWindow::OnCreate() und fügen Sie RegisterWinAppSdkPlugin in der Zeile direkt danach hinzu.

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

Aktualisieren von main.dart

Fügen Sie oben neben lib/main.dartden vorhandenen Importen den folgenden Import hinzu:

import 'package:flutter/services.dart';

Fügen Sie diese Funktion unterhalb der vorhandenen getPackageFamilyName() Funktion hinzu (außerhalb einer beliebigen 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;
  }
}

Fügen Sie in der _MyHomePageState Klasse ein neues Feld neben dem vorhandenen _packageFamilyNameFeld hinzu:

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

Aktualisieren Sie initState(), um die neue Funktion aufzurufen.

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

Zeigen Sie schließlich die Laufzeitversion in der build Methode an. Fügen Sie dieses Widget innerhalb der Column untergeordneten Liste direkt hinter die Container ein, die die Paketidentität anzeigt.

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

Erstellen und Ausführen

Erstellen Sie die Anwendung neu:

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

Nun sollte die Ausgabe wie folgt angezeigt werden:

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

Das Verzeichnis .winapp/include enthält alle erforderlichen Header für Windows App SDK, einschließlich:

  • winrt/ – WinRT C++-Projektionsheader für den Zugriff auf Windows-Runtime APIs
  • Microsoft.UI.*.h – WinUI 3-Header für moderne UI-Komponenten
  • MddBootstrap.h - Windows App SDK-Startvorgang
  • WindowsAppSDK-VersionInfo.h - Versionsinformationen
  • Und viele weitere Windows App SDK Komponenten

Weitere Informationen zur erweiterten Windows App SDK-Verwendung finden Sie in der Dokumentation Windows App SDK.

7. Paket mit MSIX

Sobald Sie bereit sind, Ihre App zu verteilen, können Sie sie mit demselben Manifest als MSIX verpacken.

Vorbereiten des Paketverzeichnisses

Erstellen Sie zunächst Ihre Anwendung im Releasemodus:

flutter build windows

Erstellen Sie dann ein Verzeichnis mit Ihren Releasedateien:

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

Die Flutter-Windows-Build-Ausgabe enthält die ausführbare Datei, flutter_windows.dll, und einen Ordner data – die alle benötigt werden.

Generieren eines Entwicklungszertifikats

Vor dem Verpacken benötigen Sie ein Entwicklungszertifikat zum Signieren. Generieren Sie eines, falls Sie das noch nicht getan haben.

winapp cert generate --if-exists skip

Signieren und Packen

Jetzt können Sie Folgendes packen und signieren:

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

Hinweis: Der pack Befehl verwendet automatisch das Package.appxmanifest aus Ihrem aktuellen Verzeichnis und kopiert es in den Zielordner, bevor er verpackt wird.

Installieren des Zertifikats

Bevor Sie das MSIX-Paket installieren können, müssen Sie dem Entwicklungszertifikat auf Ihrem Computer vertrauen. Führen Sie diesen Befehl als Administrator aus (Sie müssen diesen Vorgang nur einmal pro Zertifikat ausführen):

winapp cert install .\devcert.pfx

Installieren und Ausführen

Tipp

Wenn Sie in Schritt 5 verwendet haben winapp run , ist das Paket möglicherweise bereits auf Ihrem System registriert. Verwenden Sie winapp unregister zunächst, um die Entwicklungsregistrierung zu entfernen, und installieren Sie dann das Release-Paket.

Installieren Sie das Paket, indem Sie auf die generierte .msix Datei doppelklicken oder PowerShell verwenden:

Add-AppxPackage .\flutterapp.msix

Tipp

Der MSIX-Dateiname enthält die Version und Architektur (z. B. flutterapplication1_1.0.0.0_x64.msix). Überprüfen Sie Ihr Verzeichnis auf den genauen Dateinamen. Wenn Sie nach Codeänderungen neu verpacken müssen, erhöhen Sie die Version in Ihrem Package.appxmanifest – Windows erfordert eine höhere Versionsnummer, um ein installiertes Paket zu aktualisieren.

Tipps

  1. Sobald Sie bereit für die Verteilung sind, können Sie Ihr MSIX mit einem Codesignaturzertifikat von einer Zertifizierungsstelle signieren, damit Ihre Benutzer kein selbstsigniertes Zertifikat installieren müssen.
  2. Der dienst Azure Trusted Signing ist eine hervorragende Möglichkeit, Ihre Zertifikate sicher zu verwalten und die Anmeldung in Ihre CI/CD-Pipeline zu integrieren.
  3. Der Microsoft Store signiert das MSIX für Sie, sodass Sie vor der Übermittlung nicht unterschreiben müssen.

Nächste Schritte