0. Prerequisites

  1. Export C functions to JS calls: mainlyEMSCRIPTEN_KEEPALIVEMacros specific to this Emscripten environment.
#include <stdio.h>

#ifndef EM_PORT_API
#	if defined(__EMSCRIPTEN__)
#		include <emscripten.h>
#		if defined(__cplusplus)
#			define EM_PORT_API(rettype) extern "C" rettype EMSCRIPTEN_KEEPALIVE
#		else
#			define EM_PORT_API(rettype) rettype EMSCRIPTEN_KEEPALIVE
#		endif
#	else
#		if defined(__cplusplus)
#			define EM_PORT_API(rettype) extern "C" rettype
#		else
#			define EM_PORT_API(rettype) rettype
#		endif
#	endif
#endif

EM_PORT_API (int) sum(int *ptr, int count) {
    int total = 0;
    for(int i = 0; i < count; i++) {
        total += ptr[i];
    }
    return total;
}
Copy the code
// js
Module._sum(ptr, 10);
Copy the code
  1. Compile C code into WASM using Emscripten. Emscripten’s environment is difficult to configure, mainly due to network issues, but fortunately there is a Docker environment to use directlytrzeci/emscripten. Recommend to write abuild.shThe file makes it easy to modify the compilation script
# build.sh
docker run \
  --rm \
  -v $(pwd):$(pwd) \
  -u $(id -u):$(id -g) \
  trzeci/emscripten \
  emcc helloworld.c -o helloworld.js
Copy the code
  1. Use the compiled WASM. If the compilation is successful.wasmFile and its corresponding.jsGlue file, its directory:
. ├ ─ ─ build. Sh ├ ─ ─ index. The HTML ├ ─ ─ test. The cc ├ ─ ─ test. The js └ ─ ─ test. The wasmCopy the code

Write test code in index.html

<script>
  var Module = {};
  Module.onRuntimeInitialized = () = > {
  	Call the wASM method in this callback
  };
</script>
<! Glue layer code in the background -->
<script src="test.js"></script>
Copy the code
  1. WASM Table: storage function

This is a Javascript wrapper object wrapped around WebAssemble Table with a class array structure that stores multiple function references. Table objects created in Javascript or WebAssemble can be accessed and changed by Javascript or WebAssemble at the same time.

1. Call addFunction of JS function in C

Emscripten provides several methods for calling JavaScript in C environments, including:

  1. EM_JS/EM_ASM macro inline JavaScript code
  2. Emscripten_run_script function
  3. JavaScript function injection (more accurately described as “Implement C API in JavaScript”, which implements the C function API in JavaScript)
  4. Use addFunction to pass a function pointer to C code to call

Click on the links for detailed instructions on the first three methods

The fourth method is described below, mainly in conjunction with Calling JavaScript functions as function Pointers from C

You can use addFunction to return an integer value that represents a function pointer. Passing that integer to C code then lets it call that value as a function pointer, and the JavaScript function you sent to addFunction will be called.

You can use the return value (number) of addFunction to represent the pointer to the function. The pointer (number) is then passed to the C code, which then calls the value as a function pointer, and the JavaScript function sent to addFunction is called.

Module has an addFunction method that returns a numeric type.

When I tried to call it, I was prompted to export this function at compile time

docker run \
  --rm \
  -v $(pwd):/src \
  -u $(id -u):$(id -g) \
  emscripten/emsdk \
  emcc test.cc -o test.js \
  #I'm going to add it here
  -s EXTRA_EXPORTED_RUNTIME_METHODS="['addFunction']"
Copy the code

The wASM table is set to grow

At this point, add one more line to the compiled script

-s ALLOW_TABLE_GROWTH
Copy the code

You should build with -s ALLOW_TABLE_GROWTH to allow new functions to be added to the table. Otherwise by default the table has a fixed size.

Plus after compilation, run it again and find yi is wrong and lacks function signature

To view the document

When using addFunction on LLVM wasm backend, you need to provide an additional second argument, a Wasm function signature string. Each character within a signature string represents a type. The first character represents the return type of a function, and remaining characters are for parameter types.

  • ‘v’: void type
  • ‘i’: 32-bit integer type
  • ‘j’: 64-bit integer type (currently does not exist in JavaScript)
  • ‘f’: 32-bit float type
  • ‘d’: 64-bit float type

The second argument to addFunction needs to specify the return value type of the function and the parameter type

Finally successful, this time you have a pointer to the function, passed it into C code can be called.

Let’s look at the implementation of C code:

// Declare the function signature. When addFunction is called in JS, the signature of the second function must be consistent with this declaration
typedef void testExternalJSMethod(int p);

// Export a receiver function
EM_PORT_API (void) pass_fn_ptr(int ptr) {
    ((testExternalJSMethod*)ptr)(1);
}
Copy the code
// js
Module.onRuntimeInitialized = () = > {
    function jsFunction(i) {
        console.log('Function defined in JS');
        console.log('Arguments from c:', i);
    }

	// The type of the return value of the function is declared first, and the type of the other parameters is written first
    var fPtr = Module.addFunction(jsFunction, 'vi');
    Module._pass_fn_ptr(fPtr);
};
Copy the code

Finally, look at the output:

2. Advantages of addFunction

  1. Compared with the third way is more flexible, the third in the js function implementation involved in the compilation process, and the real application to C call the function is often mixed in the business code, or with TS to achieve, so the third way will be very troublesome;
  2. EM_ASM emscripten_run_script This way will directly inline JS code into C, there is no maintenance;
#include <emscripten.h>

int main(a) {
	EM_ASM(console.log('Hello, Emscripten! '));
	return 0;
}
Copy the code

X. Reference documents

  1. C/C++ programming for WebAssembly
  2. Calling JavaScript functions as function pointers from C