2009-10-27 14 views
58

Obecnie próbuję utworzyć aplikację Java, która używa funkcjonalności CUDA. Połączenie między CUDA i Javą działa dobrze, ale mam inny problem i chciałem zapytać, czy moje przemyślenia na ten temat są poprawne.Przekazywanie wskaźników między C i Java przez JNI

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

Mój pomysł polegał na zmniejszeniu narzutu, który pochodzi z kopiowania danych do i od GPU, pozostawiając dane w pamięci GPU i po prostu przekazując odniesienie do niego, aby inne funkcje mogły z niego korzystać.

Po wypróbowaniu trochę czasu, pomyślałem sobie, to nie powinno być możliwe, ponieważ wskaźniki są usuwane po zakończeniu aplikacji (w tym przypadku, gdy funkcja C kończy działanie). Czy to jest poprawne? A może po prostu źle w C, aby zobaczyć rozwiązanie?

Edytuj: Cóż, aby nieco rozszerzyć pytanie (lub wyraźniej): Czy pamięć przydzielona przez natywne funkcje JNI jest przydzielana po zakończeniu funkcji? Czy nadal mogę uzyskać do niego dostęp, dopóki nie zakończy się aplikacja JNI lub gdy zwolnię ją ręcznie?

Dzięki za wejście :)

+0

także https://stackoverflow.com/q/5802340/632951 – Pacerier

Odpowiedz

41

użyłem następujące podejście:

w kodzie JNI utworzyć struct, które pomieścić odniesień do obiektów, które potrzebują. Kiedy po raz pierwszy tworzysz tę strukturę, zwróć jej wskaźnik do java jako long. Następnie, z java, możesz wywołać dowolną metodę z tym long jako parametrem, aw C przesłać ją do wskaźnika do twojej struktury.

Struktura będzie znajdować się w stercie, więc nie będzie ona czyszczona między różnymi połączeniami JNI.

EDYCJA: Myślę, że nie można użyć długiego ptr = (long)&address;, ponieważ adres jest statyczną zmienną. Użyj go tak, jak sugerował Gunslinger47, tj. Utwórz nową instancję klasy lub struct (używając new lub malloc) i przekaż jej wskaźnik.

+0

Tak to zrobiłem teraz - wysyłam wskaźnik z powrotem do java i przekazuję go do C. Ale jak mogę powiedzieć C, że to, co właśnie otrzymałem, to adres? długa * ptr = (długa *) i adres; Próbowałem, ale nie otrzymałem wartości, która powinna być pod adresem, otrzymuję tylko adres (lub inne adresy, ale bez wartości :() – Volker

+0

... powinny istnieć gwiazdki przed ptr i po drugim długim ... – Volker

+8

'MyClass * pObject = ...; długi lp = (długi) pObject; pObject = (* pObject) lp;' – Gunslinger47

10

Java nie będzie wiedział, co zrobić ze wskaźnikiem, ale powinien on być w stanie przechowywać wskaźnik od wartości zwracanej native funkcja jest następnie przekazać je do innej funkcji dla natywnego do załatwienia. Wskaźniki C to nic więcej niż wartości numeryczne w rdzeniu.

Inny contibutor musiałby ci powiedzieć, czy wskazana pamięć graficzna zostanie wyczyszczona między wywołaniami JNI i czy będą jakieś obejścia.

+2

O swoim drugim akapicie: Jedyną rzeczą, aby zwrócić uwagę na to, aby upewnić się, że każda pamięć przydzielona zostanie dealokowane również. Zalecanym podejściem jest posiadanie jakiejś metody close/dispose() na obiekcie przechowującym referencję. finalizatorzy są kuszący, ale mają kilka wad, więc warto ich unikać, jeśli to możliwe. – Fredrik

+0

Nie powinienem był pisać "jedynej rzeczy" btw ... JNI jest pełne dołów, w które można wpaść. – Fredrik

+0

Mam już zawarte funkcje, aby zwolnić przydzieloną pamięć, więc to nie powinno być problemu :) Głównym problemem jest nadal: Czy pamięć pozostaje przydzielona, ​​jeśli ja ją uwolnię? Mam na myśli, włączając w to adresy i wartości ... wiem, że dostanę wycieki pamięci i tak dalej, jeśli tego nie zrobię, więc już to zawarłem ;-) – Volker

6

Jeśli dynamicznie przydzielasz pamięć (na stercie) wewnątrz funkcji natywnej, nie jest ona usuwana. Innymi słowy, jesteś w stanie zachować stan między różnymi wywołaniami w natywne funkcje, używając wskaźników, statycznych vars, itp.

