how to use the JNI ThrowNew()? - java-native-interface

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

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);
}
}

JVM crashes when trying to create a NewObject through JNI [duplicate]

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.

JNI for C++ class UnsatisfieldLinkError

Hy guys!
I know there is too many topics about JNI here but, I am newbie whit JNI, so i decide to write a new one.
I am trying to write a JNI to access some C++ class in Java layer, but im getting the error:
Exception in thread "main" java.lang.UnsatisfiedLinkError: arm.test.lib.FooClass.init(I)V
at arm.test.lib.FooClass.init(Native Method)
at arm.test.lib.FooClass.<init>(FooClass.java:13)
at arm.test.app.Main.main(Main.java:9)
<end of output>
I have the following project tree
source
├── build 
├── CMakeLists.txt
├── foo
| ├── src
| | ├── foo.cpp
| | └── foo.h
| ├── include
| | └── foo.h -> ../src/foo.h
| └── CMakeLists.txt
└── java-jni
├── c
├── CMakeLists.txt
│   └── jni_wrapper.cpp
└── java
└── src
└── arm
└── test
├── app
│   └── Main.java
└── lib
└── FooClass.java
The CMakeLists.txt from source folder:
cmake_minimum_required(VERSION 2.8)
project(java-cpp)
include_directories(foo)
option(BUILD_TESTS "Build the test suite (requires foo)" ON)
if(BUILD_TESTS)
enable_testing()
endif()
add_subdirectory(foo)
add_subdirectory(java-jni)
The CMakeLists.txt from foo folder:
cmake_minimum_required(VERSION 2.8)
project(foo)
add_compile_options(-std=c++11)
set(PROJECT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
set(SRCS_PATH "${PROJECT_PATH}/src")
set(CPPLIB_INCLUDE_DIR "${PROJECT_PATH}/include" CACHE INTERNAL "foo include directories")
include_directories(${CPPLIB_INCLUDE_DIR})
file(GLOB SRCS "${SRCS_PATH}/*.cpp" "${SRCS_PATH}/*.h")
add_library(${PROJECT_NAME} SHARED ${SRCS})
Foo is a simple class, constructor and some functions...
foo.h
#ifndef CLASS_H
#define CLASS_H
class Foo {
private:
int m_nb;
public:
Foo(int &n_nb);
int getValue() const;
void increment();
};
foo.cpp
#include <iostream>
#include "foo.h"
Foo::Foo(int &n_nb) :m_nb(n_nb)
{
std::cout << n_nb << std::endl;
}
int Foo::getValue() const
{
return this->m_nb;
}
void Foo::increment()
{
this->m_nb++;
}
And the CMakeLists.txt from java-jni
cmake_minimum_required(VERSION 2.8)
# REQUIRED JAVA
FIND_PACKAGE(Java COMPONENTS Development)
INCLUDE(UseJava)
SET(CMAKE_JAVA_COMPILE_FLAGS "-source" "1.8" "-target" "1.8")
SET(JAVA_SOURCE_FILES
arm/test/app/Main.java
arm/test/lib/FooClass.java)
# Build Java classes
FILE(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/java/bin")
SET(class_files)
FOREACH(_java_file ${JAVA_SOURCE_FILES})
# _java_file: relative file name
# _class_file: relative class name
STRING(REGEX REPLACE "\\.java$"
".class" _class_file
"${_java_file}")
ADD_CUSTOM_COMMAND(
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/java/bin/${_class_file}"
COMMAND
${Java_JAVAC_EXECUTABLE}
${CMAKE_JAVA_COMPILE_FLAGS}
-sourcepath "${CMAKE_CURRENT_SOURCE_DIR}/java/src"
-d "${CMAKE_CURRENT_BINARY_DIR}/java/bin"
"${CMAKE_CURRENT_SOURCE_DIR}/java/src/${_java_file}"
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/java/src/${_java_file}")
LIST(APPEND class_files "java/bin/${_class_file}")
ENDFOREACH()
ADD_CUSTOM_TARGET(JavaJNIClasses ALL DEPENDS ${class_files})
# Make the JNI header file
ADD_CUSTOM_COMMAND(
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/include/FooClass_jni.h"
COMMAND
${Java_JAVAH_EXECUTABLE}
-o "${CMAKE_CURRENT_BINARY_DIR}/include/FooClass_jni.h"
-classpath "${CMAKE_CURRENT_BINARY_DIR}/java/bin"
arm.test.lib.FooClass
DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/java/bin/arm/test/lib/FooClass.class")
ADD_CUSTOM_TARGET(JavaJNIHeaders ALL DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/include/FooClass_jni.h")
# Require JNI
FIND_PACKAGE(JNI REQUIRED)
# Builds the JNI wrapper
INCLUDE_DIRECTORIES("${CPPLIB_INCLUDE_DIR}"
"${CMAKE_CURRENT_BINARY_DIR}/include"
${JNI_INCLUDE_DIRS}
)
ADD_LIBRARY(foo_jni SHARED c/jni_wrapper.cpp)
link_directories("${CMAKE_BINARY_DIR}/foo")
SET_TARGET_PROPERTIES(
foo_jni PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
LIBRARY_OUTPUT_DIRECTORY_DEBUG "${CMAKE_CURRENT_BINARY_DIR}"
LIBRARY_OUTPUT_DIRECTORY_RELEASE "${CMAKE_CURRENT_BINARY_DIR}"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_CURRENT_BINARY_DIR}"
RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_CURRENT_BINARY_DIR}")
TARGET_LINK_LIBRARIES(foo_jni foo)
ADD_DEPENDENCIES(foo_jni JavaJNIHeaders)
# Testing
IF(BUILD_TESTS)
FIND_PACKAGE(Java COMPONENTS Runtime)
ADD_TEST(
NAME run-jni
COMMAND
"${Java_JAVA_EXECUTABLE}"
-cp java-jni/java/bin
-Djava.library.path=${CMAKE_CURRENT_BINARY_DIR}
arm.test.app.Main
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")
ENDIF()
Inside C path is the wrapper C to java.
#include "foo.h"
#include "FooClass_jni.h"
static jfieldID _get_self_id(JNIEnv *env, jobject thisObj)
{
static int init = 0;
static jfieldID fidSelfPtr;
if(!init)
{
jclass thisClass = env->GetObjectClass(thisObj);
fidSelfPtr = env->GetFieldID(thisClass, "self_ptr", "J");
}
return fidSelfPtr;
}
static Foo *_get_self(JNIEnv *env, jobject thisObj)
{
jlong selfPtr = env->GetLongField(thisObj, _get_self_id(env, thisObj));
return *(Foo**)&selfPtr;
}
static void _set_self(JNIEnv *env, jobject thisObj, Foo *self)
{
jlong selfPtr = *(jlong*)&self;
env->SetLongField(thisObj, _get_self_id(env, thisObj), selfPtr);
}
extern "C" JNIEXPORT void JNICALL Java_arm_test_lib_Foo_init(JNIEnv *env, jobject thisObj, jint nb)
{
Foo *self = new Foo(nb);
_set_self(env, thisObj, self);
}
extern "C" JNIEXPORT jint JNICALL Java_arm_test_lib_Foo_getValue(JNIEnv *env, jobject thisObj)
{
Foo *self = _get_self(env, thisObj);
return self->getValue();
}
extern "C" JNIEXPORT void JNICALL Java_arm_test_lib_Foo_increment(JNIEnv *env, jobject thisObj)
{
Foo *self = _get_self(env, thisObj);
self->increment();
}
extern "C" JNIEXPORT void JNICALL Java_arm_test_lib_Foo_finalize(JNIEnv *env, jobject thisObj)
{
Foo *self = _get_self(env, thisObj);
if(self != NULL)
{
delete self;
_set_self(env, thisObj, NULL);
}
}
When i ran the cmake file inside java-jni the javah generate to my this file.
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class arm_test_lib_FooClass */
#ifndef _Included_arm_test_lib_FooClass
#define _Included_arm_test_lib_FooClass
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: arm_test_lib_FooClass
* Method: init
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_arm_test_lib_FooClass_init
(JNIEnv *, jobject, jint);
/*
* Class: arm_test_lib_FooClass
* Method: getValue
* Signature: ()I
*/
JNIEXPORT jint JNICALL Java_arm_test_lib_FooClass_getValue
(JNIEnv *, jobject);
/*
* Class: arm_test_lib_FooClass
* Method: increment
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_arm_test_lib_FooClass_increment
(JNIEnv *, jobject);
/*
* Class: arm_test_lib_FooClass
* Method: finalize
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_arm_test_lib_FooClass_finalize
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
finaly i got the FooClass.java
package arm.test.lib;
public class FooClass{
static {
System.loadLibrary("foo_jni");
}
private long self_ptr;
public FooClass(int nb)
{
init(nb);
}
private native void init(int nb);
public native int getValue();
public native void increment();
protected native void finalize();
}
the code is available in:
https://github.com/eduardoaugustojulio/java-cpp-study
Best regards.
Your jni_wrapper.cpp is written for Java class Foo, but you try to use it from class FooClass.

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