- - PR -
前回「Symbian OSのファイル操作を悩まずに使いこなす」では基本機能シリーズの最終回と宣言しつつ、ファイル回りの低水準のAPIについてアレコレ説明したところで紙幅が尽きてしまいました。
というわけで、今回はファイル関連の残り半分、ファイルを使って情報の永続化を行うためにSymbian OSが用意している「ストリーム(とストア)」というフレームワークについて説明を行います(注1)。
| 注1:「何でもかんでもフレームワークなのか!」といわれそうですが……。「ハイ」そのとおりです。無手勝流でコードを書き進めたり、車輪の再発明を恐れないことが許されたフロンティアの時代は、組み込みにおいてももはや遠い過去のことなのです。 |
前回解説されなかった“謎”のコード
では、前回の最後の部分からリプレイです。Carbide.c++が生成したスケルトンコードでは、RFileからの読み込み結果をHBufCのインスタンスに直接受けるのではなく、いったんRFileReadStreamというクラスのインスタンスを構築し、それ経由で値の設定を行っています(図1)。

図1 ECommand2ハンドラの未解説部分(前回の図5)
なぜ、RFileのメソッドである
IMPORT_C TInt Read(TDes8& aDes) const; ……(A) |
などを使ってファイルの読み書きを行わないのでしょうか? ここにポイントがあります。
(A)の引数を見てみると、aDesは「バイナリディスクリプタ」です(コラム参照)。考えてみれば当たり前ですが、入出力されるデータが何を意味しているのかはファイルを使う側だけが知っていることです。だから低レベルのファイル操作APIでは、どのようなデータでも受けられる(だけど、意味については関知しない)バイナリ形式でインターフェイスを取ることになります。
とはいえ、プログラム中で扱うデータの形式とファイルに読み書きするときのデータの形式にギャップがあるのは、ファイルを使うプログラムを書く方からすると面白くアリマセン。やりたいことは32bit型の整数や文字列(注2)などをファイルに適切に保存して、使いたいときに持ってくることなのであって、バイナリ形式に変換し、保存するときのアレコレをいちいち考慮したいわけではないからです。そこで、ギャップを埋めるものが必要になってきます。そのために用意されている仕掛けが、ストリームです。
| 注2:エンコーディングを意識するなぞ、もってのほかです。 |
| ▼コラム バイナリディスクリプタ | ||
連載第5回「貴重な情報源“ヘッダファイル”を読んでいますか?」で説明したとおり、Symbian OSでは文字が16ビットの幅である「UCS-2」である場合と8ビット幅である「CP1252」である場合、それぞれのためにTDesC16/TDes16、およびTDesC8/TDes8という型を用意し、条件コンパイルによって文字列を表す、TDesC/TDesに割り当てています。この結果、文字の幅をコード上から意識することなくプログラムが行えるようになっています。逆にいえば1文字に必要なビット幅をプログラム上で意識するのはご法度です。せっかく隠ぺいしているのに台無しですよね。ところが、(A)にある引数の型は隠されてしかるべき情報、すなわち“8bitの幅を持つ”と明言しているディスクリプタです。なぜこのような書き方をするかというと、ここで扱うものが文字列ではなく単なるバイト列(ということはバイナリデータですね)であるからです。 ディスクリプタはデータの長さと実体(書き換え可能な場合は、加えてバッファの長さ)を管理する仕掛けですから、文字列であろうがバイナリデータであろうが扱いに差はアリマセン(注3)。そのため、CP1252文字列を扱うためのディスクリプタは、そのままバイト列を扱うための仕掛けに転用できます。 TDesC8/TDes8(およびそれらの派生)を明示的に使い、文字列ではなくバイト列(バイナリデータ)を扱うディスクリプタをバイナリディスクリプタと呼びます。連載第5回の最後でその存在が予告されていたものがコレです。「文字列もバイト列も同じインターフェイスで使えて便利だなぁ」くらいに思っておいてください。ちなみに連載第5回で、
と書きましたが、ファイル操作はファイルサーバが提供している機能なので、(A)でTDes8&が引数として登場してくるのはまったくそのとおりなのです。
|
ストリーム
Javaなどがそうであるように、最近の開発環境(言語+フレームワーク)では、入出力の源泉となるクラス(以後、ソースと記します)と、入出力のインターフェイスを行うクラス(ストリーム)を分離する構成を取るのが一般的です。ストリームはその名前から明らかなように、データを順次読み込み、または書き出しする操作を抽象化するためのもので、“読み書きの方向”と“現在位置”という概念を持っています(任意の位置にシークできるかどうかはソース次第です)。また、読み込みストリーム、書き出しストリームは異なるクラスで提供されることがもっぱらです。
もちろん、Symbian OSでも図1にあるとおりストリームが提供されているわけですが、それは以下のメリットが期待できるからです。
A)データを読み書きする利用者側のコードからソースの実装を分離し、差異を隠ぺいできる
B)実行中のプログラムが扱う形式(内部形式)と保存するときの形式(外部形式)に変換を行うためのレイヤを設定できる
一般にストリームというとA)の性質(利点)が強調されがちですが、内部形式←→外部形式変換がサポートされることも同様に重要です。ということで、これらの性質にフォーカスして、Symbian OSのストリームの概観を説明していきます。ただし、Symbian OSにおいても、ストリームは読み込み、書き出しで異なるクラス定義を持っており、両方同時に追いかけていくと混乱を来す恐れもあるので、まずは読み込みストリームからはじめることにします。
RReadStream
では、図1で出てきたRFileReadStreamをDocBrowser(もしくは、s32file.h)で確認してみましょう。RReadStreamというクラスが基底クラスであることが分かります。これこそが読み込み側ストリームの大本です。先ほどのA)の性質を満たすよう、RReadStream(s32strm.h在中)は以下の派生クラスを持ちます(表1)。
# |
クラス名 |
ソース |
定義場所 |
1 |
RFileReadStream | ファイル | s32file.h |
2 |
RMemReadStream | メモリ(ポインタと長さ) | s32mem.h |
3 |
RDesReadStream | メモリ(TDesC8) | s32mem.h |
4 |
RBufReadStream | CBufBase派生の動的配列 | s32mem.h |
5 |
RStoreReadStream | ストア | s32std.h |
6 |
RDictionaryReadStream | ディクショナリストア | s32stor.h |
7 |
RDbColReadStream | TableまたはViewのRowset | d32dbms.h |
8 |
RSqlColumnReadStream | DBのカラム | sqldb.h |
このように多様なソースが読み込みストリームの派生クラスによって支援されています。ところで、図1ではHBufCを以下のように構築していました。
HBufC* fileData = HBufC::NewLC(inputFileStream, 32); |
しかし、HBufCのファクトリメソッドであるHBufC::NewLCのシグネチャは以下のとおりです。
static IMPORT_C HBufC16 *NewL(RReadStream &aStream, TInt aMaxLength); |
つまり、HBufC::NewLC自体は、RReadStreamの派生クラスを経由することで、ファイルに限らず表1にあるソースを用いてTDesCのインスタンスを構築できるわけです。ソースをファイルからDBに変えたとしても、ストリームを差し替えればほかのプログラムに手を入れる必要がないのですから、これは確かにアリガタイことです。これこそ、A)が提供しようとしていた利便性です。
対して、B)の性質は派生クラスを通じてではなく、RReadStreamのメソッド定義自体によって保証されます。RReadStreamの定義をのぞいてみましょう(図2)。

