简体   繁体   English

jni 不支持 void*、unsigned int* 等类型……怎么办?

[英]jni not support types as void*, unsigned int*, … What to do?

I have .so (shared library) written in C++, lets call it functionality.so in which I implement different functions, here is list of some functions:我有用 C++ 编写的.so (共享库),我们称之为功能。so 在其中我实现了不同的功能,这里是一些功能的列表:

1. unsigned long Initialize(void* userData);
2. unsigned long Uninitialize(void);
3. unsigned long DeviceOpen( unsigned long id, unsigned long* device);
4. unsigned long DeviceClose( unsigned long device );

and so on...等等...

I want to use this library's ( functionality.so ) functionality in my java application for android.我想在 android 的 java 应用程序中使用这个库的( function.so )功能。 For that I create jni folder in my android application project folder and place there files:为此,我在我的 android 应用程序项目文件夹中创建 jni 文件夹并将文件放在那里:

  1. Android.mk Android.mk

     LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE:= Test_library LOCAL_SRC_FILES:= Test_library.c ## Linking functionality library LOCAL_LDLIBS:= -lfunctionality include $(BUILD_SHARED_LIBRARY)
  2. Test_library.c Test_library.c

     #include <string.h> #include <jni.h> #include "Test_library.h" jint Java_com_Dsm_Test_DsmLibraryTest_vtUninitialize(JNIEnv* env, jobject thiz) { return Uninitialize( ); } jint Java_com_Dsm_Test_DsmLibraryTest_vtDeviceClose(JNIEnv* env, jobject thiz, jint hDevice) { return DeviceClose( hDevice ); }
  3. Test_library.h测试库.h

    A header file where Initialize , Uninitialize , DeviceOpen , DeviceClose functions are declared.声明了InitializeUninitializeDeviceOpenDeviceClose函数的 header 文件。

After this I run ndk-build and create a Test_library.so library and load it in my java application and use them like this:在此之后,我运行ndk-build并创建一个 Test_library.so 库并将其加载到我的 java 应用程序中并像这样使用它们:

// Some code

public native int Uninitialize( );

public native int DeviceClose( int hDevice );

static {
    System.loadLibrary("Test_library");
}

Everything Runs fine.一切运行良好。 After I want to add other two functions在我想添加其他两个功能之后

1. unsigned long Initialize(void* userData);
2. unsigned long DeviceOpen( unsigned long id, unsigned long* device);

` `

Now Questions:现在问题:

  1. How I can write this two functions as native in java?如何在 java 中将这两个函数编写为本机函数? As there are no void* or unsigned long* types in java因为 java 中没有void*unsigned long*类型
  2. How I can write same functions in Test_library.c as in jni.h there are no void ** or unsigned long* types我如何在 Test_library.c 中编写与 jni.h 中相同的函数没有void ** 或unsigned long*类型

Thanks for help.感谢帮助。

You can use jlong to pass a pointer (or a pointer to pointer, or whatever) back to Java.您可以使用jlong 将指针(或指向指针的指针,或其他)传递回 Java。 Java code won't be able to use it for anything, other than passing it as an argument to one of your other methods; Java 代码除了将其作为参数传递给您的其他方法之一之外,不能用于任何事情; but often that's all you really want.但通常这就是你真正想要的。 If, on the other hand, you want Initialize() to be called with data set up in Java, then void * isn't appropriate;另一方面,如果您希望使用 Java 中设置的数据调用Initialize() ,则void *不合适; you'll need to use a Java class, and use reflection in JNI to get the information you need out of it.您需要使用 Java class,并在 JNI 中使用反射来获取您需要的信息。

Crudely, you could wrap malloc() and free() :粗略地说,你可以包装malloc()free()

jlong Java_c_utils_malloc(JNIEnv* env, jclass clazz, jint size) {
    return (jlong) malloc(size);
}

void Java_c_utils_free(JNIEnv* env, jclass clazz, jlong ptr) {
   free((void *) ptr);
}

and then use them (to no effect:) in Java:然后在Java中使用它们(无效:):

long ptr = utils.malloc(100);
// Store ptr for a while
utils.free(ptr);

