由於某個需求,現在需要訂閱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
}
}
運行後,沒有再報錯了