2011-01-20 8 views
6

バイナリプロトコル(Javad GRILプロトコル)用のデコーダを作成しています。それは、次の形式のデータと、百に関するメッセージから構成されていますC/C++でのバイナリメッセージストリームの解析

struct MsgData { 
    uint8_t num; 
    float x, y, z; 
    uint8_t elevation; 
    ... 
}; 

フィールドが隙間なく互いに従うANSI-エンコードされたバイナリ数です。このようなメッセージを解析する最も簡単な方法は、バイトの入力配列を適切な型にキャストすることです。問題は、ストリーム内のデータがパックされている、すなわち整列されていないことである。

x86では、これは#pragma pack(1)を使用して解決できます。ただし、これは他のプラットフォームでは動作しません。また、データが整列していないため、パフォーマンスのオーバーヘッドが発生します。

もう1つの方法は、メッセージタイプごとに特定の解析関数を書くことですが、前述したように、プロトコルには数百のメッセージが含まれています。

さらに別の方法として、Perl unpack()関数のようなものを使用し、メッセージ形式をどこかに格納する方法があります。さあ、#define MsgDataFormat "CfffC"とし、unpack(pMsgBody, MsgDataFormat)と呼んでください。これははるかに短いですが、それでもエラーが発生しやすく、冗長です。さらに、メッセージには配列が含まれている可能性があるため、フォーマットが複雑になる可能性があるため、パーサは遅く複雑になります。

一般的で効果的なソリューションはありますか?私はthis postを読んで周りを回ったが、より良い方法を見つけることができなかった。

おそらくC++には解決策がありますか?

+0

を私が使用したとしタプル型を使用してメッセージを定義する場合は、タプルメンバを反復処理する関数テンプレートを記述し、使用しているタイプに応じて適切な抽出関数を呼び出すことができます。しかし、私は自動的にこれらのタプルから構造体に変換するアイデアを考え出すことはできません。 – sbi

+0

MSVC++を使用しているとすれば、 '#pragma pack(1)'は他のプラットフォームでも動作するはずです。パッキングは、オペレーティングシステムアライメントフィックスアップではなく、ビットシフトとマスクの観点から実装されています。 –

+0

あなたのデータは不揃いで、整列していません。ですから正しい方法は@larsmansによって提案された 'unpack'のようなバイト単位のアクセスだけです。 – 9dan

答えて

1

単純な答えはありません。メッセージが単純にキャストできない特定のバイナリ形式の場合、そのためのパーサーを書く以外に選択肢はありません。メッセージの説明(xmlや簡単に解析された説明の形式など)がある場合、その説明から解析コードを自動的に生成しないのはなぜですか?それは、キャストと同じくらい速くはありませんが、手で各メッセージを書くよりも早く生成する光景になります...

3

バイナリ入力を解析する私のソリューションは、Readerクラスを使用するため、何が読み込まれているかを定義し、リーダーはオーバーラン、アンダーランなどをチェックできます。あなたの場合

msg.num = Reader.getChar(); 
msg.x = Reader.getFloat(); 
msg.y = Reader.getFloat(); 
msg.z = Reader.getFloat(); 
msg.elevation = Reader.getChar(); 

は、それはまだ仕事とエラーが発生しやすいがたくさんあるが、少なくともそれはエラーのチェックに役立ちます。

+3

"リーダークラス" == 'std :: istream'または' std :: streambuf'。 –

+0

@Billy:そうです。私はしばらくの間、Readerクラスを使用していたので、私は決してより標準的なシステムのための使用を持っていませんでした。よく目撃された。 – stefaanv

+1

ええ、これは私が "すべてのメッセージに対して特殊な構文解析ルーチンを書く"というものです) – gaga

7

