Przekazywanie wskaźników między C i Javą przez JNI

W tej chwili próbuję stworzyć aplikację Java, która wykorzystuje funkcjonalność CUDA. Połączenie między CUDA i Java działa dobrze, ale mam inny problem i chciałem zapytać, Czy moje przemyślenia na ten temat są poprawne.

Kiedy wywołuję natywną funkcję z Javy, przekazuję do niej pewne dane, funkcje obliczają coś i zwracają wynik. Czy jest możliwe, aby pierwsza funkcja zwróciła odniesienie (wskaźnik) do tego wyniku, które mogę przekazać do JNI i wywołać inną funkcję czy dalsze obliczenia z wynikiem?

Moim pomysłem było zmniejszenie kosztów związanych z kopiowaniem danych do i z GPU poprzez pozostawienie danych w pamięci GPU i przekazanie odniesienia do nich, aby inne funkcje mogły z nich korzystać.

Po pewnym czasie, pomyślałem sobie, że nie powinno to być możliwe, ponieważ wskaźniki są usuwane po zakończeniu aplikacji(w tym przypadku, gdy C-Funkcja się kończy). Czy to prawda? Czy jestem po prostu źle w C, aby zobaczyć rozwiązanie?

Edytuj: Cóż, aby rozwinąć nieco pytanie( lub uczynić to jaśniej): czy pamięć przydzielana przez natywne funkcje JNI jest dealokowana po zakończeniu funkcji? Czy mogę nadal mieć do niego dostęp, dopóki aplikacja JNI się nie skończy lub gdy zwolnię ją ręcznie?

Dzięki za wkład :)

Author: Volker, 2009-10-27

7 answers

Zastosowałem następujące podejście:

W kodzie JNI Utwórz strukturę zawierającą odniesienia do potrzebnych obiektów. Podczas tworzenia tej struktury zwróć jej wskaźnik do Javy jako long. Następnie, z Javy po prostu wywołujesz dowolną metodę z parametrem long, a w C rzucisz ją na wskaźnik do swojej struktury.

Struktura będzie w stercie, więc nie będzie czyszczona pomiędzy różnymi wywołaniami JNI.

EDIT: nie wydaje mi się, żeby można było używać długiego ptr = (long)&address; od adres jest zmienną statyczną. Użyj go w sposób sugerowany przez Gunslinger47, tzn. Utwórz nową instancję klasy lub strukturę (używając new lub malloc) i przekaż jej wskaźnik.

 42
Author: Denis Tulskiy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-28 16:21:15

W C++ możesz użyć dowolnego mechanizmu, który chcesz przydzielić / zwolnić pamięć: stos, malloc / free, new/delete lub dowolną inną niestandardową implementację. Jedynym wymogiem jest to, że jeśli przydzielono blok pamięci z jednym mechanizmem, trzeba go zwolnić z tym samym mechanizmem, więc nie można wywołać {[1] } na zmiennej stosu i nie można wywołać delete na mallocPamięci ed.

JNI ma własne mechanizmy przydzielania / zwalniania JVM pamięć:

  • NewObject / DeleteLocalRef
  • NewGlobalRef / DeleteGlobalRef
  • NewWeakGlobalRef / DeleteWeakGlobalRef

Są one zgodne z tą samą regułą, jedynym hackiem jest to, że lokalne refy mogą być usuwane "en masse" albo jawnie, z PopLocalFrame, lub pośrednio, gdy natywna metoda kończy działanie.

JNI nie wie, w jaki sposób przydzielono ci pamięć, więc nie może jej zwolnić, gdy funkcja zakończy działanie. Zmienne stosu zostaną oczywiście zniszczone, ponieważ nadal pisanie C++, ale pamięć GPU pozostanie ważna.

Jedynym problemem jest wtedy, jak uzyskać dostęp do pamięci przy kolejnych wywołaniach, a następnie możesz użyć sugestii Gunslinger47: {]}

JNIEXPORT jlong JNICALL Java_MyJavaClass_Function1() {
    MyClass* pObject = new MyClass(...);
    return (long)pObject;
}

JNIEXPORT void JNICALL Java_MyJavaClass_Function2(jlong lp) {
    MyClass* pObject = (MyClass*)lp;
    ...
}
 14
Author: Dan Berindei,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-17 19:36:07

Java nie wiedziałaby, co zrobić ze wskaźnikiem, ale powinna być w stanie zapisać wskaźnik z wartości zwracanej przez natywną funkcję, a następnie przekazać go innej natywnej funkcji, aby mogła sobie z nim poradzić. Wskaźniki C są niczym innym jak wartościami liczbowymi w rdzeniu.

