/*************************************************************************/ /* godot_android.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ /* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ /* "Software"), to deal in the Software without restriction, including */ /* without limitation the rights to use, copy, modify, merge, publish, */ /* distribute, sublicense, and/or sell copies of the Software, and to */ /* permit persons to whom the Software is furnished to do so, subject to */ /* the following conditions: */ /* */ /* The above copyright notice and this permission notice shall be */ /* included in all copies or substantial portions of the Software. */ /* */ /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ #ifdef ANDROID_NATIVE_ACTIVITY #include #include #include #include #include #include #include #include #include "file_access_android.h" #include #include #include #include "os_android.h" #include "globals.h" #include "main/main.h" #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "godot", __VA_ARGS__)) #define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "godot", __VA_ARGS__)) extern "C" { JNIEXPORT void JNICALL Java_com_android_godot_Godot_registerSingleton(JNIEnv * env, jobject obj, jstring name,jobject p_object); JNIEXPORT void JNICALL Java_com_android_godot_Godot_registerMethod(JNIEnv * env, jobject obj, jstring sname, jstring name, jstring ret, jobjectArray args); JNIEXPORT jstring JNICALL Java_com_android_godot_Godot_getGlobal(JNIEnv * env, jobject obj, jstring path); }; class JNISingleton : public Object { OBJ_TYPE( JNISingleton, Object ); struct MethodData { jmethodID method; Variant::Type ret_type; Vector argtypes; }; jobject instance; Map method_map; JNIEnv *env; public: void update_env(JNIEnv *p_env) { env=p_env; } virtual Variant call(const StringName& p_method,const Variant** p_args,int p_argcount,Variant::CallError &r_error) { print_line("attempt to call "+String(p_method)); r_error.error=Variant::CallError::CALL_OK; Map::Element *E=method_map.find(p_method); if (!E) { print_line("no exists"); r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; return Variant(); } int ac = E->get().argtypes.size(); if (acp_argcount) { print_line("manyargs"); r_error.error=Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; r_error.argument=ac; return Variant(); } for(int i=0;iget_type(),E->get().argtypes[i])) { r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument=i; r_error.expected=E->get().argtypes[i]; } } jvalue *v=NULL; if (p_argcount) { v=(jvalue*)alloca( sizeof(jvalue)*p_argcount ); } for(int i=0;iget().argtypes[i]) { case Variant::BOOL: { v[i].z=*p_args[i]; } break; case Variant::INT: { v[i].i=*p_args[i]; } break; case Variant::REAL: { v[i].f=*p_args[i]; } break; case Variant::STRING: { String s = *p_args[i]; jstring jStr = env->NewStringUTF(s.utf8().get_data()); v[i].l=jStr; } break; case Variant::STRING_ARRAY: { DVector sarray = *p_args[i]; jobjectArray arr = env->NewObjectArray(sarray.size(),env->FindClass("java/lang/String"),env->NewStringUTF("")); for(int j=0;jSetObjectArrayElement(arr,j,env->NewStringUTF( sarray[i].utf8().get_data() )); } v[i].l=arr; } break; case Variant::INT_ARRAY: { DVector array = *p_args[i]; jintArray arr = env->NewIntArray(array.size()); DVector::Read r = array.read(); env->SetIntArrayRegion(arr,0,array.size(),r.ptr()); v[i].l=arr; } break; case Variant::REAL_ARRAY: { DVector array = *p_args[i]; jfloatArray arr = env->NewFloatArray(array.size()); DVector::Read r = array.read(); env->SetFloatArrayRegion(arr,0,array.size(),r.ptr()); v[i].l=arr; } break; default: { ERR_FAIL_V(Variant()); } break; } } print_line("calling method!!"); Variant ret; switch(E->get().ret_type) { case Variant::NIL: { print_line("call void"); env->CallVoidMethodA(instance,E->get().method,v); } break; case Variant::BOOL: { ret = env->CallBooleanMethodA(instance,E->get().method,v); print_line("call bool"); } break; case Variant::INT: { ret = env->CallIntMethodA(instance,E->get().method,v); print_line("call int"); } break; case Variant::REAL: { ret = env->CallFloatMethodA(instance,E->get().method,v); } break; case Variant::STRING: { jobject o = env->CallObjectMethodA(instance,E->get().method,v); String singname = env->GetStringUTFChars((jstring)o, NULL ); } break; case Variant::STRING_ARRAY: { jobjectArray arr = (jobjectArray)env->CallObjectMethodA(instance,E->get().method,v); int stringCount = env->GetArrayLength(arr); DVector sarr; for (int i=0; iGetObjectArrayElement(arr, i); const char *rawString = env->GetStringUTFChars(string, 0); sarr.push_back(String(rawString)); } ret=sarr; } break; case Variant::INT_ARRAY: { jintArray arr = (jintArray)env->CallObjectMethodA(instance,E->get().method,v); int fCount = env->GetArrayLength(arr); DVector sarr; sarr.resize(fCount); DVector::Write w = sarr.write(); env->GetIntArrayRegion(arr,0,fCount,w.ptr()); w = DVector::Write(); ret=sarr; } break; case Variant::REAL_ARRAY: { jfloatArray arr = (jfloatArray)env->CallObjectMethodA(instance,E->get().method,v); int fCount = env->GetArrayLength(arr); DVector sarr; sarr.resize(fCount); DVector::Write w = sarr.write(); env->GetFloatArrayRegion(arr,0,fCount,w.ptr()); w = DVector::Write(); ret=sarr; } break; default: { print_line("failure.."); ERR_FAIL_V(Variant()); } break; } print_line("success"); return ret; } jobject get_instance() const { return instance; } void set_instance(jobject p_instance) { instance=p_instance; } void add_method(const StringName& p_name, jmethodID p_method,const Vector& p_args, Variant::Type p_ret_type) { MethodData md; md.method=p_method; md.argtypes=p_args; md.ret_type=p_ret_type; method_map[p_name]=md; } JNISingleton() {} }; //JNIEnv *JNISingleton::env=NULL; static HashMap jni_singletons; struct engine { struct android_app* app; OS_Android *os; JNIEnv *jni; ASensorManager* sensorManager; const ASensor* accelerometerSensor; ASensorEventQueue* sensorEventQueue; bool display_active; bool requested_quit; int animating; EGLDisplay display; EGLSurface surface; EGLContext context; int32_t width; int32_t height; }; /** * Initialize an EGL context for the current display. */ static int engine_init_display(struct engine* engine,bool p_gl2) { // initialize OpenGL ES and EGL /* * Here specify the attributes of the desired configuration. * Below, we select an EGLConfig with at least 8 bits per color * component compatible with on-screen windows */ const EGLint gl2_attribs[] = { // EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_BLUE_SIZE, 4, EGL_GREEN_SIZE, 4, EGL_RED_SIZE, 4, EGL_ALPHA_SIZE, 0, EGL_DEPTH_SIZE, 16, EGL_STENCIL_SIZE, EGL_DONT_CARE, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_NONE }; const EGLint gl1_attribs[] = { // EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_BLUE_SIZE, 4, EGL_GREEN_SIZE, 4, EGL_RED_SIZE, 4, EGL_ALPHA_SIZE, 0, EGL_DEPTH_SIZE, 16, EGL_STENCIL_SIZE, EGL_DONT_CARE, EGL_NONE }; const EGLint *attribs=p_gl2?gl2_attribs:gl1_attribs; EGLint w, h, dummy, format; EGLint numConfigs; EGLConfig config; EGLSurface surface; EGLContext context; EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); eglInitialize(display, 0, 0); /* Here, the application chooses the configuration it desires. In this * sample, we have a very simplified selection process, where we pick * the first EGLConfig that matches our criteria */ eglChooseConfig(display, attribs, &config, 1, &numConfigs); LOGI("Num configs: %i\n",numConfigs); /* EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is * guaranteed to be accepted by ANativeWindow_setBuffersGeometry(). * As soon as we picked a EGLConfig, we can safely reconfigure the * ANativeWindow buffers to match, using EGL_NATIVE_VISUAL_ID. */ eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format); ANativeWindow_setBuffersGeometry(engine->app->window, 0, 0, format); //ANativeWindow_setFlags(engine->app->window, 0, 0, format|); surface = eglCreateWindowSurface(display, config, engine->app->window, NULL); const EGLint context_attribs[] = { EGL_CONTEXT_CLIENT_VERSION,2, EGL_NONE }; context = eglCreateContext(display, config, EGL_NO_CONTEXT, p_gl2?context_attribs:NULL); if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) { LOGW("Unable to eglMakeCurrent"); return -1; } eglQuerySurface(display, surface, EGL_WIDTH, &w); eglQuerySurface(display, surface, EGL_HEIGHT, &h); print_line("INIT VIDEO MODE: "+itos(w)+","+itos(h)); //engine->os->set_egl_extensions(eglQueryString(display,EGL_EXTENSIONS)); engine->os->init_video_mode(w,h); engine->display = display; engine->context = context; engine->surface = surface; engine->width = w; engine->height = h; engine->display_active=true; //engine->state.angle = 0; // Initialize GL state. //glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST); glEnable(GL_CULL_FACE); // glShadeModel(GL_SMOOTH); glDisable(GL_DEPTH_TEST); LOGI("GL Version: %s - %s %s\n", glGetString(GL_VERSION),glGetString(GL_VENDOR), glGetString(GL_RENDERER)); return 0; } static void engine_draw_frame(struct engine* engine) { if (engine->display == NULL) { // No display. return; } // Just fill the screen with a color. //glClearColor(0,1,0,1); //glClear(GL_COLOR_BUFFER_BIT); if (engine->os && engine->os->main_loop_iterate()==true) { engine->requested_quit=true; return; //should exit instead } eglSwapBuffers(engine->display, engine->surface); } static void engine_term_display(struct engine* engine) { if (engine->display != EGL_NO_DISPLAY) { eglMakeCurrent(engine->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); if (engine->context != EGL_NO_CONTEXT) { eglDestroyContext(engine->display, engine->context); } if (engine->surface != EGL_NO_SURFACE) { eglDestroySurface(engine->display, engine->surface); } eglTerminate(engine->display); } engine->animating = 0; engine->display = EGL_NO_DISPLAY; engine->context = EGL_NO_CONTEXT; engine->surface = EGL_NO_SURFACE; engine->display_active=false; } /** * Process the next input event. */ static int32_t engine_handle_input(struct android_app* app, AInputEvent* event) { struct engine* engine = (struct engine*)app->userData; if (!engine->os) return 0; switch(AInputEvent_getType(event)) { case AINPUT_EVENT_TYPE_KEY: { int ac = AKeyEvent_getAction(event); switch(ac) { case AKEY_EVENT_ACTION_DOWN: { int32_t code = AKeyEvent_getKeyCode(event); if (code==AKEYCODE_BACK) { //AInputQueue_finishEvent(AInputQueue* queue, AInputEvent* event, int handled); if (engine->os) engine->os->main_loop_request_quit(); return 1; } } break; case AKEY_EVENT_ACTION_UP: { } break; } } break; case AINPUT_EVENT_TYPE_MOTION: { Vector touchvec; int pc = AMotionEvent_getPointerCount(event); touchvec.resize(pc); for(int i=0;i>8; switch(AMotionEvent_getAction(event)&AMOTION_EVENT_ACTION_MASK) { case AMOTION_EVENT_ACTION_DOWN: { engine->os->process_touch(0,0,touchvec); //System.out.printf("action down at: %f,%f\n", event.getX(),event.getY()); } break; case AMOTION_EVENT_ACTION_MOVE: { engine->os->process_touch(1,0,touchvec); //for(int i=0;ios->process_touch(4,pidx,touchvec); //System.out.printf("%d - s.up at: %f,%f\n",pointer_idx, event.getX(pointer_idx),event.getY(pointer_idx)); } break; case AMOTION_EVENT_ACTION_POINTER_DOWN: { engine->os->process_touch(3,pidx,touchvec); //System.out.printf("%d - s.down at: %f,%f\n",pointer_idx, event.getX(pointer_idx),event.getY(pointer_idx)); } break; case AMOTION_EVENT_ACTION_CANCEL: case AMOTION_EVENT_ACTION_UP: { engine->os->process_touch(2,0,touchvec); //for(int i=0;iuserData; // LOGI("**** CMD %i\n",cmd); switch (cmd) { case APP_CMD_SAVE_STATE: // The system has asked us to save our current state. Do so. //engine->app->savedState = malloc(sizeof(struct saved_state)); //*((struct saved_state*)engine->app->savedState) = engine->state; //engine->app->savedStateSize = sizeof(struct saved_state); break; case APP_CMD_CONFIG_CHANGED: case APP_CMD_WINDOW_RESIZED: { #if 0 // android blows if (engine->display_active) { EGLint w,h; eglQuerySurface(engine->display, engine->surface, EGL_WIDTH, &w); eglQuerySurface(engine->display, engine->surface, EGL_HEIGHT, &h); engine->os->init_video_mode(w,h); //print_line("RESIZED VIDEO MODE: "+itos(w)+","+itos(h)); engine_draw_frame(engine); } #else if (engine->display_active) { EGLint w,h; eglQuerySurface(engine->display, engine->surface, EGL_WIDTH, &w); eglQuerySurface(engine->display, engine->surface, EGL_HEIGHT, &h); // if (w==engine->os->get_video_mode().width && h==engine->os->get_video_mode().height) // break; engine_term_display(engine); } engine->os->reload_gfx(); engine_draw_frame(engine); engine->animating=1; /* EGLint w,h; eglQuerySurface(engine->display, engine->surface, EGL_WIDTH, &w); eglQuerySurface(engine->display, engine->surface, EGL_HEIGHT, &h); engine->os->init_video_mode(w,h); //print_line("RESIZED VIDEO MODE: "+itos(w)+","+itos(h)); }*/ #endif } break; case APP_CMD_INIT_WINDOW: //The window is being shown, get it ready. // LOGI("INIT WINDOW"); if (engine->app->window != NULL) { if (engine->os==NULL) { //do initialization here, when there's OpenGL! hackish but the only way engine->os = new OS_Android(_gfx_init,engine); // char *args[]={"-test","gui",NULL}; __android_log_print(ANDROID_LOG_INFO,"godot","pre asdasd setup..."); #if 0 Error err = Main::setup("apk",2,args); #else Error err = Main::setup("apk",0,NULL); String modules = Globals::get_singleton()->get("android/modules"); Vector mods = modules.split(",",false); mods.push_back("GodotOS"); __android_log_print(ANDROID_LOG_INFO,"godot","mod count: %i",mods.size()); if (mods.size()) { jclass activityClass = engine->jni->FindClass("android/app/NativeActivity"); jmethodID getClassLoader = engine->jni->GetMethodID(activityClass,"getClassLoader", "()Ljava/lang/ClassLoader;"); jobject cls = engine->jni->CallObjectMethod(app->activity->clazz, getClassLoader); jclass classLoader = engine->jni->FindClass("java/lang/ClassLoader"); jmethodID findClass = engine->jni->GetMethodID(classLoader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); static JNINativeMethod methods[] = { {"registerSingleton", "(Ljava/lang/String;Ljava/lang/Object;)V",(void *)&Java_com_android_godot_Godot_registerSingleton}, {"registerMethod", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V",(void *)&Java_com_android_godot_Godot_registerMethod}, {"getGlobal", "(Ljava/lang/String;)Ljava/lang/String;", (void *)&Java_com_android_godot_Godot_getGlobal}, }; jstring gstrClassName = engine->jni->NewStringUTF("com/android/godot/Godot"); jclass GodotClass = (jclass)engine->jni->CallObjectMethod(cls, findClass, gstrClassName); __android_log_print(ANDROID_LOG_INFO,"godot","godot ****^*^*?^*^*class data %x",GodotClass); engine->jni->RegisterNatives(GodotClass,methods,sizeof(methods)/sizeof(methods[0])); for (int i=0;ijni->FindClass(m.utf8().get_data()); jstring strClassName = engine->jni->NewStringUTF(m.utf8().get_data()); jclass singletonClass = (jclass)engine->jni->CallObjectMethod(cls, findClass, strClassName); __android_log_print(ANDROID_LOG_INFO,"godot","****^*^*?^*^*class data %x",singletonClass); jmethodID initialize = engine->jni->GetStaticMethodID(singletonClass, "initialize", "(Landroid/app/Activity;)Lcom/android/godot/Godot$SingletonBase;"); jobject obj = engine->jni->CallStaticObjectMethod(singletonClass,initialize,app->activity->clazz); __android_log_print(ANDROID_LOG_INFO,"godot","****^*^*?^*^*class instance %x",obj); jobject gob = engine->jni->NewGlobalRef(obj); } } #endif if (!Main::start()) return; //should exit instead and print the error engine->os->main_loop_begin(); } else { //i guess recreate resources? engine->os->reload_gfx(); } engine->animating=1; engine_draw_frame(engine); } break; case APP_CMD_TERM_WINDOW: // The window is being hidden or closed, clean it up. // LOGI("TERM WINDOW"); engine_term_display(engine); break; case APP_CMD_GAINED_FOCUS: // When our app gains focus, we start monitoring the accelerometer. if (engine->accelerometerSensor != NULL) { ASensorEventQueue_enableSensor(engine->sensorEventQueue, engine->accelerometerSensor); // We'd like to get 60 events per second (in us). ASensorEventQueue_setEventRate(engine->sensorEventQueue, engine->accelerometerSensor, (1000L/60)*1000); } engine->animating = 1; break; case APP_CMD_LOST_FOCUS: // When our app loses focus, we stop monitoring the accelerometer. // This is to avoid consuming battery while not being used. if (engine->accelerometerSensor != NULL) { ASensorEventQueue_disableSensor(engine->sensorEventQueue, engine->accelerometerSensor); } // Also stop animating. engine->animating = 0; engine_draw_frame(engine); break; } } void android_main(struct android_app* state) { struct engine engine; // Make sure glue isn't stripped. app_dummy(); memset(&engine, 0, sizeof(engine)); state->userData = &engine; state->onAppCmd = engine_handle_cmd; state->onInputEvent = engine_handle_input; engine.app = state; engine.requested_quit=false; engine.os=NULL; engine.display_active=false; FileAccessAndroid::asset_manager=state->activity->assetManager; // Prepare to monitor accelerometer engine.sensorManager = ASensorManager_getInstance(); engine.accelerometerSensor = ASensorManager_getDefaultSensor(engine.sensorManager, ASENSOR_TYPE_ACCELEROMETER); engine.sensorEventQueue = ASensorManager_createEventQueue(engine.sensorManager, state->looper, LOOPER_ID_USER, NULL, NULL); ANativeActivity_setWindowFlags(state->activity,AWINDOW_FLAG_FULLSCREEN|AWINDOW_FLAG_KEEP_SCREEN_ON,0); state->activity->vm->AttachCurrentThread(&engine.jni, NULL); // loop waiting for stuff to do. while (1) { // Read all pending events. int ident; int events; struct android_poll_source* source; // If not animating, we will block forever waiting for events. // If animating, we loop until all events are read, then continue // to draw the next frame of animation. int nullmax=50; while ((ident=ALooper_pollAll(engine.animating ? 0 : -1, NULL, &events, (void**)&source)) >= 0) { // Process this event. if (source != NULL) { // LOGI("process\n"); source->process(state, source); } else { nullmax--; if (nullmax<0) break; } // If a sensor has data, process it now. // LOGI("events\n"); if (ident == LOOPER_ID_USER) { if (engine.accelerometerSensor != NULL) { ASensorEvent event; while (ASensorEventQueue_getEvents(engine.sensorEventQueue, &event, 1) > 0) { if (engine.os) { engine.os->process_accelerometer(Vector3(event.acceleration.x, event.acceleration.y, event.acceleration.z)); } } } } // Check if we are exiting. if (state->destroyRequested != 0) { if (engine.os) { engine.os->main_loop_request_quit(); } state->destroyRequested=0; } if (engine.requested_quit) { engine_term_display(&engine); exit(0); return; } // LOGI("end\n"); } // LOGI("engine animating? %i\n",engine.animating); if (engine.animating) { //do os render engine_draw_frame(&engine); //LOGI("TERM WINDOW"); } } } JNIEXPORT void JNICALL Java_com_android_godot_Godot_registerSingleton(JNIEnv * env, jobject obj, jstring name,jobject p_object){ String singname = env->GetStringUTFChars( name, NULL ); JNISingleton *s = memnew( JNISingleton ); s->update_env(env); s->set_instance(env->NewGlobalRef(p_object)); jni_singletons[singname]=s; Globals::get_singleton()->add_singleton(Globals::Singleton(singname,s)); } static Variant::Type get_jni_type(const String& p_type) { static struct { const char *name; Variant::Type type; } _type_to_vtype[]={ {"void",Variant::NIL}, {"boolean",Variant::BOOL}, {"int",Variant::INT}, {"float",Variant::REAL}, {"java.lang.String",Variant::STRING}, {"[I",Variant::INT_ARRAY}, {"[F",Variant::REAL_ARRAY}, {"[java.lang.String",Variant::STRING_ARRAY}, {NULL,Variant::NIL} }; int idx=0; while (_type_to_vtype[idx].name) { if (p_type==_type_to_vtype[idx].name) return _type_to_vtype[idx].type; idx++; } return Variant::NIL; } static const char* get_jni_sig(const String& p_type) { static struct { const char *name; const char *sig; } _type_to_vtype[]={ {"void","V"}, {"boolean","Z"}, {"int","I"}, {"float","F"}, {"java.lang.String","Ljava/lang/String;"}, {"[I","[I"}, {"[F","[F"}, {"[java.lang.String","[Ljava/lang/String;"}, {NULL,"V"} }; int idx=0; while (_type_to_vtype[idx].name) { if (p_type==_type_to_vtype[idx].name) return _type_to_vtype[idx].sig; idx++; } return ""; } JNIEXPORT jstring JNICALL Java_com_android_godot_Godot_getGlobal(JNIEnv * env, jobject obj, jstring path) { String js = env->GetStringUTFChars( path, NULL ); return env->NewStringUTF(Globals::get_singleton()->get(js).operator String().utf8().get_data()); } JNIEXPORT void JNICALL Java_com_android_godot_Godot_registerMethod(JNIEnv * env, jobject obj, jstring sname, jstring name, jstring ret, jobjectArray args){ String singname = env->GetStringUTFChars( sname, NULL ); ERR_FAIL_COND(!jni_singletons.has(singname)); JNISingleton *s = jni_singletons.get(singname); String mname = env->GetStringUTFChars( name, NULL ); String retval = env->GetStringUTFChars( ret, NULL ); Vector types; String cs="("; int stringCount = env->GetArrayLength(args); print_line("Singl: "+singname+" Method: "+mname+" RetVal: "+retval); for (int i=0; iGetObjectArrayElement(args, i); const char *rawString = env->GetStringUTFChars(string, 0); types.push_back(get_jni_type(String(rawString))); cs+=get_jni_sig(String(rawString)); } cs+=")"; cs+=get_jni_sig(retval); jclass cls = env->GetObjectClass(s->get_instance()); print_line("METHOD: "+mname+" sig: "+cs); jmethodID mid = env->GetMethodID(cls, mname.ascii().get_data(), cs.ascii().get_data()); if (!mid) { print_line("FAILED GETTING METHOID "+mname); } s->add_method(mname,mid,types,get_jni_type(retval)); } #endif