Yash Gaur
Yash Gaur

Reputation: 143

How to compile C file with Library imports to webassembly file (Emscripten)

I have a simple C program that needs to parse Json data. For that I have imported JSON-C library. My C code is -

#include"json.h"
#include <emscripten.h>

EMSCRIPTEN_KEEPALIVE
int addnumbers(int a, int b) {
    FILE *fp;
    char buffer[1024];
    struct json_object *parsed_json;
    struct json_object *name;
    struct json_object *age;
    struct json_object *friends;
    struct json_object *friend;
    size_t n_friends;

    size_t i;

    fp = fopen("test.json","r");
    fread(buffer, 1024, 1, fp);
    fclose(fp);

    parsed_json = json_tokener_parse(buffer); 

    json_object_object_get_ex(parsed_json, "name", &name);
    json_object_object_get_ex(parsed_json, "age", &age);
    json_object_object_get_ex(parsed_json, "friends", &friends);

    printf("Name: %s\n", json_object_get_string(name));
    printf("Age: %d\n", json_object_get_int(age));

    n_friends = json_object_array_length(friends);


    for(i=0;i<n_friends;i++) {
        friend = json_object_array_get_idx(friends, i);
        // printf("%lu. %s\n",i+1,json_object_get_string(friend));
    }
    return n_friends;
}

Process I followed :- Compiled the library(specifically json.h file) into bit code using command-

emcc json.h -o json.bc

and then compiled my C program using -

emcc json.c -o j_plumbing.bc -s EXTRA_EXPORTED_RUNTIME_METHODS=['ccall','cwrap'] -s ENVIRONMENT='web,worker' -s EXPORT_ES6=1 -s MODULARIZE=1 -s USE_ES6_IMPORT_META=0

Then together I compiled both the files to get wasm file with this command :-

emcc json.bc j_plumbing.bc -o js_plumbing.js -s EXTRA_EXPORTED_RUNTIME_METHODS=['ccall','cwrap'] -g4 -s LINKABLE=1 -s EXPORT_ALL=1 -s ENVIRONMENT='web,worker' -s EXPORT_ES6=1 -s MODULARIZE=1 -s USE_ES6_IMPORT_META=0 

and this is how I'm calling it from Vue file

public draw_outline() {
        Module().then(myModule => {
            console.log(myModule)
            const result = myModule.ccall('addnumbers',
                'number',
                ['number', 'number'],
                [4, 6]);
            console.log("Value from wasm file", result);
        });
    }
but this is the error I'm getting-

002210ee:1 Uncaught (in promise) RuntimeError: function signature mismatch
    at fclose (wasm-function[524]:0x1a777)
    at addnumbers (wasm-function[148]:0x6a45)
    at Module._addnumbers (webpack-internal:///./src/components/js_plumbing.js:1098:4989)
    at Object.ccall (webpack-internal:///./src/components/js_plumbing.js:199:628)
    at eval (webpack-internal:///./node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/ts-loader/index.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/components/Extraction.vue?vue&type=script&lang=ts&:128:31)
    at Object.Module.onRuntimeInitialized (webpack-internal:///./src/components/js_plumbing.js:1109:95)
    at doRun (webpack-internal:///./src/components/js_plumbing.js:1117:140)
    at run (webpack-internal:///./src/components/js_plumbing.js:1117:436)
    at runCaller (webpack-internal:///./src/components/js_plumbing.js:1113:15)
    at removeRunDependency (webpack-internal:///./src/components/js_plumbing.js:373:843)

Can anyone point out what i'm doing wrong here? Any help is appreciated

Upvotes: 1

Views: 1212

Answers (1)

quepas
quepas

Reputation: 1003

Explanation

If you read closely the error and the call stack, you can notice that the problem originated in the fclose() function. A WebAssembly module generated using emscripten has its virtual file system which does not understand the local file system on your machine. Therefore, any access to local files will fail, as the fp = fopen("test.json","r"); does and it returns NULL. This NULL-value of fp pointer is the reason for the error of fclose(fp).

Due to my inability to work with your code (sorry) I have replicated the error in a slightly different setting, but after the quick solution!

Quick solution

Map virtual file system of WebAssembly/emscripten to local file system using e.g. NODEFS. You can find more information about this solution in my other answer https://stackoverflow.com/a/60510997/1319478.

#include <stdio.h>
#include <emscripten.h>
#include <emscripten/bind.h>

void test_fun()
{
   FILE *fp;
   EM_ASM(
       FS.mkdir('/temp');
       FS.mount(NODEFS, {root : '.'}, '/temp'););
   fp = fopen("temp/test.json", "r");
   fclose(fp);
}

EMSCRIPTEN_BINDINGS(Module)
{
   emscripten::function("test_fun", &test_fun);
}

The simplest replication of the error

This example code tries to close a file with a pointer of NULL value.

#include <stdio.h>
#include <emscripten.h>
#include <emscripten/bind.h>

void test_fun()
{
   fclose(NULL);
}

EMSCRIPTEN_BINDINGS(Module)
{
   emscripten::function("test_fun", &test_fun);
}

If you compile this example with additional debug information -g:

emcc example.cpp -o example.js --bind -s WASM_ASYNC_COMPILATION=0 -g

And then try to execute a test script node test.js, where test.js is as follows:

var Module = require('./example.js');

Module.test_fun();

Then, what you get is the same error:

RuntimeError: function signature mismatch
    at fclose (wasm-function[32]:127)
    at test_fun() (wasm-function[17]:9)
    ...

Upvotes: 1

Related Questions