本篇文章主要讲述如何使用dart ffi 调用 golang静态库
一、生成golang静态库
本篇教程代码目录结构如下:
进入go-lib目录使用如下命令生成golang 静态库文件 lib.a 以及c 程序:
go build -buildmode=c-shared -o lib.a lib.go
lib.go源码如下:
//Package go_lib
/*
* @Time : 2021年11月22日 11:37:39
* @Author : user
* @Project : SEGI
* @File : lib.go
* @Software: GoLand
* @Describe:
*/
package main
//#include <file.h>
import "C"
import (
"fmt"
"os"
"os/exec"
)
//export GetKey
func GetKey() *C.char {
fmt.Println("hello,dart")
theKey := "123-456-789"
return C.CString(theKey)
}
//export Sum
func Sum(a, b int) C.int {
return C.int(a + b)
}
//export CreateFile
func CreateFile(filePath *C.char) C.CreateFileResponse {
file, err := os.OpenFile(C.GoString(filePath), os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0766)
path, _ := exec.LookPath(os.Args[0])
if err != nil {
return C.CreateFileResponse{
errMsg: C.CString(err.Error()),
path:C.CString(path),
fileName: C.CString(""),
ok: C.FALSE,
}
}
defer file.Close()
fileStat, err := file.Stat()
if err != nil {
return C.CreateFileResponse{
errMsg: C.CString(err.Error()),
path:C.CString(path),
fileName: C.CString(""),
ok: C.FALSE,
}
}
return C.CreateFileResponse{
errMsg: C.CString(""),
path:C.CString(path),
fileName: C.CString(fileStat.Name()),
ok: C.TRUE,
}
}
func main() {
//createFileResponse := C.CreateFileResponse{
// errMsg: C.CString("hello"),
// ok: C.TRUE,
//}
//fmt.Println(createFileResponse)
//fmt.Println(C.GoString(createFileResponse.errMsg))
}
file.h C头文件定义如下:
#define BOOL int
#define TRUE 1
#define FALSE 0
typedef struct{
char *errMsg;
char *path;
char *fileName;
BOOL ok;
}CreateFileResponse;
基于lib.go生成的静态库文件,有以下几种测试功能,返回不定长的字符串、整数加法、以及文件读写。
二、golang 数据类型、C数据类型、Dart数据类型说明
以下是数据类型对应关系
golang数据类型 | CGO数据类型 | C数据类型 | Dart数据类型 |
byte | C.char | char | Uint8 |
int8 | C.schar或C.int8_t | signed char或int8_t | Int8 |
uint8 | C.uchar或C.uint8_t | unsigned char或uint8_t | Uint8 |
int16 | C.short或C.int16_t | signed short或int16_t | Int16 |
uint16 | C.ushort或C.uint16_t | unsigned short或uint16_t | Uint16 |
int32 | C.int或C.int32_t | int或int32_t | Int32 |
uint32 | C.uint或C.uint32_t | unsigned int或uint32_t | Uint32 |
int32 | C.long或C.int32_t | long或int32_t | Int32 |
uint32 | C.ulong或C.uint32_t | unsigned long或uint32_t | Uint32 |
int64 | C.longlong或C.int64_t | long long int或int64_t | Int64 |
uint64 | C.ulonglong或C.uint64_t | unsinged long long int或uint64_t | Uint64 |
float32 | C.float | float | Float |
float64 | C.double | double | Double |
unit | C.size_t | size_t | Uint32或Uint64 |
string | *C.char | char * | Pointer<utf8> |
三、dart 调用golang生成的静态库
dart_learn_example.dart源码如下:
import 'dart:ffi';
import 'dart:io';
import 'package:ffi/ffi.dart';
import 'package:path/path.dart' as path;
class CreateFileResponse extends Struct {
external Pointer<Utf8> errMsg;
external Pointer<Utf8> path;
external Pointer<Utf8> fileName;
@Int64()
external int ok;
}
typedef GetKeyFunc = Pointer<Utf8> Function();
typedef SumFunc = Int64 Function(Int64 a, Int64 b);
typedef CreateFileFuncNative = CreateFileResponse Function(
Pointer<Utf8> filePath);
typedef CreateFileFunc = CreateFileResponse Function(Pointer<Utf8> filePath);
void main() async {
var libraryPath = path.join(Directory.current.path, "example", "lib.a");
final dylib = DynamicLibrary.open(libraryPath);
final GetKeyFunc getKey =
dylib.lookup<NativeFunction<GetKeyFunc>>('GetKey').asFunction();
print(getKey().toDartString());
final sum = dylib
.lookup<NativeFunction<SumFunc>>('Sum')
.asFunction<int Function(int a, int b)>();
print(sum(100, 200));
final createFile =
dylib.lookupFunction<CreateFileFuncNative, CreateFileFunc>('CreateFile');
final filePath = './test.dat'.toNativeUtf8();
final createFileResponse = createFile(filePath);
print(Directory.current);
print(createFileResponse.path.toDartString());
print(createFileResponse.fileName.toDartString());
calloc.free(filePath);
}
源码中定义结构体接收golang静态库生成的结构体响应,具体的代码写法和数据类型参见第二小章节介绍的数据类型对应关系表。
dart官方ffi调用文档链接如下:C interop using dart:ffi | Dart,根据github里的仓库C代码,可以反推出cgo代码,当要传输不定长字符串时,使用*C.char作为golang端的输入输出参数,如果使用golang原生的string,需要在dart 中定义GoSting对象用来接收golang的string值,这种实现方式比较少见,资料也比较少。
如有不对的地方,希望能够指正,互相学习,互相进步!