如何正確讀取RTI中enum

斑鸠斑鸠發表於2024-08-30

如何正確讀取 RTI 中的 enum

背景

假設錄製的 RTI 資料儲存.dat 檔案中,使用 DB browser for SQLite 檢視.dat 檔案,其中包含了要讀取的 topic 資料,這個 topic 對應的 IDL 中含義 enum 型別,假設其定義如下:

module Sample {
    enum State {
        UNKNOWN = -1,
        MANUAL = 1,
        STARTED = 2,
        READY = 3
    };

    struct Info {
        Header header;
        State state;
        string name;
        boolean valid;
    };
};

之後使用 RTI 的介面從.dat 中讀取了資料,返回的資料型別為dds::core::xtypes::DynamicData dynamic_raw,然後從中讀取需要的欄位,將其儲存起來。

對於其他型別的資料,例如string或者boolean,可以透過下面的方式讀取:

inline void SampleReader::FillData(dds::core::xtypes::DynamicData dynamic_raw) {
  if (dynamic_raw.member_exists("name")) {
    idl_message_.name(dynamic_raw.value<std::string>("name"));
  }

  if (dynamic_raw.member_exists("valid")) {
    idl_message_.valid(dynamic_raw.value<bool>("valid"));
  }
}

但是對於enum型別的資料,直接透過value讀取是不行的,

inline void SampleReader::FillData(dds::core::xtypes::DynamicData dynamic_raw) {
  if (dynamic_raw.member_exists("state")) {
    idl_message_.state(dynamic_raw.value<Sample::State>("state"));
  }
}

這樣做會提示未定義符號,例如:

undefined symbol: _ZNK3rti4core6xtypes15DynamicDataImpl5valueIN3dds4core9safe_enumIN15Sample8State_defENS9_4typeEEEEET_RKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE

這個原因比較奇怪,實際上這個型別Sample::State被匯出了,idl_message_就是Sample::State型別的,可在讀取的時候就不能這麼使用。考慮到 enum 可能是一個 int 型別,所以可以透過下面的方式讀取:

auto state{dynamic_raw.value<int32_t>("state")};
idl_message_.state(static_cast<Sample::State>(state));

這樣也不行,編譯都過不了,報錯為:

error: invalid conversion from ‘int’ to ‘dds::core::safe_enum<Sample::State_def>::inner_enum’ {aka ‘Sample::State_def::type’} [-fpermissive]

也就是不能將int型別直接轉換為Sample::State型別。那麼應該如何正確讀取 DDS 中的 enum 呢?

解決方案

檢視原始碼,發現列舉Sample::State的定義為

struct State_def {
    enum type {
        UNKNOWN = -1,
        MANUAL = 1,
        STARTED = 2,
        READY = 3
    };
    static type get_default(){ return UNKNOWN;}
};

typedef ::dds::core::safe_enum< State_def > State;
NDDSUSERDllExport std::ostream& operator << (std::ostream& o,const State& sample);

Sample::State實際是dds::core::safe_enum的例項化,而safe_enum是一個模版類,其定義如下:

template<typename def, typename inner = typename def::type>
class UserDllExport safe_enum : public def {
public:

    /**
     * The underlying \p enum type
     *
     * To convert from an integer, \p static_cast it to the member type \p inner_enum:
     *
     * \code
     * int reliability_as_int = 2;
     * ReliabilityKind reliability = static_cast<ReliabilityKind::inner_enum>(reliability_as_int);
     * \endcode
     */
    typedef inner inner_enum;

public:

    /**
     * @brief Initializes the enumeration to zero
     *
     * @note Zero may not be a valid enumerator. It's best to assign a valid
     * enumerator whenever possible:
     * \code
     * Reliability reliability = Reliability::RELIABLE;
     * \endcode
     */
    safe_enum() : val(static_cast<inner>(0))
    {
    }

    /**
     * @brief Copy constructor
     */
    safe_enum(inner_enum v) : val(v) {}

    /**
     * @brief Retrieves the actual \p enum value
     */
    inner_enum underlying() const { return val; }


private:
    inner_enum val;
};

inner_enum的註釋中提到,可以透過static_castint轉換為inner_enum,所以可以透過下面的方式讀取:

inline void SampleReader::FillData(dds::core::xtypes::DynamicData dynamic_raw) {
  if (dynamic_raw.member_exists("state")) {
    auto state{dynamic_raw.value<int32_t>("state")};
    idl_message_.state(static_cast<Sample::State::inner_enum>(state));
  }
}

原來解決方案就在原始碼裡面……

相關文章