#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 и размещаю там файлы:
-
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)
-
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 ); }
-
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);
`
Теперь вопросы:
- Как я могу написать эти две функции как собственные в Java? Поскольку в Java нет типов void* или unsigned long*
- Как я могу написать те же функции в 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.