2009-10-27 11 views
58

Al momento, sto provando a creare un'applicazione Java che utilizza la funzionalità CUDA. La connessione tra CUDA e Java funziona bene, ma ho un altro problema e volevo chiederti se i miei pensieri a riguardo sono corretti.Passare i puntatori tra C e Java attraverso JNI

Quando chiamo una funzione nativa da Java, gli passaggio alcuni dati, le funzioni calcola qualcosa e restituisce un risultato. È possibile, lasciare che la prima funzione restituisca un riferimento (puntatore) a questo risultato che posso passare a JNI e chiamare un'altra funzione che esegue ulteriori calcoli con il risultato?

La mia idea era di ridurre il sovraccarico derivante dalla copia dei dati da e verso la GPU lasciando i dati nella memoria della GPU e semplicemente passando un riferimento ad esso in modo che altre funzioni possano usarlo.

Dopo aver provato un po 'di tempo, ho pensato per me stesso, questo non dovrebbe essere possibile, perché i puntatori vengono eliminati al termine dell'applicazione (in questo caso, quando termina la funzione C). È corretto? O sono solo cattivo in C per vedere la soluzione?

Modifica: Bene, per espandere leggermente la domanda (o renderla più chiara): la memoria è allocata dalle funzioni native JNI deallocate quando termina la funzione? O posso ancora accedervi finché l'applicazione JNI non termina o quando la svendo manualmente?

Grazie per il vostro input :)

+0

anche https://stackoverflow.com/q/5802340/632951 – Pacerier

risposta

41

ho usato il seguente approccio:

nel codice JNI, creare una struttura che terrebbe i riferimenti a oggetti necessari. Quando si crea questa struttura per la prima volta, restituire il puntatore a java come long. Quindi, da java si chiama qualsiasi metodo con questo long come parametro e in C lo si lancia a un puntatore alla propria struct.

La struttura si troverà nell'heap, quindi non verrà cancellata tra diverse chiamate JNI.

MODIFICA: Non penso che sia possibile utilizzare il ptr = (long)&address; lungo poiché l'indirizzo è una variabile statica. Usalo come suggerito da Gunslinger47, cioè crea una nuova istanza di classe o una struct (usando new o malloc) e passa il suo puntatore.

+0

Ecco come l'ho fatto ora - rimando il puntatore a java come un lungo e lo passo a C. Ma come faccio a dire a C che il tempo che ha appena ricevuto è un indirizzo? long * ptr = (long *) & address; Questo è quello che ho provato, ma non ottengo il valore che dovrebbe essere all'indirizzo, ottengo solo l'indirizzo stesso (o alcuni altri indirizzi ma nessun valore :() – Volker

+0

... dovrebbero esserci degli asterischi prima di ptr e dopo il secondo lungo ... – Volker

+8

'MyClass * pObject = ...; long lp = (long) pObject; pObject = (* pObject) lp;' – Gunslinger47

10

Java non saprebbe cosa fare con un puntatore, ma dovrebbe essere in grado di memorizzare un puntatore dal valore di ritorno di una funzione nativa poi passarlo a un'altra funzione nativa per da affrontare. I puntatori C non sono altro che valori numerici al centro.

Un altro continuatore dovrebbe dirti se la memoria grafica puntata o meno verrebbe cancellata tra le invocazioni JNI e se ci sarebbe qualche soluzione.

+2

Circa il secondo comma: L'unica cosa da guardare fuori per è quello di assicurarsi che qualsiasi memoria allocata viene deallocato pure. L'approccio consigliato è di avere un qualche tipo di metodo close/dispose() sull'oggetto che detiene il riferimento. i finalizzatori sono allettanti, ma sono dotati di un paio di inconvenienti che valgono la pena di evitarli se possibile. – Fredrik

+0

Non avrei dovuto scrivere "l'unica cosa" tra ... JNI è pieno di buche in cui cadere. – Fredrik

+0

Ho già incluso funzioni per liberare memoria allocata, quindi non dovrebbe esserci alcun problema :) Il problema principale è ancora: la memoria rimane allocata se la rete è libera? Voglio dire, includendo indirizzi e valori ... so che otterrò perdite di memoria e così via se non lo faccio, quindi l'ho già incluso ;-) – Volker

6

