Как се различава JNIEnv глобалното рефериране от jobject в C?

Към момента имам моята JNI среда и обекти за обект, запазени локално. Открих, че за да може моят JNI да работи с ICS и други устройства, трябва да поправя своя JNI код. Това е грешката, която получавам:

02-20 10:20:59.523: E/dalvikvm(21629): JNI ERROR (app bug): attempt to use stale local reference 0x38100019
02-20 10:20:59.523: E/dalvikvm(21629): VM aborting
02-20 10:20:59.523: A/libc(21629): Fatal signal 11 (SIGSEGV) at 0xdeadd00d (code=1), thread 21629

Объркан съм как да създам/унищожа тези глобали и дали изобщо го правя правилно.

Моето приложение в момента работи добре на всички устройства преди ICS, използвайки този код:

BYTE Java_my_eti_commander_RelayAPIModel_00024NativeCalls_InitRelayJava( JNIEnv *env, jobject obj  ) {

    myEnv = (env);
    myObject = obj;

    changeID = (*myEnv)->GetStaticMethodID( myEnv, myObject, "changeItJavaWrapper", "(S)V"  );
    getID    = (*myEnv)->GetStaticMethodID( myEnv, myObject, "getItJavaWrapper"   , "(S)S"   );
    putID    = (*myEnv)->GetStaticMethodID( myEnv, myObject, "putItJavaWrapper"   , "(B)V" );
    flushID  = (*myEnv)->GetStaticMethodID( myEnv, myObject, "flushItJavaWrapper" , "()V"   );
    delayID  = (*myEnv)->GetStaticMethodID( myEnv, myObject, "delayItJavaWrapper" , "(S)V"  );

    RelayAPI_SetBaud= WrapSetBaud;
    RelayAPI_get    = WrapGetIt;
    RelayAPI_put    = WrapPutIt;
    RelayAPI_flush  = WrapFlushIt;
    RelayAPI_delay  = WrapDelayIt;
    ...
}

Под GetStaticMethodID извикванията, всички променливи RelayAPI_ са функционални указатели, които водят тук:

void WrapSetBaud( WORD w ) {
    return (*myEnv)->CallStaticVoidMethod( myEnv, myObject, changeID, w );
}

short WrapGetIt( WORD time ) {
    return (*myEnv)->CallStaticShortMethod( myEnv, myObject, getID, time );
}

void WrapPutIt( BYTE buff ) {
    return (*myEnv)->CallStaticVoidMethod( myEnv, myObject, putID, buff );
}

void WrapFlushIt( void ) {
    return (*myEnv)->CallStaticVoidMethod( myEnv, myObject, flushID );
}

void WrapDelayIt( WORD wait ) {
    return (*myEnv)->CallStaticVoidMethod( myEnv, myObject, delayID, wait );
}

Накрая се връща към моя Java код тук:

public static void changeItJavaWrapper( short l ) throws IOException {
    mModelService.changeitJava( l );
}

public static void flushItJavaWrapper() {
    mModelService.flushitJava();
}

public static void putItJavaWrapper( byte p ) {
    mModelService.putitJava( p );
}

public static void delayItJavaWrapper( short wait ) {
   mModelService.delayitJava( wait );
}

public static short getItJavaWrapper( short s ) throws IOException {
    return mModelService.getitJava( s );
}

Промених инициализациите си на:

myEnv = (*env)->NewGlobalRef(env,obj);
myObject = (*env)->NewGlobalRef(env,obj);

Но съм изключително объркан от това, тъй като те имат еднакви параметри и просто няма смисъл. Не мога да намеря документация за този метод никъде, колкото и глупаво да звучи, this tutorial, this page, и the oracle docs не има някаква информация за самия метод NewGlobalRef.

РЕДАКТИРАНЕ

jmethodID changeID;
jmethodID getID;
jmethodID putID;
jmethodID flushID;
jmethodID delayID;
jobject myObject;
jclass    bluetoothClass;
JNIEnv *myEnv;

person JuiCe    schedule 21.02.2013    source източник


Отговори (1)


Първо: myEnv = (*env)->NewGlobalRef(env,obj); е просто грешен. Не трябва да кеширате тази стойност.

Това, което ви е позволено, е да кеширате идентификатори на методи, идентификатори на полета, препратки към класове, ... (но не забравяйте да почистите тези неща след това). Но кеширането на тези стойности изисква специални мерки.

Защо? Проблемът е, че JVM има право да зарежда и разтоварва класове според нуждите на програмата. Следователно може да се случи класът да бъде разтоварен веднага щом последният екземпляр на класа бъде унищожен от събирача на отпадъци. Веднага щом това се случи, вашите кеширани идентификатори вече не са валидни. Възможно е идентификаторите да бъдат същите, след като JVM зареди отново класа, но това не е гарантирано.

Решение: Ако искате да кеширате тези идентификатори, трябва да кажете на JVM, че не е разрешено да разтоварва клас. Точно това прави NewGlobalRef. Вие просто увеличавате препратката за препратката, предадена на NewGlobalRef, така че броят на препратките никога да не падне до нула и събирането на отпадъци не може да изчисти посочения елемент.

Внимание: Създаването на NewGlobalRef има сериозен недостатък: Освен в Java, трябва да се уверите, че извиквате DeleteGlobalRef, ако вече не се нуждаете от тази препратка, за да активирате повторно събирането на боклука на препратката. (Тъй като събирачът на боклук не знае дали все още имате нужда от тази справка или не) Или с други думи: Трябва да сте сигурни, че сами почиствате боклука си, в противен случай ще оставите изтичане на памет.

