I have a peculiar issue with Android and a JNI library that I am using. The JNI library is named libBase.so and is properly included in the APK. I am able to do System.loadLibrary("Base") and the library gets loaded and can be used without any problems.
Consider the following code snippets:
/* NativeObject.java */
public class NativeObject
{
/* ... */
static
{
System.loadLibrary("Base");
}
}
/* ObjectUtil.java */
public class ObjectUtil
{
public static native NativeObject readNative(String path);
public static native void writeNative(String path, NativeObject obj);
}
When I attempt to do ObjectUtil.readNative() in the beginning of my program, I get the following issues in logcat:
05-19 18:57:38.508: WARN/dalvikvm(365): No implementation found for native Lcom/navimatics/base/ObjectUtil;.readNative (Ljava/lang/String;)Lcom/navimatics/base/NativeObject;
05-19 18:59:15.409: ERROR/AndroidRuntime(365): java.lang.UnsatisfiedLinkError: readNative
My understanding is that because the class ObjectUtil references NativeObject, loading the ObjectUtil class should force the loading of the NativeObject class and thus the execution of the NativeObject static initializer which performs the System.LoadLibrary() call. It is not clear then why I would get the aforementioned logcat messages.
However if I modify ObjectUtil.java to read as follows there is no exception and the program works:
/* ObjectUtil.java */
public class ObjectUtil
{
public static native NativeObject readNative(String path);
public static native void writeNative(String path, NativeObject obj);
static
{
System.loadLibrary("Base");
}
}
It also works if I do this:
/* ObjectUtil.java */
public class ObjectUtil extends NativeObject
{
public static native NativeObject readNative(String path);
public static native void writeNative(String path, NativeObject obj);
}
Any help on this would be greatly appreciated.
UPDATE:
The native side code that I was asked for is this:
static jobject JNICALL readNative(JNIEnv *env, jclass jcls, jstring jpath)
{
// ...
}
static void JNICALL writeNative(JNIEnv *env, jclass jcls, jstring jpath, jobject jobj)
{
// ...
}
static JNINativeMethod methods[] =
{
{ "readNative", "(Ljava/lang/String;)Lcom/navimatics/base/NativeObject;", readNative },
{ "writeNative", "(Ljava/lang/String;Lcom/navimatics/base/NativeObject;)V", writeNative },
};
The methods are registered using JNIEnv.RegisterNatives().
You have two extra parenthesis in initializing the JNINativeMethod array, this code shouldn't compile.
Check the return code of RegisterNatives(), it should be zero on success.
Also in case you're compiling a C++ code, you should declare the native methods extern "C" to avoid getting them mangled.
Related
I have a C++ library with objects and am using this in conjunction with Java over JNI. Ideally, I'd like to tie the life cycle of the C++ objects to their Java equivalents and make it controllable from Java. Important to note is that the application is multi-threaded, so the C++ objects are accessed from multiple threads, and that those objects aren't modified after their initial construction. My current implementation looks something like this:
struct Tree {
int age = 0;
std::string type;
}
typedef Tree* TreeHandle;
/*
* Class: com_example_Tree
* Method: initialize
* Signature: ([Lcom/example/Tree;)I
*/
JNIEXPORT jlong JNICALL Java_com_example_Tree_initialize
(JNIEnv * env, jclass cls, jobject j_tree) {
auto tree_ptr = new Tree();
return (jlong) tree_ptr;
}
/*
* Class: com_example_Tree
* Method: dispose
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_com_example_Tree_dispose
(JNIEnv * env, jclass cls, jlong j_tree_handle) {
auto tree_ptr = reinterpret_cast<TreeHandle*>(j_tree_handle);
delete tree_ptr;
}
My Java part then looks like this:
class Tree implements AutoClosable {
static {
System.load("/path/to/tree.so");
}
private long handle;
public Tree() {
this.handle = initialize();
}
#Override
protected void finalize() {
if (this.handle != 0) {
this.close();
}
}
#override
public synchronized void close() {
dispose(this.handle);
this.handle = 0;
}
private static native long initialize();
private static native void dispose();
}
However, the result is that I have some global objects floating around in memory that are only accessible via a C-style pointer. Is there a more modern way of achieving the same, i.e., using smart pointers or directly designing the C++ objects as members of the Java class?
I'm struggling with a piece of code and at this point I'm questioning if what I'm trying to achieve is even possible.
Below is the code of the various DLL and the actual piece of code that's giving me an headache
First DLL:
// DLL_A
// This DLL is the main SDK which gives me the capability to create and manage extensions
class IViewportExtensionBase
{
public:
virtual ~IViewportExtensionBase();
...
};
template<typename ExtensionType>
class IViewportExtension : public IViewportExtensionBase
{
protected:
IViewportExtension()
: IViewportExtensionBase(ExtensionType::_GetName())
{ }
};
class ExtensionsManager
{
public:
static IViewportExtensionBase* GetExtension(const std::string& extensionName)
{
return _extensions[extensionName];
}
private:
std::unordered_map<std::string, IViewportExtensionBase*> _extensions;
};
Second DLL:
// DLL_B
// This is another library of the SDK which links DLL_A to add a custom capabilities to the extensions
class ViewportExtensionAdditions
{
public:
virtual ~ViewportExtensionAdditions()
{ }
virtual void CustomAddition()
{ }
};
template<typename ExtensionType>
class ICustomViewportExtension : public IViewportExtension<ExtensionType>, public ViewportExtensionAdditions
{ };
Extension DLL
// DLL_EXTENSION
// This DLL contains the actual extension which implements the interface defined by DLL_B 8of course this links DLL_B too)
class ViewportExtension_A : public ICustomViewportExtension<ViewportExtension_A>
{
public:
void CustomAddition() override;
...
}
Actual program giving me the error:
// This is a piece of code of the final program which loads the extensions and then tries to call the custom capabilities added by DLL_B
// This program knows about the headers of both DLL_A and DLL_B and links them, but it doesn't know about the headers of DLL_EXTENSION which
// is dynamically loaded by the extensions manager
auto extension = (ViewportExtensionAdditions*)ExtensionsManager::GetExtension("ViewportExtension_A");
if (extension != nullptr)
{
// This call doesn't work, meaning that is never calls the overridden method in ViewportExtension_A
// but calls some other method which looks like the virtual table is somewhat off
extension->CustomAddition();
}
I'm afraid that deriving a class injecting another (what I'm doing in DLL_B) and then casting to that other class (what I'm doing in the acqual program) is not possible.
If actually turns out it's not possible, what could be a way to achieve what I'm trying to do?
There are two class A, B
m_Class, m_MethodID are not NULL..
But system will crash after call env->CallVoidMethod, Why?
public Class A extends Activity {
protected void onActivityResult(int reqCode, int resultCode, Intent data) {
super.onActivityResult(reqCode, resultCode, data);
B BClass = new B();
BClass.setFunction(this, "testFunc");
}
public void testFunc() {
Log.e("", "Test");
}
}
public Class B {
public native void setFunction(Object caller, String method);
}
-------------JNI-------------
JNIEXPORT void JNICALL Java_com_B_setFunction(JNIEnv *env, jobject thiz, jobject clsCaller, jstring sMethod) {
string sMethodName = jstring2str(env, sMethod);
jclass m_Class = env->GetObjectClass(clsCaller);
jmethodID m_MethodID = env->GetMethodID(m_Class, sMethodName.c_str(), "()V");
env->CallVoidMethod(m_Class, m_MethodID);
}
I had resolve this issue..
I refer to this page Android NDK: calling java method from JNI C class
and modify the code in JNI
from
env->CallVoidMethod(m_Class, m_MethodID);
to
env->CallVoidMethod(clsCaller, m_MethodID);
Because CallVoidMethod is trigger by instance(clsCaller) not class(m_Class)
For example, I have a Java class:
public class JniTest {
public int member_int_;
public int member_int_2_;
public boolean member_bool_;
public static int member_static_int_ = 90;
public String member_str_;
void print() {};
}
in JNI, I want to get each member without knowing their names in advance:
JNIEXPORT void JNICALL Java_com_tencent_qqmail_protocol_JniMethodTest_TestObject(JNIEnv * env, jclass cls, jobject obj) {
*for (each member in obj)* { ... } //how to implement ?
}
Starter : Trail: The Reflection API
Method getDeclaredMethods returns an array of Method objects, excluding inherited methods
Method getDeclaredFields returns an array of Field objects, excluding inherited fields
Method getSuperclass returns the super class
As #technomage commented, doing all the work in JNI is hard to do correctly (especially if you don't like memory leak and crash)
If you are not already "fluent" in JNI, start with simpler goals.
I'm working on an application that can be managed with a shared library. I've created an interface class, in this class there are some virtual methods/members. For example there is a method named Initialize(), if you want to implement something at the start, you should override this method.
Now I want to make a event/callback like system for managing application. How? If you want to log a message to console+log file, you'll call a method like Log("Failed to initialize MySQL") but I can't do it with a interface. I've tried something like that but I've failed;
//ManagerBase Header
class ManagerBase
{
public:
virtual void Initialize();
void Log(char* message);
}
//Manager Library
class Manager : public ManagerBase
{
public:
void Initialize() { Log((char*)"Manager's initialize!"); }
}
Can anybody help me improve this implementation?
EDIT1: I've implemented "Log()" function in my application. (like printf("%s", message);) When I compile Manager Library, compiler says "Log() method not implemented!"
EDIT2: Here is my codes, if someone wants to look them inside.
EDIT3: I'm getting this error while I'm trying to compile Manager library: undefined reference to `ManagerBase::Log(char)' [in Manager.cpp]*
I think the better fit for this is composition. Rather than attempting to define the variable behaviour in your base class (ManagerBase), you can have Manager define when the behaviour should occur, and pass that behaviour into Manager as its own object. ManagerBase is no longer needed.
// *** Behaviour interfaces ***
// ILogger.h
class ILogger
{
public:
~ILogger(){};
virtual void Log( const char *pMessage ) = 0;
};
//*******************************
// IDatabse.h
class IDatabase
{
public:
~IDatabase(){}
virtual void CreateDB( const char *pDBName ) = 0;
};
//*******************************
// ** Manager **
// Manager.h
class ILogger;
class IDatabase;
class CManager
{
public:
CManager(
ILogger &logger,
IDatabase &db );
void Initialise( const char *pDBName );
private:
ILogger &m_logger;
IDatabase &m_db;
};
// Manager.cpp
#include "ILogger.h"
#include "IDatabase.h"
#include "Manager.h"
CManager::CManager( ILogger &logger, IDatabase &db )
: m_logger(logger) // this is like registering a Log callback
, m_db(db) // and a (set of) db callback/s
{
}
void CManager::Initialise( const char *pDBName )
{
m_logger.Log( "Initialising" ); // this is akin to raising a log 'event'
m_db.CreateDB( pDBName ); // and a create DB event.
}
//*******************************
// ** Application **
// Application.h
#include "ILogger.h"
#include "IDatabase.h"
#include "Manager.h"
class CApp : public ILogger, public IDatabase
{
/* You don't have to have your main app inherit the interface. You could
implement them as their own objects, owned by CApp, or at least
something that is reachable when contructing CManager */
public:
CApp()
: m_Manager( *this, *this )
{
m_Manager.Initialise("MyAwesomeDatabaseOfDoom");
}
// implement overrides for Log and CreateDB here.
void Log( const char* pMessage ){}
void CreateDB( const char *pDBName ){}
private:
CManager m_Manager;
};
//*******************************
// ** Application **
// main.cpp
int main()
{
CApp();
return 0;
};
This is just a quick mock up, so apologies if it doesn't compile. I hope it at least illustrates the idea.
If you want to be able to change your behaviour at runtime, you can use ILogger/IDatabase member pointers rather than references in CManager, and expose setters so that you can set whatever behaviour defining object you like. This lets you do interesting things like, for example, changing what you log to based on a config file setting, by implementing a CFileLogger and a CDatabaseLogger version of ILogger and setting them appropriately.