loading...

Dart and C : how to ffi and wasm (5) structure and object

kyorohiro profile image kyorohiro (kiyohiro kawamura) ・5 min read

This document is a continuation of the previous one.

In This document, I introduce to how to use struct and object at ffi and wasm.

How to use Struct and Object

Create clang function

#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;
}

Call from linux server at dart:ffi

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);

}



main.dart

Call from web browser at dart:js

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);

}

Explanation

clang structure 's buffer is defined at clang's spec.
you can parse this structre at dart lang too.

Next Time

About Observer Pattern or Thread

PS

Here's the code for this one
https://github.com/kyorohiro/dart_clang_codeserver/tree/05_struct

Discussion

markdown guide