I'm wondering if there is a way to access R class attribute from native code, I need it to read some generated ID that may change every time i do a clean build of my project and I would prefer not to pass them manually to the native part.
EDIT
As suggested from #trashkalmar here is the solution:
static const char* const strClassName = "your/app/package/R$string";
clazz = env->FindClass(strClassName);
if (clazz == NULL) {
LOGE("Can't find class %s\n", strClassName);
return result;
}
jfieldID field = env->GetStaticFieldID(clazz , "you_string_resource", "I");
jint value = env->GetStaticIntField(clazz, field);
Access your R class and read its fields as any other classes.
Related
I am modifying an existing library written in native C to get it to work on Android. To access some hardware elements, I need to use some Java functions. So, I'm using JNI to call Java functions from native C.
My approach is the the following:
Use JNI_OnLoad to get a reference to the Java VM.
Get the JNI environment (JNIEnv)
Find the Java class using FindClass
Create an instance of the Java class using NewObject
Create a reference to the Java method I need to call.
Later on, I call a different function which actually calls the Java method using the previously stored reference. All objects and references are stored in global variables in order to initialize them on JNI_OnLoad and use them at the additional function.
The problem is that when I call the actual Java method I always get 0 (it should return an int). If I check for exceptions, it seems there is one but but I cannot see exactly what is creating the exception.
If I call the method from the JNI_OnLoad function, then it works. This makes me think that some reference is lost when the JNI_OnLoad function exits. I am converting all objects to global references but there must be still something getting lost.
Here is a minimal example of my setup. Any help will be highly appreciated.
Thanks!
JC
Native C:
JNIEnv *jni_env = NULL;
jclass jni_cls = NULL;
jobject jni_obj = NULL;
jmethodID jni_myJavaFunc= NULL;
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
(void)reserved;
jint ver = JNI_VERSION_1_6;
if (vm == NULL)
return JNI_ERR;
if ((*vm)->GetEnv(vm, (void **)&jni_env, ver) != JNI_OK)
return JNI_ERR;
jni_cls = (jclass)(*jni_env)->NewGlobalRef(jni_env, (*jni_env)->FindClass(jni_env, "org/java/MyJavaClass"));
if (jni_cls == NULL)
return JNI_ERR;
jmethodID init = (*jni_env)->GetMethodID(jni_env, jni_cls, "<init>", "()V");
if (init == NULL)
return JNI_ERR;
jni_obj = (*jni_env)->NewGlobalRef(jni_env, (*jni_env)->NewObject(jni_env, jni_cls, init));
if (jni_obj == NULL)
return JNI_ERR;
jni_myJavaFunc = (*jni_env)->GetMethodID(jni_env, jni_cls, "myJavaFunc", "()I");
return ver;
}
int myNativeFunc()
{
if (jni_myJavaFunc != NULL) {
(*jni_env)->ExceptionClear(jni_env);
int n = (*jni_env)->CallIntMethod(jni_env, jni_obj, jni_myJavaFunc);
if (!(*jni_env)->ExceptionCheck(jni_env))
return n;
else
return -1;
}
}
Java class:
public class MyJavaClass extends Activity
{
public MyJavaClass()
{
}
public int myJavaFunc()
{
return 1;
}
}
i want to use native library from other project. here is my library.
it is my first time to use ndk in android studio. i succedd to load library .so, but failed when i want to access the file. here is the error
here is my java code that load the library.
here is my main java code
can you solve my problem? thanks
With the link to the example project that you provided in comments, the life is really easy.
You need the file https://github.com/CassieLuoli/react-native-smartconnection/blob/master/android/src/main/java/com/mediatek/demo/smartconnection/JniLoader.java as is in your project. Download it from GitHub without changing the class name or the package and use it in your Java app, like they do in their example.
You are missing the JNI layer in your C part. To let Java native interface GetLibVersion() to be able to find a matching function in C part, you need to define a C function name with Java_ai_widya_mediatekso_JniLoader_GetLibVersion(JNIEnv *env, jobject thiz) as the crash log told you. Don't miss the JNI parameters in the C function.
If you want to have the exact same function name in the C part as the Java part, you can register a new name to JVM. Call below function in your JNI_Onload().
static int registerNativeMethods(JNIEnv* env)
{
jclass clazz;
const char* className = "ai/widya/mediatekso/JniLoader";
clazz = env->FindClass(className);
if (clazz == NULL) {
ALOGE("Native registration unable to find class '%s'", className);
return JNI_FALSE;
}
JNINativeMethod methods[] = {
{"GetLibVersion", "()V", (void*) GetLibVersion },
{"GetProtoVersion", "()V", (void*) GetProtoVersion },
};
if (env->RegisterNatives(clazz, methods, 2) < 0) {
ALOGE("RegisterNatives failed for '%s'", className);
return JNI_FALSE;
}
return JNI_TRUE;
}
And don't forget to add the JNI parameters to in your C functions like this GetLibVersion(JNIEnv *env, jobject thiz).
I've seen plenty of questions about exactly the same error, but none of them seems to be trying to do this simple thing and still fail.
I have in my class header, as private members:
static JNIEnv* env;
static jclass copterServiceClass;
static jmethodID mavlinkMsgMethod;
Then in the source for that class:
JNIEnv* JU_Calls::env = 0;
jclass JU_Calls::copterServiceClass = 0;
jmethodID JU_Calls::mavlinkMsgMethod = 0;
bool JU_Calls::setupJNICalls() {
if (cached_jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
__android_log_print(ANDROID_LOG_ERROR, TAG, "Unable to get Java Env from cached JavaVM");
return -1;
}
jclass dataClass = env->FindClass("eu/deye/copterdroidair/copterdroidair/Services/CopterService");
copterServiceClass = (jclass) env->NewGlobalRef(dataClass);
mavlinkMsgMethod = env->GetMethodID(copterServiceClass, "MavlinkMsg", "(Ljava/lang/String;)V");
jobject javaObjectRef = env->NewObject(copterServiceClass, mavlinkMsgMethod);
jstring msg = env->NewStringUTF("aaaa");
env->CallVoidMethod(javaObjectRef, mavlinkMsgMethod, msg);
return true;
}
Note: cached_jvm is assigned at JNI_OnLoad.
It fails with the aforementioned error when executing NewObject.
Invalid indirect reference 0x416f68a0 in decodeIndirectRef
I tried passing NULL to the CallVoidMethod as the message, as seen on other's questions, but as it's logical, the problem is before, so useless.
Hope you can help me, as always, great SO masters ;)
EDIT: While I think Bangyno answer is the right one, what I've eventually done to solve the problem as quickest as possible, was to declare the Java Methods that would be called from C++ as static. That way I don't have to call the constructor, and everything makes a lot more sense. Because the Java class to which I was calling, was an Android Service, thus calling the constructor was erroneous for sure.
Apart from declaring the Java method static, the resulting C++ code results as follows:
jclass copterServiceClass = env->FindClass("eu/deye/copterdroidair/copterdroidair/Services/CopterService");
jmethodID mavlinkMsgMethod = env->GetStaticMethodID(copterServiceClass, "MavlinkMsg", "(ILjava/lang/String;)V");
jstring msg = env->NewStringUTF(str);
env->CallStaticVoidMethod(copterServiceClass, mavlinkMsgMethod, severity, msg);
env->DeleteLocalRef(msg);
Is very important not to forget the last line, because otherwise it will fill your JNI table and crash.
Here I want to discuss is the error message:
Invalid indirect reference 0x416f68a0 in decodeIndirectRef
It means you give the wrong argument, the second one "mavlinkMsgMethod".
In my experience, if you change "mavlinkMsgMethod" to a number such as "5", the 0x416f68a0 will change to 0x5.
The right way to use newObject is use to invoke the constructor. It should look like this, just a sample:
jclass dataClass = env->FindClass("eu/deye/copterdroidair/copterdroidair/Services/CopterService");
mavlinkMsgMethod = env->GetMethodID(copterServiceClass, "<init>", "(Ljava/lang/String;)V");
jstring str = env->NewStringUTF("testing");
jobject javaObjectRef = env->NewObject(copterServiceClass, mavlinkMsgMethod, str);
basic issue, but complicated code required:
I'd like to get the account name used on an Android device using the Android NDK mainly. For security purposes as much as possible should be done in native code. While analyzing the calls suggested in *1 (see below) in Android's source codes, it turned out that it's not possible to access the raw database file and just read out the account name due to missing permissions probably.
Therefore my next approach was to use the code of *1 (see below) and put it into a separated java class "Account" in the src-folder. Unfortunately it requires the activity context and I have no clue how to request it in native code, since everything should be done from native code without including the main activity. I assume it might be the 2nd parameter thiz of the JNI call, but I'm very unsure about it.
My current native code is based on *2 and looks like this, but it's not working so far. Do you have any hints or better solutions? Can I call the AccountManager from native code directly maybe?
// based on http://www.rgagnon.com/javadetails/java-0284.html
JNIEXPORT jstring JNICALL Java_com_example_nils_myapplication_MyNDK_getMyString(JNIEnv* env, jobject thiz){
const char *str;
jclass myclass_class =(jclass) env->NewGlobalRef
(env->FindClass ("com/example/nils/myapplication/Account"));
jmethodID constructorID = env->GetMethodID
(myclass_class, "", "()V");
jmethodID methodID = env->GetMethodID
(myclass_class, "getUsername", "(Landroid/content/Context;)Ljava/lang/String;");
jobject myclass_object = env->NewObject
(myclass_class, constructorID);
jstring s = (jstring) env->CallObjectMethod
(myclass_object, methodID, thiz);
[...]
Here the Java class
public class Account {
public Account(){
};
// based on https://stackoverflow.com/questions/2727029/how-can-i-get-the-google-username-on-android
public static String getUsername(Context c) {
AccountManager manager = AccountManager.get(c);
android.accounts.Account[] accounts = manager.getAccountsByType("com.google");
LinkedList<String> possibleEmails = new LinkedList<String>();
for (android.accounts.Account account : accounts) {
// TODO: Check possibleEmail against an email regex or treat
// account.name as an email address only for certain account.type values.
possibleEmails.add(account.name);
}
if (!possibleEmails.isEmpty() && possibleEmails.get(0) != null) {
String email = possibleEmails.get(0);
String[] parts = email.split("#");
if (parts.length > 1)
return parts[0];
}
return null;
}
}
In reply to comment #1: Of course, the native code has to be called somewhere initially. For testing purposes I placed that call in the onCreate() method of my main activity.
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyNDK a = new MyNDK();
Log.i("MyTag", a.getMyString());
In reply to the 2nd comment: MyNDK is just a simple class to load the native library, while defining the getMyString() method.
public class MyNDK {
static{
System.loadLibrary("MyLibrary");
}
public native String getMyString();
}
*1 How can I get the google username on Android?
*2 http://www.rgagnon.com/javadetails/java-0284.html
Well, thanks for the hints. I solved it. So here is the solution:
The constructor requires < init > as name
jmethodID constructorID = env->GetMethodID
(myclass_class, "<init>", "()V")
Furthermore I picked up Michael's advice and just added the context as a parameter in the java and corresponding C function. thiz2 is our desired context, while thiz is the instance of MyNDK as Michael mentioned above already.
public native String getMyString(Context c);
JNIEXPORT jstring JNICALL Java_com_example_nils_myapplication_MyNDK_getMyString
(JNIEnv * env, jobject thiz, jobject thiz2) {
Well, what's left? The account name is not returned, but for testing purposes I used a fixed return string. That solved the actual JNI problem and the app runs fine, while there must be still an error in the java implementation for receiving the account name.
Anyway. Sufficiently solved. Thanks everyone.
So I'm developing a small project with Cocos2Dx but I'm trying to add Bluetooth functionality, and that implies calling a non-static method to be able to access the Main Activity's association to the Android API. Almost everything that I've seen tells me to follow this procedure:
- Create an instance of the main activity (environment->NewGlobalRef is the one I'm using)
- Get method from activity and execute it (environment->GetObjectClass)
And here's the code. In java we have the following (omitting logical stuff like onCreate, onResume, etc):
public class TSP extends Cocos2dxActivity{
public void CnxAttempt(){
Log.e("TSP_BT","aTTEMPTING!");
}
}
That's it! Just for now, I only want to show a Log message, confirming that the function is executed. Now, the fun part is at C++:
static JNIEnv* getJNIEnv(void){
JNIEnv *env = 0;
// get jni environment
if (gJavaVM->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK){
CCLog("Failed to get the environment using GetEnv()");
}
if (gJavaVM->AttachCurrentThread(&env, 0) < 0){
CCLog("Failed to get the environment using AttachCurrentThread()");
}
return env;
}
typedef struct JniMethodInfo_{
JNIEnv * env; // The environment
jclass classID; // classID
jmethodID methodID; // methodID
} JniMethodInfo; // Struct that stores most of the important information to relate to Java code
static bool getMethodInfo(JniMethodInfo &methodinfo, const char *methodName, const char *paramCode){
jmethodID methodID = 0;
JNIEnv *pEnv = 0;
jobject methodObject = NULL;
bool bRet = false;
do {
pEnv = getJNIEnv();
if (! pEnv){
CCLog("getMethodInfo -- pEnv false");
break;
}
jclass localRef = pEnv->FindClass("org/cocos2dx/tsp/TSP");
if (localRef == NULL) {
CCLog("getMethodInfo -- localRefCls false");
break; // exception thrown
}
gCallbackObject = pEnv->NewGlobalRef(localRef);
if (gCallbackObject == NULL){
CCLog("getMethodInfo -- CallbackOBJ false");
break;
}
jclass classID = pEnv->GetObjectClass(methodObject);
if (!classID){
CCLog("getMethodInfo -- classID false");
break;
}
methodID = pEnv->GetMethodID(classID, methodName, paramCode);
if (!methodID){
CCLog("getMethodInfo -- methodID false");
break;
}
methodinfo.classID = classID;
methodinfo.env = pEnv;
methodinfo.methodID = methodID;
CCLog("getMethodInfo -- methodinfo created");
bRet = true;
} while(0);
return bRet;
}
void CnxAttempt(){
JniMethodInfo methodInfo; // Creating a JniMethodInfo object to store all the data
if (! getMethodInfo(methodInfo, "CnxAttempt", "()V")){
CCLog("getMethodInfo is FALSE :(");
return;
}
methodInfo.env->CallVoidMethod(methodObject,methodInfo.methodID);
methodInfo.env->DeleteLocalRef(methodInfo.classID);
}
And that's it! While calling CnxAttempt on C++, it goes BOOM because it doesn't recognise the method within the Java class and can't get to it...
Can someone give me a hand? If something is not clear please let me know. Thanks a bunch in advance!!
Creating a new global reference does does not create a new object. The difference between local and global references (from the docs) is:
Local references are valid for the duration of a native method call, and are automatically freed after the native method returns. Global references remain valid until they are explicitly freed.
If you want to call a non-static method to an object you need to either pass the object to the native method (if it exists - shouldn't the main activity already exist?), create a new one using the NewObject* functions, or by calling some factory method.
Then get the class object of the object, get the methodID and then call the method.