Flutter FFI调用C++ DLL遇回调错误:无法在Isolate外调用原生回调
问题解决:Flutter FFI 跨线程Native回调错误
问题根源
你遇到的cannot invoke a native callback outside of an isolate错误,是因为Flutter的Pointer.fromFunction生成的回调绑定到创建它的Isolate线程,而你的DLL在独立线程中触发回调,脱离了原Isolate上下文,导致权限限制报错。
解决方案
核心是使用NativeCallable创建可跨线程调用的回调,并通过Isolate的SendPort将Native回调的消息转发到Dart Isolate处理,同时修正函数签名和内存管理问题。
步骤1:修正C++代码的语法错误
你的C++代码中export "C"是错误写法,应该改为extern "C":
extern "C" __declspec(dllexport) void StartServerSession(const char* Name, const char* js, int& sessionID, int& ERESULT, void(eventhandler)(const char*, int)) {} extern "C" __declspec(dllexport) void InitializeServer(void(eventhandler)(const char*, int)) { // 你的初始化逻辑 }
步骤2:修改Dart FFI代码
以下是完整的修正代码,关键修改已标注:
import 'dart:isolate'; import 'dart:ffi'; import 'package:ffi/ffi.dart'; import 'package:flutter/material.dart'; final DynamicLibrary dllPath = DynamicLibrary.open('path to dll'); // 修正InitializeServer的函数签名:回调参数是void(*)(const char*, int) typedef InitializeFunc = Void Function(Pointer<Void> eventHandler); typedef InitializeFun = void Function(Pointer<Void> eventHandler); // 修正StartServerSession的函数签名:int&对应Pointer<Int32>,不是双重指针 typedef StartServerSessionC = Void Function( Pointer<Utf8> name, Pointer<Utf8> js, Pointer<Int32> sessionID, Pointer<Int32> eResult, Pointer<Void> eventHandler, ); typedef StartServerSession = void Function( Pointer<Utf8> name, Pointer<Utf8> js, Pointer<Int32> sessionID, Pointer<Int32> eResult, Pointer<Void> eventHandler, ); // 处理Native回调消息的Dart函数(在Isolate中执行) void handleNativeResponse(String response) { print('Response: $response'); } final initializeServer = dllPath.lookupFunction<InitializeFunc, InitializeFun>('InitializeServer'); final startServerSession = dllPath.lookupFunction<StartServerSessionC, StartServerSession>('StartServerSession'); void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: const Text('FFI Demo')), body: Center( child: ElevatedButton( onPressed: () async { await callStartServerIsolate(); }, child: const Text('Call DLL Function'), ), ), ), ); } } // Isolate入口函数 void startServerFunction(SendPort sendPort) async { // 创建ReceivePort接收Native回调的消息 final receivePort = ReceivePort(); sendPort.send(receivePort.sendPort); // 监听Native回调消息,转发到Dart处理函数 receivePort.listen((message) { if (message is String) { handleNativeResponse(message); } }); // 分配内存 final name = 'path to second dll'.toNativeUtf8(); final js = '{"key": "value"}'.toNativeUtf8(); // 替换为你的JSON值 final sessionID = calloc<Int32>(); final eResult = calloc<Int32>(); // 创建可跨线程的Native回调:将Native传递的参数转为Dart字符串,通过SendPort发送 final nativeCallable = NativeCallable<Void Function(Pointer<Utf8>, Int32)>.isolate( (responsePtr, length) { final response = responsePtr.toDartString(length: length); receivePort.sendPort.send(response); }, exceptionalReturn: nullptr, ); try { // 初始化DLL,传入跨线程回调 initializeServer(nativeCallable.nativeFunction.cast<Void>()); // 启动会话 startServerSession( name, js, sessionID, eResult, nativeCallable.nativeFunction.cast<Void>(), ); print('Session ID: ${sessionID.value}, Result: ${eResult.value}'); sendPort.send("task Complete"); } finally { // 释放资源:必须释放,避免内存泄漏 calloc.free(name); calloc.free(js); calloc.free(sessionID); calloc.free(eResult); nativeCallable.close(); // 关闭NativeCallable receivePort.close(); } } Future<void> callStartServerIsolate() async { final receivePort = ReceivePort(); final sendPort = receivePort.sendPort; // 启动Isolate await Isolate.spawn(startServerFunction, sendPort); // 监听Isolate返回的消息 await for (final message in receivePort) { if (message is SendPort) { // 保存Native回调的消息端口(可选,根据需求处理) } else if (message == "task Complete") { receivePort.close(); break; } } }
关键修改说明
- 函数签名修正:C++的
int&参数对应Dart的Pointer<Int32>,而非Pointer<Pointer<Int32>>,之前的类型转换会导致内存访问错误。 - 使用NativeCallable:
NativeCallable.isolate()创建的回调允许在任意Native线程调用,回调内部通过SendPort将消息发送到Dart Isolate,避免直接在Native线程执行Dart代码。 - 内存管理:添加了
calloc.free()和nativeCallable.close(),避免内存泄漏。 - 消息转发:Native回调不直接执行
print,而是将字符串通过SendPort发送到Isolate,由Dart线程处理UI/日志操作。
内容的提问来源于stack exchange,提问作者sanjay
相关产品推荐
相关产品推荐

