JVM crashes when trying to create a NewObject through JNI [duplicate] - c++
So on my commentators request I have finally found an MCVE that reproduces my error. So the general setup is that Java uses JNI to call into a dll, and the dll grabs hold of the running JVM and stores a pointer to the JNIEnv, which it uses to call methods in a java class (the java class being called from c++ is not necessarily the original calling java object, which is why the input jobject is not used for the callbacks). Before I explain any further just let me post all the code:
JniTest.java
package jnitest;
public class JniTestJava {
public static void main(String[] args) {
try {
System.load("<path-to-dll>");
} catch (Throwable e) {
e.printStackTrace();
}
DllFunctions dllFunctions = new DllFunctions();
dllFunctions.setup();
dllFunctions.singleIntFunctionCall();
dllFunctions.doubleIntFunctionCall();
dllFunctions.singleStringFunctionCall();
dllFunctions.doubleStringFunctionCall();
}
public void javaStringFunction(String input){
System.out.println(input);
}
public void javaIntFunction(int input){
System.out.println(input);
}
}
DllFunctions.java
package jnitest;
public class DllFunctions{
public native void singleIntFunctionCall();
public native void doubleIntFunctionCall();
public native void singleStringFunctionCall();
public native void doubleStringFunctionCall();
public native void setup();
}
JniTestCpp.h
#include <jni.h>
#ifndef _Included_jnitest_JniTestJava
#define _Included_jnitest_JniTestJava
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT void JNICALL Java_jnitest_DllFunctions_setup(JNIEnv* java_env, jobject);
JNIEXPORT void JNICALL Java_jnitest_DllFunctions_singleIntFunctionCall(JNIEnv* java_env, jobject);
JNIEXPORT void JNICALL Java_jnitest_DllFunctions_doubleIntFunctionCall(JNIEnv* java_env, jobject);
JNIEXPORT void JNICALL Java_jnitest_DllFunctions_singleStringFunctionCall(JNIEnv* java_env, jobject);
JNIEXPORT void JNICALL Java_jnitest_DllFunctions_doubleStringFunctionCall(JNIEnv* java_env, jobject);
#ifdef __cplusplus
}
#endif
#endif
JniTestCpp.cpp
#include "JniTestCpp.h"
#include "JniTestClass.h"
JniTestClass jniTestClass;
extern "C"
{
JNIEXPORT void JNICALL Java_jnitest_DllFunctions_setup(JNIEnv* java_env, jobject) {
jniTestClass.setup();
}
JNIEXPORT void JNICALL Java_jnitest_DllFunctions_singleIntFunctionCall(JNIEnv* java_env, jobject) {
jniTestClass.callJavaIntFunction();
}
JNIEXPORT void JNICALL Java_jnitest_DllFunctions_doubleIntFunctionCall(JNIEnv* java_env, jobject) {
jniTestClass.callJavaIntFunction();
jniTestClass.callJavaIntFunction();
}
JNIEXPORT void JNICALL Java_jnitest_DllFunctions_singleStringFunctionCall(JNIEnv* java_env, jobject) {
jniTestClass.callJavaStringFunction();
}
JNIEXPORT void JNICALL Java_jnitest_DllFunctions_doubleStringFunctionCall(JNIEnv* java_env, jobject) {
jniTestClass.callJavaStringFunction();
jniTestClass.callJavaStringFunction();
}
}
JniTestClass.h
#include <jni.h>
class JniTestClass {
typedef jint(JNICALL * GetCreatedJavaVMs)(JavaVM**, jsize, jsize*);
public:
void setup();
void callJavaStringFunction();
void callJavaIntFunction();
void throwException(jthrowable ex);
private:
jobject myObject;
jclass myClass;
JNIEnv* env;
};
JniTestClass.cpp
#include "JniTestClass.h"
#include <Windows.h>
#include <fstream>
void JniTestClass::setup() {
jint jni_version = JNI_VERSION_1_4;
GetCreatedJavaVMs jni_GetCreatedJavaVMs;
jsize nVMs = 0;
jni_GetCreatedJavaVMs = (GetCreatedJavaVMs) GetProcAddress(GetModuleHandle(
TEXT("jvm.dll")), "JNI_GetCreatedJavaVMs");
jni_GetCreatedJavaVMs(NULL, 0, &nVMs);
JavaVM** buffer = new JavaVM*[nVMs];
jni_GetCreatedJavaVMs(buffer, nVMs, &nVMs);
buffer[0]->GetEnv((void **) &env, jni_version);
delete buffer;
myClass = env->FindClass("jnitest/JniTestJava");
myObject = env->NewObject(myClass, env->GetMethodID(myClass, "<init>", "()V"));
}
void JniTestClass::callJavaStringFunction() {
jmethodID myMethod = env->GetMethodID(myClass, "javaStringFunction", "(Ljava/lang/String;)V");
if (env->ExceptionCheck()) {
throwException(env->ExceptionOccurred());
}
env->CallVoidMethod(myObject, myMethod, env->NewStringUTF("String!"));
if (env->ExceptionCheck()) {
throwException(env->ExceptionOccurred());
}
}
void JniTestClass::callJavaIntFunction() {
jmethodID myMethod = env->GetMethodID(myClass, "javaIntFunction", "(I)V");
if (env->ExceptionCheck()) {
throwException(env->ExceptionOccurred());
}
env->CallVoidMethod(myObject, myMethod, 1);
if (env->ExceptionCheck()) {
throwException(env->ExceptionOccurred());
}
}
void JniTestClass::throwException(jthrowable ex) {
env->ExceptionClear();
jclass clazz = env->GetObjectClass(ex);
jmethodID getMessage = env->GetMethodID(clazz,
"toString",
"()Ljava/lang/String;");
jstring message = (jstring) env->CallObjectMethod(ex, getMessage);
const char *mstr = env->GetStringUTFChars(message, NULL);
printf("%s \n", mstr);
throw std::runtime_error(mstr);
}
The intent here is that JniTestCpp should only have JNI exported functions and no declared classes. The idea behind the JniTestClass is that it should hold all the JNI pointers and variables (object, class and environment pointer) and provide methods that JniTestCpp can use.
Now, the way this code is presented it crashes on the dllFunctions.doubleStringFunctionCall(); call in JniTest.java with the following output:
1
1
1
String!
#
# A fatal error has been detected by the Java Runtime Environment:
#
# EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x6e306515, pid=1268, tid=8028
#
# JRE version: Java(TM) SE Runtime Environment (7.0_80-b15) (build 1.7.0_80-b15)
# Java VM: Java HotSpot(TM) Client VM (24.80-b11 mixed mode, sharing windows-x86 )
# Problematic frame:
# V [jvm.dll+0xc6515]
and below I have shown the 10 top stack frames from the hs_err_pidXXX.log file:
Stack: [0x02150000,0x021a0000], sp=0x0219f49c, free space=317k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
V [jvm.dll+0xc6515]
V [jvm.dll+0xc66c9]
C [JniTestCpp.dll+0x13d52] JNIEnv_::GetMethodID+0x42
C [JniTestCpp.dll+0x14ecf] JniTestClass::callJavaStringFunction+0x3f
C [JniTestCpp.dll+0x16068] Java_jnitest_DllFunctions_doubleStringFunctionCall+0x28
j jnitest.DllFunctions.doubleStringFunctionCall()V+0
j jnitest.JniTestJava.main([Ljava/lang/String;)V+38
v ~StubRoutines::call_stub
V [jvm.dll+0x1429aa]
V [jvm.dll+0x20743e]
What suprises me is that if I, in JniTestCpp.cpp don't declare JniTestClass jniTestClass as a static object, but rather declare it and call setup() on it in each method like shown below, it does not crash, but produces the expected results. Also, I must say that it is rather strange that i works when calling doubleIntFunctionCall(); but not doubleStringFunctionCall();
JniTestCpp.cpp - This does not crash
#include "JniTestCpp.h"
#include "JniTestClass.h"
extern "C"
{
JNIEXPORT void JNICALL Java_jnitest_DllFunctions_setup(JNIEnv* java_env, jobject) {
JniTestClass jniTestClass;
jniTestClass.setup();
}
JNIEXPORT void JNICALL Java_jnitest_DllFunctions_singleIntFunctionCall(JNIEnv* java_env, jobject) {
JniTestClass jniTestClass;
jniTestClass.setup();
jniTestClass.callJavaIntFunction();
}
JNIEXPORT void JNICALL Java_jnitest_DllFunctions_doubleIntFunctionCall(JNIEnv* java_env, jobject) {
JniTestClass jniTestClass;
jniTestClass.setup();
jniTestClass.callJavaIntFunction();
jniTestClass.callJavaIntFunction();
}
JNIEXPORT void JNICALL Java_jnitest_DllFunctions_singleStringFunctionCall(JNIEnv* java_env, jobject) {
JniTestClass jniTestClass;
jniTestClass.setup();
jniTestClass.callJavaStringFunction();
}
JNIEXPORT void JNICALL Java_jnitest_DllFunctions_doubleStringFunctionCall(JNIEnv* java_env, jobject) {
JniTestClass jniTestClass;
jniTestClass.setup();
jniTestClass.callJavaStringFunction();
jniTestClass.callJavaStringFunction();
}
}
Sorry for the long post, but this was the only way I felt like I was able to unambiguously present my problem.
UPDATE
In function void JniTestClass::callJavaStringFunction(), if i change it to the following:
void JniTestClass::callJavaStringFunction() {
jmethodID myMethod = env->GetMethodID(myClass, "javaStringFunction", "(Ljava/lang/String;)V");
if (env->ExceptionCheck()) {
throwException(env->ExceptionOccurred());
}
jstring j_string = env->NewStringUTF("String!");
env->CallVoidMethod(myObject, myMethod, j_string);
if (env->ExceptionCheck()) {
throwException(env->ExceptionOccurred());
}
env->DeleteLocalRef(j_string);
}
where I now call DeleteLocalRef() on the jstring created with NewStringUTF(), the program still crashes but prints out this exception message:
java.lang.NoSuchMethodError: javaStringFunction
There are several mistakes in your code.
jobject myObject and jclass myClass are reused across JNI calls.
All jobjects created inside a JNI method are local references by default. Whenever a JNI method returns, all local references are automatically released.
If you want to reuse jobject (or jclass which is also an object reference) across method calls, you should convert it to a global reference using NewGlobalRef. When a global reference is no longer needed, it should be deleted by DeleteGlobalRef, otherwise referenced object will never be garbage-collected.
JNIEnv* is cached.
In general, JNIEnv* should never be stored for later reuse. Instead you should use JNIEnv* provided as the first argument to each JNI function. Alternatively it may be obtained by GetEnv call. Note that each thread has its own JNIEnv* which is not applicable to other threads.
Related
Is it possible to call a JNIEXPORT function in Linux from c++?
I am new to linux. (Windows users) using the .so file (dll-like) library I want to use a JNIEXPORT function in Centos7(linux). example: test.so JNIEXPORT jboolean JNICALL JNI_foo(JNIEnv* env, jobject obj, jstring pathName) { UNREFERENCED_PARAMETER(obj); // implements... return true; } main.cpp (in linux) #include <dlfcn.h> #include <jni.h> typedef jboolean(JNICALL *Foo_TestFunc)(JNIEnv* env, jobject obj, jstring pathName); int main() { void* handle = dlopen("test.so", RTLD_NOW + RTLD_GLOBAL); if(handle) { *(void**)(&Foo_TestFunc) = dlsym(handle, "JNI_foo"); JNIEnv jenv; jobject jobj; jstring jPath = jenv.NewStringUTF("/home/neohtux/Desktop/test.dat"); jboolean isOk = Foo_TestFunc(&jenv, jobj, jPath); } } Q1) Foo_TestFunc(first, second, path) first parameter and in the second parameter What should I convey? (on Linux) Q2) jenv.NewStringUTF("/home/neohtux/Desktop/test.dat"); Why am I getting segmentfalut in this function?
Issue creating Java Native Interface(JNI)
As the title suggested i need some help creating JNI wrapper for this method std::shared_ptr<xxx::FooInterface> createFooFactory() noexcept; The error that i am getting is this /Volumes/../../projects/xxx_fooFactory.cpp:34:19: error: reinterpret_cast from 'std::__ndk1::shared_ptr<xxx::FooInterface>' to 'jlong' (aka 'long long') is not allowed return reinterpret_cast<jlong>(nativeObject); I am not able to figure out what i am missing or doing wrong, All the related code are as follows, would be great if someone can provide feedback/advice as to what i might be missing. I must mention that this would be my first time writing jni wrapper. I have a class FooFactory.java package xxx.FooFactory; import PlatformInterface; public class FooFactory extends PlatformInterface { protected FooFactory() { super(); setNativeObject(nCreate()); } #Override public void destroyNativeObject(long nativeObject) { destroyNativeObject(nativeObject); } private static native long nCreate(); private static native void nDestroy(long nativeObject); } AutoGenerated jni header xxx_fooFactory.h /* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class xxx_fooFactory */ #ifndef _Included_xxx_fooFactory #define _Included_xxx_fooFactory #ifdef __cplusplus extern "C" { #endif /* * Class: xxx_fooFactory * Method: nCreate * Signature: ()J */ JNIEXPORT jlong JNICALL Java_xxx_fooFactory_nCreate (JNIEnv *, jclass); /* * Class: xxx_fooFactory * Method: nDestroy * Signature: (J)V */ JNIEXPORT void JNICALL Java_xxx_fooFactory_nDestroy (JNIEnv *, jclass, jlong); #ifdef __cplusplus } #endif #endif xxx_fooFactory.cpp #include <jni.h> #include "xxx_fooFactory.h" .... #include <xxx/FooFactory.h> using namespace xxx; extern "C" { /* * Class: xxx_fooFactory * Method: nCreate * Signature: ()J */ JNIEXPORT jlong JNICALL Java_xxx_fooFactory_nCreate (JNIEnv *env, jclass) { JAVA_BINDING_TRY{ auto fooFactory = createFooFactory(); if (fooFactory == nullptr) { throw JavaBindingException(JavaBindingException::kIllegalStateException, "Unable to create foo factory."); } auto nativeObject = std::shared_ptr<FooInterface>(fooFactory); return reinterpret_cast<jlong>(nativeObject); } JAVA_BINDING_CATCH(env); return 0; } /* * Class: xxx_fooFactory * Method: nDestroy * Signature: (J)V */ JNIEXPORT void JNICALL Java_xxx_fooFactory_nDestroy (JNIEnv *env, jclass, jlong nativeObject) { JAVA_BINDING_TRY { if (nativeObject == 0) return; delete reinterpret_cast<std::shared_ptr<FooInterface>*>(nativeObject); } JAVA_BINDING_CATCH(env); } }
how to use the JNI ThrowNew()?
I have a method like this: extern "C" JNIEXPORT jint JNICALL Java_tt_reducto_ndksample_StringTypeOps_exception (JNIEnv *env, jobject jobj) { (env)->ExceptionClear(); jclass exception_cls = (env)->FindClass("java/lang/IllegalArgumentException"); (env)->ThrowNew(exception_cls, "key0 is not invalid"); return -1; } it was worked. but if return jstring like this: extern "C" JNIEXPORT jstring JNICALL Java_tt_reducto_ndksample_StringTypeOps_exception (JNIEnv *env, jobject jobj) { (env)->ExceptionClear(); jclass exception_cls = (env)->FindClass("java/lang/IllegalArgumentException"); (env)->ThrowNew(exception_cls, "key0 is not invalid"); return env->NewStringUTF("22"); } it was crushed. JNI DETECTED ERROR IN APPLICATION: JNI ThrowNew called with pending exception java.lang.IllegalArgumentException: key0 is not invalid i was try catch: try { StringTypeOps.exception() }catch (e: java.lang.Exception) { Log.d("::","Exception") e.printStackTrace() } I'm not sure what the restrictions are on using the ThrowNew() function
JNI Error : java.lang.NoSuchMethodError: no static method
Error Message : Pending exception java.lang.NoSuchMethodError thrown by 'void com.exampleTest.NDKT.I(android.app.Activity):-2' java.lang.NoSuchMethodError: no static method "Lcom/exampleTest/NDKT;.PN()Ljava/lang/String;" I should try ndk c++ call java method because i want you java method thought android package name know i tried below java call method String -> void change => c++ : jmethodID jMethod = pE->GetStaticMethodID(jClass, "GO", "()V"); java code "this" -> "MainActivity.this" - Java code CallNDK class{ public static void initialize(final Activity a,final SIMECB cb) { System.loadLibrary("simpleinit"); _CB = cb; _INSTANCE = new CallNDK(); new Thread(new Runnable() { public void run() { _INSTANCE.I(a); } }).start(); } } ==> initialize () -> Activity = MainActivity.this - NDK CallNDK.cpp static string mPN; //Packege Name #ifdef __ANDROID__ void CallNDK::I(JNIEnv* pE, jobject pA){ // Package Name Method call CHECK::PN(pE, mPN); } CHECK.h #ifndef CHECK_H_ #define CHECK_H_ #ifdef __ANDROID__ #include <jni.h> #endif using namespace std; class CHECK { public: #ifdef __ANDROID__ static void PN( JNIEnv* pE, string& pO); #endif }; #endif CHECK.cpp #include "CHECK.h" #ifdef __ANDROID__ #include <jni.h> #endif using namespace std; #ifdef __ANDROID__ void CHECK::PN(JNIEnv* pE, string& pO){ jclass classes = pE->FindClass("com/exampleTest/NDKT"); jmethodID jMethod = pE->GetMethodID(classes, "SimpleMethod", ()Ljava/lang/String;"); if(!jMethod) { CALLUtil::LOG("class loader method access"); return; } jstring jsResult = (jstring)pE->CallStaticObjectMethod(jClass, jMethod); const char* cResult = pE->GetStringUTFChars(jsResult, NULL); std::string sResult(cResult); pO = sResult; } - NDK of call java class code enter code here NDKT Class { private static int _MAX_TRY_INVOKE_COUNT = 5; public static String SimpleMethod(Activity a){ for(int i = 0; i < _MAX_TRY_INVOKE_COUNT; i++) { try { return a.getApplicationContext().getPackageName(); } catch (Exception e) { try { Thread.sleep(50); } catch (Exception e2) { } } } return "" } } Error JNI DETECTED ERROR IN APPLICATION: JNI FindClass called with pending exception 'java.lang.NoSuchMethodError' thrown in void com.exampleTest.CallNDK.I(android.app.Activity):-2 in call to FindClass from void com.exampleText.CallNDK.I(android.app.Activity) Pending exception java.lang.NoSuchMethodError thrown by 'void com.exampleTest.NDKT.I(android.app.Activity):-2' java.lang.NoSuchMethodError: no static method "Lcom/exampleTest/NDKT;.PN()Ljava/lang/String;"
Auto generate function forwarders with c-preprocessor
I need to write a macro that auto-generates a function that forwards all the arguments to another (member) function. I need in to simplify writing JNI glue in case if you need to know why I need it. I'll omit other reason why I need it done this way, I simply mention that I cannot use boost (although, I may rip off needed pieces and translate from boost to my own macros); I also checked some other libs (jace etc), but didn't find anything that would fit my needs. In short, here's example of a JNI function: class TestClass { void nativeTest(JNIEnv *, jobject, jint, jboolean) { ... } static TestClass* getPeer(JNIEnv *, jobject obj) { ... } } JNIEXPORT void JNICALL Java_com_noname_media_TestClass_nativeTest( JNIEnv *env, jobject obj, jint i, jboolean b ) { TestClass* peer = TestClass::getPeer(env, obj, i, b); if(peer) return peer->nativeTest(env, obj, i, b); return; } Now, I want to write some JNI_FUNCTION macro that would auto-generate all that Java_com_noname_media_TestClass_nativeTest. After some thought, I think I can do it something like this: #define JNI_FUNCTION(functionName, functionReturn, functionArgs) \ JNIEXPORT functionReturn JNICALL \ Java_com_noname_media_TestClass##functionName(**WTF**) \ { TestClass* peer = TestClass::getPeer(**WTF**); if(peer) return peer->functionName(**WTF**); return; } Then, to use the JNI_FUNCTION I could do something like this: JNI_FUNCTION(nativeTest, void, (JNIEnv *, jobject, jint, jboolean)); The problem is that I don't know how to "crack" the function parameters because I need to add auto-numbered argument names for each entry in the list of functionArgs. Other gotchas: return type can be some type or void, but for void case I may have separate JNI_VOID_FUNCTION in case it cannot be done easily using regular way. All jni functions in my case will always have at least two args in the list of functionArgs, e.g. it cannot be empty list (). I don't have to use functionArgs as a single argument that contains multiple arguments, I'm ok with this way as well: #define JNI_FUNCTION(functionName, functionReturn, ...) Whatever works... perhaps I need some kind of macro that allows me to extract some macro at some position, like ARG_1(...) etc, but so far I can't wrap it all that in my brain how to do it. PS. I recall some super cool examples on usage of c-preprocessor with very good explanation here in SO, but can't find them now, if you have it bookmarked, maybe all I need is to look into them. EDIT: Basically, the trick is to add auto-numbered names to each argument and then pass them as-is to a member function. The reason I need it done this way is because on top of all that I have some other autogeneration done with preprocessor. In short, this macro will actually be used in a group of similar macro (something like in ATL/WTL): JNI_TABLE_BEGIN(ClassName) JNI_FUNCTION(native1, void, (JNIEnv *, jobject, jint)) JNI_FUNCTION(native2, void, (JNIEnv *, jobject, jint)) JNI_FUNCTION(native3, jint, (JNIEnv *, jobject)) JNI_TABLE_END()
Here is solution using Boost.Preprocessor Sequences: #include <boost/preprocessor/cat.hpp> #include <boost/preprocessor/seq/size.hpp> #include <boost/preprocessor/seq/enum.hpp> #include <boost/preprocessor/list/append.hpp> #include <boost/preprocessor/seq/for_each_i.hpp> #include <boost/preprocessor/control/expr_if.hpp> #include <boost/preprocessor/comparison/equal.hpp> #include <boost/preprocessor/facilities/expand.hpp> #define RET_TYPE_void 1)(1 #define IS_NOT_VOID(type) BOOST_PP_EQUAL(BOOST_PP_SEQ_SIZE((BOOST_PP_CAT(RET_TYPE_,type))),1) #define JNI_PARAMS(r, data, i, elem) (elem p##i) #define JNI_ARGS_PASS(r, data, i, elem) (p##i) #define JNI_FUNCTION(functionName, functionReturn, PARAMS ) \ JNIEXPORT functionReturn JNICALL \ Java_com_noname_media_TestClass##functionName(BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_FOR_EACH_I(JNI_PARAMS,_,PARAMS))) \ { \ TestClass* peer = TestClass::getPeer(BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_FOR_EACH_I(JNI_ARGS_PASS,_,PARAMS))); \ if(peer) \ BOOST_PP_EXPR_IF(IS_NOT_VOID(functionReturn),return) peer->functionName(BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_FOR_EACH_I(JNI_ARGS_PASS,_,PARAMS))); \ BOOST_PP_EXPR_IF(IS_NOT_VOID(functionReturn),return functionReturn();) \ } \ /**/ JNI_FUNCTION(nativeTest, void, (jobject)(jint)(jboolean)) Expands to: JNIEXPORT void JNICALL Java_com_noname_media_TestClassnativeTest(jobject p0, jint p1, jboolean p2) { TestClass* peer = TestClass::getPeer(p0, p1, p2); if(peer) peer->nativeTest(p0, p1, p2); } So, your example would became: #define JNI_TABLE_BEGIN(name) class name { public: #define JNI_TABLE_END() }; JNI_TABLE_BEGIN(ClassName) JNI_FUNCTION(native1, void, (JNIEnv *)(jobject)(jint) ) JNI_FUNCTION(native2, void, (JNIEnv *)(jobject)(jint) ) JNI_FUNCTION(native3, jint, (JNIEnv *)(jobject) ) JNI_TABLE_END() And it expands to: class ClassName { public: JNIEXPORT void JNICALL Java_com_noname_media_TestClassnative1(JNIEnv * p0, jobject p1, jint p2) { TestClass* peer = TestClass::getPeer(p0, p1, p2); if(peer) peer->native1(p0, p1, p2); } JNIEXPORT void JNICALL Java_com_noname_media_TestClassnative2(JNIEnv * p0, jobject p1, jint p2) { TestClass* peer = TestClass::getPeer(p0, p1, p2); if(peer) peer->native2(p0, p1, p2); } JNIEXPORT jint JNICALL Java_com_noname_media_TestClassnative3(JNIEnv * p0, jobject p1) { TestClass* peer = TestClass::getPeer(p0, p1); if(peer) return peer->native3(p0, p1); return jint(); } };
So far I have one idea that might be suitable as a solution. Looking through SO answers I found an example on how to count number of arguments. Using this macro I can concatenate count of args from functionArgs and call some predefined macro, eg. JNI_FUNCTION_5 that takes 5 args in the list of args. All I needed is to be able to extract some argument from list of VA_ARGS. Some macro like __VA_ARG_N(num). Here's a way to extract some argument from __VA_ARGS__: #define ARG_REST(arg, ...) __VA_ARGS__ #define ARG0(arg0, ...) arg0 #define ARG1(...) ARG0(ARG_REST(__VA_ARGS__)) #define ARG2(...) ARG1(ARG_REST(__VA_ARGS__)) ... etc. Then I wrote special macro that generates list argumet-type pairs, or arguments only. So, eventually, I managed to do this: JNI_FUNCTION(void, nativeSetOrientation, (JNIEnv *, jobject, jint, jboolean)); JNI_FUNCTION(void, nativeStartRecording, (JNIEnv *, jobject, jstring, jint)); The only problem that would be nice to fix is to add special handling for void returnType, something like this: if(peer) IS_NOT_VOID(returnType, return) peer->functionName(**WTF**); IS_NOT_VOID(returnType, return returnType();) Where IS_NOT_VOID should have this action: #define IS_NOT_VOID(type, expr) if(type == void) expr that is IS_NOT_VOID(void, return void();) -> expands to nothing IS_NOT_VOID(int, return int();) -> expands to return int(); Any idea how to do it properly? except the obvious solution to iterate all possible types and create 30 defines for all types that can be passed to JNI functions. Something like this: #define _IF_NOT_VOID(type, expr) _IF_NOT_VOID##type(expr) #define _IF_NOT_VOIDvoid(expr) //void type... #define _IF_NOT_VOIDjboolean(expr) expr #define _IF_NOT_VOIDjbyte(expr) expr #define _IF_NOT_VOIDjchar(expr) expr