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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.23 07:36:05