Now, if we wrapped some other functions that needed a block of memory as an argument, we could wrap them too, and let them accept a jlong argument, the same way free() does.现在,如果我们包装一些需要 memory 块作为参数的其他函数,我们也可以包装它们,让它们接受一个jlong 参数,就像free()一样。 The fact that the Java variable ptr represents a memory address is completely opaque in Java, but it's useful nonetheless. Java 变量ptr表示 memory 地址的事实在 Java 中是完全不透明的,但它仍然有用。

Window system implementations for Java (i,e., AWT, SWT) use this same sort of thing to associate the native widget handle with the Java component. Window 系统实现 Java(即,AWT,SWT)使用同样的东西将本机小部件句柄与 ZD52387880E1EA22817A72D375921381 相关联。

Now, if you want your Initialize() to be able to take useful arguments from Java, then a void * isn't going to cut it.现在,如果您希望您的Initialize()能够从 Java 中获取有用的 arguments,那么void *不会削减它。 You'd need to write your method to accept a Java object as an argument;您需要编写方法来接受 Java object 作为参数; that's the only way to allow you to manipulate the object in Java.这是允许您在 Java 中操作 object 的唯一方法。

I don't want to duplicate all the code here, but Sun's JNI tutorial is here .我不想在这里复制所有代码,但是 Sun 的 JNI 教程在 这里 This is the section on calling arbitrary methods of a Java object (either the this object, or one passed to your method as an argument) and this is an analogous section on accessing the fields of an object. This is the section on calling arbitrary methods of a Java object (either the this object, or one passed to your method as an argument) and this is an analogous section on accessing the fields of an object.

What you want to do is this: For the void* function, use direct byte buffers.您要做的是:对于 void* function,使用直接字节缓冲区。 On the Java side:在 Java 端:

native long initialize(java.nio.ByteBuffer userData);

When you call the method, make sure to allocate a direct ByteBuffer (see java.nio.ByteBuffer , and JNI nio usage ):调用该方法时,请确保分配一个直接的 ByteBuffer(请参阅java.nio.ByteBufferJNI nio 用法):

ByteBuffer myData = ByteBuffer.allocateDirect(size);
long res = initialize(myData);

On the C side, you do this:在 C 端,您可以这样做:

unsigned long res = Initialize(env->GetDirectBufferAddress(env, buffer));
return (jlong)res;

You read and write from the buffer on the Java side with the ByteBuffer methods.您可以使用ByteBuffer方法从 Java 端的缓冲区读取和写入。

