由于某个需求,现在需要订阅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.(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.(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
}
}
运行后,没有再报错了