Dart とC言語の連携 : dart:js と dart:ffi の両立 (5) Object 構造体

[前回](https://qiita.com/kyorohiro/items/fb4754a1a7e62cc62e45)の続きです。
構造体とオブジェクトを使ってみましょう

# 構造体を扱って見る


## C言語の関数を作成する。

```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

```dart: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ブラウザー

```dart: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