How to return variable return value from JNI function? - java-native-interface

My JNI method is:
public native int MD_GetState(int index, int result);
My C function is:
JNIEXPORT jint JNICALL Java_com_test_MD_1GetState
(JNIEnv *env, jobject obj, jint index, jint result)
{
jint* state, errCode;
jint ret = GetInfo(Index, state, errCode);
if(ret != 0) {
result = errCode;
} else {
result = state;
}
return ret;
}
GetInfo(...) is a function in a DLL, which for the purpose of this question is a black-box implementation. I would like to return the value of either state or errCode depending on whether this function returns 0 (no error) or otherwise.
Is this the correct way to do it, or is there a better way to implement this?
#user207421 In my Java code I have the following method:
public native int methodA(int index, int type, byte[] value);
And the corresponding C function:
JNIEXPORT jint JNICALL Java_com_test_methodA
(JNIEnv *env, jobject obj, jint index, jint type, jbyteArray array)
{
jboolean isCopy;
jsize len = (*env)->GetArrayLength(env, array);
jbyte* bp = (*env)->GetByteArrayElements(env, array, &isCopy);
if (!bp) {
return MDR_INSUFFICIENT_RESOURCES;
}
jint ret = GetValueA(index, type, bp, len);
int mode = 0;
// if error code is returned then do not save changes
if(ret != 0) {
mode = JNI_ABORT;
}
(*env)->ReleaseByteArrayElements(env, array, bp, mode);
return ret;
}
GetValueA(...) makes changes to bp and I am able to return the data back to my Java code in the same way, so I imagine it should be a similar implementation?
#cdhowie According to the documentation I have, the possible values for state and errCode do not overlap, so I thought it was possible to implement it this way. If there is a better way to do this then please let me know, thanks!

You have few options here.
You can throw exception from JNI code in case there is an error and add error code inside Exception
Take a look here: http://jnicookbook.owsiak.org/recipe-No-019/
You can create something 'like' union. It will not be an union (sensu stricto), but will serve similar purpose
Java
class RetVal {
int index;
int result;
}
and then, you will have to access fields of this class inside JNI code.
You will be able to update values of the object and return (via object) values as you like.
Note that you can alter the object's state inside JNI
Take a look here: http://jnicookbook.owsiak.org/recipe-No-020/
You can use type size hack
You can return long instead of int. This way, you can compose your long value from two int values.
int a = 1;
int b = 1;
long ret = ((long)a) << 32 | ((long)b);
You can alter array of int values
Take a look here: http://jnicookbook.owsiak.org/recipe-No-013/
You can return values inside java.util.Vector
Take a look here: http://jnicookbook.owsiak.org/recipe-no-045/

Related

GetFloatArrayElements get wrong result

i have searched the solution from the internet and my code look like same with other solution. i think GetFloatArrayElements is for get array element.
here is my code:
JNIEXPORT void JNICALL
Java_draw(JNIEnv *env, jfloatArray point){
float temp[6];
float x = 0;
jfloat *body = env->GetFloatArrayElements(point, 0);
for(int i =0; i < 6 ; i++){
temp[i] = body[i];
x+= body[i];
__android_log_print(ANDROID_LOG_ERROR, "TRACKERS123", "[%f]", *(body + i) );
}
env->ReleaseFloatArrayElements(point, body, 0);
}
result is always like this:
[21.774231]
[0.000000]
[21.707932]
[21.776413]
[0.000000]
[0.000000]
i have checked the calling code from debug. here is the example value:
The signature of your Java_draw method is wrong: (source)
The first argument is always a JNIEnv*.
The second argument depends on whether the method is static or non-static:
If static, the second argument is a jclass.
If non-static, the second argument is a jobject representing the object instance.
The third argument is your float[].
So the signature should be:
JNIEXPORT void JNICALL Java_draw(JNIEnv *env, jclass klass, jfloatArray point) // static
or
JNIEXPORT void JNICALL Java_draw(JNIEnv *env, jobject obj, jfloatArray point) // non-static

How to insert a jint into a jobjectArray?