Бих казал също, че не е добра идея да създавате глобална реф за обект (освен ако наистина искате да запазите обекта жив), тъй като това означава, че обектът никога няма да попадне в боклука и следователно никога няма да бъде освободен.

По-добър вариант: Ако искате да кеширате тези идентификатори, за да ускорите достъпа до определен обект, запазете глобална препратка за класа (използвайки FindClass) и вземете идентификаторите от обекта на класа, който FindClass връща.

Ето един (непълен) пример за това, което имам предвид. Обикновено създавам структура, съдържаща всички идентификационни номера, които са ми необходими за достъп до клас, само за да поддържам пространствата си от имена чисти. Можете да си представите това по следния начин:

/*! \brief Holds cached field IDs for MyClass.java */
typedef struct MyClass {
    int loaded;   /*!< Nonzero if the information are valid */

    jclass clazz;    /*!< Holds a global ref for the class */
    jfieldID aField; /*!< Holds the field ID of aField */
}tMyClass;

static tMyClass me = { 0 };

Най-лесният начин е да предоставите функция "свързване" за вашия обект, която извършва инициализацията на дефинираната по-горе структура.

/*! \brief This function fetches field IDs for a specific class in order to have
           faster access elsewhere in the code 

    \param env  a valid JNI environment 

    \return
            -  0 if OK
            - <0 if an error occured */
int MyClass_connect(JNIEnv *env)
{
    jobject theClass = env->FindClass("MyClass");
    if (theClass == NULL) goto no_class;

    me.clazz = (jclass) env->NewGlobalRef(theClass);    // make it global to avoid class unloading and therefore
                                                    // invalidating the references obtained.
    if (me.clazz == NULL) goto no_memory;

      me.aField = env->GetFieldID(me.clazz, "aField", "I")    ;
    if (me.aField == NULL) goto no_aField;

    me.loaded = 1;
    return 0;

no_aField:
    env->DeleteGlobalRef(me.clazz);
no_memory:
no_class:
    return -1;
}

След като извикате MyClass_connect успешно, можете да използвате me.aField, за да съкратите кода за достъп до полето във вашия код. Разбира се, трябва да предоставите функция за прекъсване на връзката, която се извиква, когато MyClass вече не е необходим:

void MyClass_disconnect(JNIEnv *env)
{
    if (me.loaded == 0) return;

    env->DeleteGlobalRef(me.clazz);
    memset(me, 0, sizeof(tMyClass));
}

Съжалявам за тази малко дълга публикация, но се надявам, че това ще ви помогне малко да разрешите объркването ви и ще ви даде малко представа за вътрешната работа на JNI заедно с малко рецепти как да се справите с това ефективно.

Редактиране: Можете да намерите документация за JNI извикванията на уебсайта на oracle

person junix    schedule 21.02.2013
comment
благодаря много за това. случайно да познавате добри уебсайтове, които имат повече информация за това в C? - person JuiCe; 21.02.2013
comment
Търсите информация за какво точно? JNI? Най-добри практики? - person junix; 21.02.2013
comment
Все още съм малко изгубен, тъй като използвам GetStaticMethodID вместо GetFieldID. За да мога да извикам метода, който намирам по-късно в моя код, трябва също да имам JNI Env, запазен глобално, показан във втория ми кодов блок. -- Също така не знам каква е вашата променлива tUsb_Device, с какво се сравнява числото за този параметър? - person JuiCe; 22.02.2013
comment
@JuiCe Типът tUsb_Device е грешка при копиране и поставяне. Взех кода от проект, върху който работя. Поправих това. съжалявам Що се отнася до функциите Get*ID, в общи линии всичко е същото. Всички те извличат специфични атрибути на клас или обект. GetStaticMethodID например връща ИД на статичен метод на класа, докато GetFieldID дава ИД на поле в класа и т.н. В моята публикация включих връзка към документацията на JNI. Там ще намерите цялата необходима информация. По отношение на JNIEnv не трябва да запазвате тази показалка. Защо мислиш, че трябва? - person junix; 23.02.2013
comment
Само защото го бях направил във втория си кодов блок, използвайки CallStatic*Method. Не мога да намеря никакви примери за CallStatic*Method, които не използват JNI среда като параметър. The docs показва средата там. Аз също не съм много запознат със C, така че ако пропускам нещо очевидно, съжалявам. - person JuiCe; 23.02.2013
comment
@JuiCe Няма нужда от извинение. Може би не разбирам достатъчно добре вашето приложение. Кой (коя нишка работи в коя среда) инициира извикването на функциите Wrap*? От решаващо значение е нишката, която прави JNI извиквания, да е прикрепена към JVM и да има свой собствен JNIEnv (защото това е специфично за нишката). - person junix; 23.02.2013
comment
функциите Wrap* се извикват навсякъде в моя C код. C кодът е съществуваща библиотека, която преди е била използвана за Palm Pilots. Setit/Getit/Putit/Flushit/delayit по-рано сочеше към bluetooth нишката на Palm pilot, но сега всички водят обратно към bluetooth нишката на края на Java. Ако не съм достатъчно ясен, уведомете ме. Начинът, по който разполагам с кода си сега, работи, което ме озадачава. Редактирам въпроса си с това как декларирам променливите в горната част на файла .c. - person JuiCe; 25.02.2013