You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Dart命令行应用中跨桌面平台打开窗口?

解决方案:Dart命令行应用中嵌入跨平台窗口

方案1:通过dart:ffi调用原生窗口API

直接利用Dart的FFI特性对接各平台原生窗口接口,实现轻量窗口,完全不依赖Flutter:

  • Windows:调用user32.dll创建基础窗口
  • macOS:桥接Objective-C的NSWindow API
  • Linux:调用GTK或X11的C接口

示例代码(Windows平台简化版):

import 'dart:ffi';
import 'package:ffi/ffi.dart';

// 定义user32.dll函数签名
typedef CreateWindowExC = Pointer<Void> Function(
  Uint32 dwExStyle,
  Pointer<Utf16> lpClassName,
  Pointer<Utf16> lpWindowName,
  Uint32 dwStyle,
  int x,
  int y,
  int nWidth,
  int nHeight,
  Pointer<Void> hWndParent,
  Pointer<Void> hMenu,
  Pointer<Void> hInstance,
  Pointer<Void> lpParam,
);
typedef CreateWindowExDart = Pointer<Void> Function(
  int dwExStyle,
  Pointer<Utf16> lpClassName,
  Pointer<Utf16> lpWindowName,
  int dwStyle,
  int x,
  int y,
  int nWidth,
  int nHeight,
  Pointer<Void> hWndParent,
  Pointer<Void> hMenu,
  Pointer<Void> hInstance,
  Pointer<Void> lpParam,
);

void main() {
  final user32 = DynamicLibrary.open('user32.dll');
  final createWindowEx = user32.lookupFunction<CreateWindowExC, CreateWindowExDart>('CreateWindowExW');
  
  final className = 'MyDartWindow'.toNativeUtf16();
  final windowName = 'Mini Window'.toNativeUtf16();
  
  // 创建窗口
  final hwnd = createWindowEx(
    0, className, windowName, 0x00CF0000, // WS_OVERLAPPEDWINDOW样式
    100, 100, 300, 200,
    nullptr, nullptr, nullptr, nullptr,
  );
  
  // 显示窗口
  final showWindow = user32.lookupFunction<Int32 Function(Pointer<Void>, Int32), int Function(Pointer<Void>, int)>('ShowWindow');
  showWindow(hwnd, 1);
  
  // 简易消息循环
  final msg = calloc<Uint8>(256);
  while (user32.lookupFunction<Int32 Function(Pointer<Void>), int Function(Pointer<Void>)>('GetMessageW')(msg, nullptr, 0, 0) != 0) {
    user32.lookupFunction<Void Function(Pointer<Void>), void Function(Pointer<Void>)>('TranslateMessage')(msg);
    user32.lookupFunction<Void Function(Pointer<Void>), void Function(Pointer<Void>)>('DispatchMessageW')(msg);
  }
  
  // 释放资源
  calloc.free(className);
  calloc.free(windowName);
  calloc.free(msg);
}

方案2:独立Flutter模块作为子进程启动

把窗口部分拆成独立的Flutter桌面应用,在命令行程序需要时通过子进程启动,避免污染原命令行项目的依赖环境:

  1. 新建极简Flutter桌面项目,只实现所需窗口UI
  2. 在Dart命令行应用中,通过Process.run()启动Flutter应用的可执行文件
  3. 可通过命令行参数或本地Socket实现进程间通信

示例代码:

import 'dart:io';

void main() async {
  // 适配不同平台的Flutter可执行文件路径
  final flutterWindowPath = Platform.isWindows 
      ? r'..\flutter_window\build\windows\runner\Release\flutter_window.exe'
      : Platform.isMacOS
          ? '../flutter_window/build/macos/Build/Products/Release/flutter_window.app/Contents/MacOS/flutter_window'
          : '../flutter_window/build/linux/x64/release/bundle/flutter_window';
  
  // 启动Flutter窗口进程
  final process = await Process.start(flutterWindowPath, []);
  
  // 监听窗口关闭事件
  process.exitCode.then((code) {
    print('Flutter窗口已关闭,退出码:$code');
  });
}

方案3:使用轻量跨平台UI库的Dart绑定

选择libui这类轻量跨平台UI库的第三方Dart绑定,无需依赖Flutter即可创建窗口,适合简单UI场景:

示例代码(伪代码,需对应实际包调整):

import 'package:libui/libui.dart';

void main() {
  uiInit();
  
  final window = uiNewWindow('Mini Window', 300, 200, 0);
  uiWindowOnClosing(window, (_) {
    uiQuit();
    return 1;
  });
  
  // 添加关闭按钮
  final button = uiNewButton('Close');
  uiButtonOnClicked(button, (_) {
    uiControlDestroy(button.cast());
    uiWindowClose(window);
  });
  uiWindowSetChild(window, button.cast());
  
  uiControlShow(window.cast());
  uiMain();
  uiUninit();
}

避坑提示

  • 不要直接在命令行项目中添加Flutter依赖,否则Dart VM会强制将程序识别为Flutter应用,导致命令行功能异常
  • 使用FFI时需适配各平台API差异,建议封装跨平台适配层
  • 子进程方案优势是隔离性强,无需修改原命令行项目结构,缺点是进程间通信需要额外处理

内容的提问来源于stack exchange,提问作者Joran

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 17:40:22