Inny kontibutor musiałby ci powiedzieć, czy wskazywana pamięć graficzna zostanie wyczyszczona pomiędzy wywołaniami JNI i czy będą jakieś ograniczenia.

 10
Author: Gunslinger47,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-17 08:20:33

Wiem, że na to pytanie udzielono już oficjalnej odpowiedzi, ale chciałbym dodać swoje rozwiązanie: Zamiast próbować przekazać wskaźnik, umieść wskaźnik w tablicy Java (o indeksie 0) i przekaż go JNI. Kod JNI może pobrać i ustawić element tablicy za pomocą GetIntArrayRegion/SetIntArrayRegion.

W moim kodzie potrzebuję warstwy natywnej do zarządzania deskryptorem pliku (otwartym gniazdem). Klasa Java posiada tablicę int[1] i przekazuje ją do funkcji natywnej. Natywna funkcja może z nim zrobić cokolwiek (get/set) i umieścić z powrotem wynik w tablicy.

 7
Author: noamtm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-28 08:18:35

Jeśli przydzielasz pamięć dynamicznie (na stercie) wewnątrz natywnej funkcji, nie jest ona usuwana. Innymi słowy, możesz zachować stan pomiędzy różnymi wywołaniami do funkcji natywnych, używając wskaźników, statycznych var, itp.

Pomyśl o tym inaczej: co można bezpiecznie zachować w wywołaniu funkcji, wywołanym z innego programu C++? To samo dotyczy tutaj. Gdy funkcja jest zakończona, wszystko na stosie dla tego wywołania funkcji jest niszczone; ale wszystko na sterta zostanie zachowana, chyba że zostanie usunięta.

Krótka odpowiedź: tak długo, jak nie dealokujesz wyniku, który wracasz do funkcji wywołującej, pozostanie on ważny do ponownego wejścia później. Po prostu posprzątaj, jak skończysz.

 6
Author: Marc Paradise,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-27 22:19:33

Chociaż przyjęta odpowiedź od @ denis-tulskiy ma sens, osobiście śledziłem sugestie z tutaj .

Więc zamiast używać typu pseudo-wskaźnika, takiego jak jlong (lub jint jeśli chcesz zaoszczędzić trochę miejsca na 32-bitowym łuku), użyj ByteBuffer. Na przykład:

MyNativeStruct* data; // Initialized elsewhere.
jobject bb = (*env)->NewDirectByteBuffer(env, (void*) data, sizeof(MyNativeStruct));

Które można później ponownie wykorzystać za pomocą:

jobject bb; // Initialized elsewhere.
MyNativeStruct* data = (MyNativeStruct*) (*env)->GetDirectBufferAddress(env, bb);

W bardzo prostych przypadkach rozwiązanie to jest bardzo łatwe w użyciu. Załóżmy, że masz:

struct {
  int exampleInt;
  short exampleShort;
} MyNativeStruct;

Po stronie Javy wystarczy, że do:

public int getExampleInt() {
  return bb.getInt(0);
}

public short getExampleShort() {
  return bb.getShort(4);
}

Co oszczędza od pisania partii kodu kotła ! Należy jednak zwrócić uwagę na kolejność bajtów, jak wyjaśniono tutaj .

 6
Author: malat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:46:34

Najlepiej zrobić to dokładnie tak, jak niebezpieczne.allocateMemory tak.

Utwórz swój obiekt, a następnie wpisz go do (uintptr_t), który jest 32/64-bitową niepodpisaną liczbą całkowitą.

return (uintptr_t) malloc(50);

void * f = (uintptr_t) jlong;
To jedyny prawidłowy sposób.

Tutaj sprawdzanie stanu zdrowia psychicznego jest niebezpieczne.allocateMemory tak.

inline jlong addr_to_java(void* p) {
  assert(p == (void*)(uintptr_t)p, "must not be odd high bits");
  return (uintptr_t)p;
}

UNSAFE_ENTRY(jlong, Unsafe_AllocateMemory(JNIEnv *env, jobject unsafe, jlong size))
  UnsafeWrapper("Unsafe_AllocateMemory");
  size_t sz = (size_t)size;
  if (sz != (julong)size || size < 0) {
    THROW_0(vmSymbols::java_lang_IllegalArgumentException());
  }
  if (sz == 0) {
    return 0;
  }
  sz = round_to(sz, HeapWordSize);
  void* x = os::malloc(sz, mtInternal);
  if (x == NULL) {
    THROW_0(vmSymbols::java_lang_OutOfMemoryError());
  }
  //Copy::fill_to_words((HeapWord*)x, sz / HeapWordSize);
  return addr_to_java(x);
UNSAFE_END
 0
Author: bond,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-04 14:45:19