2011-08-31 9 views
40

Muszę zaimplementować niektóre funkcje do aplikacji Android, używając NDK, a tym samym JNI.Jak utworzyć obiekt z JNI?

Oto kod C, z moich obaw, że napisałem:

#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; 
}  

Moje problemy są mniej lub bardziej wyjaśnione wewnątrz kodu. Może również: czy typ zwrotu funkcji (jobject) jest prawidłowy?

Teraz 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"); 
    } 
} 

Kiedy próbuję uruchomić kod, to nie działa.

+3

Proszę wyjaśnić "nie działa". –

+1

Chociaż prawdopodobnie masz na myśli "odpowiednio", uważam, że ważne jest traktowanie obiektów z szacunkiem. :) – quasimodo

+0

@quasimodo Masz rację. :) Zmontowałem ten błąd, dzięki. – pmichna

Odpowiedz

67

Od Point jest wewnętrzna klasa, tak aby uzyskać byłoby

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

$ konwencja dla klas wewnętrznych nie jest naprawdę jasno udokumentowane w autorytatywnych widowisko, ale jest zakorzenione w kodzie tak dużo pracy, że jest mało prawdopodobne, aby się zmienić. Mimo to, byłoby czuć się bardziej niezawodny, jeśli ograniczyłbyś swój kod JNI do pracy z klasami najwyższego poziomu.

Potrzebujesz konstruktora, który pobiera dwa argumenty jako argumenty. Podpis na to (II)V, więc:

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

Następnym razem, to jakiś błąd w kodzie obsługi, tak że będziesz miał pojęcia, których część nie działa!

+1

Uwaga: ta odpowiedź nie zadziała. Zobacz komentarze w odpowiedzi Seva poniżej, dlaczego (argumenty konstruktora muszą zawierać zewnętrzną klasę) – Colin

+0

@Colin: Tak, przeoczyłem, że 'Point' nie jest statyczny. –

4

Niektóre problemy z kodem.

Po pierwsze, dlaczego tworzenie własnej klasy Point, a nie przy użyciu biblioteki, pod warunkiem android.graphics.Point?

drugie, spec klasy dla klas zagnieżdżonych jest inaczej - byłoby to "com/example/ndktest/NDKTest $ Point". Zagnieżdżanie klas różni się od pakietów.

Po trzecie, nie sądzę, że JNI pozwala tworzyć instancje niestatycznych klas zagnieżdżonych. Musisz przekazać obiekt klasy zagnieżdżenia 'this wskaźnik przy tworzeniu obiektu - nie ma takiego argumentu.

Wreszcie, chociaż widziałem wskazówki, jak używać "void (V)" jako podpis metody konstruktora, ale jest to niezgodne z resztą sygnatur metod; normalnie, metoda z dwoma parametrami wewnętrznymi i pustym typem powrotu to "(II) V".

Na marginesie uważam, że znacznie łatwiej jest przekazać prymitywne typy i tablice prymitywu wpisane z NDK na Javę. Tworzenie obiektu/dostęp jest kłopotliwy i trudny do debugowania.

+0

@Hennig Makholm Na przykład stworzyłem własną klasę punktów. Mogło to być równie dobrze. Zmieniłem klasę na najwyższy poziom, zmieniono "void (V) na" (II) V ", a teraz działa. – pmichna

+2

Dokumentacja mówi:" Ten identyfikator musi zostać uzyskany przez wywołanie GetMethodID() z jako nazwą metody i void (V) jako typ zwracany. "Ja też byłem zdezorientowany przez to, kiedy pierwszy raz to przeczytałem, myślałem, że to oznacza" użyj podpisu 'void (V) ', ale to faktycznie znaczyło" użyj kodu typu 'V' (dla pustki) podczas konstruowania podpisu ". Pewnie 'void (V)' wygląda jak dziwna sygnatura, ale wtedy '' "' jest dziwnym sposobem na określenie konstruktora. Kiedy wszystko jest magiczne, mylące rzeczy są naprawdę mylące! http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/functions.html – steveha

+9

W rzeczywistości można utworzyć wystąpienia niestatycznych klas zagnieżdżonych, metoda jest tylko trochę nieoczywista: wszystkie konstruktory klasy zagnieżdżonej mają domyślny pierwszy parametr typu klasy zewnętrznej. Tak więc dla powyższego przykładu, z klasą punktową niestatyczną, sygnatura konstruktora będzie miała postać "" "," (Lcom/example/ndktest/NDKTest; II) V "'. Możesz to zobaczyć, uruchamiając 'javap -s -p com.example.ndktest.NDKTest $ Point' z katalogu klas po kompilacji. – benkc

9

Specyfikacja jest poprawna, ale w tym przypadku nieco myląca. GetMethodID wymaga nazwy metody i podpisu metody. specification says:

uzyskanie ID Metoda konstruktora, zasilanie < init, > jak nazwa sposób i porowatości (v) Typ powrotnego.

Zauważ, że to mówi typ zwracany nie podpis. Chociaż void(V) wygląda na powierzchownie podobne do podpisu, specyfikacja mówi, że podpis musi określać typ zwracanej pustki (to jest, V).

Poprawną sygnaturą dla konstruktora bezargumentowego jest ()V. Jeśli konstruktor ma argumenty, powinny zostać opisane między nawiasami, jak zauważyli inni komentatorzy.

0

Trzy kroki powinny stworzyć obiekt Point z 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

Jest to użyteczne, ponieważ pokazuje przykładowy przykład kodu. Kod się jednak nie powiedzie, ponieważ podpis metody jest nieprawidłowy. Powinno to być '" (II) V "', jak wymieniono w innych odpowiedziach. – ephemer

1

W JNI zawsze można użyć narzędzia javap dla znalezienia sygnatur metod. Po prostu uruchom javap -s com.example.ndktest.NDKTest i skopiuj sygnatury metod z danych wyjściowych.