如何在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, ), ), ], ), ), ); } }
关键注意事项
- 窗口查找逻辑:部分程序启动后会创建多个窗口,可通过
GetWindowAPI过滤主窗口(如排除子窗口、对话框)。 - 样式调整:根据EXE程序的特性,可能需要调整
SetWindowLongPtr的参数,确保窗口正常显示。 - 延迟等待:
WaitForInputIdle和延迟等待是为了确保程序窗口已创建完成,避免找不到句柄的情况。 - 权限问题:部分系统级程序可能无法被嵌入,需确保程序有足够权限修改窗口属性。
内容的提问来源于stack exchange,提问作者Yogesh Kumawat
相关产品推荐
相关产品推荐

