Dart/Flutter FFI 如何调用带输出参数的原生函数
Dart FFI 实现原生out参数调用的替代方案
Dart本身没有out参数语法,对接带输出参数的原生函数时,可直接依托FFI的指针能力实现和C# out参数完全等效的逻辑:原生out参数的本质是传入一块可写内存的指针,由原生逻辑往这块内存写入结果值,Dart侧只需手动分配对应类型的可写内存、将指针传入、调用完成后读取内存中的结果即可。
针对OpenPrinterA调用的等价实现
对照给出的C#调用逻辑,逐点匹配原生函数的参数规则,实现步骤如下:
- 匹配函数调用约定:OpenPrinterA是Windows系统API,采用stdcall调用约定,定义FFI绑定关系时需要显式指定对应规则,避免出现栈内存损坏问题
- 适配ANSI字符串参数:OpenPrinterA是ANSI版本接口,需要将Dart字符串转成以null结尾的可被C原生识别的字符串格式,和C#中
CharSet.Ansi、MarshalAs(UnmanagedType.LPStr)的行为一致 - 处理out句柄参数:C#中的
out IntPtr hPrinter对应C原生签名的HANDLE* phPrinter,需要在Dart侧分配一块可存储指针值的内存,传入该内存的指针,调用完成后直接读取内存中存储的句柄值,等价于C#中out参数自动回填的逻辑 - 适配空指针参数:第三个
pd参数传空指针,和C#中IntPtr.Zero完全对应
实现代码
import 'dart:ffi'; import 'package:ffi/ffi.dart'; // 加载winspool原生库 final winspool = DynamicLibrary.open('winspool.drv'); // 定义OpenPrinterA的FFI签名类型 typedef OpenPrinterAC = Int32 Function( Pointer<Utf8> printerNamePtr, Pointer<Pointer<Void>> printerHandlePtr, Pointer<Void> defaultPtr, ); typedef OpenPrinterADart = int Function( Pointer<Utf8> printerNamePtr, Pointer<Pointer<Void>> printerHandlePtr, Pointer<Void> defaultPtr, ); // 绑定原生函数实现,指定stdcall调用约定匹配Windows API规则 final openPrinter = winspool .lookupFunction<OpenPrinterAC, OpenPrinterADart>( 'OpenPrinterA', callingConvention: CallingConvention.windowsStdcall, ); void callOpenPrinter() { const targetPrinterName = 'Printer1'; // 转换字符串为ANSI格式的C字符串 final namePtr = targetPrinterName.toNativeUtf8(); // 分配存储打印机句柄的内存,初始值为空指针,对应C#中hPrinter = IntPtr.Zero的初始化逻辑 final handleStoragePtr = calloc<Pointer<Void>>(); handleStoragePtr.value = nullptr; try { // 执行原生调用 final callSuccess = openPrinter( namePtr, handleStoragePtr, nullptr, // 对应C#中传入的IntPtr.Zero ); if (callSuccess != 0) { // 读取到的handleStoragePtr.value就是原生返回的打印机句柄,和C#中out参数回填的hPrinter值完全等价 final hPrinter = handleStoragePtr.value; // 后续可基于hPrinter完成其他打印API的调用 } } finally { // 手动释放所有分配的原生内存,避免泄漏 calloc.free(namePtr); calloc.free(handleStoragePtr); } }
注意事项
- 函数签名中不要把out参数对应的指针类型写错:如果把
Pointer<Pointer<Void>>错写为Pointer<Void>,传入的只是句柄值本身的指针,原生函数无法将返回的句柄写回Dart侧的可观测内存,就失去了out参数的作用 - 若需要匹配C#中
SetLastError = true的能力,可在FFI函数调用完成后,通过动态库中导出的GetLastError()方法获取系统最后错误码,行为完全一致 - 所有手动通过calloc/malloc分配的原生内存,必须在使用完成后手动释放,Dart不会自动回收这类原生侧的内存空间
内容的提问来源于stack exchange,提问作者Taju Rahman
相关产品推荐
相关产品推荐