私がVC10とGCC 4.5.1(on ideone.com)でコンパイルしました。私は、このすべてのC++ 1xのニーズは、古いコンパイラでも利用できるようにする必要があります(std::tr1::tuple<tuple>だと思います。

メンバーごとにいくつかのコードを入力する必要がありますが、それは非常に最小限のコードです。 (最後に私の説明を参照してください。)

#include <iostream> 
#include <tuple> 

typedef unsigned char uint8_t; 
typedef unsigned char byte_t; 

struct MsgData { 
    uint8_t num; 
    float x; 
    uint8_t elevation; 

    static const std::size_t buffer_size = sizeof(uint8_t) 
             + sizeof(float) 
             + sizeof(uint8_t); 

    std::tuple<uint8_t&,float&,uint8_t&> get_tied_tuple() 
    {return std::tie(num, x, elevation);} 
    std::tuple<const uint8_t&,const float&,const uint8_t&> get_tied_tuple() const 
    {return std::tie(num, x, elevation);} 
}; 

// needed only for test output 
inline std::ostream& operator<<(std::ostream& os, const MsgData& msgData) 
{ 
    os << '[' << static_cast<int>(msgData.num) << ' ' 
     << msgData.x << ' ' << static_cast<int>(msgData.elevation) << ']'; 
    return os; 
} 

namespace detail { 

    // overload the following two for types that need special treatment 
    template<typename T> 
    const byte_t* read_value(const byte_t* bin, T& val) 
    { 
     val = *reinterpret_cast<const T*>(bin); 
     return bin + sizeof(T)/sizeof(byte_t); 
    } 
    template<typename T> 
    byte_t* write_value(byte_t* bin, const T& val) 
    { 
     *reinterpret_cast<T*>(bin) = val; 
     return bin + sizeof(T)/sizeof(byte_t); 
    } 

    template< typename MsgTuple, unsigned int Size = std::tuple_size<MsgTuple>::value > 
    struct msg_serializer; 

    template< typename MsgTuple > 
    struct msg_serializer<MsgTuple,0> { 
     static const byte_t* read(const byte_t* bin, MsgTuple&) {return bin;} 
     static byte_t* write(byte_t* bin, const MsgTuple&)  {return bin;} 
    }; 

    template< typename MsgTuple, unsigned int Size > 
    struct msg_serializer { 
     static const byte_t* read(const byte_t* bin, MsgTuple& msg) 
     { 
      return read_value(msg_serializer<MsgTuple,Size-1>::read(bin, msg) 
          , std::get<Size-1>(msg)); 
     } 
     static byte_t* write(byte_t* bin, const MsgTuple& msg) 
     { 
      return write_value(msg_serializer<MsgTuple,Size-1>::write(bin, msg) 
           , std::get<Size-1>(msg)); 
     } 
    }; 

    template< class MsgTuple > 
    inline const byte_t* do_read_msg(const byte_t* bin, MsgTuple msg) 
    { 
     return msg_serializer<MsgTuple>::read(bin, msg); 
    } 

    template< class MsgTuple > 
    inline byte_t* do_write_msg(byte_t* bin, const MsgTuple& msg) 
    { 
     return msg_serializer<MsgTuple>::write(bin, msg); 
    } 
} 

template< class Msg > 
inline const byte_t* read_msg(const byte_t* bin, Msg& msg) 
{ 
    return detail::do_read_msg(bin, msg.get_tied_tuple()); 
} 

template< class Msg > 
inline const byte_t* write_msg(byte_t* bin, const Msg& msg) 
{ 
    return detail::do_write_msg(bin, msg.get_tied_tuple()); 
} 

int main() 
{ 
    byte_t buffer[MsgData::buffer_size]; 

    std::cout << "buffer size is " << MsgData::buffer_size << '\n'; 

    MsgData msgData; 
    std::cout << "initializing data..."; 
    msgData.num = 42; 
    msgData.x = 1.7f; 
    msgData.elevation = 17; 
    std::cout << "data is now " << msgData << '\n'; 
    write_msg(buffer, msgData); 

    std::cout << "clearing data..."; 
    msgData = MsgData(); 
    std::cout << "data is now " << msgData << '\n'; 

    std::cout << "reading data..."; 
    read_msg(buffer, msgData); 
    std::cout << "data is now " << msgData << '\n'; 

    return 0; 
} 

私にとって、これは

 
buffer size is 6 
initializing data...data is now [0x2a 1.7 0x11] 
clearing data...data is now [0x0 0 0x0] 
reading data...data is now [0x2a 1.7 0x11] 

を印刷します(私は3つのデータメンバを含むようにあなたの​​種類を短縮してきましたが、これは単なるテストのためでした。各メッセージタイプの)

は、そのbuffer_size静的定数二get_tied_tuple()メンバ関数一つconstconst 1つの非同じ方法で実装さの両方を定義する必要があります。 (もちろん、これらは非会員でも構いませんが、関連しているデータメンバーのリストに近づけようとしました)
一部のタイプ(たとえばstd::string)では、次のような特殊なオーバーロードを追加する必要があります。それらはdetail::read_value()detail::write_value()の機能です。
残りの機械は、すべてのメッセージタイプで同じままです。

完全なC++ 1xサポートでは、get_tied_tuple()メンバー関数の明示的な戻り値型を完全に型取りする必要はありませんが、実際には試していません。

+0

うわー。それはクールです、私はそれを考える時間が必要です) – gaga

+0

タプルを使用するための素晴らしい例...かなり良い構文になります。 C++ 11 rocks.Allあなたはideone.comで完全なソースを提供する方が良い! – oliver

1

純粋なC++(プラグマを使用しないで)のすべてのメッセージにspecicfic解析ルーチンを書くことを避けることはできません。

すべてのメッセージがシンプルなPODのような構造であれば、最も簡単な解決策はコードジェネレータを書くことです:他のC++のものを使わずにヘッダーに構造体を入れて、単純なパーサー(perl/Python/bashスクリプトを使用して、正規表現を使用するだけで十分です)、または1つを探してください。これは、どのメッセージでも変数名を見つけることができます。

MsgData msg; 
your_stream >> msg; 
0

YourStreamType & operator>>(YourStreamType &stream, MsgData &msg) { 
    stream >> msg.num >> msg.x >> msg.y >> msg.z >> msg.elevation; 
    return stream; 
} 

あなたのメッセージが含まれているとあなたが行われる必要があります任意の基本的なタイプのためにYourStreamTypeoperator>>を特殊:このように、自動的にそれを読むために任意のメッセージのためのいくつかのコードを生成するためにそれを使用あなたはいつもあなたのメモリを自分で揃えることができます。

uint8_t msg[TOTAL_SIZE_OF_THE_PARTS_OF_MsgData]; 

sizeof(MsgData)として返しますMsgData +パディングバイトのサイズを、あなたにそのような定数のために列挙型を使用して

enum { TOTAL_SIZE_OF_THE_PARTS_OF_MsgData = 
    2*sizeof(uint8_t)+ 
    3*sizeof(float)+sizeof(THE_OTHER_FIELDS) 
} 

を計算することができ、いくつかのマシンでも実績のある概念です。

msgアレイにバイナリメッセージを読み込みます。後で、MsgData値に値をキャストすることができます

unsigned ofs = 0; 
MsgData M; 
M.num = (uint8_t)(&msg[ofs]); 
ofs += sizeof(M.num); 
M.x = (float)(&msg[ofs]); 
ofs += sizeof(M.x); 

など...

か、タイプキャストを好きではない場合のmemcpyを使用します。

memcpy(&M.x,&msg[ofs],sizeof(M.x)); ...