背景
在做嵌入式Linux时选用了Flutter做GUI,但是底层必然使用C写的,那么最终怎么交互呢?这里调研了FFI,FFI赋予了Dart语言调用.so动态链接库的能力。
虽然项目后来采用了Dbus进行通信,还是记录下研究过程吧。
Flutter侧
这个方法应该是跟JNI非常相似的
调用流程:
- 从FFI角度和Dart角度分别定义函数类型:
typedef HelloWorldFunc = ffi.Void Function();
typedef HelloWorld = void Function();
// 带参数
typedef HelloWorldFunc = ffi.Int32 Function(ffi.Int32 param);
typedef HelloWorld = int Function(int param);
- 打开链接库
ffi.DynamicLibrary.open(libraryPath);
- 声明dart函数并关联到C侧函数
final HelloWorld hello = dylib
.lookup<ffi.NativeFunction<HelloWorldFunc>>('hello_world')
.asFunction();
- 愉快的调用
void test_C() {
print("test_C called ");
hello();
}
消息传递
- 函数返回值传递
- SendPort传递
重点讲一下SendPort传递消息吧,因为dart是线程隔离的,如果你在C语言侧起了新的的线程执行了一些任务并且想把结果传回dart,就要用到这个了。这里一定注意内存泄漏!!!
C语言代码
static void* test_thread_send_string(void *arg);
static void* test_thread_send_struct(void *arg);
static void* test_thread_send_list(void *arg);
static void FreeFinalizer(void* v, void* value);
int test(Dart_Port send_port_){
send_port = send_port_;
pthread_t thread_string;
pthread_t thread_struct;
pthread_t thread_list;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&thread_string, NULL, test_thread_send_string, NULL);
pthread_create(&thread_struct, NULL, test_thread_send_struct, NULL);
pthread_create(&thread_list, NULL, test_thread_send_list, NULL);
return 0;
}
void * test_thread_send_string(void *arg){
const char* testMessage = "This is a test message from C";
Dart_CObject c_test_message;
c_test_message.type = Dart_CObject_kString;
c_test_message.value.as_string = (char*)(testMessage);
Dart_PostCObject_DL(send_port, &c_test_message);
pthread_exit(NULL);
}
void * test_thread_send_struct(void *arg){
typedef struct {
int num;
char *text_1;
char text_2[1000];
} TestMessageStruct;
TestMessageStruct * testMessage = (TestMessageStruct *)malloc(sizeof(TestMessageStruct));
testMessage->num = 123;
testMessage->text_1 = "This is a test message structure from C";
sprintf(testMessage->text_2,"This is a test message structure from C");
char * p = testMessage->text_1;
Dart_CObject c_data;
c_data.type = Dart_CObject_kInt64;
c_data.value.as_int64 = (intptr_t)(testMessage);
printf("befor sleep 10s testMessageStruct->num:%d testMessage->text_1:%s \n",testMessage->num,testMessage->text_1);
Dart_PostCObject_DL(send_port, &c_data);
sleep(10);
// Becasue the testMessage have been free in the dart side ,it will print a random number or null.
// But it can't free the memary of text_1 , it is recommend that use the way of text_2 when you are designing the struct.
printf("after sleep 10s testMessageStruct->num:%d testMessage->text_1:%s \n",testMessage->num,p);
pthread_exit(NULL);
}
void * test_thread_send_list(void *arg){
// printf("test_thread_send_list \n");
typedef struct {
int num;
char * text;
} TestMessageStruct;
TestMessageStruct * testMessageStruct = (TestMessageStruct *)malloc(sizeof(TestMessageStruct));
testMessageStruct->num = 123;
testMessageStruct->text = "This is a test message struct from C";
Dart_CObject c_data;
c_data.type = Dart_CObject_kInt64;
c_data.value.as_int64 = (intptr_t)(testMessageStruct);
const char* testMessage = "This is a test message list[1] from C";
Dart_CObject c_test_message;
c_test_message.type = Dart_CObject_kString;
c_test_message.value.as_string = (char*)(testMessage);
Dart_CObject* c_list_arr[] = {&c_data, &c_test_message, &c_data};
Dart_CObject c_list;
c_list.type = Dart_CObject_kArray;
c_list.value.as_array.values = c_list_arr;
c_list.value.as_array.length = sizeof(c_list_arr) / sizeof(c_list_arr[0]);
Dart_PostCObject_DL(send_port, &c_list);
pthread_exit(NULL);
}
static void FreeFinalizer(void* v, void* value){
free(value);
}
intptr_t InitDartApiDL(void* data) {
return Dart_InitializeApiDL(data);
}
Flutter
// 定义消息handler函数
typedef MessageHandler = void Function(dynamic msg);
MessageHandler messageHandlerAsync = (dynamic msg) {
print(msg.runtimeType);
// string test
if (msg.runtimeType.toString() == "String") {
String stringMsg = msg;
print("messageHandlerAsync called : $stringMsg");
}
// struct test
// final msgAddress = msg as int;
if (msg.runtimeType == int) {
print("int type as an address of struct");
TestMessageStruct testMessageStruct =
Pointer<TestMessageStruct>.fromAddress(msg).ref;
print("messageHandlerAsync called : ${testMessageStruct.num}");
// 实际传过来的地址,一定free,避免内存泄露
malloc.free(Pointer<TestMessageStruct>.fromAddress(msg));
}
// list test
if (msg.runtimeType == List<dynamic>) {
print('list test :${msg[1]}');
}
};
// 使用时注册监听即可
void main() {
initializeApi(NativeApi.initializeApiDLData);
final interactiveCRequests = ReceivePort()..listen(messageHandler);
final int nativePort = interactiveCRequests.sendPort.nativePort;
test(messageHandlerAsync);
}
文章讲述了在嵌入式Linux系统中,使用Flutter作为GUI,但底层使用C语言实现时,如何通过FFI进行交互。作者提到了使用Dbus进行通信,但主要探讨了使用FFI调用C的动态链接库,并详细阐述了如何定义函数类型、打开链接库、调用C函数以及通过SendPort在多线程间传递消息,包括字符串、结构体和列表。文章强调了内存管理和避免泄漏的重要性。
5978

被折叠的 条评论
为什么被折叠?



