美文网首页
记录flutter中引入c++ sdk

记录flutter中引入c++ sdk

作者: 太空蛙 | 来源:发表于2022-12-25 14:49 被阅读0次

使用flutter开发移动端项目时,接入跨平台的c++ sdk是很常见的。
一般我们会在项目里新建一个插件来封装引入的sdk,里面定义对应的数据结构、调用逻辑,对外暴露出dart接口供项目使用。

1、创建原生插件

flutter create --template=plugin hello

2、引入sdk

虽然sdk代码是c++写的,但是不同平台的硬件资源、底层的api基本上是不同的,所以一般是android一个so文件,ios一个framework,需要分别操作。

2.1、android平台引入

以opencv为例,从opencv官网下载解压后,放到hello/android/src/main/jniLibs/(需要用的arm架构 如:armeabi-v7a)/libopencv_java4.so。

添加CMakeLists.txt

cmake_minimum_required(VERSION 3.10)
project(hello)
#头文件路径
include_directories(../include)
#引入多个so文件
add_library(lib_opencv SHARED IMPORTED)
add_library(lib_xxx SHARED IMPORTED)

set_target_properties(lib_opencv PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libopencv_java4.so)
set_target_properties(lib_xxx PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libXxx.so)

# find_library(log-lib log)
#里面封装调用sdk的cpp文件
add_library(hell SHARED ../ios/Classes/hello.cpp)
target_link_libraries(
    hello 
    lib_opencv 
    lib_xxx

    GLESv2
    EGL
    log
)

修改build.gradle

android {
  ...
    sourceSets {
        main.java.srcDirs += 'src/main/kotlin'
        main.jniLibs.srcDirs = ["libs"]
    }

    defaultConfig {
        minSdkVersion 21

        externalNativeBuild {
            cmake {
                cppFlags '-frtti -fexceptions -std=c++11'
                arguments "-DANDROID_STL=c++_shared"
            }
        }

        ndk {
            abiFilters 'armeabi-v7a'
        }
    }

    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
  ...
}
2.2、ios平台引入

ios平台其实是以模块私有化的形式来处理,把sdk放到hello/ios/(opencv2.framework 、xxx.framework)。

修改hello.podspec

  # telling CocoaPods not to remove framework
  s.preserve_paths = 'opencv2.framework', 'xxx.framework'

  # telling linker to include opencv2 framework
  s.xcconfig = { 
    'OTHER_LDFLAGS' => '-framework opencv2 -framework xxx',
  }

  # including OpenCV framework
  s.vendored_frameworks = 'opencv2.framework', 'xxx.framework'
  # s.vendored_libraries = 'path/name.a'

  s.frameworks = 'AVFoundation'
  s.library = 'c++'

新建hello.cpp

#include <opencv2/opencv.hpp>
#include <chrono>
#include <iostream>

#if defined(__ANDROID__)
#include <android/log.h>
#include "xxx.h"
#endif

#if defined(TARGET_OS_IPHONE)
#include <xxx/xxx.h>
#endif

#if defined(__GNUC__)
    // Attributes to prevent 'unused' function from being removed and to make it visible
    #define FUNCTION_ATTRIBUTE __attribute__((visibility("default"))) __attribute__((used))
#elif defined(_MSC_VER)
    // Marking a function for export
    #define FUNCTION_ATTRIBUTE __declspec(dllexport)
#endif

using namespace std;
using namespace cv;

long long int get_now() {
    return chrono::duration_cast<std::chrono::milliseconds>(
            chrono::system_clock::now().time_since_epoch()
    ).count();
}

void platform_log(const char *fmt, ...) {
    va_list args;
    va_start(args, fmt);
#ifdef __ANDROID__
    __android_log_vprint(ANDROID_LOG_VERBOSE, "ndk", fmt, args);
#else
    vprintf(fmt, args);
#endif
    va_end(args);
}
//对应sdk里的数据结构
typedef struct MyPoint 
{
    float x;
    float y;
}MyPoint;
//对应sdk里的数据结构
typedef struct MySize 
{
    float width;
    float height;
}MySize;

