mirror of
https://github.com/netbirdio/gomobile-tvos-fork.git
synced 2026-05-22 18:43:29 -07:00
mobile/bind: replace seq serialization with direct calls
The seq serialization machinery is a historic artifact from when Go mobile code had to run in a separate process. Now that Go code is running in-process, replace the explicit serialization with direct calls and pass arguments on the stack. The benefits are a much smaller bind runtime, much less garbage (and, in Java, fewer objects with finalizers), less argument copying, and faster cross-language calls. The cost is a more complex generator, because some of the work from the bind runtime is moved to generated code. Generated code now handles conversion between Go and Java/ObjC types, multiple return values and memory management of byte slice and string arguments. To overcome the lack of calling C code between Go packages, all bound packages now end up in the same (fake) package, "gomobile_bind", instead of separate packages (go_<pkgname>). To avoid name clashes, the package name is added as a prefix to generated functions and types. Also, don't copy byte arrays passed to Go, saving call time and allowing read([]byte)-style interfaces to foreign callers (#12113). Finally, add support for nil interfaces and struct pointers to objc. This is a large CL, but most of the changes stem from changing testdata. The full benchcmp output on the CL/20095 benchmarks on my Nexus 5 is reproduced below. Note that the savings for the JavaSlice* benchmarks are skewed because byte slices are no longer copied before passing them to Go. benchmark old ns/op new ns/op delta BenchmarkJavaEmpty 26.0 19.0 -26.92% BenchmarkJavaEmptyDirect 23.0 22.0 -4.35% BenchmarkJavaNoargs 7685 2339 -69.56% BenchmarkJavaNoargsDirect 17405 8041 -53.80% BenchmarkJavaOnearg 26887 2366 -91.20% BenchmarkJavaOneargDirect 34266 7910 -76.92% BenchmarkJavaOneret 38325 2245 -94.14% BenchmarkJavaOneretDirect 46265 7708 -83.34% BenchmarkJavaManyargs 41720 2535 -93.92% BenchmarkJavaManyargsDirect 51026 8373 -83.59% BenchmarkJavaRefjava 38139 21260 -44.26% BenchmarkJavaRefjavaDirect 42706 28150 -34.08% BenchmarkJavaRefgo 34403 6843 -80.11% BenchmarkJavaRefgoDirect 40193 16582 -58.74% BenchmarkJavaStringShort 32366 9323 -71.20% BenchmarkJavaStringShortDirect 41973 19118 -54.45% BenchmarkJavaStringLong 127879 94420 -26.16% BenchmarkJavaStringLongDirect 133776 114760 -14.21% BenchmarkJavaStringShortUnicode 32562 9221 -71.68% BenchmarkJavaStringShortUnicodeDirect 41464 19094 -53.95% BenchmarkJavaStringLongUnicode 131015 89401 -31.76% BenchmarkJavaStringLongUnicodeDirect 134130 90786 -32.31% BenchmarkJavaSliceShort 42462 7538 -82.25% BenchmarkJavaSliceShortDirect 52940 17017 -67.86% BenchmarkJavaSliceLong 138391 8466 -93.88% BenchmarkJavaSliceLongDirect 205804 15666 -92.39% BenchmarkGoEmpty 3.00 3.00 +0.00% BenchmarkGoEmptyDirect 3.00 3.00 +0.00% BenchmarkGoNoarg 40342 13716 -66.00% BenchmarkGoNoargDirect 46691 13569 -70.94% BenchmarkGoOnearg 43529 13757 -68.40% BenchmarkGoOneargDirect 44867 14078 -68.62% BenchmarkGoOneret 45456 13559 -70.17% BenchmarkGoOneretDirect 44694 13442 -69.92% BenchmarkGoRefjava 55111 28071 -49.06% BenchmarkGoRefjavaDirect 60883 26872 -55.86% BenchmarkGoRefgo 57038 29223 -48.77% BenchmarkGoRefgoDirect 56153 27812 -50.47% BenchmarkGoManyargs 67967 17398 -74.40% BenchmarkGoManyargsDirect 60617 16998 -71.96% BenchmarkGoStringShort 57538 22600 -60.72% BenchmarkGoStringShortDirect 52627 22704 -56.86% BenchmarkGoStringLong 128485 52530 -59.12% BenchmarkGoStringLongDirect 138377 52079 -62.36% BenchmarkGoStringShortUnicode 57062 22994 -59.70% BenchmarkGoStringShortUnicodeDirect 62563 22938 -63.34% BenchmarkGoStringLongUnicode 139913 55553 -60.29% BenchmarkGoStringLongUnicodeDirect 150863 57791 -61.69% BenchmarkGoSliceShort 59279 20215 -65.90% BenchmarkGoSliceShortDirect 60160 21136 -64.87% BenchmarkGoSliceLong 411225 301870 -26.59% BenchmarkGoSliceLongDirect 399029 298915 -25.09% Fixes golang/go#12619 Fixes golang/go#12113 Fixes golang/go#13033 Change-Id: I2b45e9e98a1248e3c23a5137f775f7364908bec7 Reviewed-on: https://go-review.googlesource.com/19821 Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
This commit is contained in:
+31
-88
@@ -12,6 +12,12 @@ import java.util.logging.Logger;
|
||||
public class Seq {
|
||||
private static Logger log = Logger.getLogger("GoSeq");
|
||||
|
||||
// also known to bind/seq/ref.go and bind/objc/seq_darwin.m
|
||||
private static final int NULL_REFNUM = 41;
|
||||
|
||||
// use single Ref for null Seq.Object
|
||||
public static final Ref nullRef = new Ref(NULL_REFNUM, null);
|
||||
|
||||
static {
|
||||
// Look for the shim class auto-generated by gomobile bind.
|
||||
// Its only purpose is to call System.loadLibrary.
|
||||
@@ -26,70 +32,34 @@ public class Seq {
|
||||
} catch (IllegalAccessException e) {
|
||||
log.severe("LoadJNI class bad field: " + e);
|
||||
}
|
||||
|
||||
initSeq();
|
||||
init();
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private long memptr; // holds C-allocated pointer
|
||||
private static native void init();
|
||||
|
||||
public Seq() {
|
||||
ensure(64);
|
||||
// Empty method to run class initializer
|
||||
public static void touch() {}
|
||||
|
||||
private Seq() {
|
||||
}
|
||||
|
||||
private static void throwException(String msg) throws Exception { // JNI helper
|
||||
throw new Exception(msg);
|
||||
}
|
||||
|
||||
// ctx is an android.context.Context.
|
||||
static native void setContext(java.lang.Object ctx);
|
||||
|
||||
// Ensure that at least size bytes can be written to the Seq.
|
||||
// Any existing data in the buffer is preserved.
|
||||
public native void ensure(int size);
|
||||
|
||||
// Moves the internal buffer offset back to zero.
|
||||
// Length and contents are maintained. Data can be read after a reset.
|
||||
public native void resetOffset();
|
||||
|
||||
public native void log(String label);
|
||||
|
||||
public native boolean readBool();
|
||||
public native byte readInt8();
|
||||
public native short readInt16();
|
||||
public native int readInt32();
|
||||
public native long readInt64();
|
||||
public long readInt() { return readInt64(); }
|
||||
|
||||
public native float readFloat32();
|
||||
public native double readFloat64();
|
||||
public native String readUTF16();
|
||||
public String readString() { return readUTF16(); }
|
||||
public native byte[] readByteArray();
|
||||
|
||||
public native void writeBool(boolean v);
|
||||
public native void writeInt8(byte v);
|
||||
public native void writeInt16(short v);
|
||||
public native void writeInt32(int v);
|
||||
public native void writeInt64(long v);
|
||||
public void writeInt(long v) { writeInt64(v); }
|
||||
|
||||
public native void writeFloat32(float v);
|
||||
public native void writeFloat64(double v);
|
||||
public native void writeUTF16(String v);
|
||||
public void writeString(String v) { writeUTF16(v); }
|
||||
public native void writeByteArray(byte[] v);
|
||||
|
||||
public void writeRef(Ref ref) {
|
||||
if (ref == null)
|
||||
ref = RefTracker.nullRef;
|
||||
public static int incRef(Seq.Object o) {
|
||||
Ref ref = o.ref();
|
||||
tracker.inc(ref);
|
||||
writeInt32(ref.refnum);
|
||||
return ref.refnum;
|
||||
}
|
||||
|
||||
public Ref readRef() {
|
||||
int refnum = readInt32();
|
||||
public static Ref getRef(int refnum) {
|
||||
return tracker.get(refnum);
|
||||
}
|
||||
|
||||
static native void initSeq();
|
||||
|
||||
// Informs the Go ref tracker that Java is done with this ref.
|
||||
static native void destroyRef(int refnum);
|
||||
|
||||
@@ -98,30 +68,9 @@ public class Seq {
|
||||
return tracker.createRef(o);
|
||||
}
|
||||
|
||||
// sends a function invocation request to Go.
|
||||
//
|
||||
// Blocks until the function completes.
|
||||
// If the request is for a method, the first element in src is
|
||||
// a Ref to the receiver.
|
||||
public static native void send(String descriptor, int code, Seq src, Seq dst);
|
||||
|
||||
protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
free();
|
||||
}
|
||||
private native void free();
|
||||
|
||||
public static Seq recv(Seq in, int code, int refnum) {
|
||||
Seq out = new Seq();
|
||||
if (code == -1) {
|
||||
// Special signal from seq.FinalizeRef.
|
||||
tracker.dec(refnum);
|
||||
return out;
|
||||
}
|
||||
|
||||
Ref r = tracker.get(refnum);
|
||||
r.obj.call(code, in, out);
|
||||
return out;
|
||||
// decRef is called from seq.FinalizeRef
|
||||
static void decRef(int refnum) {
|
||||
tracker.dec(refnum);
|
||||
}
|
||||
|
||||
// An Object is a Java object that matches a Go object.
|
||||
@@ -133,7 +82,6 @@ public class Seq {
|
||||
// generated abstract Stub.
|
||||
public interface Object {
|
||||
public Ref ref();
|
||||
public void call(int code, Seq in, Seq out);
|
||||
}
|
||||
|
||||
// A Ref is an object tagged with an integer for passing back and
|
||||
@@ -148,11 +96,11 @@ public class Seq {
|
||||
public static final class Ref {
|
||||
// refnum < 0: Go object tracked by Java
|
||||
// refnum > 0: Java object tracked by Go
|
||||
int refnum;
|
||||
public final int refnum;
|
||||
|
||||
int refcnt; // for Java obj: track how many times sent to Go.
|
||||
|
||||
public Seq.Object obj; // for Java obj: pointers to the Java obj.
|
||||
public final Seq.Object obj; // for Java obj: pointers to the Java obj.
|
||||
|
||||
Ref(int refnum, Seq.Object o) {
|
||||
this.refnum = refnum;
|
||||
@@ -174,10 +122,6 @@ public class Seq {
|
||||
|
||||
static final class RefTracker {
|
||||
private static final int REF_OFFSET = 42;
|
||||
private static final int NULL_REFNUM = 41; // also known to bind/seq/ref.go
|
||||
|
||||
// use single Ref for null Seq.Object
|
||||
private static final Ref nullRef = new Ref(NULL_REFNUM, null);
|
||||
|
||||
// Next Java object reference number.
|
||||
//
|
||||
@@ -200,7 +144,7 @@ public class Seq {
|
||||
// We don't keep track of the Go object.
|
||||
return;
|
||||
}
|
||||
if (refnum == nullRef.refnum) {
|
||||
if (refnum == NULL_REFNUM) {
|
||||
return;
|
||||
}
|
||||
// Count how many times this ref's Java object is passed to Go.
|
||||
@@ -225,7 +169,7 @@ public class Seq {
|
||||
log.severe("dec request for Go object "+ refnum);
|
||||
return;
|
||||
}
|
||||
if (refnum == nullRef.refnum) {
|
||||
if (refnum == Seq.nullRef.refnum) {
|
||||
return;
|
||||
}
|
||||
// Java objects are removed on request of Go.
|
||||
@@ -241,7 +185,7 @@ public class Seq {
|
||||
|
||||
synchronized Ref createRef(Seq.Object o) {
|
||||
if (o == null) {
|
||||
return nullRef;
|
||||
return Seq.nullRef;
|
||||
}
|
||||
if (next == Integer.MAX_VALUE) {
|
||||
throw new RuntimeException("createRef overflow for " + o);
|
||||
@@ -262,10 +206,9 @@ public class Seq {
|
||||
//
|
||||
// When we have real code, examine the tradeoffs.
|
||||
synchronized Ref get(int refnum) {
|
||||
if (refnum > 0) {
|
||||
if (refnum == nullRef.refnum) {
|
||||
return nullRef;
|
||||
}
|
||||
if (refnum == NULL_REFNUM) {
|
||||
return nullRef;
|
||||
} else if (refnum > 0) {
|
||||
Ref ref = javaObjs.get(refnum);
|
||||
if (ref == null) {
|
||||
throw new RuntimeException("unknown java Ref: "+refnum);
|
||||
|
||||
+40
-5
@@ -68,8 +68,6 @@ public class SeqTest extends InstrumentationTestCase {
|
||||
Testpkg.setStructVar(s1);
|
||||
assertEquals("var StructVar", s1.String(), Testpkg.getStructVar().String());
|
||||
|
||||
// TODO(hyangah): handle nil return value (translate to null)
|
||||
|
||||
AnI obj = new AnI();
|
||||
obj.name = "this is an I";
|
||||
Testpkg.setInterfaceVar(obj);
|
||||
@@ -118,9 +116,14 @@ public class SeqTest extends InstrumentationTestCase {
|
||||
}
|
||||
|
||||
public void testUnicode() {
|
||||
String want = "Hello, 世界";
|
||||
String got = Testpkg.StrDup(want);
|
||||
assertEquals("Strings should match", want, got);
|
||||
String[] tests = new String[]{
|
||||
"abcxyz09{}",
|
||||
"Hello, 世界",
|
||||
"\uffff\uD800\uDC00\uD800\uDC01\uD808\uDF45\uDBFF\uDFFF"};
|
||||
for (String want : tests) {
|
||||
String got = Testpkg.StrDup(want);
|
||||
assertEquals("Strings should match", want, got);
|
||||
}
|
||||
}
|
||||
|
||||
public void testNilErr() throws Exception {
|
||||
@@ -420,5 +423,37 @@ public class SeqTest extends InstrumentationTestCase {
|
||||
return null;
|
||||
}
|
||||
}));
|
||||
assertEquals("Go nil interface is null", null, Testpkg.NewNullInterface());
|
||||
assertEquals("Go nil struct pointer is null", null, Testpkg.NewNullStruct());
|
||||
}
|
||||
|
||||
public void testPassByteArray() {
|
||||
Testpkg.PassByteArray(new Testpkg.B.Stub() {
|
||||
@Override public void B(byte[] b) {
|
||||
byte[] want = new byte[]{1, 2, 3, 4};
|
||||
MoreAsserts.assertEquals("bytes should match", want, b);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void testReader() {
|
||||
byte[] b = new byte[8];
|
||||
try {
|
||||
long n = Testpkg.ReadIntoByteArray(b);
|
||||
assertEquals("wrote to the entire byte array", b.length, n);
|
||||
byte[] want = new byte[b.length];
|
||||
for (int i = 0; i < want.length; i++)
|
||||
want[i] = (byte)i;
|
||||
MoreAsserts.assertEquals("bytes should match", want, b);
|
||||
} catch (Exception e) {
|
||||
fail("Failed to write: " + e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public void testGoroutineCallback() {
|
||||
Testpkg.GoroutineCallback(new Testpkg.Receiver.Stub() {
|
||||
@Override public void Hello(String msg) {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include <jni.h>
|
||||
#include "seq.h"
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_go_Seq_setContext(JNIEnv* env, jclass clazz, jobject ctx) {
|
||||
JavaVM* vm;
|
||||
if ((*env)->GetJavaVM(env, &vm) != 0) {
|
||||
LOG_FATAL("failed to get JavaVM");
|
||||
}
|
||||
setContext(vm, (*env)->NewGlobalRef(env, ctx));
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package java // import "golang.org/x/mobile/bind/java"
|
||||
|
||||
// #cgo LDFLAGS: -llog
|
||||
//
|
||||
//#include <jni.h>
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/mobile/internal/mobileinit"
|
||||
)
|
||||
|
||||
//export setContext
|
||||
func setContext(vm *C.JavaVM, ctx C.jobject) {
|
||||
mobileinit.SetCurrentContext(unsafe.Pointer(vm), unsafe.Pointer(ctx))
|
||||
}
|
||||
@@ -5,8 +5,6 @@
|
||||
// Package java implements the Java language bindings.
|
||||
//
|
||||
// See the design document (http://golang.org/s/gobind).
|
||||
//
|
||||
// Currently, this works only for android.
|
||||
package java
|
||||
|
||||
import "C"
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef __GO_SEQ_HDR__
|
||||
#define __GO_SEQ_HDR__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <android/log.h>
|
||||
#include <jni.h>
|
||||
|
||||
#define LOG_INFO(...) __android_log_print(ANDROID_LOG_INFO, "go/Seq", __VA_ARGS__)
|
||||
#define LOG_FATAL(...) __android_log_print(ANDROID_LOG_FATAL, "go/Seq", __VA_ARGS__)
|
||||
|
||||
// Platform specific types
|
||||
typedef struct nstring {
|
||||
void *chars; // utf16 encoded
|
||||
jsize len; // length in bytes
|
||||
} nstring;
|
||||
typedef struct nbyteslice {
|
||||
void *ptr;
|
||||
jsize len;
|
||||
} nbyteslice;
|
||||
typedef jlong nint;
|
||||
|
||||
extern void go_seq_dec_ref(int32_t ref);
|
||||
extern int32_t go_seq_to_refnum(JNIEnv *env, jobject o);
|
||||
extern jobject go_seq_from_refnum(JNIEnv *env, int32_t refnum, jclass proxy_class, jmethodID proxy_cons);
|
||||
|
||||
extern void go_seq_maybe_throw_exception(JNIEnv *env, jobject msg);
|
||||
extern jstring go_seq_get_exception_message(JNIEnv *env);
|
||||
|
||||
extern jbyteArray go_seq_to_java_bytearray(JNIEnv *env, nbyteslice s, int copy);
|
||||
extern nbyteslice go_seq_from_java_bytearray(JNIEnv *env, jbyteArray s, int copy);
|
||||
|
||||
extern jstring go_seq_to_java_string(JNIEnv *env, nstring str);
|
||||
extern nstring go_seq_from_java_string(JNIEnv *env, jstring s, int copy);
|
||||
|
||||
// push_local_frame retrieves or creates the JNIEnv* for the current thread
|
||||
// and pushes a JNI reference frame. Must be matched with call to pop_local_frame.
|
||||
extern JNIEnv *go_seq_push_local_frame(jint cap);
|
||||
// Pop the current local frame, releasing all JNI local references in it
|
||||
extern void go_seq_pop_local_frame(JNIEnv *env);
|
||||
|
||||
#endif // __GO_SEQ_HDR__
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,262 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// C support functions for bindings. This file is copied into the
|
||||
// generated gomobile_bind package and compiled along with the
|
||||
// generated binding files.
|
||||
|
||||
#include <android/log.h>
|
||||
#include <errno.h>
|
||||
#include <jni.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
#include "seq.h"
|
||||
#include "_cgo_export.h"
|
||||
|
||||
#define NULL_REFNUM 41
|
||||
|
||||
static JavaVM *jvm;
|
||||
// jnienvs holds the per-thread JNIEnv* for Go threads where we called AttachCurrentThread.
|
||||
// A pthread key destructor is supplied to call DetachCurrentThread on exit. This trick is
|
||||
// documented in http://developer.android.com/training/articles/perf-jni.html under "Threads".
|
||||
static pthread_key_t jnienvs;
|
||||
|
||||
static jclass seq_class;
|
||||
static jmethodID seq_throw_exc;
|
||||
static jmethodID seq_getRef;
|
||||
static jmethodID seq_decRef;
|
||||
static jmethodID seq_incRef;
|
||||
|
||||
static jmethodID throwable_getMessage;
|
||||
|
||||
static jfieldID ref_objField;
|
||||
|
||||
// env_destructor is registered as a thread data key destructor to
|
||||
// clean up a Go thread that is attached to the JVM.
|
||||
static void env_destructor(void *env) {
|
||||
if ((*jvm)->DetachCurrentThread(jvm) != JNI_OK) {
|
||||
LOG_INFO("failed to detach current thread");
|
||||
}
|
||||
}
|
||||
|
||||
static JNIEnv *go_seq_get_thread_env(void) {
|
||||
JNIEnv *env;
|
||||
jint ret = (*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_6);
|
||||
if (ret != JNI_OK) {
|
||||
if (ret != JNI_EDETACHED) {
|
||||
LOG_FATAL("failed to get thread env");
|
||||
return;
|
||||
}
|
||||
if ((*jvm)->AttachCurrentThread(jvm, &env, NULL) != JNI_OK) {
|
||||
LOG_FATAL("failed to attach current thread");
|
||||
return;
|
||||
}
|
||||
pthread_setspecific(jnienvs, env);
|
||||
}
|
||||
return env;
|
||||
}
|
||||
|
||||
void go_seq_maybe_throw_exception(JNIEnv *env, jobject msg) {
|
||||
if (msg != NULL && (*env)->GetStringLength(env, msg) > 0) {
|
||||
(*env)->CallStaticVoidMethod(env, seq_class, seq_throw_exc, msg);
|
||||
}
|
||||
}
|
||||
|
||||
jstring go_seq_get_exception_message(JNIEnv *env) {
|
||||
jthrowable exc = (*env)->ExceptionOccurred(env);
|
||||
if (!exc) {
|
||||
return NULL;
|
||||
}
|
||||
(*env)->ExceptionClear(env);
|
||||
return (*env)->CallObjectMethod(env, exc, throwable_getMessage);
|
||||
}
|
||||
|
||||
jbyteArray go_seq_to_java_bytearray(JNIEnv *env, nbyteslice s, int copy) {
|
||||
if (s.ptr == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
jbyteArray res = (*env)->NewByteArray(env, s.len);
|
||||
if (res == NULL) {
|
||||
LOG_FATAL("NewByteArray failed");
|
||||
return res;
|
||||
}
|
||||
(*env)->SetByteArrayRegion(env, res, 0, s.len, s.ptr);
|
||||
if (copy) {
|
||||
free(s.ptr);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
nstring go_seq_from_java_string(JNIEnv *env, jstring str, int copy) {
|
||||
struct nstring res = {NULL, 0};
|
||||
if (str == NULL) {
|
||||
return res;
|
||||
}
|
||||
jsize nchars = (*env)->GetStringLength(env, str);
|
||||
jchar *chars = (jchar *)(*env)->GetStringChars(env, str, NULL);
|
||||
if (chars == NULL) {
|
||||
LOG_FATAL("GetStringChars failed");
|
||||
return res;
|
||||
}
|
||||
if (copy) {
|
||||
void *arr_copy = malloc(nchars*2);
|
||||
if (arr_copy == NULL) {
|
||||
LOG_FATAL("malloc failed");
|
||||
return res;
|
||||
}
|
||||
memcpy(arr_copy, chars, nchars*2);
|
||||
(*env)->ReleaseStringChars(env, str, chars);
|
||||
chars = (jchar *)arr_copy;
|
||||
}
|
||||
res.chars = chars;
|
||||
res.len = nchars;
|
||||
return res;
|
||||
}
|
||||
|
||||
nbyteslice go_seq_from_java_bytearray(JNIEnv *env, jbyteArray arr, int copy) {
|
||||
struct nbyteslice res = {NULL, 0};
|
||||
if (arr == NULL) {
|
||||
return res;
|
||||
}
|
||||
|
||||
jsize len = (*env)->GetArrayLength(env, arr);
|
||||
if (len == 0) {
|
||||
return res;
|
||||
}
|
||||
jbyte *ptr = (*env)->GetByteArrayElements(env, arr, NULL);
|
||||
if (ptr == NULL) {
|
||||
LOG_FATAL("GetByteArrayElements failed");
|
||||
return res;
|
||||
}
|
||||
if (copy) {
|
||||
void *ptr_copy = (void *)malloc(len);
|
||||
if (ptr_copy == NULL) {
|
||||
LOG_FATAL("malloc failed");
|
||||
return res;
|
||||
}
|
||||
memcpy(ptr_copy, ptr, len);
|
||||
(*env)->ReleaseByteArrayElements(env, arr, ptr, JNI_ABORT);
|
||||
ptr = (jbyte *)ptr_copy;
|
||||
}
|
||||
res.ptr = ptr;
|
||||
res.len = len;
|
||||
return res;
|
||||
}
|
||||
|
||||
int32_t go_seq_to_refnum(JNIEnv *env, jobject o) {
|
||||
if (o == NULL) {
|
||||
return NULL_REFNUM;
|
||||
}
|
||||
return (int32_t)(*env)->CallStaticIntMethod(env, seq_class, seq_incRef, o);
|
||||
}
|
||||
|
||||
jobject go_seq_from_refnum(JNIEnv *env, int32_t refnum, jclass proxy_class, jmethodID proxy_cons) {
|
||||
if (refnum == NULL_REFNUM) {
|
||||
return NULL;
|
||||
}
|
||||
// Seq.Ref ref = Seq.getRef(refnum)
|
||||
jobject ref = (*env)->CallStaticObjectMethod(env, seq_class, seq_getRef, (jint)refnum);
|
||||
if (ref == NULL) {
|
||||
LOG_FATAL("Unknown reference: %d", refnum);
|
||||
return NULL;
|
||||
}
|
||||
if (refnum > 0) { // Java object
|
||||
// return ref.obj
|
||||
return (*env)->GetObjectField(env, ref, ref_objField);
|
||||
} else {
|
||||
// return new <Proxy>(ref)
|
||||
return (*env)->NewObject(env, proxy_class, proxy_cons, ref);
|
||||
}
|
||||
}
|
||||
|
||||
// go_seq_to_java_string converts a nstring to a jstring.
|
||||
jstring go_seq_to_java_string(JNIEnv *env, nstring str) {
|
||||
jstring s = (*env)->NewString(env, str.chars, str.len);
|
||||
if (str.chars != NULL) {
|
||||
free(str.chars);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// go_seq_push_local_frame retrieves or creates the JNIEnv* for the current thread
|
||||
// and pushes a JNI reference frame. Must be matched with call to go_seq_pop_local_frame.
|
||||
JNIEnv *go_seq_push_local_frame(jint cap) {
|
||||
JNIEnv *env = go_seq_get_thread_env();
|
||||
if ((*env)->PushLocalFrame(env, cap) < 0) {
|
||||
LOG_FATAL("PushLocalFrame failed");
|
||||
}
|
||||
return env;
|
||||
}
|
||||
|
||||
// Pop the current local frame, freeing all JNI local references in it
|
||||
void go_seq_pop_local_frame(JNIEnv *env) {
|
||||
(*env)->PopLocalFrame(env, NULL);
|
||||
}
|
||||
|
||||
void go_seq_dec_ref(int32_t ref) {
|
||||
JNIEnv *env = go_seq_get_thread_env();
|
||||
(*env)->CallStaticVoidMethod(env, seq_class, seq_decRef, (jint)ref);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_go_Seq_init(JNIEnv *env, jclass clazz) {
|
||||
if ((*env)->GetJavaVM(env, &jvm) != 0) {
|
||||
LOG_FATAL("failed to get JVM");
|
||||
return;
|
||||
}
|
||||
if (pthread_key_create(&jnienvs, env_destructor) != 0) {
|
||||
LOG_FATAL("failed to initialize jnienvs thread local storage");
|
||||
return;
|
||||
}
|
||||
|
||||
seq_class = (*env)->NewGlobalRef(env, clazz);
|
||||
seq_throw_exc = (*env)->GetStaticMethodID(env, seq_class, "throwException", "(Ljava/lang/String;)V");
|
||||
if (seq_throw_exc == NULL) {
|
||||
LOG_FATAL("failed to find method Seq.throwException");
|
||||
return;
|
||||
}
|
||||
|
||||
seq_getRef = (*env)->GetStaticMethodID(env, seq_class, "getRef", "(I)Lgo/Seq$Ref;");
|
||||
if (seq_getRef == NULL) {
|
||||
LOG_FATAL("failed to find method Seq.getRef");
|
||||
return;
|
||||
}
|
||||
seq_decRef = (*env)->GetStaticMethodID(env, seq_class, "decRef", "(I)V");
|
||||
if (seq_decRef == NULL) {
|
||||
LOG_FATAL("failed to find method Seq.decRef");
|
||||
return;
|
||||
}
|
||||
seq_incRef = (*env)->GetStaticMethodID(env, seq_class, "incRef", "(Lgo/Seq$Object;)I");
|
||||
if (seq_incRef == NULL) {
|
||||
LOG_FATAL("failed to find method Seq.incRef");
|
||||
return;
|
||||
}
|
||||
jclass throwable_class = (*env)->FindClass(env, "java/lang/Throwable");
|
||||
if (throwable_class == NULL) {
|
||||
LOG_FATAL("failed to find Throwable class");
|
||||
return;
|
||||
}
|
||||
throwable_getMessage = (*env)->GetMethodID(env, throwable_class, "getMessage", "()Ljava/lang/String;");
|
||||
if (throwable_getMessage == NULL) {
|
||||
LOG_FATAL("failed to find method Throwable.getMessage");
|
||||
return;
|
||||
}
|
||||
jclass ref_class = (*env)->FindClass(env, "go/Seq$Ref");
|
||||
if (ref_class == NULL) {
|
||||
LOG_FATAL("failed to find the Seq.Ref class");
|
||||
return;
|
||||
}
|
||||
ref_objField = (*env)->GetFieldID(env, ref_class, "obj", "Lgo/Seq$Object;");
|
||||
if (ref_objField == NULL) {
|
||||
LOG_FATAL("failed to find the Seq.Ref.obj field");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_go_Seq_destroyRef(JNIEnv *env, jclass clazz, jint refnum) {
|
||||
DestroyRef(refnum);
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package java // import "golang.org/x/mobile/bind/java"
|
||||
|
||||
//#cgo LDFLAGS: -llog
|
||||
//#include <android/log.h>
|
||||
//#include <jni.h>
|
||||
//#include <stdint.h>
|
||||
//#include <string.h>
|
||||
//#include "seq_android.h"
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/mobile/bind/seq"
|
||||
"golang.org/x/mobile/internal/mobileinit"
|
||||
)
|
||||
|
||||
const maxSliceLen = 1<<31 - 1
|
||||
|
||||
const debug = false
|
||||
|
||||
// Send is called by Java to send a request to run a Go function.
|
||||
//export Send
|
||||
func Send(descriptor string, code int, req *C.uint8_t, reqlen C.size_t, res **C.uint8_t, reslen *C.size_t) {
|
||||
fn := seq.Registry[descriptor][code]
|
||||
if fn == nil {
|
||||
panic(fmt.Sprintf("invalid descriptor(%s) and code(0x%x)", descriptor, code))
|
||||
}
|
||||
|
||||
var in, out *seq.Buffer
|
||||
if req != nil && reqlen > 0 {
|
||||
in = &seq.Buffer{
|
||||
Data: (*[maxSliceLen]byte)(unsafe.Pointer(req))[:reqlen],
|
||||
}
|
||||
}
|
||||
if res != nil {
|
||||
out = new(seq.Buffer)
|
||||
}
|
||||
|
||||
fn(out, in)
|
||||
|
||||
if res != nil {
|
||||
// BUG(hyangah): the function returning a go byte slice (so fn writes a pointer into 'out') is unsafe.
|
||||
// After fn is complete here, Go runtime is free to collect or move the pointed byte slice
|
||||
// contents. (Explicitly calling runtime.GC here will surface the problem?)
|
||||
// Without pinning support from Go side, it will be hard to fix it without extra copying.
|
||||
seqToBuf(res, reslen, out)
|
||||
}
|
||||
}
|
||||
|
||||
// DestroyRef is called by Java to inform Go it is done with a reference.
|
||||
//export DestroyRef
|
||||
func DestroyRef(refnum C.int32_t) {
|
||||
seq.Delete(int32(refnum))
|
||||
}
|
||||
|
||||
func seqToBuf(bufptr **C.uint8_t, lenptr *C.size_t, buf *seq.Buffer) {
|
||||
if debug {
|
||||
fmt.Printf("seqToBuf tag 1, len(buf.Data)=%d, *lenptr=%d\n", len(buf.Data), *lenptr)
|
||||
}
|
||||
if len(buf.Data) == 0 {
|
||||
*lenptr = 0
|
||||
return
|
||||
}
|
||||
if len(buf.Data) > int(*lenptr) {
|
||||
// TODO(crawshaw): realloc
|
||||
C.free(unsafe.Pointer(*bufptr))
|
||||
m := C.malloc(C.size_t(len(buf.Data)))
|
||||
if uintptr(m) == 0 {
|
||||
panic(fmt.Sprintf("malloc failed, size=%d", len(buf.Data)))
|
||||
}
|
||||
*bufptr = (*C.uint8_t)(m)
|
||||
*lenptr = C.size_t(len(buf.Data))
|
||||
}
|
||||
C.memcpy(unsafe.Pointer(*bufptr), unsafe.Pointer(&buf.Data[0]), C.size_t(len(buf.Data)))
|
||||
}
|
||||
|
||||
// transact calls a method on a Java object instance.
|
||||
// It blocks until the call is complete.
|
||||
func transact(ref *seq.Ref, _ string, code int, inBuf *seq.Buffer) *seq.Buffer {
|
||||
var (
|
||||
out *C.uint8_t = nil
|
||||
outLen C.size_t = 0
|
||||
in *C.uint8_t = nil
|
||||
inLen C.size_t = 0
|
||||
)
|
||||
|
||||
if len(inBuf.Data) > 0 {
|
||||
in = (*C.uint8_t)(unsafe.Pointer(&inBuf.Data[0]))
|
||||
inLen = C.size_t(len(inBuf.Data))
|
||||
}
|
||||
|
||||
C.recv(C.int32_t(ref.Num), C.int(code), in, inLen, &out, &outLen)
|
||||
if outLen > 0 {
|
||||
outBuf := &seq.Buffer{
|
||||
Data: make([]byte, outLen),
|
||||
}
|
||||
copy(outBuf.Data, (*[maxSliceLen]byte)(unsafe.Pointer(out))[:outLen])
|
||||
return outBuf
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeString(out *seq.Buffer, v string) {
|
||||
out.WriteUTF16(v)
|
||||
}
|
||||
|
||||
func decodeString(in *seq.Buffer) string {
|
||||
return in.ReadUTF16()
|
||||
}
|
||||
|
||||
func init() {
|
||||
seq.FinalizeRef = func(ref *seq.Ref) {
|
||||
if ref.Num < 0 {
|
||||
panic(fmt.Sprintf("not a Java ref: %d", ref.Num))
|
||||
}
|
||||
transact(ref, "", -1, new(seq.Buffer))
|
||||
}
|
||||
|
||||
seq.Transact = transact
|
||||
seq.EncString = encodeString
|
||||
seq.DecString = decodeString
|
||||
}
|
||||
|
||||
//export setContext
|
||||
func setContext(vm *C.JavaVM, ctx C.jobject) {
|
||||
mobileinit.SetCurrentContext(unsafe.Pointer(vm), unsafe.Pointer(ctx))
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gomobile_bind
|
||||
|
||||
// Go support functions for bindings. This file is copied into the
|
||||
// generated gomobile_bind package and compiled along with the
|
||||
// generated binding files.
|
||||
|
||||
//#cgo LDFLAGS: -llog
|
||||
//#include <jni.h>
|
||||
//#include <stdint.h>
|
||||
//#include <stdlib.h>
|
||||
//#include "seq.h"
|
||||
import "C"
|
||||
import (
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/mobile/bind/seq"
|
||||
)
|
||||
|
||||
// DestroyRef is called by Java to inform Go it is done with a reference.
|
||||
//export DestroyRef
|
||||
func DestroyRef(refnum C.int32_t) {
|
||||
seq.Delete(int32(refnum))
|
||||
}
|
||||
|
||||
// encodeString encodes a Go string to utf16 and returns a Java string as a nstring
|
||||
// containing the jstring.
|
||||
// encodeString uses UTF16 as the intermediate format. Note that UTF8 is an obvious
|
||||
// alternative, but JNI only supports a C-safe variant of UTF8 (modified UTF8).
|
||||
// The returned data is always a copy, regardless of cpy, and will be freed in
|
||||
// go_seq_to_java_string.
|
||||
func encodeString(s string, cpy bool) C.nstring {
|
||||
n := C.int(len(s))
|
||||
if n == 0 {
|
||||
return C.nstring{}
|
||||
}
|
||||
// Allocate enough for the worst case estimate, every character is a surrogate pair
|
||||
worstCaseLen := 4 * len(s)
|
||||
utf16buf := C.malloc(C.size_t(worstCaseLen))
|
||||
if utf16buf == nil {
|
||||
panic("encodeString: malloc failed")
|
||||
}
|
||||
chars := (*[1<<30 - 1]uint16)(unsafe.Pointer(utf16buf))[:worstCaseLen/2 : worstCaseLen/2]
|
||||
nchars := seq.UTF16Encode(s, chars)
|
||||
return C.nstring{chars: unsafe.Pointer(utf16buf), len: C.jsize(nchars)}
|
||||
}
|
||||
|
||||
// decodeString decodes a nstring (jstring) to a Go string.
|
||||
// If cpy is set, the string contains a copy and is freed.
|
||||
func decodeString(str C.nstring, cpy bool) string {
|
||||
if str.chars == nil {
|
||||
return ""
|
||||
}
|
||||
chars := (*[1<<30 - 1]uint16)(unsafe.Pointer(str.chars))[:str.len]
|
||||
s := string(utf16.Decode(chars)) // TODO: avoid the []rune allocation
|
||||
if cpy {
|
||||
C.free(str.chars)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// fromSlice converts a slice to a jbyteArray cast as a nbyteslice. If cpy
|
||||
// is set, the returned slice is a copy to be free by go_seq_to_java_bytearray.
|
||||
func fromSlice(s []byte, cpy bool) C.nbyteslice {
|
||||
if s == nil || len(s) == 0 {
|
||||
return C.nbyteslice{}
|
||||
}
|
||||
var ptr *C.jbyte
|
||||
n := C.jsize(len(s))
|
||||
if cpy {
|
||||
ptr = (*C.jbyte)(C.malloc(C.size_t(n)))
|
||||
if ptr == nil {
|
||||
panic("fromSlice: malloc failed")
|
||||
}
|
||||
copy((*[1<<31 - 1]byte)(unsafe.Pointer(ptr))[:n], s)
|
||||
} else {
|
||||
ptr = (*C.jbyte)(unsafe.Pointer(&s[0]))
|
||||
}
|
||||
return C.nbyteslice{ptr: unsafe.Pointer(ptr), len: n}
|
||||
}
|
||||
|
||||
// toSlice takes a nbyteslice (jbyteArray) and returns a byte slice
|
||||
// with the data. If cpy is set, the slice contains a copy of the data and is
|
||||
// freed.
|
||||
func toSlice(s C.nbyteslice, cpy bool) []byte {
|
||||
if s.ptr == nil || s.len == 0 {
|
||||
return nil
|
||||
}
|
||||
var b []byte
|
||||
if cpy {
|
||||
b = C.GoBytes(s.ptr, C.int(s.len))
|
||||
C.free(s.ptr)
|
||||
} else {
|
||||
b = (*[1<<31 - 1]byte)(unsafe.Pointer(s.ptr))[:s.len:s.len]
|
||||
}
|
||||
return b
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
void init_seq(void* vm, void* classfinder);
|
||||
JNIEnv *get_thread_env(void);
|
||||
void recv(int32_t ref, int code, uint8_t *in_ptr, size_t in_len, uint8_t **out_ptr, size_t *out_len);
|
||||
@@ -236,6 +236,14 @@ type NullTest interface {
|
||||
Null() NullTest
|
||||
}
|
||||
|
||||
func NewNullInterface() I {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewNullStruct() *S {
|
||||
return nil
|
||||
}
|
||||
|
||||
func CallWithNull(_null NullTest, nuller NullTest) bool {
|
||||
return _null == nil && nuller.Null() == nil
|
||||
}
|
||||
@@ -243,3 +251,35 @@ func CallWithNull(_null NullTest, nuller NullTest) bool {
|
||||
type Issue14168 interface {
|
||||
F(seq int32)
|
||||
}
|
||||
|
||||
func ReadIntoByteArray(s []byte) (int, error) {
|
||||
if len(s) != cap(s) {
|
||||
return 0, fmt.Errorf("cap %d != len %d", cap(s), len(s))
|
||||
}
|
||||
for i := 0; i < len(s); i++ {
|
||||
s[i] = byte(i)
|
||||
}
|
||||
return len(s), nil
|
||||
}
|
||||
|
||||
type B interface {
|
||||
B(b []byte)
|
||||
}
|
||||
|
||||
func PassByteArray(b B) {
|
||||
b.B([]byte{1, 2, 3, 4})
|
||||
}
|
||||
|
||||
func GoroutineCallback(r Receiver) {
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
// Run it multiple times to increase the chance that the goroutine
|
||||
// will use different threads for the call. Use a long argument string to
|
||||
// make sure the JNI calls take more time.
|
||||
for i := 0; i < 100000; i++ {
|
||||
r.Hello("HelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHello")
|
||||
}
|
||||
close(done)
|
||||
}()
|
||||
<-done
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user