記OpenDDS訂閱內置主題的一些坑

由於某個需求,現在需要訂閱OpenDDS的內置主題,首先我們通過官方文檔來看下OpenDDS有哪些內置主題:

一共四個,分別對應參與者、主題、生產者、訂閱者,這次我們要訂閱的是Topic的信息,但是這裏沒有說明該怎麼訂閱,也沒說明這四個主題的消息格式是怎樣的,不要急,往下翻一下第六章就能看到:

我們可以看到,DCPSTopic這個主題的消息類型爲TopicBuiltinTopicData,本章最後一小節給出的是DCPSParticipant主題的C++示例代碼,那麼我們仿照着寫出DCPSTopic的Java代碼:

        DomainParticipantFactory dpf = TheParticipantFactory.WithArgs(new StringSeqHolder(args));
        if (dpf == null) {
            return;
        }
        DomainParticipant dp = dpf.create_participant(4, PARTICIPANT_QOS_DEFAULT.get(), null, DEFAULT_STATUS_MASK.value);
        if (dp == null) {
            return;
        }
        // 訂閱內置主題DCPSTopic
        Subscriber subscriber = dp.get_builtin_subscriber();
        DataReader dr = subscriber.lookup_datareader("DCPSTopic");
        TopicBuiltinTopicDataDataReader pdr = TopicBuiltinTopicDataDataReaderHelper.narrow(dr);
        TopicBuiltinTopicDataSeqHolder partData  = null;
        SampleInfoSeqHolder infos                = null;
        int ret = pdr.read(partData, infos, 20, ANY_SAMPLE_STATE.value, ANY_VIEW_STATE.value, ANY_INSTANCE_STATE.value);
        if (ret == RETCODE_OK.value) {
            TopicBuiltinTopicData[] datas =  partData.value;
            for (TopicBuiltinTopicData data : datas) {
                String topicName = data.name;
                // do-something
            }
        }

需要注意,這四個主題,各有對應的DataReader、DataSeqHolder,絕不能混用

結果運行後報錯,提示:

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x000000006f4af755, pid=11244, tid=0x00000000000015e4
#

JVM出現了錯誤,於是我們看錯誤日誌hs_err_pidXXXX.log,有如下內容:

Stack: [0x00000000031a0000,0x00000000032a0000],  sp=0x000000000329e990,  free space=1018k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
V  [jvm.dll+0x13f755]
C  [idl2jni_runtimed.dll+0x1ed69]  JNIEnv_::GetObjectClass+0x59
C  [idl2jni_runtimed.dll+0x21ad3]  deholderize<_jobjectArray *>+0x53
C  [OpenDDS_DCPS_Javad.dll+0x113b2b]  Java_DDS__1TopicBuiltinTopicDataDataReaderTAOPeer_read+0xab
C  0x00000000035a8c67

Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)
j  DDS._TopicBuiltinTopicDataDataReaderTAOPeer.read(LDDS/TopicBuiltinTopicDataSeqHolder;LDDS/SampleInfoSeqHolder;IIII)I+0
j  hnu.yhc.ddsclient.dds.DdsSubscriber.<init>(Lhnu/yhc/ddsclient/dds/DataReaderListenerBaseEx;)V+119
j  hnu.yhc.ddsclient.BuiltInTest.main([Ljava/lang/String;)V+11
v  ~StubRoutines::call_stub

很顯然是在_TopicBuiltinTopicDataDataReaderTAOPeer.read這個方法出現錯誤,並且異常來源於JNI調用的C++代碼中的JNIEnv_::GetObjectClass處,通過觀察Java_DDS__1TopicBuiltinTopicDataDataReaderTAOPeer_read方法的代碼,基本可以找到問題:TopicBuiltinTopicDataSeqHolder和SampleInfoSeqHolder沒有初始化導致的!

於是我們將其使用空構造方法初始化,再次運行後,還是出錯!這次異常棧信息如下:

Stack: [0x00000000026c0000,0x00000000027c0000],  sp=0x00000000027be800,  free space=1018k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
V  [jvm.dll+0x146099]
C  [OpenDDS_DCPS_Javad.dll+0x9b7f9]  JNIEnv_::GetArrayLength+0x59
C  [OpenDDS_DCPS_Javad.dll+0xcd33b]  copyToCxx+0x5b
C  [OpenDDS_DCPS_Javad.dll+0x113b6c]  Java_DDS__1TopicBuiltinTopicDataDataReaderTAOPeer_read+0xec
C  0x0000000002ac8c67

Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)
j  DDS._TopicBuiltinTopicDataDataReaderTAOPeer.read(LDDS/TopicBuiltinTopicDataSeqHolder;LDDS/SampleInfoSeqHolder;IIII)I+0
j  hnu.yhc.ddsclient.dds.DdsSubscriber.<init>(Lhnu/yhc/ddsclient/dds/DataReaderListenerBaseEx;)V+131
j  hnu.yhc.ddsclient.BuiltInTest.main([Ljava/lang/String;)V+11
v  ~StubRoutines::call_stub

錯誤出現在copyToCxx方法調用GetArrayLength處,我們看一下這兩個Holder的定義,果不其然內部都包含一個數組,且空構造方法沒有對它做初始化,所以還是空指針的問題:

public final class TopicBuiltinTopicDataSeqHolder {
    public TopicBuiltinTopicData[] value;

    public TopicBuiltinTopicDataSeqHolder() {
    }

    public TopicBuiltinTopicDataSeqHolder(TopicBuiltinTopicData[] var1) {
        this.value = var1;
    }
}

那麼問題來了,該把內部的數組初始化成多長呢?既然示例代碼把最大采樣量設爲20,那先按這個數值試試,結果果不其然又報錯了(這裏只放出本地棧的信息):

Stack: [0x00000000021f0000,0x00000000022f0000],  sp=0x00000000022ee170,  free space=1016k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
V  [jvm.dll+0x13f755]
C  [OpenDDS_DCPS_Javad.dll+0x9c929]  JNIEnv_::GetObjectClass+0x59
C  [OpenDDS_DCPS_Javad.dll+0xa2893]  copyToCxx+0x53
C  [OpenDDS_DCPS_Javad.dll+0xcd3a0]  copyToCxx+0xc0
C  [OpenDDS_DCPS_Javad.dll+0x113b6c]  Java_DDS__1TopicBuiltinTopicDataDataReaderTAOPeer_read+0xec
C  0x0000000002868c67

可以看到,這裏兩次進入copyToCxx方法(其實是兩個不同的重載版本),並且又調用到GetObjectClass並出錯,顯然又出現空指針問題了,我們看最初進入的copyToCxx方法代碼,發現,原來只要兩個Holder其內部的數組有內容(即長度大於0),就會遍歷並複製,所以答案就得到了:初始化兩個Holder時,數組長度取0即可

正確代碼如下:

        DomainParticipantFactory dpf = TheParticipantFactory.WithArgs(new StringSeqHolder(args));
        if (dpf == null) {
            return;
        }
        DomainParticipant dp = dpf.create_participant(4, PARTICIPANT_QOS_DEFAULT.get(), null, DEFAULT_STATUS_MASK.value);
        if (dp == null) {
            return;
        }
        // 訂閱內置主題DCPSTopic
        Subscriber subscriber = dp.get_builtin_subscriber();
        DataReader dr = subscriber.lookup_datareader("DCPSTopic");
        TopicBuiltinTopicDataDataReader pdr = TopicBuiltinTopicDataDataReaderHelper.narrow(dr);
        TopicBuiltinTopicDataSeqHolder partData  = new TopicBuiltinTopicDataSeqHolder(new TopicBuiltinTopicData[0]);
        SampleInfoSeqHolder infos                = new SampleInfoSeqHolder(new SampleInfo[0]);
        int ret = pdr.read(partData, infos, 20, ANY_SAMPLE_STATE.value, ANY_VIEW_STATE.value, ANY_INSTANCE_STATE.value);
        if (ret == RETCODE_OK.value) {
            TopicBuiltinTopicData[] datas =  partData.value;
            for (TopicBuiltinTopicData data : datas) {
                String topicName = data.name;
                // do-something
            }
        }

運行後,沒有再報錯了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章