hiClassライブラリはUNIX系シリアスプログラマのための実用C++クラス
ライブラリです。
用意されるクラスは文字列クラスやコンテナクラスからソケット通信機、
スレッドクラスまで幅広いものとなっています。
コマンドライン解析や16進ダンプといった機構もクラスとして用意されています。
hiClassライブラリはANSI標準ライブラリ・STLとの混在が可能です。
hiClassの文字列hiStringはANSI標準ライブラリのstd::stringの派生です。
現実性を考慮したガードや機能追加を行っています。
STLを否定するものではありませんが、根本的な考え方に違いがあります。
STLのコンテナがオブジェクト指向というより値指向と呼ぶべき形態をとり、
インスタンス管理を行わず値管理としているのに対し、
hiClassのコンテナはよりオブジェクト指向的でありインスタンス管理となっ
ています。
また、STLのコンテナ群は利用者のクラスに複製機能を要求しますが、
hiClassのコンテナ群は利用者クラスに複製機能を要求しません。
OSの機構に対する姿勢も全く異なります。
ANSI標準ライブラリ・STLが極端なまでにOS非依存性を重視しているのに対し、 hiClassは通信やスレッドといったOS依存が高いけれど現実のプログラミング上極めて重要な機構をクラス化し、
「実用性、現実性」を追求しています。
次のクラス、機能セットを持ちます
| クラス/機能セット | 説明 |
|---|---|
| hiArgs | コマンド起動引数解析 |
| hiChain<T> | 不定個インスタンス並び |
| hiConnector | TCPソケットの接続口(サーバ側) |
| hiDebug | デバグトレース出力機能セット |
| hiDict<K,T> | 辞書 |
| hiIDict<T> | intをキーとする辞書 |
| hiSDict<T> | 文字列をキーとする辞書 |
| hiFile | ファイル (バイトストリーム通信機) |
| hiTextFile | テキストファイル |
| hiForm | 数値 <--> 文字列変換器 |
| hiFormX | 16進文字列変換 |
| hiInfoBox | テキストファイルによる簡易辞書 |
| hiMemStream | メモリ媒体 (バイトストリーム通信機) |
| hiPtr<T> | 自動消去式ポインタ |
| hiRange | 範囲(先頭位置、長さ) |
| hiPRange | 範囲(先頭位置、最後+1の位置) |
| hiRef<T> | 参照カウンタ付き自動消去式ポインタ |
| hiRegex | 正規表現 |
| hiSelector | i/oイベントselect機 |
| hiSSL,hiSSLSock,hiSSLConnector | SSL通信機 |
| hiStream | バイトストリーム仮想通信機ベース |
| hiString | 文字列 |
| hiThread<M> | メッセージ駆動式スレッド |
| hiThread_base | 単純スレッドベース |
| hiLock | 自動初期化されるpthread_mutex_t変数 |
| hiAutoLock | 自動開錠式ロック機構 |
| hiSync/hiSyncSymmetry | スレッド同期機構 |
| hiSyncQueue | 同期式のQUEUE |
| hiTimespec | 時刻(ナノ秒まで表現) |
| hiTime | 時間(ナノ秒まで表現) |
| hiTCPSock | TCPソケット (バイトストリーム通信機) |
| hiUDPSock | UDPソケット |
| hiXML | 単純XMLデータ構造 |
| hiXY | x,yの2個のlong値を持つ構造体 |
| hi_env | 環境インスタンス |
| u | 基本enumセット |
| 0初期化クラス群 | 0初期化されるクラス |
hiClassライブラリで用意されるクラスの一部を、プログラム断片を
ならべて説明します。
文字列を表すクラスhiStringが用意されています。
文字列リテラルによる初期化/代入ができます。
hiString s1 = "abc"; // "abc"という値を持つ文字列 hiString s2; // 空文字列 hiString s3 = NULL; // 空文字列 hiString s4 = s1; // 複製 // s1 = "XYZ"; // 代入,内容が"XYZ"に置き換わる s1 = NULL; // 空文字に置き換わる文字列としての比較ができます。
if( s1=="abc" ) {...} // s1の内容が"abc"と等しいなら ...
if( s1< "xyz" ) {...} // s1の文字コード列が"xyz"文字コード列より小さいなら
if( s1==s2 ) {...} // s1がs2と等しいなら ...
if( s1< s2 ) {...} // s1の文字コード列がs2の文字コード列より小さいなら
bool変換、const char*変換が出来ます。
hiString s;
//...
if( s ) { // sが文字列を持つ(空文字列でない)なら...
ifstream ifs(s); // sをconst char*変換(ifstreamの引数はconst char*)
//...
}
is_in(),contains()関数で部分文字列の検索ができます。
if( s.is_in("aabcdefgg") ) {...} // sが"aabcdefgg"の中にあれば ...
if( s.contains("def" ) ) {...} // sが"def"を含んでいれば...
部分文字列検索時は検索結果の範囲が得られます。
hiRange range;
if( range=s.contains(hi_pos(2),"XX") ){ // sが0オリジン2の位置以降に
// "XX"を含んでいれば
s.replace(range,"abc"); // その部分を"abc"に置き換える
}
一括分割ができます。
hiString s1="ab:cd:ef";
hiChain<hiString> CS = divide(s1,':'); // s1を':'で分解する
// "ab" "cd" "ef"が得られる
hiString s2=concat(CS,':'); // ':'を挟んで結合する
// "ab:cd:ed"が得られる
hiString s3="ab-cd xy 'A B'";
hiChain<hiString> tkns = tokens(s3); // トークン分割する
// "ab-cd" "xy" "A B"が得られる
正規表現hiRegexとのパターン比較/部分文字列検索ができます。
hiRegex re("aB*c");
hiString s ="aBBBBc";
if( re==s ) {...} // パターンがマッチすれば...
if( s ==re ) {...} // パターンがマッチすれば...
部分比較はhiRegex側からはis_in、hiString側からはcontainsメソッドで行い
ます。
hiRegex re= "[A-Z]+";
hiString s ="abCDefGhIJklm";
hiString s2=s;
hiRange pos;
while( pos= re.is_in(s,nextof(pos) ){ // reがsの中にあるか
pos= s.replace(pos,"#"); // 見つけた範囲を"#"に置き換える
}
pos=0;
while( pos= s2.contains(nextof(pos),re) ){ // s2がreを含むか
pos= s2.replace(pos,"#"); // 見つけた範囲を"#"に置き換える
}
// この例では、発見した範囲を、新しい文字列"#"で置き換え(replace)
// その範囲の後ろ(nextof(pos))をさらに探索している。
// どちらも同じab#ef#h#klmとなる
整数引数が「位置」を意味するか「長さ」を意味するかを明確にするための
explicit typedefとしてhi_len,hi_posを用意してあります。
*** コード
hiString s("abcdef");
cerr<<s.substr(hi_pos(2),hi_len(3))<<endl;// 位置2から長さ3
cerr<<s.substr(hi_pos(2),hi_pos(3))<<endl;// 位置2から位置3
*** 出力
cde
cd
範囲はhiRangeクラスで表します。先頭位置オフセットと長さの情報を持ちま
す。領域の後ろ(右)の位置オフセットをhi_pos型で取り出すnextof(const
hiRange&)関数なども用意されています。もちろんexplicit typedefという機能はC++にはありません。enum定義あるい はsturctを用いています。
hiClassライブラリにはテンプレートを用いたコンテナクラスを幾つか用意し てあります。
全てインスタンス管理機能をもち、自分自身が消去されるとき、内容インスタンスも消去します(hiRefは参照カウントが0になる時内容消去)。
hiRef以外は自分自身の複製機能を持ちません。
不定個のインスタンスの並びを管理します。

要素の登録には多くの場面で <<オペレータ(右側:後ろから追加) が用 いられます。
hiChain<hiString> CS;
CS << new hiString("abc");
//...
CS << new hiString("zzz");
先頭(左)への追加や指定位置への追加機能も用意されています。
foreach(CS){
cerr<<"data="<< CS()<<endl;
}
hiChain<T>::c_iter it(CS);
foreach(it){
cerr<<"data="<<*it<<endl;
}
for((X).standby();(X).next();)
と展開されるマクロに過ぎません。
利用者はforeachを使わずstandby(),next()あるいはtop(),last(),prev()といったメソッドで位置を変更しながらアクセスすることが出来ます。
反復子には++.--,==,*,->といったSTLにそろえたインターフェイスも用意してありますので、STLのアルゴリズム群を適用させることもできます。
hiChain<T>を関数戻りとしたい場合は移動子hiChain<T>::moverを使います。
hiDict<K,T>は型Kの値をキーとするハッシュ辞書です。Kは幾つかの事項を 満足すれはどんな型でも使うことができます。

hiClassでは使用頻度が高いであろう、文字列をキーとする辞書(hiDict
オペレータの組み合わせによるアクセス:
残念ながら、オペレータによるアクセスに比べ、視認性に劣りますが、関数アクセスの方が慣れているという方はこちらを使っても問題ありません。
hiPtr<T>は自動消去式のポインタです。セットされたインスタンスはhiPtr<T>解体時に消去されます。 殆どポインタと同様の操作が出来ます。
hiPtr<T>の特徴は hiPtr<T>からhiPtr<T>への移動にはmove_to(),move_from()関数の他、移動子クラスhiPtr<T>::moverを用意してあります。
hiRef<T>は参照カウンタを持つ自動消去ポインタです。
複数のhiRef<T>から1個のTインスタンスを参照します。
複製構築子や=オペレータによる複製時に参照カウンタがカウントアップされ、
hiRef≤T>の解体時に参照カウンタがカウントダウンされます。
参照カウンタが0となると、セットされたインスタンスは消去されます。
通信はhiClassの基本です。通信媒体を仮想化した機構の上に
TCP、メモリ、ファイルなどを用いて情報のやり取りが出来る
ようになっています。またUDP通信機もクラス化されています。
TCPソケット通信機が用意されています。
TCPコネクションの張り方は
という形をとります。 ソケットやファイル、あるいはメモリ等媒体によらず同じ形でバイナリデー
タの送受信(読み書き)が行えます。
基本的な通信機構として仮想バイトストリーム通信機構(hiStream)を用意してあります。 を使います。 が用意されています。_read(),_write()関数を用意できるなら、利用者がhiStream通信機を作成することも可能です。 の2つの外部関数です。operator<<,operator>>はこれら2つの関数を呼び
出します。利用者のクラスであっても、この関数を用意すればhiStream通信機
を使った通信が出来ます。 hiStreamではありませんがUDP通信機構hiUDPSockもあります。 i/oイベントの多重待ちを行うクラスhiSelectorが用意されています。
ファイルディスクリプタio機構クラス(hiFdElemntクラスの派生) のイベントはいずれも多重にhiSelectorで待つことが出来ます。 マルチスレッドは極めて強力な仕組みですが、バグを生みやすいもので
もあります。 形で使えるスレッドクラスを用意しています。
を用意しています。 単純なスレッド関数の作成法を2つ用意してあります。 例えば、hiThread_baseを用いる手法では
といった形となります。u::joinは「スレッド完了を後でjoinで待つ」という
指定です。特に待つ必要がない場合はu::detachを指定してください。
といった形となります。情報をdo_関数()の引数で引き渡すことが出来ます。 排他ロック変数hiLockを用意しています。
これはpthread_mutex_tに初期化機能を付けただけの簡単なものです。
が、しかし、pthread_mutex_tの初期化忘れによるデッドロックは起こし
やすいバグです。
hiLockを用いれば避けることが出来ます。
lock() , unlock()メソッドをもちます。 スレッド間同期機構hiSync,hiSyncV<M>,hiSyncSymmetry,hiSyncStock<M>
を用意してあります。
hiSyncは片側がwait()で待っているのをwakeup()で起こします。
wakeup側は常に即復帰します。wakeup()が先に呼ばれていた場合、
wait()は即復帰します。 hiThread<M>はスレッド間でメッセージの送付を行う機構です。
秒以下で時刻を管理するhiTimespecと秒以下で時間を管理するhiTimeを
用意してあります。
印字機能を持ちます。
hiTimespec指定(時刻指定)のsleep関数が用意されています。時間演算と
組み合わせて、実働部時間による差が蓄積しないsleepを実現できます。
起動引数を解析する、環境情報を得る、情報設定ファイルを取り扱うとい
う作業が実用プログラムでは必要になることが多いものです。 例えば
環境変数を得たり、ディレクトリ変更を行ったり、
hiClassのエラーメッセージの取り扱いを変えたりといった動作環境操作
がhi_envインスタンスを使って出来ます。
テキストによる簡易の辞書形式を用意してあります。
記述形式は キー=内容 です。
デバグ用にトレース文を仕込む方法は原始的ではありますが、単純で分かりやすく確実な手法です。 基本的には 関数の最初で _TRC_ マクロを使い、途中出力したい場所で_DSP_マクロを使って下さい。 データを16進ダンプしたいことは良くあります。 本格的なGUIを構築するものではありませんが、自照式のon/offボタン
や時系列グラフ、ランプなどを用意し、状態モニタや操作パネルの作成ができます もちろん普通のボタンもあります。単純なテキスト入力や、
メニューなどの機能も用意されています。 現時点ではあくまで補助的なもので、インターフェイス変更の可能性
もありますが、デモやデバグなどに役に立つものだとは考えています。
hiXの仕様はマニュアルの付録に載せてあります。
hiSDict<myClass> dic;
dic["name-1"] = new myClass(); // キー指定で登録
if( dic["name-3"] ){ dic().doSomething(); } // キー検索/アクセス
アクセスには2つのモデルが用意されています。
です。
次のようなアクセスが可能です。
hiSDict<hiString> DS;
DS["name-1"] = new hiString("abc"); // キー指定で登録
DS["name-2"] = new hiString("XYZ");
//
if( DS["name-3"] ){ // キー検索
cout<<DS(); // 検索結果にアクセス
}
関数とポインタによるアクセスと混在させても構いません。
関数とポインタによるアクセス:
次のようなアクセスが可能です。
hiSDict<hiString> DS;
DS.set("name-1",new hiString("abc")); // キー指定で登録
DS.set("name-2",new hiString("XYZ"));
//
hiString* sP;
if( sP=DS.search("name-3") ){ // キー検索
cout<<*sP; // 検索結果にアクセス
}

void foo(){
hiPtr<hiString> strP=new hiString("abc"); // ポインタを直接セット
//--- アクセス
if( strP ){ // nullで無ければ...
strP->append("XYZ"); // ->でアクセス
cerr << *strP << endl; // *でアクセス
}
//--- 自動消去
if( なにかあれば ) return; // 自動消去
boo(); // もしboo()でexceptionがあっても自動消去
} // 自動消去
* 複製を禁止
してあることです。これによりhiPtr<T>でのインスタンス参照は必ず1箇所のみで行われ混乱を避けています。
hiPtr<hiString> strP=new hiString("abc");
hiPtr<hiString> strP2(strP);// !NG 禁止です。コンパイルエラーとなります
殆どポインタと同様の操作が出来ます。
hiPtr
* 複製可能
です。
hiRef<T>の複製は、セットされたインスタンスはそのままで、リファレンスカウントが+1されます。
hiRef<hiString> strR=new hiString("abc"); // 参照数1
hiRef<hiString> strR2(strR); // 参照数2
hiRef<hiString> strR3;
strR3 = strR; // 参照数3
//.. strR,strR2,strR3全ての解体子が動いた時点でhiString("abc")は消去され
// ます。

次の例はクライアント(tcpHelloC)からサーバ(tcpHelloS)に接続し、サーバ
からクライアントのに文字列が送られ、クライアントで印字後、接続を断ちま
す。

hiConnector co(hi_port(50013)); // co("50013")も可
hiTCPSock so(co);
hiTCPSock so("hostX:50013"); // so("hostX",hi_port(50013))も可
データの送受信は「仮想バイトストリーム通信機構」に則って行われます。
hiSDict<hiString> DS,DS2;
// ファイル"original.dat"にデータが予め入ってるとして...
hiFile f_in("original.dat",hiFile::r); // ファイル(入力用)
f_in >> DS; // ファイルから読込み
hiTCPSock so("hostX:50304"); // TCPソケット
so << DS; // TCPソケット送信
so >> DS2; // TCPソケット受信
hiFile f_out("processed.dat",hiFile::w); // ファイル(出力用)
f_out << DS2; // ファイルへ書き込み
送受信には
通信されるデータクラスにhiStreamに対する送受信機能が用意されていれば、実hiStream通信機のいずれとも送受信が可能です。例えば、先の例ではhiSDict
実hiStream通信機としては
基本のデータ型、intやlong,doubleそれにhiStringなどにはhiStream通信機能が与えられています。またコンテナにもhiStream通信機能が備わっています。
hiStream通信のためにデータ型に要求されるのは
void put(hiStream&,const データ型&)
void get(hiStream&,データ型&)
hiStream機構の支援を受けないため、電文作成に手間がかかりますが、TCPと異なりデータ滞留によるプログラム停止などが発生しませんので、リアルタイム性の要求されるもの、状態モニタ、ヘルスチェック、プロセス同期などに有用です。
hiConnector~hiFilePairのi/o機構クラスにはいずれもそれぞれのイベント
専用のユーザベースクラスがあります。その派生クラスを利用者クラスとし
て作りイベント受け取りメソッドをオーバーロードし、インスタンスを各io
機構クラスのインスタンスに登録しておきます。
hiSelectorに登録されたio機構クラスインスタンスにイベントが発生すると、
hiSelectorからそのio機構クラスが呼ばれ、そこから利用者クラスの関数が
呼び出されます。
class myClass:public hiConnector_user,
public hiTCPSock_user,
public hiUDPSock_user{
public:
bool event(hiTCPSock&p,hiSelector&se){ // hiTCPSockのイベントで呼ばれる
// ...
return true;
}
bool event(hiConnector&co,hiSelector&se){// hiConnectorのイベントで呼出
sos<<new hiTCPSock(co); // <1> hiTCPSock生成
sos().set_user(*this); // <2> hiTCPSockにユーザ登録
se.set(sos()); // <3> hiTCPSockをhiSelectorに登録
return true;
}
bool data_come(const unsigned char* data,// hiUDPSockのイベントで呼ばれる
long len,
hiSockAddr& from,
hiUDPSock& sock,
hiSelector& se){
// ...
return true;
}
hiChain
hiClassでは
その他次のクラス
起動はstart()関数で行う。
class myThread:public hiThread_base{
public:
myThread(int _n):n(_n){} // 情報は構築子で引き渡す
void thread_func(){ // スレッド起動される関数
while(--n>0) {...} // 何か重い処理等
}
int n;
};
****
int main(){
myThread th(10000);
th.start(u::join);
th.join();
}
hiThreadFUNCnマクロを使う手法は
class myThread:public hiUser{
public:
hiThreadFUNC1(myThread,tfunc,int); // 引数1個のスレッド関数の定義
void tfunc(int n){ // スレッド起動される関数
while(--n>0){...} // 何か重い処理等
}
};
***
int main(){
myThread th;
pthread_t tid= th.do_tfunc(10000,u::join);
join(tid);
}
自動開錠機hiAutoLockを用意しています。
これは構築子で施錠(lock)し解体子で開錠(unlock)するもので、
unlock忘れによるデッドロックを避けることが出来ます。
通常はlock()/unlock()を使うよりこのhiAutoLockを使う方が安全です。
hiLock lock_val;
void boo(){
hiAutoLock boo_lock(lock_val);
//
if( なにかの事情 ) return; // でも大丈夫。確実にunlockされる
//
foo(); // 仮にexceptionがthrowされてもunlockされる
//
}; // もちろん関数の終わりではunlockされる

hiSyncV
hiSyncSymmetryはsync()関数で同期をとります。先に呼んだ方が待たされます。
hiSyncStock
メッセージ送付側のスレッドからput_message()メソッドでメッセージを送ると、
受け取り側のスレッドのmessage_come()メソッドが呼び出されます。
メッセージはqueue管理されており、大量にput_message()が発行された場合
message_come()が終了ししだい再びmessage_come()が呼び出されます。
put_message()のたびにスレッドが起動されるのではありません。
例えば、ファイルi/oと通信を別スレッドとし、数個のバッファを相互に
送付する形で効率のよいファイル転送システムを構築することなどができます。
上の図は,
様子を表した概念図です。
という形で、ファイルからの読み込みと送信を並行して行う
という形で、受信とファイルへの書き込みを並行して行う
hiTimerspecには構築子およびset関数で次のようにして値が設定できます。
hiTimespec ts(u::current); // 現在時刻
hiTimespec ts2(500,u::msec_after); // 現在より500ミリ秒後
hiTimespec ts3(u::local,2002,u::year,6,u::month,6,u::day); // local時刻
// 2002年6月6日
ts.set(2.1,u::sec_after); // 現在時刻より2.1秒後
hiTimeには構築子およびset関数で次のようにして値が設定できます。
hiTime t(100,u::usec); // 100マイクロ秒
t.set(0.5,u::sec); // 0,5秒
hiTimespec,hiTimeには時刻/時間の演算が用意されています。
hiTimespec start(u::current);
foo(); // なにか時間のかかる処理
hiTimespec end(u::current);
cout<<"foo start:"<<start<<", end:"<<end<<endl;
cout<<"foo took:"<<(end-start)<<" sec."<<endl;
*****
foo start 2002-06-21/20:01:48.161+09:00, end:2002-06-21/20:01:48.872+09:00
foo took:0.711739 sec.
秒以下のsleep関数(外部関数)を用意してあります。
sleep(0.5,u::sec); // 0.5秒sleepする

ただ、これらは単純とは言え煩雑で、プログラムの本質でないにも関わらず
かなりの工数がかかり、かつバグを生みやすいものです。
hiClassライブラリでは
を用意しました。
% com -A 数値 [-B 名前] {-X 名前|-Y 名前}
# -A引数は必須
# -B引数はオプショナル
# -X,-Y引数はどちらか片方のみを必ず指定する必要がある
などという起動引数の解析は次のように行うことができます。
int main(int argc,char*argv[]){
int A_val;
hiString B_val,X_val,Y_val;
hiArgs args;
args.arg_int("-A",A_val,hiArgs::mandatory); // -Aはintで必須
args.arg_str("-B",B_val); // -Bは文字列
args.arg_str("-X",X_val"); // -Xは文字列
args.arg_str("-Y",Y_val); // -Yは文字列
args.arg_group(hiArgs::mandatory|hiArgs::only_one,
"-X","-Y"); // -Xと-Yはどちらか片方だけ
// が必ず指定される
if( !args.check(argc,argv) ) help(); // 引数を解析して、変数にセット
//...
}
引数の数だけ応用プログラムでループするといった作業は不要です。
if( hi_env["REPORT_EOF"]=="yes" ){ // 環境変数REPORT_EOFがyesなら
hi_env.err_msg_on(u::eof); // eof発生もメッセージ出力する
}
// config.ib
output = "./test.out"
X_do = yes
//Y_do = yes
msg1 = <<<
?1は?2では使用できません。
>>>
******
hiInfoBox ib("config.ib");
hiString ofile=ib["output"};
ofstream ofs(ofile);
if( !ib["X_do"] ) cerr<<ib.expand("msg1","X","this configuration");
if( !ib["Y_do"] ) cerr<<ib.expand("msg1","Y","this configuration");
//...
hiDebugで用意するマクロセットによるトレースでは
という特徴を持ちます。
% dbgSmpl dbgSmpl.C strTst.C
dbgSmpl-8023:-->dbgSmpl:main
dbgSmpl-8023:| -->in loop
dbgSmpl-8023:| | -->foo dbgSmpl.C
dbgSmpl-8023:| | | open dbgSmpl.C OK
dbgSmpl-8023:| | | copying
dbgSmpl-8023:| | | done
dbgSmpl-8023:| | <--foo
dbgSmpl-8023:| <--in loop
dbgSmpl-8023:| -->in loop
dbgSmpl-8023:| | -->foo strTst.C
dbgSmpl-8023:| | | open strTst.C OK
dbgSmpl-8023:| | | copying
dbgSmpl-8023:| | | done
dbgSmpl-8023:| | <--foo
dbgSmpl-8023:| <--in loop
dbgSmpl-8023:<--dbgSmpl:main
hiFormXは単純16進表示と、アドレス付き表示の機能を持ちます。
char buf[64];
// ...
cerr<<"buf="<<hiFormX(buf,16)<<endl; // 単純16進表示
cerr<<hiFormX(buf,64,0)<<endl; // アドレス付き表示(ここでは0から)
****
buf=b88e0461626364656667004008532940
0000: b8 8e 04 61 62 63 64 65 66 67 00 40 08 53 29 40 : @@`abcde fg`@`S)@
0010: 01 52 29 40 41 92 0f 40 d0 bc 04 08 58 f9 ff bf : `R)@A@`@ @@``X@@@
0020: 58 58 58 2d 59 59 59 2d 5a 5a 5a 08 68 f9 ff bf : XXX-YYY- ZZZ`h@@@
0030: 18 62 1a 40 34 42 29 40 80 af 00 40 78 f9 ff bf : `b`@4B)@ @@`@x@@@
