Introduction
There are 3 parts that let JS talk to Go:
- The C++ binding
- Installing the binding
- Calling Go
Not all the code is shown, check out the source code for specifics.
Part 1 - The C++ Binding
The binding is the C++ glue code that will hook up your Go code to the JS runtime. The binding itself is composed of two main parts.
Part 1.1 - The C++ Binding
The binding is a c++ class that implements the jsi::HostObject
interface. At the very least it's useful for it to have a get
method defined. The type of the get
method is:
jsi::Value get(jsi::Runtime &runtime, const jsi::PropNameID &name) override;
It returns a jsi::Value
(a value that is safe for JS). It's given the JS runtime and the prop string used by JS when it get
s the field. e.g. global.nativeTest.foo
will call this method with PropNameID === "foo"
.
Part 1.2 - The C++ Binding's install
Now that we've defined our HostObject, we need to install it into the runtime. We use a static member function that we'll call later to set this up. It looks like this:
void TestBinding::install(jsi::Runtime &runtime,
std::shared_ptr<TestBinding> testBinding) {
// What is the name that js will use when it reaches for this?
// i.e. `global.nativeTest` in JS
auto testModuleName = "nativeTest";
// Create a JS object version of our binding
auto object = jsi::Object::createFromHostObject(runtime, testBinding);
// set the "nativeTest" propert
runtime.global().setProperty(runtime, testModuleName, std::move(object));
}
Part 2. Installing the binding (on Android)
Since we have a reference to the runtime in Java land, we'll have to create a JNI method to pass the runtime ptr to the native C++ land. We can add this JNI method to our TestBinding file with a guard.
#if ANDROID
extern "C" {
JNIEXPORT void JNICALL Java_com_testmodule_MainActivity_install(
JNIEnv *env, jobject thiz, jlong runtimePtr) {
auto testBinding = std::make_shared<example::TestBinding>();
jsi::Runtime *runtime = (jsi::Runtime *)runtimePtr;
example::TestBinding::install(*runtime, testBinding);
}
}
#endif
Then on the Java side (after we compile this into a shared library), we register this native function and call it when we're ready.
// In MainActivity
public class MainActivity extends ReactActivity implements ReactInstanceManager.ReactInstanceEventListener {
static {
// Load our jni
System.loadLibrary("test_module_jni");
}
//... ellided ...
@Override
public void onReactContextInitialized(ReactContext context) {
// Call our native function with the runtime pointer
install(context.getJavaScriptContextHolder().get());
}
// declare our native function
public native void install(long jsContextNativePointer);
}
Part 3. Calling Go
Now that our binding is installed in the runtime, we can make it do something useful.
jsi::Value TestBinding::get(jsi::Runtime &runtime,
const jsi::PropNameID &name) {
auto methodName = name.utf8(runtime);
if (methodName == "runTest") {
return jsi::Function::createFromHostFunction(
runtime, name, 0,
[](jsi::Runtime &runtime, const jsi::Value &thisValue,
const jsi::Value *arguments,
size_t count) -> jsi::Value { return TestNum(); });
}
return jsi::Value::undefined();
}
Here we return a jsi::Function
when JS calls global.nativeTest.runTest
. When JS calls it (as in global.nativeTest.runTest()
) we execute the code inside the closure, which just returns TestNum()
. TestNum is a Go function that's exported through cgo so that it is available to c/c++. Our Go code looks like this:
package main
import "C"
// TestNum returns a test number to be used in JSI
//export TestNum
func TestNum() int {
return int(9001)
}
func main() {
}
cgo builds a header and creates a shared library that is used by our binding.
Building
- Look at the CMakeLists.txt for specifics on building the C++ code.
- Look at from-go/build.sh for specifics on building the go code.
A Go Shared Library for C + Java
It's possible to build the Go code as a shared library for both C and Java, but you'll have to define your own JNI methods. It would be nice if gomobile bind also generated C headers for android, but it doesn't seem possible right now. Instead you'll have to run go build -buildmode=c-shared
directly and define your jni methods yourself. Take a look at from-go/build.sh
and testnum.go for specifics.