jni не поддерживает типы как void *, unsigned int*, …. Что делать?

#java #eclipse #types #java-native-interface

#java #eclipse #типы #java-native-interface

Вопрос:

У меня есть .so (разделяемая библиотека), написанная на C , давайте назовем это функциональностью.итак, в котором я реализую разные функции, вот список некоторых функций:

 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 );
  

и так далее …

Я хочу использовать эту библиотеку (functionality.so ) функциональность в моем Java-приложении для Android. Для этого я создаю папку jni в папке проекта моего приложения Android и размещаю там файлы:

  1. 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

     #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

    Заголовочный файл, в котором объявлены функции Initialize, Uninitialize, DeviceOpen, DeviceClose.

После этого я запускаю ndk-build и создаю Test_library.so библиотека и загрузите ее в мое Java-приложение и используйте их следующим образом:

 // Some code

public native int Uninitialize( );

public native int DeviceClose( int hDevice );

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

Все работает нормально. После я хочу добавить две другие функции

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

`

Теперь вопросы:

  1. Как я могу написать эти две функции как собственные в Java? Поскольку в Java нет типов void* или unsigned long*
  2. Как я могу написать те же функции в Test_library.c, что и в jni.h, там нет типов void ** или unsigned long*

Спасибо за помощь.

Ответ №1:

Вы можете использовать jlong для передачи указателя (или указателя на указатель, или чего угодно) обратно в Java. Java-код не сможет использовать его ни для чего, кроме передачи его в качестве аргумента одному из ваших других методов; но часто это все, что вам действительно нужно. Если, с другой стороны, вы хотите, Initialize() чтобы вызывались данные, настроенные на Java, то void * это не подходит; вам нужно будет использовать класс Java и использовать отражение в JNI, чтобы извлечь из него необходимую информацию.

Грубо говоря, вы могли бы обернуть 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);
}
  

а затем использовать их (безрезультатно!) в Java:

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

Теперь, если бы мы обернули некоторые другие функции, которым в качестве аргумента требовался блок памяти, мы могли бы обернуть и их, и позволить им принимать jlong аргумент, таким же образом, как это free() делается. Тот факт, что переменная Java ptr представляет адрес памяти, полностью непрозрачен в Java, но, тем не менее, он полезен.

Реализации оконных систем для Java (например, AWT, SWT) используют то же самое, чтобы связать собственный дескриптор виджета с компонентом Java.

Теперь, если вы хотите, чтобы ваш Initialize() мог принимать полезные аргументы из Java, то a void * не собирается это сокращать. Вам нужно было бы написать свой метод, чтобы принимать объект Java в качестве аргумента; это единственный способ, позволяющий вам манипулировать объектом в Java.

Я не хочу дублировать здесь весь код, но здесь находится руководство Sun по JNI. Это раздел о вызове произвольных методов объекта Java (либо this объекта, либо того, который передается вашему методу в качестве аргумента), и это аналогичный раздел о доступе к полям объекта.

Комментарии:

1. «вам нужно будет использовать класс Java и использовать отражение в JNI, чтобы извлечь из него необходимую информацию». Можете ли вы это объяснить?

2. «а затем использовать их (безрезультатно!) в Java:» Можете ли вы это объяснить?

3. «Вам нужно было бы написать свой метод, чтобы принимать объект Java в качестве аргумента; это единственный способ, позволяющий вам манипулировать объектом в Java». Как?

4. Я добавил ссылки на необходимые справочные материалы (которые в основном представляют собой примеры кода), показывающие, как вызывать методы и / или получать доступ к полям объекта Java из JNI.

5. У кого есть еще примеры кода с полезной информацией по этому поводу?

Ответ №2:

Что вы хотите сделать, так это следующее: для функции void * используйте прямые байтовые буферы. На стороне Java:

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

При вызове метода обязательно выделите прямой байтбуфер (см. java.nio.ByteBuffer и использование JNI nio):

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

На стороне C вы делаете это:

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

Вы читаете и записываете из буфера на стороне Java с помощью методов ByteBuffer.

Вы также можете выделить байтовый буфер на стороне C с помощью env->NewDirectByteBuffer(env, ptr, size .

Теперь, для второй функции, я предполагаю, что для возврата результата используется аргумент unsigned long* . Вы можете использовать тот же подход (прямые байт-буферы), но я бы рекомендовал другой, который не повлек бы за собой выделения буфера для такого маленького значения.

На стороне Java:

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

На стороне C:

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

Затем вы вызываете метод из Java:

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

Для получения дополнительной информации о доступе к массиву из JNI см. Операции с массивами JNI

Ответ №3:

Я считаю ответ @pron лучшим на данный момент.

Что касается автоматической генерации кода JNI, учтите, что jnigen очень прост в использовании и мощен.

Получите Javadocs и найдите класс NativeCodeGenerator. Он имеет прикладное сопоставление между типами Java и собственными типами CPP, например, String с char *, int[] с int *, FloatBuffer с float * и т.д.

Jnigen, как описано на GitHub

jnigen — это небольшая библиотека, которую можно использовать как с libgdx, так и без него, что позволяет писать код на C / C встраиваемым в исходный код Java.

jnigen состоит из двух частей:

  • Проверьте исходные файлы Java в определенной папке, обнаружьте собственные методы и приложенную реализацию C и выдайте исходный файл C и заголовок, аналогичный тому, что вы создали бы вручную с помощью JNI.

  • Предоставьте генератор для сценариев сборки Ant, которые создают собственный исходный код для каждой платформы.

Пример: Это ваши собственные методы 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 );
*/
  

После запуска jnigen generator у вас будет файл *.cpp с вашим кодом на C и привязками. Файл *.h также создается автоматически.

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);
}
  

Вот как выглядели бы ваши методы с 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);
*/ 
  

Ответ №4:

Рассматривали ли вы возможность использования автоматического генератора оболочек для этого? Я использовал SWIG уже несколько раз с большим успехом. Причина, по которой я упоминаю об этом, заключается в том, что он имеет хорошо разработанные шаблоны для работы с такими вещами, как указатели, включая void* и так далее.

Генерация оболочки может быть такой же простой, как передача в заголовочном файле библиотеки, если методы, которые вы хотите обернуть, такие же простые, как те, которые вы перечислили выше. Даже если у вас есть некоторые методы, которые являются более сложными (передача структур, возврат указателей в память и т.д.), SWIG позволяет вам добавлять код к сгенерированным оболочкам, если это необходимо.

Еще одним плюсом является то, что SWIG может переноситься на различные языки, а не только на Java.