メッセージクラスを直接記述する方法を述べる。
次のコードは、nine::Messageクラスの定義から、メッセージ定義に関連する部分を抜き出したものである:
class Message
{
public:
virtual int getMarshalSize() const;
virtual void marshal(Buffer* pBuf) const;
virtual void unmarshal(Buffer* pBuf);
struct Header {
int commId;
uint16_t messageId;
} header;
};
まずはユニークなメッセージIDを用意する。
IDのユニークさの確保はアプリケーション側の責任である。 ここでは、ID = 0x100 (KIND=0x1, TYPE=0x00) を使おう。
この ID は、メッセージオブジェクトの header.messageId メンバの値にならなければならない。nine 内部で生成された場合でもセットされるよう、コンストラクタに記述する。
class MyMessage: public nine::Message
{
public:
enum { MESSAGE_ID = 0x100; }
MyMessage() {
header.messageId = MESSAGE_ID;
}
};
class MessageTypeRegistry
{
public:
typedef Message* (*t_pfFactory)(uint16_t);
static uint16_t Register(uint16_t, t_pfFactory, bool override = false);
};
第一引数はメッセージID, 第二引数はメッセージオブジェクトの生成関数、第三引数は既存の登録と衝突した場合に上書きするかどうかである。
呼び出しはメッセージを使う前に行う必要がある。 C++ の初期化テクニックを用いるか、main() で呼びだすのが良い。
nine::Message* CreateMyMessage(uint16_t msgid) { return new MyMessage(); }
int main()
{
nine::MessageRegistry::Register(MyMessage::MESSAGE_ID, &CreateMyMessage, false);
}
メッセージの内容は、メンバ変数として宣言すればよい。 メンバへのアクセスはカプセル化してもよいし、直接アクセスさせてもいい。
class MyMessage: public nine::Message
{
public:
// メッセージの内容に相当するメンバ変数
uint16_t m_u16;
std::string m_str;
virtual int getMarshalSize() const {
return 2 + nine::MarshalHelper::GetMarshalSize(m_str);
}
virtual void marshal(Buffer* pBuf) const {
pBuf->writeUInt16(m_u16);
pBuf->writeString(m_str);
}
virtual void unmarshal(Buffer* pBuf) {
pBuf->readUInt16(&m_u16);
pBuf->readString(&m_str);
}
...
メッセージ実装のうち、メッセージIDの指定とメッセージクラスの登録を簡単に記述するためのクラステンプレートが提供されている
template <int KIND, int TYPE, typename IMPL>
class MessageTmpl: public Message
{
public:
enum { _MESSAGE_ID = ((KIND & 0x0F) << 12) | (TYPE & 0x0FFF) };
static Message* Create(uint16_t msgid) { return new IMPL(); }
enum {
MESSAGE_KIND = KIND, //!< テンプレートパラメータKIND を参照するための列挙体値。
MESSAGE_TYPE = TYPE, //!< テンプレートパラメータTYPE を参照するための列挙体値。
};
static uint16_t MESSAGE_ID; // for registration
// 大域変数初期化がうまく動かない場合はこれを明示的に呼ぶ。
static uint16_t Enable() {
MESSAGE_ID = MessageTypeRegistry::Register(
_MESSAGE_ID, &MessageTmpl< KIND, TYPE, IMPL >::Create);
return MESSAGE_ID;
}
protected:
MessageTmpl() {
header.messageId = MESSAGE_ID; //use parameter to instanciate template
}
};
template <int KIND, int TYPE, typename IMPL>
uint16_t MessageTmpl< KIND, TYPE, IMPL >::MESSAGE_ID = MessageTypeRegistry::Register(
MessageTmpl< KIND, TYPE, IMPL >::_MESSAGE_ID, &MessageTmpl< KIND, TYPE, IMPL >::Create);
メッセージID をテンプレート引数に与える。 コンストラクタでは、header.messageId にメッセージID をセットするコードが用意されている。 さらに、static メンバ変数(クラス変数)の初期化を利用したメッセージクラスの登録もされるようになっている。
class MyMessage: public nine::MessageTmpl< 0x1, 0x00, MyMessage >
{
public:
uint16_t m_u16;
std::string m_str;
virtual int getMarshalSize() const;
virtual void marshal(Buffer* pBuf) const;
virtual void unmarshal(Buffer* pBuf);
};