Pomyśl o tym inaczej: co możesz zrobić, aby bezpiecznie pozostać w wywołaniu funkcji, wywołanym z innego Program C++? To samo dotyczy tutaj. Kiedy funkcja zostanie zakończona, wszystko na stosie dla tej funkcji zostanie zniszczone; ale wszystko na stosie jest zachowane, chyba że wyraźnie go usuniesz.

Krótka odpowiedź: tak długo, jak nie zwolnisz wyniku, do którego wracasz, funkcja pozostanie ważna do ponownego wjazdu później. Po zakończeniu upewnij się, że wszystko zostało wyczyszczone.

13

W C++ możesz użyć dowolnego mechanizmu, który chcesz przydzielić/zwolnić pamięć: stosu, malloc/free, new/delete lub jakiejkolwiek innej implementacji niestandardowej. Jedynym wymaganiem jest to, że jeśli przydzieliłeś blok pamięci za pomocą jednego mechanizmu, musisz zwolnić go za pomocą tego samego mechanizmu, więc nie możesz wywołać free na zmiennej stosu i nie możesz zadzwonić pod delete na pamięć.

JNI posiada własne mechanizmy alokacji/zwalnianiu pamięci JVM:

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

one według tej samej reguły program Jedyny haczyk to to, że lokalny refs może być usunięty "en masse" jawnie, z PopLocalFrame, lub niejawnie, kiedy natywna metoda wychodzi.

JNI nie wie, w jaki sposób przydzielono Ci pamięć, więc nie może go zwolnić po wyjściu z funkcji. Zmienne stosu zostaną oczywiście zniszczone, ponieważ wciąż piszesz C++, ale pamięć GPU pozostanie ważna.

Jedyny problem to to, jak uzyskać dostęp do pamięci na kolejnych wezwań, a następnie można użyć Gunslinger47 za sugestię:

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; 
    ... 
} 
+0

Nie należy '(* pObject) lp;' be '(MyClass *) lp;'? – donturner

6

wiem, to pytanie została już oficjalnie odpowiedział, ale chciałbym dodać moje rozwiązanie: Zamiast próbować przekazać wskaźnik, umieść wskaźnik w tablicy Javy (w indeksie 0) i przekaż go JNI. Kod JNI może pobrać i ustawić element tablicy za pomocą GetIntArrayRegion/SetIntArrayRegion.

W moim kodzie, potrzebuję warstwy macierzystej do zarządzania deskryptorem pliku (otwarte gniazdo). Klasa Java przechowuje tablicę int[1] i przekazuje ją do macierzystej funkcji. Natywna funkcja może zrobić cokolwiek z nim (get/set) i przywrócić wynik w tablicy.

+0

Jak przekonwertować przesłany długi wskaźnik do tablicy w java –

6

Podczas gdy zaakceptowana odpowiedź z @ denis-tulskiy ma sens, osobiście podążałem za sugestiami od here.

Więc zamiast używać typu pseudo-pointer, takich jak jlong (lub jint jeśli chcesz zaoszczędzić trochę miejsca na 32-bitową łuku), użyj zamiast tego ByteBuffer. Na przykład:

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

które można później ponownego użytku z:

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

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

struct { 
    int exampleInt; 
    short exampleShort; 
} MyNativeStruct; 

po stronie Java, wystarczy zrobić:

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

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

co pozwala zaoszczędzić od pisania wiele kodu boilerplate! Należy jednak zwrócić uwagę na porządkowanie bajtów zgodnie z wyjaśnieniami here.

0

Najlepiej to zrobić tak, jak robi to Unsafe.allocateMemory.

Utwórz obiekt, a następnie wpisz go (uintptr_t), który jest 32/64 bitową liczbą całkowitą bez znaku.

return (uintptr_t) malloc(50); 

void * f = (uintptr_t) jlong; 

To jedyny poprawny sposób na zrobienie tego.

Tutaj sprawdza się poczytalność Unsafe.allocateMemory.

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

To jest ** nie ** standardowa definicja 'uintptr_t'. To bardzo zły pomysł. Jest zdefiniowany jako wystarczająco duży, aby pomieścić dowolny wskaźnik, i może wynosić ** dowolną ** wymaganą długość. Zazwyczaj w systemie 64-bitowym będzie to 64-bitowy, ale standard nie dopuszcza nawet takiego założenia. Nigdy nie należy przyjmować założeń dotyczących rozmiaru 'uintptr_t'. – StephenG

+1

W ten sposób JVM przydziela pamięć w systemach 32- i 64-bitowych. Przypisanie uintptr_t pozwala na czystą konwersję do jlonga. Nie zamierzam debatować, czy to dobrze, czy nie, ale jest to sposób, w jaki robi to JVM. – bond