I tried to compile WebRTC with my customized function, but I always get the error like this:
In file included from ../../sdk/android/src/jni/pc/octon_frame_encryptor.cc:5:
gen/sdk/android/generated_peerconnection_jni/OctonFrameEncryptor_jni.h:41:14: error: function 'webrtc::jni::JNI_OctonFrameEncryptor_GetOctonFrameEncryptor' has internal linkage but is not defined [-Werror,-Wundefined-internal]
static jlong JNI_OctonFrameEncryptor_GetOctonFrameEncryptor(JNIEnv* env, const
^
gen/sdk/android/generated_peerconnection_jni/OctonFrameEncryptor_jni.h:48:10: note: used here
return JNI_OctonFrameEncryptor_GetOctonFrameEncryptor(env,
^
1 error generated.
I don't know what is wrong with my function, here is my octon_frame_encryptor.cc inside src/jni/pc. For JNI_OctonFrameEncryptor_SetKey it seems that it does not throw compiling error.
#include "api/crypto/octon_frame_encryptor.h"
#include <vector>
#include "sdk/android/generated_peerconnection_jni/OctonFrameEncryptor_jni.h"
#include "sdk/android/src/jni/jni_helpers.h"
namespace webrtc {
namespace jni {
static jlong JNI_OctonFrameEncryptor_GetOctonFrameEncryptor(
JNIEnv* jni,
const base::android::JavaParamRef<jobject>& thiz,
const base::android::JavaParamRef<jintArray>& key_bytes) {
jintArray key_bytes_array = key_bytes.obj();
jsize size = jni->GetArrayLength(key_bytes_array);
jint* key_bytes_ptr = jni->GetIntArrayElements(key_bytes_array, 0);
std::vector<uint8_t> key_bytes_vector(key_bytes_ptr, key_bytes_ptr + size);
return jlongFromPointer(new OctonFrameEncryptor(key_bytes_vector));
}
static void JNI_OctonFrameEncryptor_SetKey(
JNIEnv* jni,
const base::android::JavaParamRef<jobject>& thiz,
const base::android::JavaParamRef<jintArray>& key_bytes) {
jintArray key_bytes_array = key_bytes.obj();
jsize size = jni->GetArrayLength(key_bytes_array);
jint* key_bytes_ptr = jni->GetIntArrayElements(key_bytes_array, 0);
std::vector<uint8_t> key_bytes_vector(key_bytes_ptr, key_bytes_ptr + size);
OctonFrameEncryptor* encryptor =
reinterpret_cast<OctonFrameEncryptor*>(thiz.obj());
encryptor->SetKey(key_bytes_vector);
}
} // namespace jni
} // namespace webrtc
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);
}
}
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
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.
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