Se si assegna la memoria dinamicamente (nell'heap) all'interno della funzione nativa, non viene eliminato. In altre parole, puoi mantenere lo stato tra diverse chiamate in funzioni native, usando puntatori, vars statici, ecc.

Pensaci in un modo diverso: cosa potresti fare in modo sicuro per mantenere una chiamata di funzione, chiamata da un altro Programma C++? Le stesse cose si applicano qui. Quando una funzione viene chiusa, qualsiasi cosa in pila per quella chiamata di funzione viene distrutta; ma qualsiasi cosa sull'heap viene conservata a meno che non venga cancellata in modo esplicito.

Risposta breve: finché non si rilascia il risultato che si sta tornando alla funzione di chiamata, rimarrà valido per il successivo ingresso. Assicurati di pulirlo quando hai finito.

13

In C++ è possibile utilizzare qualsiasi meccanismo che si desidera allocare/liberare memoria: lo stack, malloc/free, new/delete o qualsiasi altra implementazione personalizzata. L'unico requisito è che se hai assegnato un blocco di memoria con un meccanismo, devi liberarlo con lo stesso meccanismo, quindi non puoi chiamare free su una variabile stack e non puoi chiamare delete nella memoria malloc edita.

JNI ha i suoi meccanismi di allocazione/liberazione della memoria JVM:

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

Questi seguono la stessa regola, la l'unico problema è che i ref locali possono essere cancellati "en masse" esplicitamente, con PopLocalFrame, o implicitamente, quando il metodo nativo viene chiuso.

JNI non sa come è stata allocata la memoria, quindi non può liberarla quando la funzione viene chiusa. Le variabili di stack saranno ovviamente distrutte perché stai ancora scrivendo C++, ma la tua memoria GPU rimarrà valida.

L'unico problema allora è come accedere alla memoria invocazioni successive, e quindi è possibile utilizzare il suggerimento di 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; 
    ... 
} 
+0

Non dovrebbe '(* pObject) lp;' be '(MyClass *) lp;'? – donturner

6

So che questa domanda è stata già ufficialmente risposto, ma vorrei aggiungere la mia soluzione: Invece di provare a passare un puntatore, posizionare il puntatore in un array Java (all'indice 0) e passarlo a JNI. Il codice JNI può ottenere e impostare l'elemento dell'array usando GetIntArrayRegion/SetIntArrayRegion.

Nel mio codice, ho bisogno del livello nativo per gestire un descrittore di file (un socket aperto). La classe Java contiene un array int[1] e lo passa alla funzione nativa. La funzione nativa può fare qualsiasi cosa con esso (get/set) e rimettere il risultato nella matrice.

+0

Come convertire un puntatore lungo trasferito in array in java –

6

Mentre la risposta accettata da @ denis-tulskiy ha senso, ho seguito personalmente i suggerimenti da here.

Così, invece di utilizzare un tipo di pseudo-pointer, come jlong (o jint se si vuole risparmiare un po 'di spazio sul 32bit arco), utilizzare invece un ByteBuffer. Per esempio:

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

che potrete poi ri-uso con:

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

Per i casi molto semplici, questa soluzione è molto facile da usare. Supponiamo di avere:

struct { 
    int exampleInt; 
    short exampleShort; 
} MyNativeStruct; 

Sul lato Java, dovete semplicemente fare:

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

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

che consente di risparmiare un sacco di scrivere di codice standard! Si dovrebbe tuttavia prestare attenzione all'ordinamento dei byte come spiegato here.

0

È meglio farlo esattamente come fa Unsafe.allocateMemory.

Creare il proprio oggetto quindi digitarlo su (uintptr_t) che è un numero intero senza segno a 32/64 bit.

return (uintptr_t) malloc(50); 

void * f = (uintptr_t) jlong; 

Questo è l'unico modo corretto per farlo.

Ecco il controllo di integrità che Unsafe.allocateMemory esegue.

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

Questa è ** non ** la definizione dello standard di 'uintptr_t'. È una pessima idea di farlo. È definito come abbastanza grande da contenere qualsiasi puntatore e può essere ** qualsiasi ** lunghezza necessaria. In genere su un sistema a 64 bit questo sarebbe 64 bit, ma lo standard non consente nemmeno tale ipotesi. Non dovresti mai formulare ipotesi sulla dimensione di 'uintptr_t'. – StephenG

+1

Ecco come la JVM alloca la memoria su sistemi a 32 e 64 bit. L'assegnazione di uintptr_t consente una conversione pulita in jlong. Non discuterò se è un buon modo o no, ma è il modo in cui la JVM lo fa. – bond