JNI for C++ class UnsatisfieldLinkError - c++

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.

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.

Logging from library with spdlog

I am trying to use spdlog in a project involving a library under windows.
I create two loggers. One for the app using the library, one for the library itself.
The library's logger is created from the app but when the library want to add a message, it crash.
Following is an simplified example.
The library
libclass.h
#ifndef LIBCLASS_H
#define LIBCLASS_H
#include <spdlog/spdlog.h>
#ifdef WIN32
# ifdef BUILD_APPLIB_SHARED
# define APPLIB_EXPORT __declspec(dllexport)
# else
# define APPLIB_EXPORT
# endif //BUILD_APPLIB_SHARED
#else
# define APPLIB_EXPORT
#endif // WIN32
class APPLIB_EXPORT LibClass
{
public:
LibClass();
~LibClass();
static std::string loggerName();
void testLog();
private:
std::shared_ptr<spdlog::logger> m_logger;
};
#endif //LIBCLASS_H
libclass.cpp
#include "libclass.h"
const std::string myLoggerName = "lib_logger";
LibClass::LibClass()
{
m_logger = spdlog::get(myLoggerName);
}
LibClass::~LibClass()
{ }
std::string LibClass::loggerName()
{
return myLoggerName;
}
void LibClass::testLog()
{
m_logger->info("Log from library");
}
The application
main.cpp
#include <spdlog/spdlog.h>
#include <applib/libclass.h>
void logtest()
{
auto logger = spdlog::get("app_logger");
logger->info("Log from application");
}
int main(int argc, char *argv[])
{
// create loggers
auto appLogger = spdlog::stdout_logger_mt("app_logger");
auto libLogger = spdlog::stdout_logger_mt(LibClass::loggerName());
// log from app
logtest();
// log from lib
LibClass lc;
lc.testLog();
return 0;
}
Spdlog uses a singleton registry for keeping track of available loggers. You get one instance of that registry per dll and exe though.
When you create the logger in the exe, it is added to the exe's registry but not the dlls. When you use spdlog::get(myLoggerName) in your dll, you are querying the registry of the dll which does not contain the "lib_logger" and thus you are getting a null shared_ptr.
Possible solutions are: create the lib_logger in the lib instead of the exe if you are only using it in the dll anyway, or pass the logger from the exe to the dll and call spdlog::register_logger with it inside the dll before calling spdlog::get(myLoggerName). Then you can use the same logger from both the exe and the dll.
An example how one can register loggers across dll boundaries can be found at https://github.com/gabime/spdlog/wiki/How-to-use-spdlog-in-DLLs
Or an example using your code:
The library
libclass.h
#ifndef LIBCLASS_H
#define LIBCLASS_H
#include <spdlog/spdlog.h>
#ifdef WIN32
# ifdef BUILD_APPLIB_SHARED
# define APPLIB_EXPORT __declspec(dllexport)
# else
# define APPLIB_EXPORT
# endif //BUILD_APPLIB_SHARED
#else
# define APPLIB_EXPORT
#endif // WIN32
class APPLIB_EXPORT LibClass
{
public:
LibClass();
~LibClass();
static std::string loggerName();
void testLog();
private:
std::shared_ptr<spdlog::logger> m_logger;
};
APPLIB_EXPORT void registerLogger(std::shared_ptr<spdlog::logger> logger);
#endif //LIBCLASS_H
libclass.cpp
#include "libclass.h"
const std::string myLoggerName = "lib_logger";
LibClass::LibClass()
{
m_logger = spdlog::get(myLoggerName);
}
LibClass::~LibClass()
{ }
std::string LibClass::loggerName()
{
return myLoggerName;
}
void LibClass::testLog()
{
m_logger->info("Log from library");
}
APPLIB_EXPORT void registerLogger(std::shared_ptr<spdlog::logger> logger)
{
spdlog::register_logger(logger);
}
The application
main.cpp
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_sinks.h>
#include <applib/libclass.h>
void logtest()
{
auto logger = spdlog::get("app_logger");
logger->info("Log from application");
}
int main(int argc, char* argv[])
{
// create loggers
auto appLogger = spdlog::stdout_logger_mt("app_logger");
auto libLogger = spdlog::stdout_logger_mt(LibClass::loggerName());
registerLogger(libLogger);
// log from app
logtest();
// log from lib
LibClass lc;
lc.testLog();
return 0;
}
Your example works fine on the current version of spdlog:
$ g++ -std=c++11 main.cpp libclass.cpp -I .
$ ./a.out
[2015-08-24 07:29:04.502] [app_logger] [info] Log from application
[2015-08-24 07:29:04.502] [lib_logger] [info] Log from library
Same when using a shared library:
# CMake config:
project(TEST)
include_directories(.)
add_library(class SHARED libclass.cpp)
target_compile_options(class PUBLIC -std=c++11)
add_executable(main main.cpp)
target_link_libraries(main class)
Results:
$ cmake .
$ make
$ ldd main
...
libclass.so => ...
$ ./main
[2015-08-25 08:57:51.864] [app_logger] [info] Log from application
[2015-08-25 08:57:51.864] [lib_logger] [info] Log from library
Make sure that you link with a DLL runtime on Windows.

