Använda winapp CLI med Flutter

Ett fullständigt arbetsexempel finns i Flutter-exemplet på den här lagringsplatsen.

Den här guiden visar hur du använder winapp CLI med ett Flutter-program för att lägga till paketidentitet och paketera din app som en MSIX.

Paketidentitet är ett grundläggande begrepp i Windows app modellen. Det gör att ditt program kan komma åt specifika Windows API:er (t.ex. meddelanden, säkerhet, AI-API:er osv.), har en ren installations-/avinstallationsupplevelse med mera.

En vanlig Flutter-Windows version saknar paketidentitet. Den här guiden visar hur du lägger till den för felsökning och sedan paketera den för distribution.

Förutsättningar

  1. Flutter SDK: Installera Flutter enligt den officiella guiden.

  2. winapp CLI: Installera winapp CLI via winget (eller uppdatera om det redan är installerat):

    winget install Microsoft.winappcli --source winget
    

1. Skapa en ny flutterapp

Följ guiden i de officiella Flutter-dokumenten för att skapa ett nytt program och köra det.

Du ska se räknarappen i Flutter som standard.

2. Uppdatera koden för att kontrollera identiteten

Vi uppdaterar appen för att kontrollera om den körs med paketidentitet. Vi använder Dart FFI för att anropa API:et Windows GetCurrentPackageFamilyName.

Lägg först till ffi paketet:

flutter pub add ffi

Ersätt sedan innehållet i lib/main.dart med följande kod. Den här koden försöker hämta den aktuella paketidentiteten med hjälp av api:et Windows. Om det lyckas visas paketfamiljenamnet i användargränssnittet. annars visas "Inte paketerad".

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. Kör utan identitet

Nu skapar och kör du appen som vanligt:

flutter build windows

Kör den körbara filen direkt (ersätt flutter_app med projektnamnet om det är annorlunda):

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

Tips/Råd

Byggutdata finns i mappen x64 oavsett datorns arkitektur – detta förväntas för Flutters Windows version.

Du bör se appen med en orange indikator för "Inte paketerad". Detta bekräftar att den körbara standarden körs utan paketidentitet.

4. Initiera Project med winapp CLI

Kommandot winapp init konfigurerar allt du behöver på en gång: appmanifest, resurser och eventuellt Windows App SDK huvuden för C++-utveckling. Manifestet definierar appens identitet (namn, utgivare, version) som Windows använder för att bevilja API-åtkomst.

Kör följande kommando och följ anvisningarna:

winapp init

När du uppmanas att göra det:

  • Paketnamn: Tryck på Retur för att acceptera standardinställningen (härledd från projektnamnet)
  • Utgivarens namn: Tryck på Enter för att acceptera standardvärdet eller ange ditt namn
  • Version: Tryck på Retur för att acceptera 1.0.0.0
  • Description: Tryck på Retur för att acceptera standardvärdet (Windows-applikation)
  • Setup SDK:er: Välj "Stabila SDK:er" för att ladda ned Windows App SDK och generera C++-huvuden (krävs för steg 6)

Det här kommandot kommer att:

  • Skapa Package.appxmanifest – manifestet som definierar appens identitet
  • Skapa Assets mapp – ikoner som krävs för MSIX-paketering och Lagringsöverföring
  • Skapa en .winapp-mapp med Windows App SDK rubriker och bibliotek
  • Skapa en winapp.yaml konfigurationsfil för att fästa SDK-versioner

Du kan öppna Package.appxmanifest för att ytterligare anpassa egenskaper som visningsnamn, utgivare och funktioner.

5. Felsöka med identitet

Om du vill testa funktioner som kräver identitet (t.ex. meddelanden) utan att helt paketera appen kan du använda winapp run. Detta registrerar ett löst layoutpaket (precis som en riktig MSIX-installation) och startar appen i ett steg. Inget certifikat eller signering krävs för felsökning.

  1. Skapa appen:

    flutter build windows
    
  2. Kör med identitet:

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

Tips/Råd

winapp run registrerar även paketet i systemet. Därför kan MSIX visas som "redan installerat" när du försöker installera det senare i steg 7. Använd winapp unregister för att rensa utvecklingspaket när du är färdig.

Nu bör du se appen med en grön indikator som visar:

Package Family Name: flutterapp.debug_xxxxxxxx

Detta bekräftar att din app körs med en giltig paketidentitet!

Tips/Råd

Avancerade felsökningsarbetsflöden (koppla felsökningsprogram, IDE-konfiguration, startfelsökning) finns i felsökningsguiden.

6. Använda Windows App SDK (valfritt)

