前回の続きです。
構造体とオブジェクトを使ってみましょう
構造体を扱って見る
C言語の関数を作成する。
ky.c
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
// [Linux]
// find . -name "*.o" | xargs rm
// gcc -Wall -Werror -fpic -I. -c ky.c -o ky.o
// gcc -shared -o libky.so ky.o
// [Wasm]
// find . -name "*.o" | xargs rm
// find . -name "*.wasm" | xargs rm
// emcc ky.c -o ky.o
// emcc ky.o -o libky.js -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' -s EXPORTED_FUNCTIONS="['_print_hello','_sum_int','_sum_double','_new_buffer','_init_buffer','_destroy_buffer','_new_product','_destroy_product','_init_product','_product_get_name','_product_get_price','_product_get_name_length']"
// cp libky.js ../web/libky.js
// cp libky.wasm ../web/libky.wasm
char* new_buffer(int size) {
char* ret = malloc(sizeof(char)*size);
return ret;
}
char* init_buffer(char* buffer, int size) {
for(int i=0;i<size;i++) {
buffer[i] = i;
}
return buffer;
}
void destroy_buffer(char* p) {
free(p);
}
//
// object
//
typedef struct {
char* name;
int name_length;
int price;
} Product;
Product* new_product(const char* name, int name_length, int price) {
Product* context = malloc(sizeof(Product));
return context;
}
void destroy_product(Product* context) {
if(context->name != NULL) {
free(context->name);
context->name = NULL;
}
free(context);
}
Product* init_product(Product* context, const char* name, const int name_length, int price) {
// copy text
context->name = malloc(sizeof(char)*(name_length+1));
// +1 is safe guard
context->name_length = (name_length + 1);
snprintf(context->name, context->name_length, "%s", name);
context->price = price;
return context;
}
char* product_get_name(Product* context) {
return context->name;
}
int product_get_price(Product* context) {
return context->price;
}
int product_get_name_length(Product* context) {
return context->name_length;
}
Dartから 関数を呼び出す:Linux Server
main.dart
import 'dart:ffi' as ffi;
import 'dart:typed_data' as typed;
import 'dart:convert' as conv;
// dart ./bin/main.dart
ffi.DynamicLibrary dylib = ffi.DynamicLibrary.open('/app/libc/libky.so');
// char* new_buffer(int size)
typedef NewBufferFunc = ffi.Pointer<ffi.Uint8> Function(ffi.Int32 size);
typedef NewBuffer = ffi.Pointer<ffi.Uint8> Function(int size);
NewBuffer _new_buffer = dylib
.lookup<ffi.NativeFunction<NewBufferFunc>>('new_buffer')
.asFunction<NewBuffer>();
ffi.Pointer<ffi.Uint8> newBuffer(int length) {
return _new_buffer(length);
}
// char* init_buffer(char*, int size)
typedef InitBufferFunc = ffi.Pointer<ffi.Uint8> Function(ffi.Pointer<ffi.Uint8> buffer, ffi.Int32 size);
typedef InitBuffer = ffi.Pointer<ffi.Uint8> Function(ffi.Pointer<ffi.Uint8> buffer, int size);
InitBuffer _init_buffer = dylib
.lookup<ffi.NativeFunction<InitBufferFunc>>('init_buffer')
.asFunction<InitBuffer>();
ffi.Pointer<ffi.Uint8> initBuffer(ffi.Pointer<ffi.Uint8> buffer, int length) {
return _init_buffer(buffer, length);
}
// void destroy_buffer(char* p)
typedef DestroyBufferFunc = ffi.Void Function(ffi.Pointer<ffi.Uint8> buffer);
typedef DestroyBuffer = void Function(ffi.Pointer<ffi.Uint8> buffer);
DestroyBuffer _destroy_buffer = dylib
.lookup<ffi.NativeFunction<DestroyBufferFunc>>('destroy_buffer')
.asFunction<DestroyBuffer>();
void destroyBuffer(ffi.Pointer<ffi.Uint8> buffer) {
_destroy_buffer(buffer);
}
// ---
// object
// ---
// Product* new_product()
typedef NewProductFunc = ffi.Pointer<ffi.Uint8> Function();
typedef NewProduct = ffi.Pointer<ffi.Uint8> Function();
NewProduct _new_prodhct = dylib
.lookup<ffi.NativeFunction<NewProductFunc>>('new_product')
.asFunction<NewProduct>();
ffi.Pointer<ffi.Uint8> newProduct() {
return _new_prodhct();
}
// void destroy_product(Product*)
typedef DestroyProductFunc = ffi.Void Function(ffi.Pointer<ffi.Uint8> context);
typedef DestroyProduct = void Function(ffi.Pointer<ffi.Uint8> context);
DestroyProduct _destroy_product = dylib
.lookup<ffi.NativeFunction<DestroyProductFunc>>('destroy_product')
.asFunction<DestroyProduct>();
void destroyProduct(ffi.Pointer<ffi.Uint8> context) {
_destroy_product(context);
}
// Product* init_product(Product*, char* name, int price)
typedef InitProductFunc = ffi.Pointer<ffi.Uint8> Function(ffi.Pointer<ffi.Uint8> context, ffi.Pointer<ffi.Uint8> name, ffi.Int32, ffi.Int32 price);
typedef InitProduct = ffi.Pointer<ffi.Uint8> Function(ffi.Pointer<ffi.Uint8> context, ffi.Pointer<ffi.Uint8> name, int nameLength, int price);
InitProduct _init_product = dylib
.lookup<ffi.NativeFunction<InitProductFunc>>('init_product')
.asFunction<InitProduct>();
ffi.Pointer<ffi.Uint8> initProduct(ffi.Pointer<ffi.Uint8> context, ffi.Pointer<ffi.Uint8> name, int nameLength, int price) {
return _init_product(context, name, nameLength, price);
}
// int product_get_price(Product*)
typedef ProductGetPriceFunc = ffi.Int32 Function(ffi.Pointer<ffi.Uint8> context);
typedef ProductGetPrice = int Function(ffi.Pointer<ffi.Uint8> context);
ProductGetPrice _product_get_price = dylib
.lookup<ffi.NativeFunction<ProductGetPriceFunc>>('product_get_price')
.asFunction<ProductGetPrice>();
int productGetPrice(ffi.Pointer<ffi.Uint8> context) {
return _product_get_price(context);
}
// char* product_get_name(Product*)
typedef ProductGetNameFunc = ffi.Pointer<ffi.Uint8> Function(ffi.Pointer<ffi.Uint8> context);
typedef ProductGetName = ffi.Pointer<ffi.Uint8> Function(ffi.Pointer<ffi.Uint8> context);
ProductGetName _product_get_name = dylib
.lookup<ffi.NativeFunction<ProductGetNameFunc>>('product_get_name')
.asFunction<ProductGetName>();
ffi.Pointer<ffi.Uint8> productGetName(ffi.Pointer<ffi.Uint8> context) {
return _product_get_name(context);
}
// int product_get_name_length(Product*)
typedef ProductGetNameLengthFunc = ffi.Int32 Function(ffi.Pointer<ffi.Uint8> context);
typedef ProductGetNameLength = int Function(ffi.Pointer<ffi.Uint8> context);
ProductGetNameLength _product_get_name_length = dylib
.lookup<ffi.NativeFunction<ProductGetNameLengthFunc>>('product_get_name_length')
.asFunction<ProductGetNameLength>();
int productGetNameLength(ffi.Pointer<ffi.Uint8> context) {
return _product_get_name_length(context);
}
void main(List<String> args) {
// product
var name = newBuffer(20);
typed.Uint8List nameAsUint8List = name.asTypedList(9);
nameAsUint8List.setAll(0,conv.ascii.encode("dart book"));
var context = newProduct();
initProduct(context, name, 9, 300);
print("price:${productGetPrice(context)}");
print("name:${conv.utf8.decode(productGetName(context).asTypedList(productGetNameLength(context)),allowMalformed: true)}");
destroyProduct(context);
}
Dartから 関数を呼び出す:Webブラウザー
main.dart
import 'dart:js' as js;
import 'dart:typed_data' as typed;
import 'dart:convert' as conv;
// webdev serve --hostname=0.0.0.0
js.JsObject Module = js.context['Module'];
var HEAP8 = Module['HEAP8'];
//HEAP8.buffer
js.JsFunction _new_buffer = Module.callMethod('cwrap',['new_buffer','number',['number']]);
int newBuffer(int length) {
return _new_buffer.apply([length]);
}
js.JsFunction _init_buffer = Module.callMethod('cwrap',['init_buffer','number',['number','number']]);
int initBuffer(int buffer, int length) {
return _init_buffer.apply([buffer, length]);
}
js.JsFunction _destroy_buffer = Module.callMethod('cwrap',['destroy_buffer','void',['number']]);
int destroyBuffer(int buffer) {
return _destroy_buffer.apply([buffer]);
}
js.JsFunction _to_uint8list= js.context['to_uint8list'];// from util.js
typed.Uint8List toUint8List(int buffer, int length) {
return _to_uint8list.apply([buffer, length]);
}
// ---
// object
// ---
js.JsFunction _new_product = Module.callMethod('cwrap',['new_product','number',[]]);
int newProduct() {
return _new_product.apply([]);
}
js.JsFunction _init_product = Module.callMethod('cwrap',['init_product','number',['number','number','number','number']]);
int initProduct(int context, int name, int nameLength, int price) {
return _init_product.apply([context, name, nameLength, price]);
}
js.JsFunction _destroy_product = Module.callMethod('cwrap',['destroy_product','void',['number']]);
int destroyProduct(int context) {
return _destroy_product.apply([context]);
}
js.JsFunction _prodcut_get_name = Module.callMethod('cwrap',['product_get_name','number',['number']]);
int productGetName(int context) {
return _prodcut_get_name.apply([context]);
}
js.JsFunction _prodcut_get_price = Module.callMethod('cwrap',['product_get_price','number',['number']]);
int productGetPrice(int context) {
return _prodcut_get_price.apply([context]);
}
js.JsFunction _prodcut_get_name_length = Module.callMethod('cwrap',['product_get_name_length','number',['number']]);
int productGetNameLength(int context) {
return _prodcut_get_name_length.apply([context]);
}
void main() {
// product
var name = newBuffer(20);
typed.Uint8List nameAsUint8List = (HEAP8 as typed.Int8List).buffer.asUint8List(name, 20);
nameAsUint8List.setAll(0, conv.ascii.encode("dart book"));
var context = newProduct();
initProduct(context, name, 9, 300);
print("price:${productGetPrice(context)}");
var nameAsString = (HEAP8 as typed.Int8List).buffer.asUint8List(productGetName(context), productGetNameLength(context));
print("name:${conv.utf8.decode(nameAsString,allowMalformed: true)}");
destroyProduct(context);
}
解説
構造体がBuffer上にどのように値が配置されるかは、C言語の仕様で決まっています。
なので、Dart側でそのBufferからデータを取り出すといった方法も実現可能です。
次回
Observer pattern について
PS
今回のコードは以下です
https://github.com/kyorohiro/dart_clang_codeserver/tree/05_struct
以下でも書いています
https://dev.to/kyorohiro/dart-and-c-how-to-ffi-and-wasm-5-structure-and-object-bjd