Android NDK, поддерживающий живые объекты С++
У меня есть следующая проблема. Я хочу написать приложение для Android, которое использует мои устаревшие классы С++. Я должен сохранить объект С++ в течение всего срока службы приложения.
Я написал аналогичное приложение в С# и решил проблему, передав указатель на С++-класс на С# и сохранив его там, используя IntPtr. Затем, когда я хотел вызвать метод для этого объекта, я просто передал этот указатель снова на С++, преобразован в указатель класса и назвал метод на нем.
Как я могу достичь аналогичного результата в Java и Android NDK? Поддерживает ли Java сохранение указателей?
Ответы
Ответ 1
Да, вы можете сделать то же самое, что и на С#.
Чтобы создать новый объект С++:
jlong
Java_package_name_new(JNIEnv *, jobject) {
return (long)(new CPP_Object());
}
Вы можете сохранить возвращаемое значение этого метода в переменной Java ptr
и передать его ко всем методам NDK, которые ему нужны:
void
Java_package_name_doSomething(JNIEnv *, jobject, jlong ptr) {
CPP_Object *obj = (CPP_Object *)ptr;
// do whatever you want with the object
}
И, наконец, удалите его с чем-то вроде:
void
Java_package_name_delete(JNIEnv *, jobject, jlong ptr) {
delete (CPP_Object *)(ptr);
}
Вместо передачи ptr
ко всем методам, которые в ней нуждаются, вы также можете получить его и установить его непосредственно из части NDK с помощью методов SetLongField
и GetLongField
: это позволяет использовать переменную Java ptr
управляемый только из части кода NDK, который я считаю более безопасным и простым в управлении.
Ответ 2
Я немного опоздал на разговор, но, поскольку я не мог найти сообщение SO, которое полностью отвечает на этот вопрос, я опубликую свое решение.
Java
На стороне Java я создаю класс с указателем long
, чтобы сохранить ссылку на объект С++. Объединение методов С++ в Java-классе позволяет использовать методы С++ в нескольких действиях. Обратите внимание, что я создаю объект С++ в конструкторе, и я удаляю объект при очистке. Это очень важно для предотвращения утечек памяти:
public class JavaClass {
// Pointer (using long to account for 64-bit OS)
private long objPtr = 0;
// Create C++ object
public JavaClass() {
createCppObject();
}
// Delete C++ object on cleanup
public void cleanup() {
deleteCppObject();
this.objPtr = 0;
}
// Native methods
public native void createCppObject();
public native void workOnCppObject();
public native void deleteCppObject();
// Load C++ shared library
static {
System.loadLibrary("CppLib");
}
}
С++
На стороне С++ я определяю функции для создания, изменения и удаления объекта. Важно отметить, что мы должны использовать new
и delete
для хранения объекта в HEAP-памяти, чтобы поддерживать его в течение всего жизненного цикла экземпляров класса Java. Я также сохраняю указатель на CppObject
прямо в JavaClass
, используя getFieldId
, SetLongField
и GetLongField
:
// Get pointer field straight from `JavaClass`
jfieldID getPtrFieldId(JNIEnv * env, jobject obj)
{
static jfieldID ptrFieldId = 0;
if (!ptrFieldId)
{
jclass c = env->GetObjectClass(obj);
ptrFieldId = env->GetFieldID(c, "objPtr", "J");
env->DeleteLocalRef(c);
}
return ptrFieldId;
}
// Methods to create, modify, and delete Cpp object
extern "C" {
void Java_com_test_jnitest_JavaClass_createCppObject(JNIEnv *env, jobject obj) {
env->SetLongField(obj, getPtrFieldId(env, obj), (jlong) new CppObject);
}
void Java_com_test_jnitest_JavaClass_workOnCppObject(JNIEnv *env, jobject obj) {
CppObject* cppObj = (CppObject*) env->GetLongField(obj, getPtrFieldId(env, obj));
// Write your code to work on CppObject here
}
void Java_com_test_jnitest_JavaClass_deleteCppObject(JNIEnv *env, jobject obj) {
CppObject* cppObj = (CppObject*) env->GetLongField(obj, getPtrFieldId(env, obj));
delete cppObj;
}
}
ПРИМЕЧАНИЯ:
- В отличие от Java, С++ не содержит сборку мусора, и объект будет жить в памяти HEAP, пока вы не будете использовать
delete
.
- Я использую
getFieldId
, SetLongField
и GetLongField
для хранения ссылки на объект из С++, но вы также можете сохранить указатель объекта jlong
из Java, как описано в других ответах.
- В моем последнем коде я реализовал класс
JavaObject
как Parcelable
, чтобы передать мой класс нескольким действиям с помощью Intent
с дополнительными функциями.
Ответ 3
Вы также можете обернуть свой код на С++ с помощью SWIG, где вы создаете экземпляр собственного объекта, создавая его на Java, и до тех пор, пока вы держите ссылку на нее, она не уничтожается (потеря ссылки делает ее пригодной для сбора мусора, которая будет вызывать деструктор в экземпляре объекта С++, когда ссылка Java завершена).
Однако, если взаимодействие между нативным кодом и кодом Java минимально, использование SWIG может быть слишком OTT.
Ответ 4
Вы также можете обратиться к примеру проекта OpenCV.