Om du har valt att konfigurera SDK:erna under winapp init har du nu åtkomst till Windows App SDK C++-huvuden i mappen .winapp/include. Eftersom Flutters Windows-körning är C++, kan du anropa Windows App SDK API:er från inbyggd kod och göra dem tillgängliga för Dart via en metodkanal. Om du bara behöver paketidentitet för distribution kan du gå vidare till steg 7.

Nu ska vi lägga till ett enkelt exempel som visar Windows App Runtime-versionen.

Skapa det inbyggda plugin-programmet

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

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

Uppdatera CMakeLists.txt

Redigera windows/runner/CMakeLists.txt för att göra tre ändringar. Leta upp add_executable blocket och lägg till "winapp_sdk_plugin.cpp" i källfillistan:

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

Lägg sedan till dessa två rader i slutet av filen för att länka WinRT-bibliotek och inkludera Windows App SDK rubriker:

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

Registrera plugin-programmet

I windows/runner/flutter_window.cpp lägger du till include-anropet överst i filen med de andra inkluderingsanropen.

#include "winapp_sdk_plugin.h"

Leta sedan upp anropet RegisterPlugins i FlutterWindow::OnCreate() och lägg till RegisterWinAppSdkPlugin på raden direkt efter det.

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

Uppdatera main.dart

Lägg till följande import överst i lib/main.dart, tillsammans med de befintliga importerna:

import 'package:flutter/services.dart';

Lägg till den här funktionen under den befintliga getPackageFamilyName() funktionen (utanför vilken klass som helst):

/// 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 I klassen lägger du till ett nytt fält bredvid det befintliga _packageFamilyName:

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

Uppdatera initState() för att anropa den nya funktionen:

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

Visa slutligen körningsversionen i build -metoden. Lägg till den här widgeten inuti listan över underordnade Column direkt efter Container som visar paketets identitet:

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

Skapa och kör

Återskapa programmet:

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

Nu bör du se utdata som:

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

Katalogen .winapp/include innehåller alla nödvändiga rubriker för Windows App SDK, inklusive:

  • winrt/ – WinRT C++-projektionshuvud för åtkomst till Windows Runtime API
  • Microsoft.UI.*.h – WinUI 3-huvuden för moderna gränssnittskomponenter
  • MddBootstrap.h – Windows App SDK bootstrapping
  • WindowsAppSDK-VersionInfo.h – Versionsinformation
  • Och många fler Windows App SDK komponenter

Mer avancerad Windows App SDK användning finns i dokumentationen Windows App SDK.

7. Paket med MSIX

När du är redo att distribuera din app kan du paketera den som en MSIX med samma manifest.

Förbereda paketkatalogen

Börja med att skapa ditt program i versionsläge:

flutter build windows

Skapa sedan en katalog med dina versionsfiler:

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

Flutter-Windows build-utdata innehåller den körbara filen flutter_windows.dll och mappen data – vilka alla behövs.

Generera ett utvecklingscertifikat

Innan du paketerar behöver du ett utvecklingscertifikat för signering. Generera en om du inte redan har gjort det:

winapp cert generate --if-exists skip

Signera och packa

Nu kan du paketera och signera:

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

Obs! Kommandot pack använder Package.appxmanifest automatiskt från din aktuella katalog och kopierar den till målmappen före paketering.

Installera certifikatet

Innan du kan installera MSIX-paketet måste du lita på utvecklingscertifikatet på datorn. Kör det här kommandot som administratör (du behöver bara göra det en gång per certifikat):

winapp cert install .\devcert.pfx

Installera och kör

Tips/Råd

Om du använde winapp run i steg 5 kanske paketet redan är registrerat i systemet. Använd winapp unregister först för att ta bort utvecklingsregistreringen och installera sedan versionspaketet.

Installera paketet genom att dubbelklicka på den genererade .msix filen eller använda PowerShell:

Add-AppxPackage .\flutterapp.msix

Tips/Råd

MSIX-filnamnet innehåller versionen och arkitekturen (t.ex. flutterapplication1_1.0.0.0_x64.msix). Kontrollera katalogen för det exakta filnamnet. Om du behöver packa om efter kodändringar ökar du Version i din Package.appxmanifest – Windows kräver ett högre versionsnummer för att uppdatera ett installerat paket.

Tips

  1. När du är redo för distribution kan du signera MSIX med ett kodsigneringscertifikat från en certifikatutfärdare så att användarna inte behöver installera ett självsignerat certifikat.
  2. Tjänsten Betrodd Azure-signering är ett bra sätt att hantera dina certifikat på ett säkert sätt och integrera inloggning i CI/CD-pipelinen.
  3. Microsoft Store signerar MSIX åt dig, du behöver inte signera innan det skickas in.

Nästa steg