2011-08-31 30 views
40

Necesito implementar algunas funciones en una aplicación Android usando NDK y, por lo tanto, JNI.¿Cómo crear un objeto con JNI?

Aquí está el código C, con mis preocupaciones, que escribí:

#include <jni.h> 
#include <stdio.h> 

jobject 
Java_com_example_ndktest_NDKTest_ImageRef(JNIEnv* env, jobject obj, jint width, jint height, jbyteArray myArray) 
{ 
    jint i; 
    jobject object; 
    jmethodID constructor; 
    jobject cls; 
    cls = (*env)->FindClass(env, "com/example/ndktest/NDKTest/Point"); 

//what should put as the second parameter? Is my try correct, according to what 
//you can find in .java file? I used this documentation: http://download.oracle.com/javase/6/docs/technotes/guides/jni/spec/functions.html#wp16027 

    constructor = (*env)->GetMethodID(env, cls, "<init>", "void(V)"); 
//http://download.oracle.com/javase/6/docs/technotes/guides/jni/spec/functions.html#wp16660 
//Again, is the last parameter ok? 

    object = (*env)->NewObject(env, cls, constructor, 5, 6); 
//I want to assign "5" and "6" to point.x and point.y respectively. 
    return object; 
}  

Mis problemas son más o menos explicadas dentro del código. Quizás también: ¿está bien el tipo de devolución de la función (jobject)?

Ahora el NDKTest.java:

package com.example.ndktest; 

import android.app.Activity; 
import android.widget.TextView; 
import android.os.Bundle; 

public class NDKTest extends Activity { 
    /** Called when the activity is first created. */ 
    public native Point ImageRef(int width, int height, byte[] myArray); 
    public class Point 
    { 

     Point(int myx, int myy) 
     { 
      x = myx; 
      y = myy; 
     } 

     int x; 
     int y; 
    } 

    @Override 
    public void onCreate(Bundle savedInstanceState) 
    { 

     super.onCreate(savedInstanceState); 
     TextView tv = new TextView(this); 
     byte[] anArray = new byte[3]; 
     for (byte i = 0; i < 3; i++) 
      anArray[i] = i; 
     Point point = ImageRef(2, 3, anArray); 
     tv.setText(String.valueOf(point.x)); 
      setContentView(tv);  
    } 



    static 
    { 
     System.loadLibrary("test"); 
    } 
} 

Cuando trato de ejecutar el código, que no funciona.

+3

Por favor, explique "no funciona". –

+1

Aunque probablemente quiso decir "respectivamente", creo que es importante tratar sus objetos con respeto. :) – quasimodo

+0

@quasimodo Tienes razón. :) Edité el error, gracias. – pmichna

Respuesta

67

Desde Point es una clase interna, la manera de conseguirlo sería

jclass cls = (*env)->FindClass(env, "com/example/ndktest/NDKTest$Point"); 

La convención $ de las clases internas no está muy claramente documentado en las especificaciones autorizadas, pero está muy arraigada en el código tanto de trabajo que es poco probable que cambie Aún así, sería sentir algo más robusto si restringió su código JNI para trabajar con clases de nivel superior.

Desea un constructor que tome dos ints como argumentos. La firma de ello es (II)V, por lo que:

constructor = (*env)->GetMethodID(env, cls, "<init>", "(II)V"); 

La próxima vez, incluyen algún tratamiento de errores en el código, de modo que usted tendrá una idea de la que parte de ella no funciona!

+1

Nota: esta respuesta no funcionará. Vea los comentarios en la respuesta de Seva a continuación para saber por qué (los argumentos del constructor deben incluir la clase externa) – Colin

+0

@Colin: Sí, pasé por alto que 'Point' no es estático. –

4

Algunos problemas con su código.

En primer lugar, ¿por qué está creando su propia clase Point en lugar de usar android.graphics.Point?

En segundo lugar, la especificación de clase para las clases anidadas es diferente - sería "com/example/ndktest/NDKTest $ Point". La anidación de clase es diferente de los paquetes.

En tercer lugar, no creo que JNI te permita crear instancias de clases anidadas no estáticas. Debe pasar el puntero del objeto de clase de anidamiento this en la creación del objeto; no hay tal argumento.

Finalmente, aunque he visto la guía para usar "void (V)" como firma de método de constructor, pero esto está fuera de línea con el resto de las firmas de métodos; normalmente, un método con dos parámetros int y un tipo de retorno vacío sería "(II) V".

Como nota al margen, me pareció mucho más limpio pasar tipos primitivos y matrices de primitivos escritos de NDK a Java. La creación/acceso a objetos es complicada y difícil de depurar.

+0

@Hennig Makholm Creé mi propia clase Point solo por ejemplo. Pudo haber sido _foo_. Cambié mi clase al nivel superior, cambié "void (V) a" (II) V "y ahora funciona. – pmichna

+2

La documentación dice:" Esta ID debe obtenerse llamando a GetMethodID() con como el nombre del método y void (V) como el tipo de retorno. "Yo también estaba confundido por esto cuando lo leí por primera vez, pensé que significaba" usar la firma 'void (V)' pero en realidad significaba "usar código de tipo' V' (para vacío) cuando construyes la firma ". Sure 'void (V)' parece una firma extraña, pero entonces '" "' es una forma extraña de especificar un constructor. Cuando todo es magia de todos modos, ¡las cosas confusas son realmente confusas! http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/functions.html – steveha

+9

De hecho, puedes crear instancias de clases anidadas no estáticas, el método es un poco no obvio: todo los constructores para la clase anidada tienen un primer parámetro implícito del tipo de clase externa. Por lo tanto, para el ejemplo anterior, con la clase Point no estática, la firma del constructor sería '" "," (Lcom/example/ndktest/NDKTest; II) V "'. Puede ver esto ejecutando 'javap -s -p com.example.ndktest.NDKTest $ Point' desde su directorio de clases después de una compilación. – benkc

9

La especificación es correcta, pero un poco engañosa en este caso. GetMethodID requiere un nombre de método y una firma de método. El specification says:

Para obtener el ID de método de un constructor, el suministro <init> como el nombre del método y vacío (V) como el tipo de retorno.

Nota que dice tipo de retorno, no firma. Aunque void(V) se parece superficialmente a una firma, la especificación le indica que la firma debe especificar un tipo de devolución anómala (es decir, V).

La firma correcta para un constructor sin argumento es ()V. Si el constructor tiene argumentos, se deben describir entre paréntesis, como lo han señalado otros comentadores.

0

tres pasos deben crear el objeto Point con JNI:

jobject 
Java_com_example_ndktest_NDKTest_ImageRef(JNIEnv* env, jobject obj, jint width, jint height, jbyteArray myArray) 
{ 
    ... 
    jclass cls = (*env)->FindClass(env, "com/example/ndktest/NDKTest$Point"); 
    jmethodID constructor = (*env)->GetMethodID(env, cls, "<init>", "void(V)"); 
    jobject object = (*env)->NewObject(env, cls, constructor, obj, 5, 6); 
    ... 
} 
+1

Esto es útil ya que muestra un ejemplo de código sucinto. Sin embargo, el código fallará, porque la firma del método es incorrecta. Debería ser '" (II) V "', como figura en otras respuestas. – ephemer

1

En JNI siempre se puede utilizar la herramienta javap para encontrar firmas de métodos. Simplemente ejecute javap -s com.example.ndktest.NDKTest y copie las firmas de métodos de la salida.

Cuestiones relacionadas