How can I properly construct and pass an array of diverse objects through jni? I seem to have trouble converting a jint into jobject.
My original function was:
extern "C" JNIEXPORT jint ... Func(...)
{
int res = CallNativeFunc();
return res;
}
Now, I am modifying the function so it will return an array of objects. The first object will be the original res, and the second object will be a jbyteArray.
I have tried:
extern "C" JNIEXPORT jobjectArray ... Func(...)
{
std::vector<unsigned char> outData;
int res = CallNativeFunc(&outData);
//construct the returning object array of size 2
jclass objectClass = env->FindClass("java/lang/Object");
jobjectArray results = env->NewObjectArray(2, objectClass, 0);
//construct the 2nd object (a jbyteArray)
jbyteArray SecondObject = env->NewByteArray(outData.size());
//transfer data into the 2nd object
env->SetByteArrayRegion(SecondObject , 0, outData.size(), reinterpret_cast<const signed char*>(outData.data()));
//transfer the 1st and 2nd objects into the return array
env->SetObjectArrayElement(results, 0, static_cast<jint>(res));
env->SetObjectArrayElement(results, 1, SecondObject);
return results;
}
The compiler fails at the following line, saying it cannot convert a jint into jobject:
env->SetObjectArrayElement(results, 0, static_cast<jint>(res));
What am I doing wrong? Is this the correct approach to do what I am trying to do? (pass an array "structure" of sorts back to the java)
An int in Java is a primitive type, not an Object. So if you want to store an int in an Object[] you need to wrap it in an Integer.
The code would look something like this (untested):
jclass integerClass = env->FindClass("java/lang/Integer");
jmethodID integerConstructor = env->GetMethodID(integerClass, "<init>", "(I)V");
jobject wrappedInt = env->NewObject(integerClass, integerConstructor, static_cast<jint>(res));
env->SetObjectArrayElement(results, 0, wrappedInt);

ReleaseStringUTFChars not working for std::string

I use std::string in my jni function, and I fail to release it using ReleaseStringUTFChars.
The error I get is:
error: no matching function for call to NIEnv::ReleaseStringUTF env->ReleaseStringUTFChars(path, dir);
I understand, that instead of string, the function expects to get char, but I don't have such variable. What should I do?
This is my jni function:
void Java_com_example_android_OpenCVActivity_test (JNIEnv * env, jclass clazz, jstring path){
std::string dir = env->GetStringUTFChars(path, 0);
....
env->ReleaseStringUTFChars(path, dir);
}
You need to pass it exactly the same value you got from GetStringUTFChars(). You're not. You're passing whatever std::string gives you, which isn't necessarily the same thing.
The book Mastering Android NDK defines the function
auto ConvertJString(JNIEnv* env, jstring str) -> std::string
{
if (!str) { return std::string(); }
auto const len = env->GetStringUTFLength(str);
auto const strChars = env->GetStringUTFChars(str, static_cast<jboolean*>(0));
std::string result(strChars, len);
env->ReleaseStringUTFChars(str, strChars);
return result;
}
This handles getting and releasing the string as well as returning an std::string.

How do I call a function that takes several ints with a vector of ints?

