ui/app: call main from Android and iOS

Android can only run c-shared libraries which means that every
Gio program must create its window and event loop from an init
function.

The same applies to iOS but for a more benign reason: the gio tool
builds programs in c-archive mode for iOS and links the binary with
a Objective-C driver.

Allow Gio programs to run off its main function by linking to and
invoking main even from Android libraries and iOS ditto.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2019-07-22 11:17:09 +02:00
parent 4e0d820a5b
commit c080a54038
13 changed files with 87 additions and 41 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ module gioui.org/apps
go 1.12
require (
gioui.org/ui v0.0.0-20190721195854-dd081c78b96d
gioui.org/ui v0.0.0-20190721200230-1ee8c08f3bf9
github.com/google/go-github/v24 v24.0.1
golang.org/x/exp v0.0.0-20190627132806-fd42eb6b336f
golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9
+6 -3
View File
@@ -139,10 +139,13 @@ func exeIOS(tmpDir, target, app string, bi *buildInfo) error {
const mainmSrc = `@import UIKit;
@import Gio;
void gio_runMain(void);
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([GioAppDelegate class]));
}
@autoreleasepool {
gio_runMain();
return UIApplicationMain(argc, argv, nil, NSStringFromClass([GioAppDelegate class]));
}
}`
if err := ioutil.WriteFile(mainm, []byte(mainmSrc), 0660); err != nil {
return err
+9 -10
View File
@@ -27,7 +27,6 @@ import java.io.UnsupportedEncodingException;
public class GioView extends SurfaceView implements Choreographer.FrameCallback {
private final static Object initLock = new Object();
private static boolean jniLoaded;
private static String dataDir;
private final SurfaceHolder.Callback callbacks;
private final InputMethodManager imm;
@@ -39,20 +38,19 @@ public class GioView extends SurfaceView implements Choreographer.FrameCallback
if (jniLoaded) {
return;
}
dataDir = appCtx.getFilesDir().getAbsolutePath();
String dataDir = appCtx.getFilesDir().getAbsolutePath();
byte[] dataDirUTF8;
try {
dataDirUTF8 = dataDir.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
System.loadLibrary("gio");
runGoMain(dataDirUTF8);
jniLoaded = true;
}
}
static byte[] dataDir() {
try {
return dataDir.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
public GioView(Context context) {
this(context, null);
}
@@ -221,6 +219,7 @@ public class GioView extends SurfaceView implements Choreographer.FrameCallback
static private native void onFrameCallback(long handle, long nanos);
static private native boolean onBack(long handle);
static private native void onFocusChange(long handle, boolean focus);
static private native void runGoMain(byte[] dataDir);
private static class InputConnection extends BaseInputConnection {
private final Editable editable;
+1 -3
View File
@@ -20,8 +20,6 @@ func dataDir() (string, error) {
return dataPath, nil
}
//export setDataDir
func setDataDir(cdir *C.char, len C.int) {
dir := C.GoStringN(cdir, len)
func setDataDir(dir string) {
dataDirChan <- dir
}
+1 -1
View File
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: Unlicense OR MIT
//+build darwin,ios
// +build darwin,ios
package app
+1 -1
View File
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: Unlicense OR MIT
//+build darwin,ios
// +build darwin,ios
@import UIKit;
@import OpenGLES;
+1 -1
View File
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: Unlicense OR MIT
//+build darwin,!ios
// +build darwin,!ios
@import AppKit;
+19 -17
View File
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: Unlicense OR MIT
#include <jni.h>
#include <dlfcn.h>
#include <android/log.h>
#include "os_android.h"
#include "_cgo_export.h"
@@ -18,6 +20,11 @@ JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
}
static const JNINativeMethod methods[] = {
{
.name = "runGoMain",
.signature = "([B)V",
.fnPtr = runGoMain
},
{
.name = "onCreateView",
.signature = "(Lorg/gioui/GioView;)J",
@@ -93,23 +100,6 @@ JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
return -1;
}
// Initialize data dir.
jmethodID dataDirMethod = (*env)->GetStaticMethodID(env, viewClass, "dataDir", "()[B");
if (dataDirMethod == NULL) {
return -1;
}
jbyteArray dirArr = (*env)->CallStaticObjectMethod(env, viewClass, dataDirMethod);
if (dirArr == NULL) {
return -1;
}
jbyte *dir = (*env)->GetByteArrayElements(env, dirArr, NULL);
if (dir == NULL) {
return -1;
}
jint n = (*env)->GetArrayLength(env, dirArr);
setDataDir((char *)dir, n);
(*env)->ReleaseByteArrayElements(env, dirArr, dir, JNI_ABORT);
return JNI_VERSION_1_6;
}
@@ -164,3 +154,15 @@ void gio_jni_CallVoidMethod(JNIEnv *env, jobject obj, jmethodID methodID) {
void gio_jni_CallVoidMethod_J(JNIEnv *env, jobject obj, jmethodID methodID, jlong a1) {
(*env)->CallVoidMethod(env, obj, methodID, a1);
}
jbyte *gio_jni_GetByteArrayElements(JNIEnv *env, jbyteArray arr) {
return (*env)->GetByteArrayElements(env, arr, NULL);
}
void gio_jni_ReleaseByteArrayElements(JNIEnv *env, jbyteArray arr, jbyte *bytes) {
(*env)->ReleaseByteArrayElements(env, arr, bytes, JNI_ABORT);
}
jsize gio_jni_GetArrayLength(JNIEnv *env, jbyteArray arr) {
return (*env)->GetArrayLength(env, arr);
}
+13 -2
View File
@@ -76,6 +76,19 @@ func jniGetStaticMethodID(env *C.JNIEnv, class C.jclass, method, sig string) C.j
return C.gio_jni_GetStaticMethodID(env, class, m, s)
}
//export runGoMain
func runGoMain(env *C.JNIEnv, class C.jclass, jdataDir C.jbyteArray) {
dirBytes := C.gio_jni_GetByteArrayElements(env, jdataDir)
if dirBytes == nil {
panic("runGoMain: GetByteArrayElements failed")
}
n := C.gio_jni_GetArrayLength(env, jdataDir)
dataDir := C.GoStringN((*C.char)(unsafe.Pointer(dirBytes)), n)
setDataDir(dataDir)
C.gio_jni_ReleaseByteArrayElements(env, jdataDir, dirBytes)
runMain()
}
//export setJVM
func setJVM(vm *C.JavaVM) {
theJVM = vm
@@ -407,8 +420,6 @@ func (w *window) showTextInput(show bool) {
}
func Main() {
// Android runs in c-shared mode where main is never reached.
panic("call to Main from outside main")
}
func createWindow(window *Window, opts *WindowOptions) error {
+3
View File
@@ -14,3 +14,6 @@ __attribute__ ((visibility ("hidden"))) jfloat gio_jni_CallFloatMethod(JNIEnv *e
__attribute__ ((visibility ("hidden"))) jint gio_jni_CallIntMethod(JNIEnv *env, jobject obj, jmethodID methodID);
__attribute__ ((visibility ("hidden"))) void gio_jni_CallVoidMethod(JNIEnv *env, jobject obj, jmethodID methodID);
__attribute__ ((visibility ("hidden"))) void gio_jni_CallVoidMethod_J(JNIEnv *env, jobject obj, jmethodID methodID, jlong a1);
__attribute__ ((visibility ("hidden"))) jbyte *gio_jni_GetByteArrayElements(JNIEnv *env, jbyteArray arr);
__attribute__ ((visibility ("hidden"))) void gio_jni_ReleaseByteArrayElements(JNIEnv *env, jbyteArray arr, jbyte *bytes);
__attribute__ ((visibility ("hidden"))) jsize gio_jni_GetArrayLength(JNIEnv *env, jbyteArray arr);
-2
View File
@@ -252,6 +252,4 @@ func createWindow(win *Window, opts *WindowOptions) error {
}
func Main() {
// iOS runs in c-archive mode, so this is never reached.
panic("call to Main from outside main")
}
+22
View File
@@ -0,0 +1,22 @@
// +build android darwin,ios
package app
// Android only supports non-Java programs as c-shared libraries.
// Unfortunately, Go does not run a program's main function in
// library mode. To make Gio programs simpler and uniform, we'll
// link to the main function here and call it from Java.
import _ "unsafe" // for go:linkname
//go:linkname mainMain main.main
func mainMain()
func runMain() {
go func() {
// Indirect call, since the linker does not know the address of main when
// laying down this package.
fn := mainMain
fn()
}()
}
+10
View File
@@ -0,0 +1,10 @@
// +build darwin,ios
package app
import "C"
//export gio_runMain
func gio_runMain() {
runMain()
}