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

如何在Flutter的同一窗口中打开Windows exe程序

Flutter Windows嵌入外部EXE程序方案

问题说明

已实现通过Flutter启动EXE程序,但需要将外部EXE嵌入Flutter同一窗口内运行,而非弹出独立新窗口。当前使用Process.start的方式无法达成窗口嵌入效果,以下是原代码:

//Here are all the dependencies
import 'dart:ffi';
import 'dart:io';
import 'package:ffi/ffi.dart';
import 'package:win32/win32.dart';
import 'package:flutter/material.dart';

import 'package:flutter_native_view/flutter_native_view.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await FlutterNativeView.ensureInitialized();
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final controller = ScrollController();
  final controllers = [
    NativeViewController(
      handle: FindWindow(
        nullptr,
        'CPRG'.toNativeUtf16(),
      ),
      hitTestBehavior: HitTestBehavior.translucent,
    ),
    NativeViewController(
      handle: FindWindow(
        nullptr,
        'This PC'.toNativeUtf16(),
      ),
      hitTestBehavior: HitTestBehavior.translucent,
    ),
  ];

  Future<bool> openFile(String filePath) async {
    try {
      bool fileExists = await File(filePath).exists();
      if (!fileExists) {
        print("File does not exist at: $filePath");
        return false;
      }

      if (Platform.isWindows) {
        print('Attempting to run: $filePath');
        try {
          Process process = await Process.start(
            filePath,
            [],
            mode: ProcessStartMode.detached,
            runInShell: true,
          );
          print("Process started: $process");
          return true;
        } catch (e) {
          print("Failed to start process: $e");
          return false;
        }
      } else {
        print("Opening files is not supported on this platform.");
        return false;
      }
    } catch (e) {
      print("Error opening file: $e");
      return false;
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        floatingActionButton: ElevatedButton(
            onPressed: () {
              openFile('D:/ThermoverseExes/Products/Combipac RG/CPRG.exe');
            },
            child: const Text('open')),
        appBar: AppBar(
          title: const Text('flutter_native_view'),
        ),
        body: ListView(
          controller: controller,
          padding: const EdgeInsets.symmetric(vertical: 16.0),
          children: [
            Padding(
              padding:
                  const EdgeInsets.symmetric(vertical: 16.0, horizontal: 32.0),
              child: NativeView(
                controller: controllers[0],
                width: 640.0,
                height: 480.0,
              ),
            ),
            Padding(
              padding:
                  const EdgeInsets.symmetric(vertical: 16.0, horizontal: 32.0),
              child: NativeView(
                controller: controllers[1],
                width: 640.0,
                height: 480.0,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

解决方案

核心原理

通过Windows API实现窗口重定向:启动EXE后获取其主窗口句柄,修改窗口样式并将其父窗口设置为Flutter中的原生视图容器,从而实现嵌入效果。

具体实现代码

修改原代码中的_MyAppState类,替换openFile方法并添加窗口处理逻辑:

class _MyAppState extends State<MyApp> {
  final controller = ScrollController();
  NativeViewController? embeddedExeController;

  // 通过进程ID查找窗口句柄
  int _findWindowByProcessId(int pid) {
    final windowHandle = calloc<IntPtr>();
    EnumWindows((hwnd, lParam) {
      final lpdwProcessId = calloc<Uint32>();
      GetWindowThreadProcessId(hwnd, lpdwProcessId);
      if (lpdwProcessId.value == pid) {
        windowHandle.value = hwnd;
        free(lpdwProcessId);
        return FALSE;
      }
      free(lpdwProcessId);
      return TRUE;
    }, 0);
    final result = windowHandle.value;
    free(windowHandle);
    return result;
  }

  Future<bool> openFile(String filePath) async {
    try {
      bool fileExists = await File(filePath).exists();
      if (!fileExists) {
        print("File does not exist at: $filePath");
        return false;
      }

      if (Platform.isWindows) {
        print('Attempting to run: $filePath');
        try {
          // 启动进程,不使用detached模式以便获取进程ID
          Process process = await Process.start(
            filePath,
            [],
            runInShell: false,
          );
          print("Process started with PID: ${process.pid}");

          // 等待程序初始化完成
          await Future.delayed(const Duration(milliseconds: 1000));
          WaitForInputIdle(process.pid, INFINITE);

          // 获取程序主窗口句柄
          int hwnd = _findWindowByProcessId(process.pid);
          if (hwnd == 0) {
            print("Failed to find window for process ${process.pid}");
            return false;
          }
          print("Found window handle: $hwnd");

          // 修改窗口样式:移除标题栏、边框,设置为子窗口
          SetWindowLongPtr(hwnd, GWL_STYLE, WS_CHILD | WS_VISIBLE);
          SetWindowPos(hwnd, HWND_TOP, 0, 0, 640, 480, SWP_NOZORDER | SWP_FRAMECHANGED);

          // 更新状态,创建嵌入视图控制器
          setState(() {
            embeddedExeController = NativeViewController(
              handle: hwnd,
              hitTestBehavior: HitTestBehavior.opaque,
            );
          });

          return true;
        } catch (e) {
          print("Failed to start process: $e");
          return false;
        }
      } else {
        print("Opening files is not supported on this platform.");
        return false;
      }
    } catch (e) {
      print("Error opening file: $e");
      return false;
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        floatingActionButton: ElevatedButton(
            onPressed: () {
              openFile('D:/ThermoverseExes/Products/Combipac RG/CPRG.exe');
            },
            child: const Text('open')),
        appBar: AppBar(
          title: const Text('flutter_native_view'),
        ),
        body: ListView(
          controller: controller,
          padding: const EdgeInsets.symmetric(vertical: 16.0),
          children: [
            if (embeddedExeController != null)
              Padding(
                padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 32.0),
                child: NativeView(
                  controller: embeddedExeController!,
                  width: 640.0,
                  height: 480.0,
                ),
              ),
          ],
        ),
      ),
    );
  }
}

关键注意事项

  • 窗口查找逻辑:部分程序启动后会创建多个窗口,可通过GetWindow API过滤主窗口(如排除子窗口、对话框)。
  • 样式调整:根据EXE程序的特性,可能需要调整SetWindowLongPtr的参数,确保窗口正常显示。
  • 延迟等待:WaitForInputIdle和延迟等待是为了确保程序窗口已创建完成,避免找不到句柄的情况。
  • 权限问题:部分系统级程序可能无法被嵌入,需确保程序有足够权限修改窗口属性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 04:42:03