The problem
I'm trying to write an interpreter for a toy language, and I want it to be able to call functions located in a DLL. In some external.dll I have:
#include <cstdio>
extern "C" {
__declspec(dllexport) void print(int val) { printf("%i\n", val); }
__declspec(dllexport) int add(int a, int b) { return a + b; }
... more functions **that I don't know then names of**
}
Suppose I have a std::string func; which is the name of a proc in the DLL, possibly "print" or "add", and a std::vector<int> args; whose size is the number of arguments of the target function. How would I call the correct DLL function accordingly? Ideally I would like to be able to call any function that can be loaded using GetProcAddress.
My workaround
I'm currently using MSVC's inline assembler to do what I want. It's something along the lines of:
int WorkaroundCall(const std::string& func, const std::vector<int>& args) {
void* proc = GetProcAddress(hmod, func.c_str()); // hmod is the DLL's HMODULE
void* spsave, * argframe;
size_t argsize = sizeof(int) * args.size();
const int* argdata = args.data();
__asm {
mov eax, esp
sub eax, argsize
mov argframe, eax
}
memcpy(argframe, argdata, argsize);
__asm {
mov spsave, esp
mov esp, argframe
xor edx, edx
call proc
mov esp, spsave
}
}
However, this is obviously a not a good solution because it uses Assembly and depends on the system (Something tells me this won't work on 64-bit). How can I do this better?
Something like:
#define EXTERNAL_API __declspec(dllimport)
typedef void (EXTERNAL_API* LPPRINTFN)( int );
typedef int (EXTERNAL_API* LPADDFN)( int, int );
// After loading the module you get the functions.
LPPRINTFN pfnPrint = (LPPRINTFN) GetProcAddress( hmod, "print" );
LPADDFN pfnAdd = (LPADDFN) GetProcAddress( hmod, "add" );
Now, since you have those strings, you may want to map them to a unique value (assume the map is global):
typedef enum FuncType {
Nothing = 0,
PrintFunc = 1,
AddFunc = 2
} EFuncType;
typedef map< string, EFuncType > TFuncNameMap;
TFuncNameMap funcNameMap;
if( pfnPrint != NULL ) funcNameMap["print"] = PrintFunc;
if( pfnAdd != NULL ) funcNameMap["add"] = AddFunc;
Finally, the call (excluding any bounds checking on the arguments vector):
int SlightlyBetterCall( const std::string& func, const std::vector<int>& args )
{
TFuncNameMap::iterator iFuncId = funcNameMap.find(func);
if( iFuncId == funcNameMap.end() )
return -1; // return some error?
int result = 0;
switch( iFuncId->second ) {
case PrintFunc:
pfnPrint( args[0] );
break;
case AddFunc:
result = pfnAdd( args[0], args[1] );
break;
}
return result;
}
You don't really need that map... Nothing to stop you from going:
if( func == "print" && pfnPrint != NULL ) {
pfnPrint( args[0] );
}
else if( func == "add" && pfnAdd != NULL ) {
result = pfnAdd( args[0], args[1] );
}
This whole thing seems a little suspicious to me, but I hope that helps you in any case =)
Ok got it:-
You are creating space on stack,and then passing the offset to proc.
If stack is not the only requirement then its good to pass the address of data in the heap.
However you can allocate space on stack using arrays.
int WorkaroundCall(const std::string& func, const std::vector<int>& args) {
void (*proc)(int);
void* proc = GetProcAddress(hmod, func.c_str()); // hmod is the DLL's HMODULE
void* spsave, * argframe;
size_t argsize = sizeof(int) * args.size();
const int* argdata = args.data();
int *dat= new int[argsize];
memcpy(dat, argdata, argsize);
proc(dat);
delete[] dat;
}
I'm getting the general sense that the only solution is to use LibFFI as suggested here by Mehrdad (which, unfortunately, doesn't support MSVC), or to continue using Assembly and maintain different versions for 32 and 64 bit (which is what I'll try to do).
Generating all method signatures as suggested by Chris Becke is impractical as the toy language is to have more types than just int.
Again, sorry - I should have made it clear that the function names in the DLL aren't known to the program at compile time.
The only way to do this in C (and hence C++), without resorting to assembly, is to declare all possible function signatures, and then call the appropriate signature based on the number of arguments.
The next thing to note is that, on 64bit builds, 'int' isn't going to cut it - the basic parameter size would be a 64bit type. As this is a windows question, I'll use types from the windows sdk to ensure that it compiles and works correctly in both Win32 and Win64.
#include <windows.h>
typedef INT_PTR CALLBACK Tfunc0(void);
typedef INT_PTR CALLBACK Tfunc1(INT_PTR p1);
typedef INT_PTR CALLBACK Tfunc2(INT_PTR p1,INT_PTR p2);
INT_PTR CallProc(FARPROC proc, const std::vector<INT_PTR>& args) {
switch(args.size())
{
case 0: return ((TFunc0*)proc)();
case 1: return ((TFunc1*)proc)(args[0]);
case 2: return ((TFunc2*)proc)(args[0],args[1]);
}
throw some_kind_of_exception;
}
Alternatively, a templated approach could allow you to more easilly call procs:
template<typename RetT>
RetT CallProc(FARPROC proc){
return (RetT)(((TFunc0*)proc)());
}
template<typename RetT,typename Param1T>
RetT CallProc(FARPROC proc, Param1T p1){
return (RetT)(((TFunc1*)proc)((INT_PTR)p1));
}
//etc.

How to pass lua string(binary) to c++ using tolua++

I hava a class likeļ¼š
class SomeClass
{
void initFromBuffer(void* buffer,int length);
void initFromString(const std::string& str);
}
Using tolua++, got the binding like:
static int SomeClass_initFromBuffer00(lua_State* tolua_S)
{
SomeClass* self = (SomeClass*) tolua_tousertype(tolua_S,1,0);
void* buffer = ((void*) tolua_touserdata(tolua_S,2,0));
int length = ((int) tolua_tonumber(tolua_S,3,0));
self->initFromBuffer(buffer,length);
}
and:
static int SomeClass_initFromString00(lua_State* tolua_S)
{
SomeClass* self = (SomeClass*) tolua_tousertype(tolua_S,1,0);
const std::string str = ((const std::string) tolua_tocppstring(tolua_S,2,0));
self->initFromString(str);
tolua_pushcppstring(tolua_S,(const char*)str);
}
Now,i want to pass binary data from lua to c++,the binary has '\0' in it,so if i use initFromString to pass it, the binary data will be trimed. But if i use initFromBuffer to pass it, i got bad ptr at `void* buffer = ((void*) tolua_touserdata(tolua_S,2,0));, the pointer is null.
So, how could i pass binary string from lua to c++?
Maybe you should stop using Tolua's bad APIs and use plain Lua's actually good APIs. Both std::string and Lua strings are capable of storing embedded null characters. The only reason tolua_tocppstring causes truncation is because the function name is a lie. It doesn't convert it to a C++ string; it converts it to a C string, a const char*.
The correct answer is to use the proper API function:
std::string fromLuaStack(lua_State *lua, int stackIx)
{
size_t len;
const char *str = lua_tolstring(lua, stackIx, &len);
return std::string(str, len);
}
Similarly, you can use lua_pushlstring to push a std::string onto the stack.
It's unfortunate that Tolua doesn't have better documentation, as there may be a function to do this all directly. If there is, I couldn't find it.