Bootstrap

使用 Flutter和Dart 和 FFI 调用 C++ 动态链接库的完整操作记录


前言

在本次项目中,我的目标是通过 Flutter 的 FFI(Foreign Function Interface)调用自定义的 C++ 动态链接库(DLL),并通过集成指纹 SDK 来获取指纹设备的信息。本文将详细记录我完成这个项目的步骤,包括如何创建 DLL 文件、在 Flutter 中加载和调用该 DLL,以及在过程中遇到的错误及其排查过程。

项目目录:

│  CMakeLists.txt
│  main.cpp
├─include
│      libzkfp.h
│      libzkfperrdef.h
│      libzkfptype.h
│      zkinterface.h
└─x64lib
        libzkfp.lib

一、C++生成动态链接库

首先,我使用了 C++ 创建了一个包含指纹 SDK 功能的动态链接库(DLL)。以下是实现的主要步骤:

1.1. 编写 C++ 代码并导出函数

我的 C++ 代码中使用了 __declspec(dllexport) 来导出需要调用的函数,例如初始化指纹设备和获取设备数量的函数。代码如下:

#include "libzkfp.h"

extern "C" {
    __declspec(dllexport) int initializeFingerprint() {
        return ZKFPM_Init();
    }

    __declspec(dllexport) int getFingerprintDeviceCount() {
        return ZKFPM_GetDeviceCount();
    }

    __declspec(dllexport) void closeFingerprintDevice(HANDLE hDevice) {
        ZKFPM_CloseDevice(hDevice);
    }

    __declspec(dllexport) void terminateFingerprintSDK() {
        ZKFPM_Terminate();
    }
}

这些函数通过 __declspec(dllexport) 关键字导出,使其可以从外部程序调用。

1.2. 使用 CMake 构建 DLL

为了生成 DLL,我使用了 CMake 工具进行构建。CMake 配置文件 CMakeLists.txt 的主要内容如下:

cmake_minimum_required(VERSION 3.10)
project(SDKExample)

# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 11)

# 包含 SDK 头文件目录
include_directories(${PROJECT_SOURCE_DIR}/include)

# 添加生成动态库(DLL)
add_library(zkfp_bridge SHARED main.cpp)

# 链接 SDK 静态库文件
target_link_libraries(zkfp_bridge ${PROJECT_SOURCE_DIR}/x64lib/libzkfp.lib)

# 设置输出目录
set_target_properties(zkfp_bridge PROPERTIES
    RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/build)

1.3. 生成 DLL

通过以下命令,我成功生成了 zkfp_bridge.dll:

mkdir build
cd build
cmake ..
cmake --build .

生成的 zkfp_bridge.dll 文件位于 build/ 目录中。

二、在 Flutter 中使用 FFI 加载并调用 DLL

2.1. 在 Dart 中加载 DLL

在 Flutter 中,我使用 dart:ffi 通过 DynamicLibrary.open() 加载生成的 zkfp_bridge.dll。Dart 代码如下:

import 'dart:ffi';
import 'dart:io';

// 加载DLL
final DynamicLibrary zkfpLib = Platform.isWindows
    ? DynamicLibrary.open('zkfp_bridge.dll')
    : throw UnsupportedError('Only Windows is supported');

// 绑定C函数
typedef InitializeFingerprintC = Int32 Function();
typedef InitializeFingerprintDart = int Function();
final InitializeFingerprintDart initializeFingerprint = zkfpLib
    .lookup<NativeFunction<InitializeFingerprintC>>('initializeFingerprint')
    .asFunction();

typedef GetDeviceCountC = Int32 Function();
typedef GetDeviceCountDart = int Function();
final GetDeviceCountDart getFingerprintDeviceCount = zkfpLib
    .lookup<NativeFunction<GetDeviceCountC>>('getFingerprintDeviceCount')
    .asFunction();

2.2. 在应用中调用 DLL

为了测试 DLL 的加载,我在 Flutter 应用启动时调用了 initializeFingerprint()getFingerprintDeviceCount(),以获取指纹设备的数量。以下是完整的 Flutter 应用程序入口:

void main() {
  final result = initializeFingerprint();
  if (result != 0) {
    print('初始化失败');
  } else {
    print('初始化成功');
    print('设备数量: ${getFingerprintDeviceCount()}');
  }
  runApp(const MyApp());
}

三、排查错误:错误代码 126 - DLL 无法加载

在运行 Flutter 应用程序时,遇到了错误:

Invalid argument(s): Failed to load dynamic library 'zkfp_bridge.dll': The specified module could not be found.
(error code: 126)

错误代码 126 表示 “The specified module could not be found”,这是由于无法找到 DLL 文件或其依赖项。以下是我排查问题的步骤:

3.1. 确认 DLL 文件路径

首先,我检查了 DLL 文件的路径。通过 Dart 中的 Directory.current.path 输出当前目录,确认 zkfp_bridge.dll 文件是否放置在应用执行路径下。Dart 代码如下:

import 'dart:io';

void main() {
  print('当前工作目录: ${Directory.current.path}');
}

确保 zkfp_bridge.dll 文件被放置在 windows/runner/ 目录中,应用程序才能正确加载它。

3.2. 确认 DLL 的依赖库

我使用了 Dependency Walker 工具检查 zkfp_bridge.dll 的依赖项,确认是否缺少其他依赖的 DLL。发现 zkfp_bridge.dll 依赖于 libzkfp.dll,于是我将这些依赖库也放置在 windows/runner/ 目录中。

3.3. 确认 DLL 和应用架构一致性

我还检查了 DLL 的架构,确保其与 Flutter 项目的架构一致。如果 Flutter 应用是 64 位的,那么 DLL 也必须是 64 位的。通过 dumpbin 工具,我确认了 zkfp_bridge.dll 是 64 位的,与 Flutter 应用一致。

四、总结

通过以上步骤,我成功解决了 Flutter 无法加载 DLL 的问题。以下是关键步骤总结:

  • 创建 C++ 动态链接库(DLL):通过 C++ 编写并导出函数,使用 CMake 构建生成 DLL。
  • 在 Flutter 中加载 DLL:使用 dart:ffi 和 DynamicLibrary.open() 来加载 DLL 并调用导出的函数。
  • 排查错误:通过确认 DLL 文件路径、检查依赖库和确保架构匹配来解决错误代码 126。
  • 最终实现:成功在 Flutter 中加载自定义的 C++ 动态链接库,并调用指纹 SDK 的功能。

通过这次项目,我进一步了解了 Flutter FFI 与 C++ DLL 的集成方式,并掌握了如何有效排查与解决动态链接库的加载问题。

;