记OpenDDS订阅内置主题的一些坑

由于某个需求,现在需要订阅OpenDDS的内置主题,首先我们通过官方文档来看下OpenDDS有哪些内置主题:

记OpenDDS订阅内置主题的一些坑_第1张图片

一共四个,分别对应参与者、主题、生产者、订阅者,这次我们要订阅的是Topic的信息,但是这里没有说明该怎么订阅,也没说明这四个主题的消息格式是怎样的,不要急,往下翻一下第六章就能看到:

记OpenDDS订阅内置主题的一些坑_第2张图片

我们可以看到,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
            }
        }

运行后,没有再报错了

你可能感兴趣的:(OpenDDS)