You can also allocate the byte buffer on the C side with env->NewDirectByteBuffer(env, ptr, size .您还可以使用env->NewDirectByteBuffer(env, ptr, size分配 C 端的字节缓冲区。

Now, for the second function, I assume the unsigned long* argument is used to return a result.现在,对于第二个 function,我假设 unsigned long* 参数用于返回结果。 You can use the same approach (direct ByteBuffers), but I would recommend a different one that wouldn't entail allocating a buffer for such a small value.您可以使用相同的方法(直接 ByteBuffers),但我会推荐一种不同的方法,它不需要为如此小的值分配缓冲区。

On the Java side:在 Java 端:

native long deviceOpen(long id, long[] device);

On the C side:在 C 侧:

unsigned long c_device;
unsigned long res = DeviceOpen((unsigned long)j_id, &c_device);
env->SetLongArrayRegion(env, j_device, 0, 1, &c_device);
return (jlong)res;

Then you call the method from Java:然后调用Java中的方法:

long[] deviceOut = new long[1];
long res = deviceOpen(id, deviceOut);
long device = deviceOut[0];

For more information on array access from JNI see JNI array operations有关从 JNI 访问数组的更多信息,请参阅JNI 数组操作

I consider @pron answer the best one, so far.到目前为止,我认为@pron 的答案是最好的。

About automatic JNI code generation, consider jnigen It is very simple to use and powerful.关于自动 JNI 代码生成,考虑jnigen ,它使用起来非常简单,功能强大。

Get the Javadocs, and look for NativeCodeGenerator class.获取 Javadocs,然后查找NativeCodeGenerator class。 It has the applied mapping between Java types and native CPP types, as String to char*, int[] to int*, FloatBuffer to float*, etc.它具有 Java 类型和原生 CPP 类型之间的应用映射,如 String 到 char*、int[] 到 int*、FloatBuffer 到 float* 等。

Jnigen as described on GitHub如 GitHub 上所述的 Jnigen

jnigen is a small library that can be used with or without libgdx which allows C/C++ code to be written inline with Java source code. jnigen 是一个小型库,可与或不与 libgdx 一起使用,它允许将 C/C++ 代码与 Java 源代码内联编写。

jnigen has two parts: jnigen 有两个部分:

  • Inspect Java source files in a specific folder, detect native methods and the attached C++ implementation, and spit out a C++ source file and header, similar to what you'd create manually with JNI. Inspect Java source files in a specific folder, detect native methods and the attached C++ implementation, and spit out a C++ source file and header, similar to what you'd create manually with JNI.

  • Provide a generator for Ant build scripts that build the native source for every platform.为 Ant 构建脚本提供生成器,为每个平台构建原生源代码。

Example: This are your Java native methods, with desired cpp code defined as comment just after the method declaration:示例:这是您的 Java 本机方法,所需的 cpp 代码在方法声明之后定义为注释:

private static native ByteBuffer newDisposableByteBuffer (int numBytes); /*
   char* ptr = (char*)malloc(numBytes);
   return env->NewDirectByteBuffer(ptr, numBytes);
*/

private native static void copyJni (float[] src, Buffer dst, int numFloats, int offset); /*
   memcpy(dst, src + offset, numFloats << 2 );
*/

After running jnigen generator, you will have the *.cpp file with your C code and bindings.运行 jnigen 生成器后,您将拥有带有 C 代码和绑定的 *.cpp 文件。 The *.h is also created automagically. *.h 也是自动创建的。

The cpp will looks like this: cpp 将如下所示:

JNIEXPORT jobject JNICALL 
Java_com_badlogic_gdx_utils_BufferUtils_newDisposableByteBuffer
(JNIEnv* env, jclass clazz, jint numBytes) {
//@line:334
   char* ptr = (char*)malloc(numBytes);
   return env->NewDirectByteBuffer(ptr, numBytes);
}

JNIEXPORT void JNICALL 
Java_com_badlogic_gdx_utils_BufferUtils_copyJni___3FLjava_nio_Buffer_2II
(JNIEnv* env, jclass clazz, jfloatArray obj_src, jobject obj_dst, jint numFloats, jint offset) {
   unsigned char* dst = (unsigned char*)env->GetDirectBufferAddress(obj_dst);
   float* src = (float*)env->GetPrimitiveArrayCritical(obj_src, 0);

//@line:348
   memcpy(dst, src + offset, numFloats << 2 );

   env->ReleasePrimitiveArrayCritical(obj_src, src, 0);
}

This is how would look your methods, with jnigen:这就是使用 jnigen 的方法的外观:

 /**
 * @param userData - a pointer. Assumed position() is Zero.
 **/
public native static long initialize(Buffer userData);/*
  return (jlong) Initialize( (void*) userData);
*/

public native static long uninitialize();/*
  return (jlong) Uninitialize();
*/

/**
 *  Assumptions : id points to a unique device
 * @param id - id
 * @param device - a long[] with length 1, to return device pointer.
 */
public native static long deviceOpen(long id, long[] device);/*
  return (jlong) DeviceOpen( (unsigned long) id, (unsigned long*) device);
*/ 

public native static long deviceClose(long device);/*
  return (jlong) DeviceClose( (unsigned long) device);
*/ 

Have you considered using an automatic wrapper generator for this?您是否考虑过为此使用自动包装器生成器? I have used SWIG a few times now with much success.我现在已经使用SWIG几次了,并取得了很大的成功。 The reason that I mention it is that it has well developed patterns for dealing with things such as pointers, including void* and so on.我提到它的原因是它有很好的模式来处理诸如指针之类的事情,包括void*等等。

Wrapper generation can be as easy as passing in the library's header file if the methods you want to wrap are as simple as the ones you list above.如果您要包装的方法与上面列出的方法一样简单,则包装器生成可以像传递库的 header 文件一样简单。 Even if you have some methods that are more complex (passing in structs, returning pointers to memory, etc) SWIG allows you to add code to the generated wrappers if needs be.即使您有一些更复杂的方法(传递结构、返回指向 memory 的指针等),SWIG 也允许您在需要时向生成的包装器添加代码。

Another plus point is that SWIG can wrap into a range of languages, not just Java.另一个优点是 SWIG 可以包装成多种语言,而不仅仅是 Java。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM