アプリケーションのデータ構造(データ型定義の集合)を、 SERでは「スキーマ」と呼びます。 アプリケーションプログラマは、 まず「スキーマファイル」を記述します。 このファイルは、Cのヘッダファイルに似ていますが、 unionが実はどれかをenumで限定する等の修正が加えられています (見た目は全然Cには似てないような気もします。 Cの(腐った)宣言の構文を真似すると、パーサを書くのが面倒だったので)。
「スキーマファイル」を「スキーマコンパイラ」にかけると、 「AP向けヘッダファイル」と「内部情報ヘッダファイル」が生成されます。
スキーマ名(スキーマファイルの冒頭で指定)が"hoge"の場合、 「AP向けヘッダファイル」のファイル名はhoge.hに、 「内部情報ヘッダファイル」のファイル名はhoge_i.hになります。
% schemac hoge.schema % ls hoge.schema hoge.h hoge_i.h % |
「AP向けヘッダファイル」には、アプリケーション向けの型定義が 記述されており、アプリケーションプログラマは これを元にアプリケーションをコーディングします。
「内部情報ヘッダファイル」には、 SER向けの内輪の情報が記述されています。 アプリケーションプログラムは、ソースファイルのどこか一箇所で、 内部情報ヘッダファイルを#includeする必要があります。 内部情報ヘッダファイルの内容については、 アプリケーションプログラマは関知する必要はありません。
「ストレージ」とは、アプリケーションが生成する オブジェクトの貯蔵庫です。SERでは、シリアライズ・デシリアライズ・ ガベージコレクト等の操作は、ストレージを単位に行ないます。
ストレージは、ひとつのスキーマに対応します(n:1対応)。 アプリケーションは、スキーマ情報へのポインタ(具体的な内容は 内部情報ヘッダファイル中に定義されている)を使用して、 ストレージを生成します。
/* スキーマ名が"hoge"の場合のストレージの生成 */ /* AP向けヘッダファイル内でSER_hoge_schemaというグローバル変数が extern宣言されている。 */ SER_Storage storage; storage = SER_create_storage(SER_hoge_schema); |
アプリケーションプログラムは、malloc()の代わりに、 ストレージとデータ型を指定して領域を確保します。
static const SER_Type type[] = {SER_TYPE_Hoge}; p = SER_malloc(storage, type); |
ここで、"SER_TYPE_Hoge" は、スキーマファイル中で "Hoge"という名前で定義したデータ型を示す記号定数であり、 「AP向けヘッダファイル」で #define されています。
ここで、typeにstatic const指定が付いていますが、 これは重要です。 シリアライズやガベージコレクトを行なうためには、 確保したオブジェクトについてそれぞれ型情報が必要ですが、 SERは、効率向上のため、わざわざ別に領域を確保して 型情報を保持するようなことは行いません(デシリアライズの時は別)。 SER_mallocの際に与えられたポインタを保持するだけです。
よって、ここでstatic指定なしで、単なる自動変数として typeを宣言すると、関数を抜けた後、型情報が壊れてしまいます (constは、まあ、どっちでもいいですが)。
SERにより確保した領域は、 そのデータ型の初期値で初期化されています。
配列を確保する場合は以下のようになります。
/* SER_TYPE_Hoge 10個分の配列を確保 */ static const SER_Type type[] = {SER_TYPE_ARRAY, 10, SER_TYPE_Hoge}; p = SER_malloc(storage, type); |
可変長配列を確保する場合は、以下のようにします。
static SER_Type type[] = {SER_TYPE_Hoge}; Hoge *p; /* size個の可変長配列を確保 */ p = SER_malloc_array(storage, type, size); |
SER_malloc_arrayで確保した可変長配列は、 その要素数をSER側で記憶しています。
size = SER_get_array_size(storage, p); /* 要素数の取得 */ |
リサイズも可能です。
p = SER_resize(storage, p, new_size); /* new_sizeは要素数 */ |
リサイズにより配列を縮めた場合、要素が後ろから単純に捨てられます。
逆に、伸ばした場合、後ろの要素は、 そのデータ型のデフォルト値で初期化されています。
アプリケーションプログラムは、 データ構造のルートとなるデータ(ルートオブジェクト)のポインタを、 ライブラリに教える必要があります。 また、ルートオブジェクトが必要な時には、ライブラリから取得します。 ルートオブジェクト(に限らず全てのオブジェクト)のアドレスは、 コンパクションにより変更される可能性があるので、 アプリケーションが継続的に保持すべきではありません。
/* ルートオブジェクトの設定 */ SER_set_root_object(storage, root); /* ルートオブジェクトの取得 */ root = SER_get_root_object(storage); |
ルートオブジェクトから 必要な全てのオブジェクトを辿ることができる状態のとき、 アプリケーションプログラムは「ガベージコレクト」を 行うことができます。
SER_garbage_collect(storage); |
ガベージコレクトでは、以下の処理を行ないます。
コンパクションの際、オブジェクトのアドレスは変更される可能性があります。 SERの管理下にあるオブジェクト内にポインタが格納されている場合、 そのポインタは自動的に更新されますが、 アプリケーションが独自に(自動(ローカル)変数やグローバル変数で) オブジェクトへのポインタを保持していた場合、整合性が崩れてしまいます。
ルートオブジェクトから 必要な全てのオブジェクトを辿ることができる状態のとき(つまり ガベージコレクトと同じ条件)、 アプリケーションプログラムは「シリアライズ」を行なうことができます。
SER_serialize(storage, encode_type_name, fp); |
シリアライズにより、指定したストレージの全オブジェクトがシリアライズされ、 指定したストリーム(大抵はファイルポインタ)に出力されます。
第2引数(encode_type_name)は、 シリアライズの際のエンコード方式を指定しますが、 現時点では、デフォルトで使用できるencode_type_nameは、 "ascii_text"のみです。(^^;
シリアライズしたデータを復元(デシリアライズ)する時には、 以下のように記述します。
status = SER_deserialize(SER_new_hoge_schema, encode_ype_name, fp, &storage); |
この時、スキーマを指定していますが、このスキーマは必ずしも シリアライズした時のスキーマと完全に同一である必要はありません。 多少の違い(拡張)であれば、SERがその違いを吸収し、 新しいスキーマに合わせてデータ構造を復元します。