I have a simple piece of code that i want to use in my java (android) application:
#include <string.h>
#include <jni.h>
jstring
Java_com_example_ndk_MainActivity_stringFromJNI( JNIEnv* env,
jobject thiz)
{
return (*env)->NewStringUTF(env, "Hello from JNI !");
}
If i use C and call this file *.c - everything is ok, but i want this code on C++, i rename this file to *.cpp (and change Android.mk). Everything is compiled but when i try to use this function in the way i've used it in C, i have a error.
*Also i modify body of the func:
return env->NewStringUTF("Hello from JNI !");
Try to use this:
public native String stringFromJNI();
static {
System.loadLibrary("hello-jni");
}
And got such an error:
09-10 17:55:46.410: W/dalvikvm(6339): No implementation found for native Lcom/example/ndk/MainActivity;.stringFromJNI ()Ljava/lang/String;
09-10 17:55:46.410: E/AndroidRuntime(6339): java.lang.UnsatisfiedLinkError: stringFromJNI
09-10 17:55:46.410: E/AndroidRuntime(6339): at com.example.ndk.MainActivity.stringFromJNI(Native Method)
09-10 17:55:46.410: E/AndroidRuntime(6339): at com.example.ndk.MainActivity.onCreate(MainActivity.java:22)
I can't understand why the same code run in C and fail (runtime) in C++.
To allow for overloading of functions, C++ uses something called name mangling. This means that function names are not the same in C++ as in plain C.
To inhibit this name mangling, you have to declare functions as extern "C":
extern "C" jstring
Java_com_example_ndk_MainActivity_stringFromJNI( JNIEnv* env,
jobject thiz)
{
return (*env)->NewStringUTF(env, "Hello from JNI !");
}
To use C calling conventions in C++ code, surround your functions with:
extern "C"
{
/* your C API */
}
Related
I'm trying to return a string from native. For that I'm using the following code.
Native:
#include <string.h>
#include <jni.h>
jstring Java_com_test_Demo_getString(JNIEnv *env, jobject javaThis) {
return (*env)->NewStringUTF(env, "hello");
}
Java:
private native String getString(); //in com.test.Demo
After this I'm generating .so files using ndk-build and including them in jniLibs.
However, the string returned is null. Any ideas on what is wrong ?
This is driving me crazy! I'm making a library with ndk, the linking was fine but I find that, when calling a method, the value of the argument passed to the corresponding c function is incorrect.
My java class is as follows
package ccme.usernet.love;
class LovePlayerEngine {
static {
System.loadLibrary("loveplayer");
}
public static native void init(int id);
}
And my C file is as follows:
#include <jni.h>
#include <android/log.h>
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "TEST", __VA_ARGS__))
JNIEXPORT void JNICALL Java_ccme_usernet_love_LovePlayerEngine_init(JNIEnv *env, jint id)
{
LOGI("INIT with id %d\n", id);
}
The compiling and library linking was fine and the app was running.
But when I called LovePlayerEngine.init(0); somewhere in my java code, I get some bad values such as 1079062016 which is not stable and will change on different runs.
My other tests such as passing variables instead of constants or passing a String all failed in getting unexpected values.
Anyone has got any clue of where the problem could be? This is sickin me, I've never encountered this in my former ndk projects.
You're missing a parameter in your call. It should be JNIEXPORT void JNICALL Java_ccme_usernet_love_LovePlayerEngine_init(JNIEnv *env, jobject obj, jint id)
The missing parameter means you're using the object as the int value by mistake.
Gabe Sechan's answer will get you up and running for now, but the second parameter for static methods is jclass, not jobject.
Java_ccme_usernet_love_LovePlayerEngine_init(JNIEnv *env, jclass cls, jint id)
EDIT: I've solved this problem (and found the next issue in the long line of JNI tribulations!) by changing the following:
Delete "static" declaration of the native method in both Java and C++, add a Java method to get an instance of my SimpleGame class, and calling
public static void callCppApiResponse (String result, String token, long context) {
**getInstance()**.cppAndroidApiResponse(token, 200, result, result.length(), context);
}
Now the information successfully makes it from Java to C++. Hopefully this may help someone else with a similar issue.
ORIGINAL POST:
I'm using the Cocos2d-x platform and writing C++ code for a cross-platform iOS/Android app, and there are certain methods that I need to be handled by the respective native iOs/Android systems, so I need to be able to call Java from C++ then call C++ from Java.
I am able to load the shared library for the app and trigger a Java response from C++, but when I try to call C++ from Java I get the following logcat output and the app crashes:
dalvik vm No implementation found for native Lorg/cocos2dx/simplegame/SimpleGame; cppSideAndroidApiResponse:(Ljava/lang/String;ILJava/lang/String;IJ)V
AndroidRuntime java.lang.UnsatisfiedLinkError: Native method not found: org.cocos2dx.simplegame.SimpleGame.cppAndroidApiResponse:(Ljava/lang/String;ILJava/lang/String;IJ)V
Here is the relevant Java code:
package org.cocos2dx.simplegame;
private static native void cppAndroidApiResponse(String token, int response, String data, int dataLen, long context);
static {
System.loadLibrary("sb");
}
public static void callCppApiResponse (String result, String token, long context) {
//This is where the error is triggered
cppAndroidApiResponse(token, 200, result, result.length(), context);
}
and in the C++ file cppSide.h:
# if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
#include "platform/android/jni/JniHelper.h"
#include <jni.h>
#endif
#ifdef __cplusplus
extern "C" {
#endif
# if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
JNIEXPORT static void JNICALL
Java_org_cocos2dx_simplegame_SimpleGame_cppAndroidApiResponse(JNIEnv *, jobject, jstring, jint, jstring, jint, jlong);
#endif
#ifdef __cplusplus
}
#endif
Then finally the cppSide.cpp file
#include "cppSide.h"
extern "C" {
JNIEXPORT static void JNICALL
Java_org_cocos2dx_simplegame_SimpleGame_cppAndroidApiResponse(JNIEnv *env, jobject obj, jstring token, jint response, jstring data, jint length, jlong context)
{ ...do stuff...}
}
I know that my shared library is loading because I get my C++ based loading screen, and I know that JNI is working at least partially because I am able to call Java from that same cppSide.cpp file in another method. I have been researching the JNI and Cocos2d-X documentation, but to no avail.
Thanks in advance!
I have a package named com.self.tryffmpeg.
There's a file MainActivity.java inside that package which declares two native functions from the C inside the JNI folder
static {
System.loadLibrary("ffmpeg");
System.loadLibrary("ffmpeg-test-jni");
}
private native int createEngine();
private native String loadFile(String file, byte[] array);
}
Inside the JNI folder, there's a C file which exports the functions needed by the MainActivity.java.
Inside the C , I exports the functions
NIEXPORT jint JNICALL Java_com_self_tryffmpeg_MainActivity_createEngine(JNIEnv* env, jclass clazz)
{
}
JNIEXPORT jintArray JNICALL Java_com_self_tryffmpeg_MainActivity_loadFile(JNIEnv* env, jobject obj,jstring file,jbyteArray array) {
}
But the error is the functions of loadFile and createEngine cannot be resolved. UnsatisfiedLinkError. Did i do anything wrong with that. I thought I export the functions correctly.
I'm new to jni, and I was going over a tutorial to implement a simple native method, but I'm getting an unsatisfiedlinkerror. As far as I know, I followed the steps in the tutorial exactly. Please help me.
Here is the java wrapper code:
package com.cookbook.jni;
public class SquaredWrapper {
// Declare native method (and make it public to expose it directly)
public static native int squared(int base);
// Provide additional functionality, that "extends" the native method
public static int to4(int base)
{
int sq = squared(base);
return squared(sq);
}
// Load library
static {
System.loadLibrary("squared");
}
}
Here's what my Android.mk file looks like:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := squared
LOCAL_SRC_FILES := squared.c
include $(BUILD_SHARED_LIBRARY)
Here's what my .c file looks like:
#include "squared.h"
#include <jni.h>
JNIEXPORT jint JNICALL Java_org_edwards_1research_demo_jni_SquaredWrapper_squared
(JNIEnv * je, jclass jc, jint base)
{
return (base*base);
}
And here is what my .h file looks like:
enter code here/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_cookbook_jni_SquaredWrapper */
#ifndef _Included_com_cookbook_jni_SquaredWrapper
#define _Included_com_cookbook_jni_SquaredWrapper
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_cookbook_jni_SquaredWrapper
* Method: squared
* Signature: (I)I
*/
JNIEXPORT jint JNICALL Java_com_cookbook_jni_SquaredWrapper_squared
(JNIEnv *, jclass, jint);
#ifdef __cplusplus
}
#endif
#endif
Your JNI signature doesn't match. In your .c file, change:
JNIEXPORT jint JNICALL Java_org_edwards_1research_demo_jni_SquaredWrapper_squared
to
JNIEXPORT jint JNICALL Java_com_cookbook_jni_SquaredWrapper_squared
Generally there are two ways to "glue" native C through JNI to a Java function. The first is what you're attempting to do here, that is use a predetermined signature that JNI will recognize and associate with your appropriate Java code. The second is to pass function pointers, signatures, and Java class names into JNI when you include the library.
Here's the second method that would bind the native function to the appropriate Java code (this would be your .c file):
#include "squared.h"
#include <jni.h>
static const char* SquaredWrapper = "com/cookbook/jni/SquaredWrapper";
jint squared(JNIEnv * env, jobject this, jint base) {
return (base*base);
}
// Methods to register for SquaredWrapper
static JNINativeMethod SquareWrapperMethods[] = {
{"squared", "(I)I", squared}
};
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env;
if ( (*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_6) != JNI_OK)
return JNI_ERR;
jclass class = (*env)->FindClass(env, SquaredWrapper);
(*env)->RegisterNatives(env, class, SquaredWrapperMethods, sizeof(SquaredWrapperMethods)/sizeof(SquaredWrapperMethods[0]));
return JNI_VERSION_1_6;
}
void JNI_OnUnload(JavaVM* vm, void* reserved) {
JNIEnv* env;
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_6) != JNI_OK)
return;
jclass class = (*env)->FindClass(env, SquaredWrapper);
(*env)->UnregisterNatives(env, class);
return;
}
It's a good deal longer but it gives you a lot more flexibility when binding native code. The definition for squared and the includes are as you would expect. on the 4th line, the static const char* SquaredWrapper is an escaped string with the fully qualified package name of the class you want to bind squared to. Near the bottom are the JNI_OnLoad and JNI_OnUnLoad functions that take care of binding and unbinding the functions on library load and unload. The last piece is the JNINativeMethod array. This array contains as each entry an array of size 3 whose components are the Java name of the method as a const char*, the JNI signature of the Java method, and the native C function pointer to bind to that method. The JNI function signature tells the environment the argument list format and return value of the Java function. The format is "(Arg1Arg2Arg3...)Ret", so a function that takes an int and double and returns a float would have a signature of "(ID)F", and a function that takes no arguments and returns void would be "()V". I use this handy cheat sheet to remember most of the shorthand:
http://dev.kanngard.net/Permalinks/ID_20050509144235.html
Good luck :)
Edit: Oh, BTW, you'll likely want to add the signatures for JNI_OnLoad and JNI_UnOnLoad to your header, and change the name of your native function prototype to reflect the new .c file.
This is kind of an obscure case, but if you get an access violation in your native code, Android will cover up the thrown exception and throw the error you got. In my case, the native code threw an access violation but Java kept running. It then tried to call a JNI method on the crashed NDK.
To find the access violation, I ended up moving the offending JNI method to another IDE to debug.
I hope this saves someone the amount of time it took me to figure this out.