extern "C" {
    FUNCTION_ATTRIBUTE
    const char* version() {
        std::cout << "version func" << std::endl;
        return CV_VERSION;
    }

    FUNCTION_ATTRIBUTE
    void test(int *a) {
        std::cout << a << std::endl;
    }

    FUNCTION_ATTRIBUTE
    void process_image(char* inputImagePath, char* outputImagePath) {
        long long start = get_now();
        Mat input = imread(inputImagePath, IMREAD_GRAYSCALE);
        Mat threshed, withContours;
        
        vector<vector<Point> > contours;
        vector<Vec4i> hierarchy;
        
        adaptiveThreshold(input, threshed, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY_INV, 77, 6);
        findContours(threshed, contours, hierarchy, RETR_TREE, CHAIN_APPROX_TC89_L1);
        
        cvtColor(threshed, withContours, COLOR_GRAY2BGR);
        drawContours(withContours, contours, -1, Scalar(0, 255, 0), 4);
        
        imwrite(outputImagePath, withContours);
        
        int evalInMillis = static_cast<int>(get_now() - start);
        std::cout << "Processing done in" << evalInMillis << std::endl;
        
    }


    FUNCTION_ATTRIBUTE
    void bytes2Mat(uint8_t* imgData, int h, int w, int channels, cv::Mat *mat){       
       if (channels == 1)
       {
           *mat = cv::Mat(h, w, CV_8UC1, imgData);
       }
       else if (channels == 3)
       {
           *mat = cv::Mat(h, w, CV_8UC3, imgData);
       }
       else if (channels == 4)
       {
           *mat = cv::Mat(h, w, CV_8UC4, imgData);
       }
    }
....
....
}

修改hello/lib/hello.dart

import 'dart:async';
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:ffi';
import 'dart:io';
import 'package:ffi/ffi.dart';

//-----对应的结构体
class MyPoint_dart extends Struct {
  @Float()
  external double x;

  @Float()
  external double y;
}

class MySize_dart extends Struct {
  @Float()
  external double width;

  @Float()
  external double height;
}
 //如果用指针,对应的c内存就要自己管理
//-----------

final DynamicLibrary helloLib = Platform.isAndroid
    ? DynamicLibrary.open("libHello.so")
    : DynamicLibrary.process();

//-----
//c 方法签名
typedef _CVersionFunc = Pointer<Utf8> Function();
typedef _CProcessImageFunc = Void Function(Pointer<Utf8>, Pointer<Utf8>);
typedef _CTestFunc = Void Function(Pointer<Int32>);

//dart 方法签名
typedef _VersionFunc = Pointer<Utf8> Function();
typedef _ProcessImageFunc = void Function(Pointer<Utf8>, Pointer<Utf8>);
typedef _TestFunc = void Function(Pointer<Int32>);

//找对应的方法
final _VersionFunc _version = helloLib
    .lookup<NativeFunction<_CVersionFunc>>("version").asFunction();
  
final _ProcessImageFunc _processImage = helloLib
    .lookup<NativeFunction<_CProcessImageFunc>>("process_image").asFunction();

final _TestFunc test = helloLib
    .lookup<NativeFunction<_CTestFunc>>("test").asFunction();
//-------

class ImageArguments {
  final String inPath;
  final String outPath;

  ImageArguments(this.inPath, this.outPath);
}

void ProcessImage(ImageArguments args) {
  _processImage(args.inPath.toNativeUtf8(), args.outPath.toNativeUtf8());
}
...
...

项目中引入对应的插件

#pubspec.yaml
dependencies:
  flutter:
    sdk: flutter


  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  ...
  hello: 
    path: ./hello
  ...

到这里,基本的配置已经完成,接下来就是引入插件里的dart文件常规使用。

2.3、引入类似sdk的配置json等资源

在ios里最简单的就是直接放到项目里,编入到ipa文件里,然后根据mainbundle路径去加载。
针对android,踩的坑比较多,比较合理的办法是先把资源文件放到'项目'/android/app/src/main/assets/xxx.json,然后在项目的pubspec.yaml中引入这些资源。

#pubspec.yaml
...
assets:
    - android/app/src/main/assets/
  ...

在使用的时候还是先从’android/app/src/main/assets/***‘加载对应的资源数据,写到外部存储目录后再使用。
c++ sdk接入过程对我来说并不流畅,特此记录下,希望能帮助有需要的人。

相关文章

网友评论

      本文标题:记录flutter中引入c++ sdk

      本文链接:https://www.haomeiwen.com/subject/xyavdrtx.html