Passando ponteiros entre C e Java através de JNI
-
06-07-2019 - |
Pergunta
No momento, eu estou tentando criar um Java-aplicação que utiliza CUDA-funcionalidade. A conexão entre CUDA e Java funciona bem, mas eu tenho um outro problema e queria perguntar, se os meus pensamentos sobre o assunto estão corretos.
Quando eu chamar uma função nativa do Java, eu passar alguns dados a ele, as funções calcula algo e retorna um resultado. É possível, para permitir que a primeira função retornar uma referência (ponteiro) para esse resultado que eu posso passar para JNI e chamar outra função que faz outros cálculos com o resultado?
A minha ideia era reduzir a sobrecarga que vem de copiar dados de e para a GPU, deixando os dados na memória da GPU e apenas passando uma referência a ele para outras funções podem usá-lo.
Depois de tentar algum tempo, eu pensei para mim mesmo, isso não deve ser possível, porque os ponteiros são apagados após o término da aplicação (neste caso, quando o termina-função C). Isso é correto? Ou estou apenas ao mau em C para ver a solução?
Edit: Bem, para expandir a questão um pouco (ou torná-lo mais claramente): memória alocada por funções nativas JNI é desalocada quando as extremidades de função? Ou eu ainda pode acessá-lo até fins de aplicação ou o JNI ou quando eu libertá-lo manualmente?
Obrigado pelo seu contributo:)
Solução
Eu usei a seguinte abordagem:
em seu código JNI, criar uma estrutura que iria realizar referências a objetos que você precisa. Quando você primeiro criar esta estrutura, retornar a sua ponteiro para java como um long
. Então, a partir java você acabou de chamar qualquer método com este long
como um parâmetro, e em C lançá-lo para um ponteiro para a sua estrutura.
A estrutura será na pilha, por isso não vai ser apuradas entre as diferentes JNI.
EDIT: Eu não acho que você pode usar longo ptr = (long)&address;
vez que o endereço é uma variável estática. Usá-lo da maneira Gunslinger47 sugeriu, ou seja, criar nova instância da classe ou um struct (usando novo ou malloc) e passar o ponteiro.
Outras dicas
Em C ++ você pode usar qualquer mecanismo que pretende atribuir / memória livre: a pilha, malloc / free, novo / exclusão ou qualquer outra implementação personalizada. A única exigência é que se você atribuído um bloco de memória com um mecanismo, você tem que libertá-la com o mesmo mecanismo, para que você não pode chamar free
em uma variável de pilha e você não pode chamar delete
na memória malloc
ed.
JNI tem seus próprios mecanismos de alocação / liberação de memória JVM:
- NewObject / DeleteLocalRef
- NewGlobalRef / DeleteGlobalRef
- NewWeakGlobalRef / DeleteWeakGlobalRef
Estes seguem a mesma regra, o único problema é que refs locais podem ser excluídos "em massa" de forma explícita, com PopLocalFrame
, ou implicitamente, quando sai do método nativo.
JNI não sei como você definidos para sua memória, por isso não pode libertá-la quando suas saídas de função. variáveis ??de pilha irá, obviamente, ser destruído, porque você ainda está escrevendo C ++, mas a sua memória GPU permanecerá válido.
O único problema, então, é como acessar a memória em invocações subsequentes e, em seguida, você pode usar a sugestão de 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;
...
}
Java não saberia o que fazer com um ponteiro, mas deve ser capaz de armazenar um ponteiro de valor de retorno de uma função nativa, em seguida, entregá-lo para outra função nativa para que possa lidar com eles. ponteiros C são nada mais do que os valores numéricos no núcleo.
Outra contibutor teria que dizer-lhe ou não o pontiagudo para gráficos memória seria apagada entre as chamadas JNI e se haveria quaisquer soluções alternativas.
Eu sei que esta pergunta já foi respondida oficialmente, mas eu gostaria de acrescentar a minha solução:
Em vez de tentar passar um apontador, coloque o ponteiro em uma matriz Java (no índice 0) e passar isso para JNI. código JNI pode obter e definir o elemento matriz usando GetIntArrayRegion
/ SetIntArrayRegion
.
No meu código, eu preciso da camada nativa para gerenciar um descritor de arquivo (uma tomada aberta). A classe Java contém uma matriz int[1]
e passa para a função nativa. A função nativa pode fazer o que quer com ele (get / set) e colocar de volta o resultado na matriz.
Se você está alocando memória dinamicamente (na pilha) dentro da função nativa, este não é apagado. Em outras palavras, você é capaz de manter o estado entre diferentes chamadas para funções nativas, usando ponteiros, estático vars, etc.
Pense nisso uma maneira diferente: o que você poderia fazer manter em segurança em uma chamada de função, chamada de outro programa C ++? As mesmas coisas se aplicam aqui. Quando uma função é encerrado, qualquer coisa na pilha para que chamada de função é destruído; mas qualquer coisa na pilha é mantida a menos que você exclua explicitamente.
A resposta curta: enquanto você não desalocar o resultado que você está voltando para a função de chamada, ele permanecerá válida para re-entrada mais tarde. Apenas certifique-se de limpá-lo quando estiver pronto.
Enquanto a resposta aceita de @ denis-tulskiy faz sentido, eu personnally seguido sugestões de aqui .
Assim, em vez de usar um tipo de pseudo-ponteiro, como jlong
(ou jint
se você quiser economizar algum espaço em 32bits arco), use em vez de um ByteBuffer
. Por exemplo:
MyNativeStruct* data; // Initialized elsewhere.
jobject bb = (*env)->NewDirectByteBuffer(env, (void*) data, sizeof(MyNativeStruct));
que você pode mais tarde re-uso com:
jobject bb; // Initialized elsewhere.
MyNativeStruct* data = (MyNativeStruct*) (*env)->GetDirectBufferAddress(env, bb);
Para casos muito simples, esta solução é muito fácil de usar. Suponha que você tem:
struct {
int exampleInt;
short exampleShort;
} MyNativeStruct;
No lado do Java, você simplesmente precisa fazer:
public int getExampleInt() {
return bb.getInt(0);
}
public short getExampleShort() {
return bb.getShort(4);
}
O que evita que você escrever muitos do código clichê! Deve-se no entanto prestar atenção a ordenação de bytes como explicado aqui .
O seu melhor para fazer isso exatamente como Unsafe.allocateMemory faz.
Crie o seu objeto, em seguida, digite-a (uintptr_t) que é um 32/64 bit inteiro sem sinal.
return (uintptr_t) malloc(50);
void * f = (uintptr_t) jlong;
Esta é a única maneira correta de fazê-lo.
Aqui está a verificação de sanidade Unsafe.allocateMemory faz.
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