2015-06-20 56 views
0

在通過JNI使用C代碼的Java項目中,我有一段本機C代碼,它獲取對一個對象及其方法之一的引用,然後啓動本地線程,傳遞這些引用它在一個結構中。當線程試圖調用該方法時,代碼會與SIGSEGV一起崩潰。從主線程調用相同的方法。當從本地pthread調用Java方法時SIGSEGV

做了一些研究我瞭解到env引用只在線程內有效,並且必須先附加任何其他本地線程。我這樣做了,但代碼在第一次調用方法時仍然崩潰。

奇怪的是,當我在創建另一個線程(在主代碼中取消註釋行)之前從主線程中調用相同的方法時,事情會運行一段時間。輸出線程在崩潰之前循環約10,000次。

方法調用是DataOutputStream.writeShort()。有問題的線程是唯一寫入DataOutputStream的線程。但是,DataOutputStream連接到DataInputStream

簡化的本地代碼:

static void write_output(struct output_state *s) { 
    int i; 
    jint sample; 
    for (i = 0; i < 2 * s->result_len; i += 2) { 
     sample = (s->result[i] << 8) + s->result[i+1]; 
     (*(s->env))->CallVoidMethod(s->env, s->tunerOut, s->writeShort, sample); 
    } 
} 

static void *output_thread_fn(void *arg) 
{ 
    struct output_state *s = arg; 
    (*(s->jvm))->AttachCurrentThread(s->jvm, &(s->env), NULL); 
    while (!do_exit) { 
     // use timedwait and pad out under runs 
     safe_cond_wait(&s->ready, &s->ready_m); 
     pthread_rwlock_rdlock(&s->rw); //sync access to s with producer thread 
     write_output(s); 
     pthread_rwlock_unlock(&s->rw); 
    } 
    (*(s->jvm))->DetachCurrentThread(s->jvm); 
    return 0; 
} 

JNIEXPORT jboolean JNICALL Java_eu_jacquet80_rds_input_SdrGroupReader_open 
    (JNIEnv *env, jobject self) { 
    jclass clsSelf = (*env)->GetObjectClass(env, self); 
    jfieldID fTunerOut = (*env)->GetFieldID(env, clsSelf, "tunerOut", "Ljava/io/DataOutputStream;"); 
    jobject tunerOut = (*env)->GetObjectField(env, self, fTunerOut); 
    jclass cls = (*env)->GetObjectClass(env, tunerOut); 
    jmethodID writeShortID = (*env)->GetMethodID(env, cls, "writeShort", "(I)V"); 

    if (!writeShortID || !cls) 
     return 0; 

    (*env)->GetJavaVM(env, &(output.jvm)); 
    output.tunerOut = tunerOut; 
    output.writeShort = writeShortID; 

    // (*env)->CallVoidMethod(env, tunerOut, writeShortID, 0); // just for testing 

    pthread_create(&controller.thread, NULL, controller_thread_fn, (void *)(&controller)); 
    usleep(100000); 
    pthread_create(&output.thread, NULL, output_thread_fn, (void *)(&output)); 
    pthread_create(&demod.thread, NULL, demod_thread_fn, (void *)(&demod)); 
    pthread_create(&dongle.thread, NULL, dongle_thread_fn, (void *)(&dongle)); 

    return 1; 
} 

是JNI引用,如jclassjfieldIDjobjectjmethodID受到同樣的限制JNIEnv,即只能在同一線程是否有效?

懷疑這一點後,我把的JNI參考資料移到output_thread()之後,立即致電AttachCurrentThread()。但是,我仍然需要跨越線程邊界傳遞jobject參考(self),並致電GetObjectClass()崩潰。

什麼是創建線程本機代碼並讓該線程調用給定類實例的特定方法的正確方法?

回答

1

原來我懷疑是正確的:jobjectjclass引用確實是本地的,即只在同一個線程內有效,直到當前的本地方法返回爲止。見http://developer.android.com/training/articles/perf-jni.html#local_and_global_references

我的將參考相關代碼移動到線程函數的方法是正確的,除了我需要首先通過調用NewGlobalRef()self轉換爲全局參考。

更新代碼:

static void write_output(struct output_state *s) { 
    int i; 
    jint sample; 
    for (i = 0; i < 2 * s->result_len; i += 2) { 
     sample = (s->result[i] << 8) + s->result[i+1]; 
     (*(s->env))->CallVoidMethod(s->env, s->tunerOut, s->writeShort, sample); 
    } 
} 

static void *output_thread_fn(void *arg) 
{ 
    struct output_state *s = arg; 
    (*(s->jvm))->AttachCurrentThread(s->jvm, &(s->env), NULL); 
    jclass clsSelf = (*(s->env))->GetObjectClass(s->env, s->self); 
    jfieldID fTunerOut = (*(s->env))->GetFieldID(s->env, clsSelf, "tunerOut", "Ljava/io/DataOutputStream;"); 
    s->tunerOut = (*(s->env))->GetObjectField(s->env, s->self, fTunerOut); 
    jclass cls = (*(s->env))->GetObjectClass(s->env, s->tunerOut); 
    s->writeShort = (*(s->env))->GetMethodID(s->env, cls, "writeShort", "(I)V"); 
    while (!do_exit) { 
     // use timedwait and pad out under runs 
     safe_cond_wait(&s->ready, &s->ready_m); 
     pthread_rwlock_rdlock(&s->rw); //sync access to s with producer thread 
     write_output(s); 
     pthread_rwlock_unlock(&s->rw); 
    } 
    (*(s->jvm))->DetachCurrentThread(s->jvm); 
    return 0; 
} 

JNIEXPORT jboolean JNICALL Java_eu_jacquet80_rds_input_SdrGroupReader_open 
    (JNIEnv *env, jobject self) { 
    jclass clsSelf = (*env)->GetObjectClass(env, self); 

    if (!writeShortID || !cls) 
     return 0; 

    output.self = (*env)->NewGlobalRef(env, self); 
    (*env)->GetJavaVM(env, &(output.jvm)); 

    (*env)->CallVoidMethod(env, tunerOut, writeShortID, 0); // just for testing 

    pthread_create(&controller.thread, NULL, controller_thread_fn, (void *)(&controller)); 
    usleep(100000); 
    pthread_create(&output.thread, NULL, output_thread_fn, (void *)(&output)); 
    pthread_create(&demod.thread, NULL, demod_thread_fn, (void *)(&demod)); 
    pthread_create(&dongle.thread, NULL, dongle_thread_fn, (void *)(&dongle)); 

    return 1; 
} 

一件事仍缺少的是DeleteGlobalRef()呼叫時輸出線程完成。這是爲了確保全局引用在不再需要時被釋放,以便垃圾收集器可以將其提取出來。

相關問題