Qt resources files with CMake and AUTORCC

Solution:
Add resource files in the add_executable() statement
Problem
(not in the add_library())
Fail to set main window icon.
Notes:
When I don't use AUTORCC I run into some compilation problems:
QtCore/qglobal.h: no such file or directory. But, I do prefer AUTORCC as a more modern CMake approach.
Without the AUTORCC (different CMakeLists.txt than the provided) and Qt-4.6.2 the current code worked.
different CMakeLists.txt)
Code
This is a minimized code of my project. Tree:
|- CMakeLists.txt
|- main_window.hpp
|- main_window.cpp
|- main.cpp
|- resources
| - resources.qrc
| - images
| - logo.png
main_window.cpp
#ifndef MAINWINDOW_HPP
#define MAINWINDOW_HPP
#include <QMainWindow>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow();
};
#endif
main_window.cpp
#include "main_window.hpp"
MainWindow::MainWindow()
{
// i tried ":/images.png", ":/resources/images/logo.png", ":/logo.png"
setWindowIcon(QIcon(":images/logo.png"));
}
main.cpp
#include <QApplication>
#include "main_window.hpp"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
app.setOrganizationName("Organization");
app.setApplicationName("Application Example");
MainWindow mainWin;
mainWin.show();
return app.exec();
}
CMakeLists.txt.
cmake_minimum_required(VERSION 3.1)
project(qt_project)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
find_package(Qt4 4.6 REQUIRED)
set(QT_USE_QTGUI TRUE)
set(QT_USE_QTXML TRUE)
include(${QT_USE_FILE})
add_definitions(${QT_DEFINITIONS})
// NOTE: it would be more convenient to be able to add the
// resource file here upon the creation of the library
add_library(mylib main_window.cpp)
// SOLVED
// BEFORE: add_executable(qt_test main.cpp)
add_executable(qt_test main.cpp resources/resources.qrc)
target_link_libraries(qt_test
mylib
${QT_LIBRARIES}
)
resources/resources.qrc
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file>images/logo.png</file>
</qresource>
</RCC>
Edit
This is the generated qrc_resources.cxx
#include <QtCore/qglobal.h>
static const unsigned char qt_resource_data[] = {
// /users/ddakop/dev/misc/qt/resources/images/logo.png
// ... removed hex data
};
static const unsigned char qt_resource_name[] = {
// images
// ... removed hex data
// logo.png
// ... removed hex data
};
static const unsigned char qt_resource_struct[] = {
// :
0x0,0x0,0x0,0x0,0x0,0x2,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x1,
// :/images
0x0,0x0,0x0,0x0,0x0,0x2,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x2,
// :/images/logo.png
0x0,0x0,0x0,0x12,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x0,
};
QT_BEGIN_NAMESPACE
extern Q_CORE_EXPORT bool qRegisterResourceData
(int, const unsigned char *, const unsigned char *, const unsigned char *);
extern Q_CORE_EXPORT bool qUnregisterResourceData
(int, const unsigned char *, const unsigned char *, const unsigned char *);
QT_END_NAMESPACE
int QT_MANGLE_NAMESPACE(qInitResources_resources)()
{
QT_PREPEND_NAMESPACE(qRegisterResourceData)
(0x01, qt_resource_struct, qt_resource_name, qt_resource_data);
return 1;
}
Q_CONSTRUCTOR_FUNCTION(QT_MANGLE_NAMESPACE(qInitResources_resources))
int QT_MANGLE_NAMESPACE(qCleanupResources_resources)()
{
QT_PREPEND_NAMESPACE(qUnregisterResourceData)
(0x01, qt_resource_struct, qt_resource_name, qt_resource_data);
return 1;
}
Q_DESTRUCTOR_FUNCTION(QT_MANGLE_NAMESPACE(qCleanupResources_resources))
System
CentOS-5, Qt-4.8.6, CMake-3.2.1, gcc-4.8.2
I think you need to link qrc_resources generated file.
I suppose you know the next info:
http://www.cmake.org/cmake/help/latest/manual/cmake-qt.7.html
Where you can see the next line:
add_executable(myexe main.cpp resource_file.qrc)
More info:
http://www.cmake.org/cmake/help/latest/prop_tgt/AUTORCC.html
I'm not sure if this is new or not, but I have figured out a way to add resources to libraries.
In my library's CMakeLists.txt I have the following:
list (APPEND RESOURCES library.qrc)
qt5_add_resources (RCC_SOURCES ${RESOURCES})
...
add_library(library ${RCC_SOURCES} ${SOURCES})
In the library, in one of my "main classes" (for me, this class was the first thing that gets created in the library and the last thing that gets destroyed), I did the following:
class libClass : public QMainWindow {
Q_OBJECT
public:
libClass() : QMainWindow(nullptr, 0) {
Q_INIT_RESOURCE(library);
}
~libClass() {
Q_CLEANUP_RESOURCE(library);
}
};