図2 RReadStreamのメソッド抜粋
引数が異なるReadL()メソッド(オーバーロードですね)と、Read<戻り値型名>L()の形式を持つメソッドが多数提供されています。個数はありますが、これらのメソッドは同じ定義の機能を提供します。
- 引数または戻り値で示される型の値が、ストリームの現在位置から読み出せると信じて読み込みを行う
この機能の意味するところは、「ストリーム中にどのような形式(外部形式)で保存されているかを隠ぺいしつつ、実行中のプログラムが扱う形式(内部形式)で取り出すことを保証する」です。読めると信じた型で値を受け取るメソッドを発行すればデータが取り出せるわけですから(注4)、これはまさにB)の性質です。
| 注4:型変換をやるとは書いてないことに注意してください。指定された型でストリームからデータを拾いにいくので、もし型が違っていればPANICの対象となります。つまり、書かれた順番どおりに読み込みを行う必要があり、この順序はInternalize・Externalize(後述)を通じて保証します。 |
なお、オーバーロードと、型名込みの命名規則(Read<戻り値型名>L()の形式)を使って、Readメソッドを大量に定義しているのは、データを読み出す型ごとにメソッドを分離して、タイプセーフにするためです。古典的なCのスタイルでAPIを定義すると、多分以下のようになると思います(図3)。

図3 Cスタイルのインターフェイス(もちろんAntiPattern)
上記のような場合、void* aStoreをTReadType aTypeの値に合わせてキャストして参照、書き込みすることになりますが、このスタイル、まったくもってタイプセーフではアリマセン。CからC++への引っ越しが完了した開発者であれば、図2のインターフェイス定義の方が、図3のスタイルよりベターであると自然に思えるはずです。
組み込み開発フォーラム 新着記事
- フルスクラッチの“Hello World”を動かしてみよう(2011/3/31)
- FlexRayプロトコルの概要(その2)(2011/3/29)
- JASA、東北地域に拠点を置く会員企業を支援(2011/3/25)
- NEC、震災の影響を受けた4拠点の生産再開を発表(2011/3/23)
- 内部ブロック図の基礎と共通要素(2011/3/22)
- インテル、被災地におけるITインフラの復旧を支援(2011/3/22)
- Facts on AUTOSAR/AUTOSAR導入の現実(2011/3/18)
- 計測器・震災被害ホットラインを開設、テクトロニクス(2011/3/18)
- ZMP、地震の揺れを多角的に計測するアプリ無償配布(2011/3/16)
- メンター、3Dテレビ・マルチメディア検証プラットフォーム(2011/3/16)
- 【番外編】タチの良い計測値、悪い計測値とは?(2011/3/15)
- tarファイルシステムをAndroidに組み込む!!(2011/3/10)














