2006年02月20日

ソケットの作成と接続

ソケットの作成と接続

 サーバ名からIPアドレスを解決した後は、そのIPアドレスに対してソケットの接続を確立します。

// ソケットの作成
TInetAddr addr ;
RSocket socket ;
addr.SetPort(80);
TInt32 addNum = TInetAddr::Cast(nameRecord.iAddr).Address() ;
addr.SetAddress( addNum );

User::LeaveIfError(socket.Open(socketServ, KAfInet, KSockStream,
KProtocolInetTcp));
socket.Connect( addr, status );
User::WaitForRequest( status );

if( status != KErrNone ){
User::Leave(KErrCouldNotConnect);
}

 TInetAddrクラスは、アドレスとポート番号をカプセル化したクラスです。このクラスのインスタンスに対して、DNSで獲得したIPアドレスと、今回用いるポート番号(80)を指定します。

 その後、TInetAddrで表されるIPアドレスとポート番号に対してRSocket::Open、RSocket::Connect関数でソケットの確立を行います。Connect関数も非同期になっておりますので、呼び出しの直後にUser::WaitForRequest関数を呼び出し、この非同期関数の実行終了を待ちます。

 Open関数での第一引数はソケットサーバのインスタンスです。第二引数以降はおそらく説明は必要ないかと思います。

HTTPのHEADメソッドの発行

 今回はHTTP/1.0のHEADメソッドを発行し、その結果を受けます。HTTPはテキストベースのプロトコルですから、単純に文字列をサーバに渡すだけです。

_LIT8(KHead, "HEAD / HTTP/1.0\n\n");
socket.Write(KHead, status);
User::WaitForRequest( status );

if( status != KErrNone ){
User::Leave(10);
}
TBuf8<256> result ;
socket.Read( result, status );
User::WaitForRequest( status );
if( status != KErrNone ){
User::Leave(KErrCouldNotConnect);
}
TBuf<256> result16 ;
result16.Copy( result) ;

 ここで注意が必要な点として、ディスクリプタの幅の問題があります。Series60ではUnicodeを用いていますが、HTTPの通信はAscii コードなどになります。したがって、通信を受けた部分では8bitのディスクリプタで受け、それを表示する段階で16bitのディスクリプタにコピーをしたものを利用しています。

最後にサンプルについて

 今回のサンプルは「コードをひとつの関数にまとめる」意味から、アクティブオブジェクトを使用しておりません。本来であれば、非同期の処理としてアクティブオブジェクトを利用する場所でも、処理の終了を同期的に待っております(User::WaitForRequest関数を使用している箇所です)。
posted by シンビアン at 11:18| Comment(0) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

今回のサンプル

今回のサンプル「DumbBrowser」は、HTTPサーバのルートコンテキスト(「http://サーバー名/」の形式のアドレス)にHTTP/1.0のHEADメソッドを用いて通信を試みます。そのためには、以下の流れになります。

1. DNSを用いてIPアドレスを解決
2. 1で取得したIPアドレスを持つサーバに対して書き込み(“HEAD / HTTP/1.0\n\n”)
3. 2に対するサーバからのレスポンスを取得

 今回のサンプルは、こちらからダウンロード可能です(DumbBrowser.zip)。

サンプルの実行

 サンプルをダウンロードして、これまでと同様にビルドしますと、エミュレータのメニューに次のようなアイコンが追加されます(画面2)。

■ 画面2

 このアプリケーションを実行し、[オプション]→[サーバにHTTP/HEADで..]とメニューを選択しますと、次のようなダイアログが現れます(画面3)。

■ 画面3

 ここにサーバ名を入れます(プロトコル名「http://」は入れないで下さい)。例えば、「www.hicorp.co.jp」と入力すると、次のような実行結果になります。なお、実行には多少の時間がかかることがあります(画面4)。

■ 画面4

RSocketServについて

 コードの説明に入る前に、RSocketServクラスについて解説をしておきたいと思います。このクラスはプラットフォームがもつネットワーク機能へアクセスする際のハンドルになります。

 プラットフォーム側では、ネットワーク機能はSocket Serverで実現されており、このSocket Serverに対して依頼を発行するときの窓口になります。したがって以下のサンプルでも、このクラスのインスタンスは、ネットワークの接続が必要な箇所で引数で渡される使われ方をします。

DNSを用いた名前解決

 DNSを用いて名前解決を行うコードは次の通りです。

// void CDumbBrowserContainer::LoadHTMLData関数より抜粋
RSocketServ socketServ ;
RHostResolver hostResolver ;

this->iHostNameLabel->SetTextL( url );

User::LeaveIfError( socketServ.Connect() );
User::LeaveIfError( hostResolver.Open(socketServ,
KAfInet, KProtocolInetUdp) );

TNameEntry nameEntry ;
TRequestStatus status ;

// DNSによる名前解決
hostResolver.GetByName( url, nameEntry , status );
User::WaitForRequest( status );

if( status != KErrNone ){
User::Leave(KErrCouldNotConnect);
}

// 取得したIPアドレスの確認
TNameRecord nameRecord = nameEntry();

TInetAddr addr ;
TBuf<15> ipAddr;
TInetAddr::Cast(nameRecord.iAddr).Output(ipAddr);
this->iIpLabel->SetTextL( ipAddr );

 名前解決は、RHostResolverクラスが行います。RHostResolverのメンバ関数であるOpen関数の第一引数に渡すのは、ソケットサーバーであるRsocketServクラスのインスタンスになります。このクラスは名前が示す通り、IPプロトコルのエンドポイントを表すクラスになります。

 また、KafInetは使用されるIPのプロトコルがIPv4であること、KProtocolInetUdpはIPレイヤの上位のプロトコルがUDPであることを表しています。おそらく、このあたりのセッションの確立方法はUnixなどのほかのOSでネットワークプログラムを作成した経験があれば、さほど難しくないと思います。

 最後に、RSocketServ、RHostResolverクラスはやはりRクラスになっていますので、作成にはConnect関数やOpen関数など、また終了処理にはCloseなどの関数を呼び指します。LoadHTMLData関数の一番下にこの終了処理がまとめて記述されています。

 実際の名前解決は、以下のコードで行われます。

// DNSによる名前解決
hostResolver.GetByName( url, nameEntry , status );
User::WaitForRequest( status );

 GetByName関数は、非同期の処理になります。したがって、本来はアクティブオブジェクトを用いて処理を行うのが本筋になりますが、ここではコードのわかり易さを優先して、非同期の関数であるGetByNameの実行終了をUser::WaitForRequest関数で待ちます。第二引数に名前解決の結果が格納されます。最後にDNSに問い合わせた結果を画面に表示します。
posted by シンビアン at 11:11| Comment(0) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

ネットワーク接続

いよいよ秋も深まり、皆様いかがお過ごしでしょうか? 今回は本連載の最終回として、ネットワークの接続を見ていきたいと思います。また、サンプルとしてHTTPを用いた簡単なアプリケーションを用意しました。この連載の読者の方に「TCP/IPとは」と書いても釈迦に説法になると思いますので、ネットワークの基本的な知識は既知のものとして扱います。
Series60でのTCP/IP

 Series60のAPIレベルでは、IPv4とIPv6の双方が使用可能です。また、IP over BluetoothやIrDAなども利用することが可能です。

エミュレータ上のネットワーク設定

 エミュレータ上で[tools]→[Preference]→[Ethernet Settings]と選択すると、次のような画面が現れます。また、複数のネットワークカードをもつマシンであれば、一番下に[Select the adapter to use]の項目から使用するネットワークカードを選択して下さい(画面1)。

■ 画面1

 基本的には、一番上にある「promiscuous mode」にチェックを入れて、proxyなどの設定を適切に行えば問題ないはずです。

補足:

エミュレータでは仮想ネットワークドライバを利用し、新たなIPを要求します。筆者の失敗談になりますが、普段仕事をしている環境では、DHCPがMac アドレス認証を利用して物理的な無線LANカードにひとつのIPアドレスのみを割り当てるようになっていました。エミュレータの仮想的なネットワークドライバがIPアドレスを要求しますので、どうしても上手く動作しないときは原因のひとつとして疑ってみてください。
posted by シンビアン at 10:33| Comment(0) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

描画対象のグラフィックスディバイスの取り出しとフォントの設定

次に文字列を描画しますが、文字列の描画にはフォント情報が必要であり、フォント情報はグラフィックスディバイスクラスが管理をしています。そこで、グラフィックスディバイスのインスタンスを取得する必要があるのです。この一連の処理は、以下の形で行われています。

// 文字列の描画
CFont* iFont;
TTypefaceSupport myTypefaceSupport;
TFontSpec myFontspec(myTypefaceSupport.iTypeface.iName.Des(), 100 );

CGraphicsDevice* device= gc.Device();
User::LeaveIfError(
device->GetNearestFontInTwips(iFont, myFontspec));
gc.UseFont( iFont );

再描画のタイミング

 再描画を強制するためには、CCoeControl クラスにある DrawNow関数を呼び出します。今回のサンプルでは、以下の様に背景色を変更するときに、この関数を呼び出しています。

void CGraphicsTestContainer::ChangeBgColor()
{
if( bgColor == KRgbBlue )
bgColor = KRgbGray ;
else
bgColor = KRgbBlue ;
DrawNow();
}

ビットマップの使用について

 今回はサンプルとしては触れていませんが、ビットマップを画面に貼り付けることも可能です。ただし、注意が必要な点として、ビットマップはいわゆる Windows形式のビットマップから独自形式であるマルチビットマップ(拡張子:mbm)に変更する必要があります。最新のSDKであれば、プロジェクトの設定ファイルであるmmpファイルに使用するビットマップを記述すれば、ビルド時にマルチビットマップに変換をしてくれます。記述例としては下記のようになります。

START BITMAP graphicslab.mbm
HEADER
TARGETPATH ..\..\..\..\wins\c\system\apps\graphicslab
SOURCEPATH ..\bitmaps
SOURCE c12 background.bmp
SOURCE c12 ball.bmp
SOURCE c12 ball_mask.bmp
SOURCE c12 other_ball.bmp
SOURCE c12 other_ball_mask.bmp

 アプリケーションがビルドされると、筆者の環境では以下のパスにマルチビットマップのファイルができています。

C:\Symbian\8.0a\S60_2nd_FP2_J\epoc32\wins\c\System\
Apps\GRAPHICSLAB\graphicslab.mbm
-----
補足:筆者の環境では、以下のパスがエミュレータの
ファイルシステムのルートになっています。
-----
C:\Symbian\8.0a\S60_2nd_FP2_J\epoc32\wins

 このマルチビットマップ形式は「複数のビットマップファイルをひとつにまとめたもの」です。したがって、プログラム上で元のビットマップを取り出すには、そのインデックスを指定する必要があります。

 このインデックスの指定は、SDKディレクトリ以下のIncludeディレクトリに自動的にヘッダファイルを生成しますので、これを利用します(graphicslab.mbg:ファイル名はマルチビットマップファイル名と同一、ただし拡張子がmbgになる)。筆者の環境では以下に生成されています。

C:\Symbian\8.0a\S60_2nd_FP2_J\epoc32\include

 次回はTCP/IPを用いた通信について見ていく予定です。
posted by シンビアン at 10:32| Comment(0) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

描画を用いた簡単なサンプル

描画を用いた簡単なサンプル

 それでは、描画の処理を用いた簡単なサンプルを見て行きましょう(GraphicsTest.zip)。今回のサンプルでは、以下の処理を行います。

1. 背景の塗りつぶし
2. 直線の描画
3. 楕円(円)の描画
4. 文字の描画

 サンプルをダウンロードしていただき、これまでと同様にビルドしていいただければ、エミュレータ上で次のようなアイコンが表示されていると思います(画面1)。


 このアプリケーションを実行すると、次のような画面になります(画面2)。


 さらにメニューから「背景色の変更」を選択すると、次のような画面になります(画面3)。


サンプルコードの確認

 描画の処理を行っているコードはGraphicsTestContainerクラスのDraw関数に記載されています。それでは、この関数の中で行われている処理を見て行きましょう。まずは、関数の処理の先頭に

void CGraphicsTestContainer::Draw(const TRect& aRect) const
{
CWindowGc& gc = SystemGc();

と書かれていると思います。これはこのアプリケーションがもっている(主に液晶画面描画用の)グラフィックスコンテキストを取得してくる部分です。SystemGC関数自体はCcoeControlクラスに定義されている関数です。

 この次には、背景や直線、楕円(円)の描画の処理が続きます。こちらの部分に関しては、コードを追っていただければ特に説明は要らないと思います。

// 背景色を現在の色に設定する。
gc.SetBrushColor( bgColor );
gc.SetBrushStyle( CGraphicsContext::ESolidBrush );
gc.DrawRect( aRect );

// 直線を描画する
gc.SetPenColor( KRgbGreen );
gc.SetPenStyle( CGraphicsContext::ESolidPen );
gc.DrawLine( TPoint(10,10), TPoint(100,100 ) );
gc.SetPenStyle( CGraphicsContext::ENullPen );

// 描画モードを変更する。
gc.SetDrawMode( CGraphicsContext::EDrawModeOR );
gc.SetBrushColor( KRgbRed );
gc.DrawEllipse( TRect(50,50,150,150 ) );
gc.SetDrawMode( CGraphicsContext::EDrawModePEN );

-----
補足:通常の描画用APIと同様に、左上端が原点(0,0)です。

 また、楕円(円)の描画部分に関しては、描画モードをビットのORをとるように設定している点にもご注意ください。
posted by シンビアン at 10:11| Comment(0) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

グラフィックス描画

アミューズメント系アプリケーションや、通常のコントロールでは表示できないような描画を行う場合、グラフィックスの処理がどうしても必要になります。今回はこのグラフィックスの処理を見ていきましょう。
グラフィックスディバイスとグラフィックスコンテキスト

 Series60では、Win32 APIなどのようにグラフィックス関連の操作は抽象化が行われてます。この抽象化により、単に「画面上ある点から他の点に線を引く」「矩形に塗りつぶす」といった処理を行うことができます。この抽象化を担うのが「グラフィックスディバイス」と「グラフィックスコンテキスト」です。

 グラフィックスディバイスは、描画対象を表すクラスになります。通常、描画の対象となるものは(携帯でしたら)液晶画面と考えるのが一番自然でしょう。しかし、画面のチラツキを抑えるためにダブルバッファリングを行うと、どうしてもビットマップを別途用意する必要があります。このようなビットマップへの書き込みも、ビットマップを表すグラフィックスディバイスを用います。

 これに対して、グラフィックスコンテキストは描画のための関数や制御を提供するクラスになります。よくある説明としては、

* グラフィックスディバイスは描画される紙やキャンパスにあたる
* グラフィックスコンテキストは描画する際に用いるペンや筆である

といわれることが多々ありますが、今回もこのように考えれば大丈夫です。

グラフィックスディバイス

 先に述べたとおり、グラフィックスディバイスは「描画の対象を抽象化する」ためのクラスです。しかし、液晶画面に描画するのか、ビットマップ領域に描画するのかで使用するクラスが異なってきます。

 Series60では、APIとしてC++のライブラリを利用していますので、複数クラスがある場合は当然、クラスの階層関係が考えられます。グラフィックスディバイス関連のクラス階層の頂点(つまりもっとも抽象的なクラス)がCGraphicsDeviceクラスになります。そして、各々の描画対象を表すクラスは、このCGraphicsDeviceクラスを継承する形で定義されています(図1)。

■ 図1 グラフィックスディバイスのクラス構成 (出典:NRIラーニングネットワーク 「ノキア Series 60 C++開発入門コース」より)

 CGraphicsDeviceクラスが持つメンバ関数としては以下のものがあります。つまり直感的には、描画を行う際に必要な情報を管理していると見ていただいても構わないと思います。

* ディスプレイ領域の管理
SizeInPixelsやSizeInTwipsなどの関数
* フォントの管理と取得
GetNearestFontInTwipsなどの関数
* カラーフォーマットの管理
DisplayMode関数

グラフィックスコンテキスト

 一方、グラフィックコンテキストは描画を行う際に利用するペンやブラシのようなものです。ですので、具体的な描画用関数はこのグラフィックスコンテキストクラスが持っています(図2)。

■ 図2 グラフィックスコンテキストのクラス構成 (出典:NRIラーニングネットワーク 「ノキア Series 60 C++開発入門コース」より)

 CgraphicsContext クラスが持つメンバとしては以下のものがあげられます。

* 自身が描画を行う対象であるグラフィックスディバイス
Device関数
* 文字描画に使用するフォントの選択
UseFontやDiscardFont関数
* 図形描画に使用する各種設定を行う関数
SetPenSize、SetBrushColorなどの関数
* 実際に図形/文字の描画を行う関数
DrawEllipse, DrawLine, DrawText, DrawBitmapなどの関数群

posted by シンビアン at 10:08| Comment(0) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

2006年02月15日

ディスクリプタについて

今回のサンプルでは、複数箇所でディスクリプタを使用しています。以降ではこのディスクリプタについて見て生きたいと思います。ディスクリプタとは、Series60の中では、以下の代用として使用されます。

* NULLで終わる文字列
* バイト配列
* バイトポインタ

 今回のサンプル(FileTest.zipとFileTest2.zip)でもファイルから文字列を読み込んでくる箇所で、文字列を格納する領域としてディスクリプタを使用しています。具体的には、CFileTestDocument::ReadFile()関数の中では以下の箇所になります。 HBufCやTPtr などがディスクリプタになります。

HBufC* buf = HBufC::NewL(128);
TPtr ptr = buf->Des();
// 中略….
rStream >> ptr ;

 ディスクリプタの種類には以下の5種類があります。

* 抽象ディスクリプタ(TDes TDesC)
* リテラルディスクリプタ(TLitC)
* バッファディスクリプタ(TBuf, TBufC)
* ヒープディスクリプタ(HBuf)
* ポインタディスクリプタ(TPtr TPtrC)

 リテラルディスクリプタは特殊になりますが、ディスクリプタは配列やポインタをカプセル化した「スマートポインタ」と考えてくださればよいかと思います。つまり、ディスクリプタの保持する領域と、領域に関する情報(確保した領域や、現在の使用量など)をカプセル化しているものです。バッファディスクリプタとポインタディスクリプタの相違は、配列をカプセル化するか、それともポインタの指す領域をカプセル化するかになります。

 そして、バッファディスクリプタ、ポインタディスクリプタ双方とも「あるメモリ上の領域に対するアクセスの方法と、領域の情報を保持している」点は変わりません。そこで(オブジェクト指向で一般に行われるように)抽象的なディスクリプタのクラスを考えることができます。これが抽象ディスクリプタになるわけです。

 ここで、ディスクリプタ関連のクラスの階層関係を見ておきましょう(図1)。

■ 図1 8ビットディスクリプタクラスの派生 (出典:NRIラーニングネットワーク 「ノキア Series 60 C++開発入門コース」より)

 上記は8ビットのディスクリプタ(つまり保存されるデータのサイズがバイト単位となる)のクラス階層を示しています。通常は文字データの格納を考え、単にTDesCのクラスを使用したときは16ビットのディスクリプタとなるようにtypedefがなされています。16ビットのディスクリプタも同一のクラス階層を持っています。

 また、クラス名に「C」がついているものは変更用の操作が提供されず、単にデータの参照のみができます。これに対して、クラス名に「C」が付かないものはデータを参照する操作と変更する操作が提供されます。

 それでは、次にバッファディスクリプタとポインタディスクリプタ、ヒープディスクリプタの3つをもう少し見ていきましょう。

TBufCとTBuf(バッファディスクリプタ)

 TBufとTBufCは「配列の領域をカプセル化したディスクリプタである」とご理解ください。データ構造(16ビットのディスクリプタ)は下図のようになります(図2)。

■ 図2 メモリの構成図 (出典:NRIラーニングネットワーク 「ノキア Series 60 C++開発入門コース」より)

TPtrとTPtrC(ポインタディスクリプタ)

 バッファディスクリプタが配列をカプセル化しているのに対して、ポインタディスクリプタはポインタで指される領域をカプセル化します(図3)。

■ 図3 メモリの構成図 (TPtrC8) (出典:NRIラーニングネットワーク 「ノキア Series 60 C++開発入門コース」より)

HBufC (ヒープディスクリプタ)

 ヒープディスクリプタはヒープ上に確保した領域をカプセル化したディスクリプタです(図4)。

■ 図4 メモリの構成図 (出典:NRIラーニングネットワーク 「ノキア Series 60 C++開発入門コース」より)

 ヒープディスクリプタは若干特殊で、変更可能なディスクリプタであるHBufは存在しません。その代わり、HBufC::Des()関数で変更可能なポインタディスクリプタを取得することができます。

再度、今回のコードの断片を

 それでは、今回のサンプルで登場したコードの断片をもう一度見てみましょう。

HBufC* buf = HBufC::NewL(128);
TPtr ptr = buf->Des();
// 中略….
rStream >> ptr ;

 ここの流れは以下のようになってます。

1. ヒープディスクリプタ(128文字分、バイト数では256バイト)を確保し
2. 1で作成したディスクリプタからDes関数を用いて変更可能なポインタディスクリプタを作成し(当然、このポインタディスクリプタがカプセル化する領域と、ヒープディスクリプタがカプセル化する領域は同一)
3. ストリーム演算子を用いてポインタディスクリプタに対してデータを代入する

今回の終わりに当たって

 今回は簡単なファイルアクセスの方法とディスクリプタについて触れました。今回は触れませんでしたが、Series60は組み込みデータベースを持っています。アプリケーションの種類によってはデータベースアクセスを行った方が開発効率が良い場合もあるでしょう。アプリケーションの種類に応じて適宜用いてください。

 また、紙面の関係からSeries60が提供するより先進的なストアやストリームネットワーク、さらにスイズル(Swizzle)については割愛しました。複雑なファイル構造や大きなファイルの読み込みを行うときは、これらの技術項目をあたってみてください。

 後半のディスクリプタについては、若干慣れない部分があるかと思います。しかし、慣れてしまえば簡単ですので、いろいろサンプルをご自身で書いてみることをお勧めします。次回はグラフィックスの処理について解説する予定です。
posted by シンビアン at 16:50| Comment(0) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

オブジェクトの入出力(ストリームの利用)

バイト単位の入出力ではなく、より抽象度の高い入出力を実現するために、C++ではストリーム演算子が提供されています。Series60でも同様のストリーム演算子が提供されており、Unicode文字列の入出力などはこのストリームを用いたほうがより簡単に行うことができます。

 このストリームを用いた入出力はRFsクラスを用いてRWriteStream とRReadStreamを作成することで実現できます(特に、書き出す対象がファイルの場合はRFileWriteStream およびRFileReadStreamになります)。

Unicode文字列のストリームを用いた出力

 ここでは、先のサンプルのコードを書き換え、Unicode文字列の入出力が可能なサンプルを見て行きたいと思います(FileTest2.zip)。

 ファイルへの書き出しは次のようになっています。

void CFileTestDocument::WriteFile()
{
RFs& rfs = CCoeEnv::Static()->FsSession();
// ファイルサーバへのセッションを取り出し
RFileWriteStream wStream ;
TInt rst = wStream.Create( rfs,
_L("c:\\temp\\test.txt"), EFileWrite | EFileStreamText );
if( rst == KErrAlreadyExists ){
wStream.Open( rfs,
_L("c:\\temp\\test.txt"), EFileWrite | EFileStreamText );
}

_LIT( KOutputText , "こんにちは" );
wStream << KOutputText ; // ストリームを使っての書き出し
wStream.Close();
}

 先ほどのサンプル(FileTest1.zip)との相違は、RFileを用いるのではなく、RfileWriteStreamを用いているのが一点、次に、

WStream << KOutputText ; // ストリームを使っての書き出し

と、ストリーム演算子(<<)を用いている点。最後に_LIT8マクロではなく、Unicode文字列を生成する_LITマクロを使用している点です。ファイルの読み込みも同様に行っていますので、CFileTestDocument::ReadFile()関数をご参照ください。

ストリーム演算子とExternalizeLとInternalizeL関数

 FileTest2.zipでは、Unicode文字列の読み込みと書き込みを行いました。これに対して、プログラマ作成の任意のオブジェクトをストリーム演算子を用いて書き出す場合、内部的には(C++のテンプレートを用いて)ExternalizeLとInternalizeLの関数を呼び出します。

 つまり、wStream をRWriteStream のインスタンスとすれば、

WStream << someObj;

と、

SomeObj.ExternalizeL( wStream );

は同じ挙動をします。

 ですので、プログラマ作成の任意のオブジェクトに対して、ストリーム演算子を利用したいときは、ExternalizeL関数とInternalizeL関数を用意します。

 もう一点注意が必要な点として、ストリーム演算子が呼び出すExternalizeL関数とInternalizeL関数は最後に「L」がついていることからわかるとおり、リーブを発生します。ですから、ストリーム演算子はリーブを発生する可能性がある点に注意してください。

 組み込みのデータ型に関しては、ストリーム演算子はストリーム演算子のインスタンスがもつ以下のメンバ関数を順次呼び出します。

TInt8 WriteInt8L() ReadInt8L()
TInt16 WriteInt16L() ReadInt16L()
TInt32 WriteInt32L() ReadInt32L()
TUint8 WriteUint8L() ReadUint8L()
TUint16 WriteUint16L() ReadUint16L()
TUint32 WriteUint32L() ReadUint32L()
TReal32 WriteReal32L() ReadReal32L()
TReal64 WriteReal64L() ReadReal64L()

 ここで注意が必要なのが、TInt 型です。TInt型は次のようにtypedef されていますので、データサイズは実行環境に依存します。

typedef signed int TInt;

 ですから、TInt に関してはWriteInt32L() やReadInt32L() などを用いる必要があります。

ファイル入出力を使用する際にリンクするライブラリ

 今回はファイル入出力を用いたサンプルを提供しましたが、ご自身でスクラッチからプログラムを書く場合は、リンクするライブラリを追加する必要があります。そのため、プロジェクトの設定ファイルを変更する必要があります。

 追加が必要なライブラリの例としては、RFileクラスを使用する場合、実装はefsrv.libにあります。また、ファイルへのストリームを使う場合はestor.libに実装が提供されています。

 これらのライブラリはプロジェクトの設定ファイルであるMMPファイルにて、指定する必要があります。具体的には今回お配りしたサンプル(FileTest2.zip)ではFileTest.mmpにて、次のようにestore.libの指定がなされています。ご確認ください。

LIBRARY eikcoctl.lib avkon.lib estor.lib
posted by シンビアン at 16:41| Comment(0) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

CDirクラスとRFileクラス

CDirクラスとRFileクラス

 CDir クラスはディレクトリを、RFileクラスはファイルを表すクラスとなっています。ディレクトリの操作や、ファイルに対する操作はおのおのCDirクラスやRFileクラスを用いて行います。ここでRFileクラスを用いた簡単なサンプルを見てみましょう(FileTest.zip)。

 このアプリケーションを実行するには、これまでと同様にビルドして、その後に以下のアイコンを探して下さい(画面1)。


 これを実行すると次のような画面が現れます(画面2)。


 [オプション]から[ファイルテスト]を選んでいただきますと、「hello world」という文字をファイルに書き込み、その書き込んだファイルから文字を取り出して出力します(画面3)。


 それでは、このサンプルの重要な部分を見ていきましょう。まずは、ファイルへの書き出しの部分です。

#include
void CFileTestDocument::WriteFile()
{
// ファイルサーバへのセッションを取り出し
RFs& rfs = CCoeEnv::Static()->FsSession();
RFile file ;
TInt rst = file.Create( rfs,
_L("c:\\temp\\test.txt"), EFileWrite | EFileStreamText );
if( rst == KErrAlreadyExists ){
file.Open( rfs,
_L("c:\\temp\\test.txt"), EFileWrite | EFileStreamText );
}
_LIT8( KOutputText , "hello world" );
file.Write(KOutputText, 11 ) ;
file.Close();

}

 ファイルの指定の部分ですが、

"c:\\temp\\test.txt"

この部分で指定しています。筆者の環境で新たにできているはずのtext.txtを探すと、

\Symbian\8.0a\S60_2nd_FP2_J\epoc32\wins\c\temp

にファイルが存在します。エミュレータ上で実行すると、新たにファイルができているはずですので、探してみてください。

補足:筆者の環境では\Symbian\8.0a\S60_2nd_FP2_J\epoc32\winsがエミュレータ上でのファイルシステムのルートになっております。

 ここで、「Series60では、Unicode を基本的に用いる」という話を思い出してください。通常_LITマクロを使用すると、Unicodeで処理がされますが、ここでは明示的に_LIT8マクロを用いて、ASCII文字列を作成しています。

_LIT8( KOutputText , "hello world" );

 ファイルの読み込みも逆の順序で行っています。次にファイル読み込みの関数を見てみましょう。

HBufC8* CFileTestDocument::ReadFile()
{
// ファイルサーバへのセッションを取り出し
RFs& rfs = CCoeEnv::Static()->FsSession();
RFile file ;
HBufC8* buf = HBufC8::NewL(128);
TPtr8 ptr = buf->Des();
TRAPD( error ,
User::LeaveIfError(
file.Open( rfs,
_L("c:\\temp\\test.txt"), EFileRead | EFileStreamText ) ); );
if( error != KErrNone ){
User::Leave(error);
}
TRAP( error ,
User::LeaveIfError( file.Read( ptr, 128) ); );
if( error != KErrNone ){
User::Leave( error );
}
file.Close();
return buf ;
}

 ここでは、ヒープディスクリプタ(HBufC8)を宣言し、そこにファイルから読み取った内容を書き込んでいます。特に、HBubC8からTPtr8を取り出している点に注意してください。ディスクリプタについては後で説明します。

上記サンプルの注意点

このサンプルではRFileクラスを用いてASCII文字列の入出力を行いました。通常Series60では文字列はUnicodeなのですが、 RFileクラスが持っているReadやWriteなどのメンバ関数はバイト単位の入出力しかサポートしておりません。なので、本来はバイナリデータの入出力でこれらの関数を使うことが多いと思いますが、今回はサンプルのわかりやすさから文字列の入出力を行っています。
posted by シンビアン at 16:35| Comment(0) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

ファイル入出力とディスクリプタ

アプリケーションを作成する場合、どうしてもファイルアクセスは必須の項目になります。今回はまず、簡単にSeries60上でのファイルアクセスを見ていきます。また、ファイルの入出力ではどうしてもデータの格納先になるデータ領域が必要です。 Series60ではデータ領域をディスクリプタとして抽象化することが可能です。ディスクリプタは今回の後半で見て行きます。
ファイルサーバ

 ファイル入出力の話をする前に、ファイルサーバの話をする必要があります。これまでも述べたとおり、Symbian OSはマイクロカーネルを採用しています。したがって、OSが提供すべきサービスの多くはサービスを代行するサーバアプリケーションによって担われます。

RFsクラス

 ファイル関連の操作の場合、ファイルサーバへのクライアント側の窓口はRFsクラスになります。このRFsクラスを用いて以下の操作を行います。

1. ファイルサーバへのセッションを確立する。
2. ファイルサーバへ、ファイル関連の要求をRFsクラスを通して行う。
3. 必要がなくなった時点でセッションを閉じる

 ファイルサーバへのセッションを確立するには、一般に次のようなコードを用います。

RFs fsSession;
// ファイルサーバへのセッションを確立
User::LeaveIfError(fsSession.Connect());
// ここで何らかの処理を行う。
fsSession.Close(); // 不必要になった時点でセッションを閉じる

 また、ファイルサーバへのセッションが残っている間にリーブが発生すると、セッションを閉じないままアプリケーションが終了する可能性があります。そこで、RFsクラスのインスタンスをクリーンアップスタックに積む必要がありますので、その時は、

CleanupClosePushL(fsSession);

とすれば、リーブ発生時にdeleteされる前にClose関数もあわせてコールされます。さらに、クリーンアップスタックからのポップとオブジェクト自体の削除を行いたければ、

CleanupStack::PopAndDestroy();

を呼び出すことで可能です。PopAndDestroyを呼び出すことで、セッションのCloseもあわせて行われます。

 また通常のアプリケーションであれば、上記の様に明示的にファイルサーバへのセッションを作成することなく、アプリケーション実行環境が用意しているデフォルトのセッションがあります。リソースの使用量などから、特に理由が無い限りはこのデフォルトのセッションを使うことをお勧めします。デフォルトのセッションは、

RFs &fs = CCoeEnv::Static()->FsSession();

と、することで取得できます。

 FRsクラスが提供する機能としては、主に以下のものがあります。

1. ファイルを表すRFileクラス、およびディレクトリを表すCDirクラスのインスタンスの取得
2. ディレクトリの作成や、ファイルおよびディレクトリの削除などの機能

posted by シンビアン at 16:31| Comment(0) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

非同期の処理

アクティブオブジェクトによる非同期イベントの待機

 次に、メニューの[タイマーのスタート]を選択することにより、アクティブオブジェクトが非同期(ここでは1秒おき)にイベントを受け取ります。そのため、非同期イベントの発生源となるタイマを設定しますが、これはCActiveTest::After()関数で行われます。

void CActiveTest::After(){
iIsRunning = ETrue;
iTimer.After(iStatus, 1000000); // タイマのスタート
SetActive();
//このアクティブオブジェクトがスケジューリングの対象であることを示す
}

 ここで注目していただきたい点として、2点挙げます。

 まず1点目は、iTimer.After関数の第一引数に渡されるiStatusメンバです。このiStatusメンバはCActiveクラスで定義されているもので、非同期のイベントの完了を表すフラグです。iTimer.After関数は第二引数で指定した時間が経過すると、このiStatusの値をKErrNone(つまりKrequestPending以外の値)に設定します。先に述べたアクティブスケジューラの挙動とあわせて確認してください。

 2点目としては、この関数の一番最後に呼び出されているSetActive()関数です。この関数も、アクティブスケジューラに対して自身が非同期のイベントの通知を待っていることを表すために用います。

非同期イベントの通知

 タイマで設定した時間が過ぎ、エラーが発生していなければ、先に登録したアクティブオブジェクトのRunLの関数が呼び出されます。今回は(大まかですが)1秒置きにこのRunL関数が呼び出されますので、このRunL関数でラベルの書き換えを行っています。

void CActiveTest::RunL()
{
After();
sec ++ ;
if( sec >= 60 ){
sec = 0 ;
min ++ ;
}
if( min >= 60 ){
min = 0 ;
hour ++ ;
}
TBuf<32> text;
_LIT(KTime, "Time %d[h]%d[m]%d[s]");
text.Format(KTime, hour, min, sec );
label->SetTextL(text);
iContainer->DrawNow();
}

 ここで、CactiveTest::After()関数を呼び出しているのは、繰り返し1秒後にタイマから通知を受ける必要があるからです。

非同期のイベント待機のキャンセル

 イベント待機のキャンセルが行われた時の処理は、DoCancel関数に記述します。ここでは、RTimerクラスを用いて1秒置きのイベントを受け取っていますので、このリソースクラスに対してタイマのキャンセルをする必要があります。

void CActiveTest::DoCancel()
{
iTimer.Cancel();
iIsRunning = EFalse;
sec = min = hour = 0 ;
label->SetTextL(_L("タイマ停止"));
}

 また、CActiveクラスのデストラクタの中ではCansel関数を呼び出していますが、この関数はそのままDoCancel関数を呼び出します。

CActiveTest::~CActiveTest()
{
Cancel();
iTimer.Close();
}

最後に

 今回は、アクティブスケジューラとアクティブオブジェクトの二つを見ていきました。特にアクティブオブジェクトはコーディング量も若干多くなると思います。今回は処理ロジックをそのままRunL関数に書き込みましたが、再利用を考えると、何らかのコールバックを登録、RunLで登録したコールバックを呼び出せば汎用性が上がると思います。次回はファイル入出力について見ていく予定です。
posted by シンビアン at 13:55| Comment(0) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

アクティブオブジェクトを用いたタイムカウンタアプリケーションのサンプル

それでは、アクティブオブジェクトと用いたサンプルとして、簡単なタイムカウンタのアプリケーションを見ていきましょう。このサンプルプログラムは、並行処理が必要なプログラムとなっています。バックエンドではタイマーの処理を、フロントエンドではユーザーからの操作を受け付けています。[activetest.zip]

 今回のサンプルも、前回以前と同様にダウンロードしてビルドしますと、次のようなアイコンが追加されます(画面1)。


 このアプリケーションを起動し、次に[オプション]から[タイマーのスタート]を選択すると、下図のような画面が現れて1秒ごとに時間経過をカウントしていきます(画面2)。


 さらに、[オプション]から[タイマーの停止]を選択すると次のような画面になり、タイマを停止します(画面3)。


 先ほども述べたとおり、Series60ではマルチスレッドのアプリケーションは推奨されていません。今回のサンプルでは、この1秒おきの通知をアクティブオブジェクトで受け取り、スレッドはあくまでも一本になります。

アクティブオブジェクトの作成と登録まで

 次に、アクティブオブジェクトの作成と登録までを見ていきましょう。先ほど述べた通り、アクティブオブジェクトは必ずCActiveクラスを継承して作成されます。今回のサンプルでは、アクティブオブジェクトとなるクラスは次のように宣言されたCactiveTestクラスになります。

class CActiveTest:public CActive
{
public:
CActiveTest(); // コンストラクタ
~CActiveTest(); // デストラクタ
void ConstructL(CEikLabel* ,
CActiveTestContainer*); // 第二フェーズコンストラクタ
void After(); // タイマー開始関数
TBool IsRunning();
void RunL(); // コールバックの関数
void DoCancel(); // キャンセル時に呼び出される関数
protected: // data
// RTimerのインスタンス
RTimer iTimer;
CEikLabel* label ; // 時間を表示するラベル
/// 以下省略
};

 このCActiveクラスのコンストラクタにおいて、このアクティブオブジェクトの優先度の設定とアクティブスケジューラへの登録が行われています。

CActiveTest::CActiveTest():
CActive(EPriorityStandard)
// EPriorityStandard は優先度が通常のものであることを表す。
{
// 中略
CActiveScheduler::Add(this);
// 自分自身をアクティブスケジューラに追加
}

 先ほども述べたとおり、アクティブスケジューラへの登録はCActiveScheduler::Add関数で行われます。
posted by シンビアン at 13:48| Comment(0) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

非同期の処理

アクティブスケジューラ

 続いてアクティブスケジューラを見ていきましょう。アプリケーション起動時にフレームワークは、アクティブスケジューラを作成します。また、アクティブスケジューラにアクティブオブジェクトを登録するには、CActiveSchedulerクラスのstatic void Add(CActive* anActive)関数を用います。

 アクティブスケジューラはループで実現されていますが、このループの中では大まかに次の処理がなされています。

1. 登録されているアクティブオブジェクトのうち、優先度上位のものから順番に次の条件を満たすことを確認する。

(1) … CActive::IsActive()がETrueを返す。このことで登録されたアクティブオブジェクトが未処理の非同期イベントの通知を待っていることを表す。
(2) … 上記で待っている非同期のイベントが終了していることを表すフラグであるCActive::iStatusの値がKRequestPending以外の値になっている。

2. 1.で調べた条件を満たすアクティブオブジェクトのRunLの関数を呼び出し、その実行終了を待つ

3. 1.に戻る

 アクティブスケジューラは、キーイベントなどのユーザー入力も処理している点にも注意する必要があります。つまり、アクティブスケジューラが呼び出す関数RunL()は、速やかに終了する必要があるわけです。仮にRunLの関数の実行に数秒かかる場合、アプリケーションは、その数秒間ユーザーの入力を受け付けなくなり、ハングしたように見えます。

補足:本稿では説明しませんが、自作のアクティブスケジューラを作成することも可能です。

アクティブオブジェクト

 上記のアクティブスケジューラの説明で見たとおり、アクティブオブジェクトのRunL関数により、非同期のイベントの通知を受けます。また、RunL以外にもiStatusなどの重要なメンバが存在します。これらのメンバはCActiveクラスで定義されており、新たにアクティブオブジェクトを作成する際には、必ずこのCActiveクラスを継承する必要があります。以下にCActiveクラスの重要なメンバとその説明を大まかに記述します。

* RunL():非同期のイベントが終了した際、コールバックとして呼ばれる関数。
* DoCancel():非同期のイベントの待機がキャンセルされたときに呼び出される関数。
* Cancel():オーバーロードしない限りはDoCancel関数を呼び出す。
* RunError(TInt):RunLがリーブした際、例外処理の関数として呼ばれる。
* SetActice():このアクティブオブジェクトが非同期のイベントの通知を待っている状態に設定する関数。
* CActive(TInt aPriority):コンストラクタ。引数にはこのアクティブオブジェクトの優先度を設定する。
* TRequestStatus iStatus :非同期のイベントの状態を表すフラグ。このフラグがKrequestPendingの時は非同期のイベントが完了していないことを表す。

posted by シンビアン at 13:46| Comment(0) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

2006年02月13日

コンストラクション(Cクラス)

次に、Cクラスのオブジェクトのコンストラクションに話を移しましょう。オブジェクトのコンストラクションに絡めて、クリーンアップスタックについて見ていきます。

 前回の記事でも多少触れさせていただきましたが、クラスのインスタンスを作成する時は通常、「2フェーズコンストラクション」を行います。2フェーズコンストラクションは、具体的には

* new(ELeave)演算子を用いてクラスのインスタンスが必要とするメモリを確保する。この新たにオーバーロードされている演算子はリーブを発生させる可能性がある。

* 作成したインスタンスが他のオブジェクトを所有する場合(つまりhas-aポインタを持つ場合)は、概ねConstructLという名前の関数でポインタの参照先インスタンスの生成とポインタの値の設定を行う。

 という流れになります。若干厄介に見えますが、これはメモリリーク対策になります。またまた繰り返しになりますが「メモリの確保が最もリソース不足によるエラーを起こしやすい」に注意してください。

 それでは、次の図を見てください。


 携帯環境ですから、非常にリソースが限られています。ですから、小さなオブジェクトの確保でも失敗をする可能性があるわけです。もし、上記のようなコードで、ptr2の初期化に失敗する可能性を考えるのであれば(これは携帯環境では十分に考えられる)、各々のポインタに対して、nullチェックを行う必要があります。これでは、プログラムを書くのが手間になってしまいます。また、リーブの機構があるのですからこれを使わない手はありません。

 そこで、例外処理をすっきりさせるため、先ほど出てきたリーブの仕組みを使うことを考えます。つまり、new(ELeave)を用いてメモリの確保を行います。このオペレータは先述の通り、メモリ確保に失敗するとリーブを発生させます。

 この場合、例外処理はすっきりしますが、新たな問題を引き起こします。それでは、次の図を見てください。


 つまり、リーブが発生した時点でTRAPマクロやTRAPDマクロで処理される箇所までスタックを遡るわけです。2回目のnew(ELeave)でリーブが発生した場合、ptr1が一時的な作業用のauto変数(コンストラクタの中であったとしても十分にあり得る)であれば、そのポインタが永遠に失われ、結果としてメモリリークの原因になります。

 そこで、次のような対策を講じます。

1. CBaseクラスを継承したクラス(ClassA)の使用するメモリのみを確保する(コンストラクタでは何も行わない)。メモリの確保に失敗すれば、リーブを発生させる。

2. ClassAによって所有される他のクラス(has-aポインタでの参照先)は、他のコンストラクション用の関数(一般にConstructL)で初期化する。ここでもリーブが発生する可能性がある。

3. 1または2でリーブが発生した場合、構築途中のオブジェクトへのポインタが失われないようにグローバルな領域(スタック)にポインタの値をコピーしておく(このスタックをクリーンアップスタックと呼ぶ)。

 それでは、2フェーズコンストラクションのサンプルを見てみましょう(Construction.zip)。このサンプルでは、自前でCBaseクラスから派生をしたCMyObjクラスを作成し、このクラスが2フェーズコンストラクションを行います。ただし、意図的にConstructLの関数内でリーブを発生させてる点に注意してください。

 サンプルをビルドしたのち、下記のアイコンのアプリケーションを実行します。


 すると、次のような画面が現れます。


 その後、メニューから「テスト」を選択します。ここでは意図的にリーブを発生させていますので、次のような画面が現れます。


 最後に次のような画面になります。


 ここで、今回のサンプルコードの重要な部分を確認しておきましょう。

CMyObj* CMyObj::NewL(CConstructionContainer* aContainer )
{
CMyObj* ptr = new(ELeave)CMyObj; // インスタンスが使用するメモリの確保
CleanupStack::PushL( ptr );
// (1)以下の処理でリーブが発生したときに備えクリーンアップスタックにpush
ptr->ConstructL(aContainer); // インスタンスのメンバの初期化
CleanupStack::Pop();
// リーブの可能性がなくなったので、クリーンアップスタックからpop
return ptr ;
}

void CMyObj::ConstructL(CConstructionContainer* aContainer)
{
this->iContainer = aContainer ;
TInt reason = KErrNoMemory; // 勝手にメモリがないことにする
User::Leave(reason); // リーブを発生(2)
aContainer->UpdateMessage(_L("初期化完了") ); // この行は実行されない
}

CMyObj::~CMyObj()
{
this->iContainer->UpdateMessage(_L("CMyObj 削除完了"));
}

 ここでは(1)にて構築途中のオブジェクトをクリーンアップスタックにプッシュし、第二フェーズのコンストラクションの(2)において意図的にリーブを発生させています。ここでクリーンアップスタックの重要な動作として、

リーブが発生したとき、クリーンアップスタックに詰まれた
全てのオブジェクトのデストラクタが呼び出される

という点に注意してください。つまり、2フェーズ目で構築に失敗し、リーブが発生しても、クリーンアップスタックにメモリ確保時に取得したポインタを積んでおけば、自動的にデストラクタが呼び出されるということです。そして、リーブ時にデストラクタを呼び出すためにCBaseクラスに仮想デストラクタが定義されているわけです。今回のサンプルコードの画面遷移でも、デストラクタが呼び出されていることが確認できると思います。

 ただし注意点として、リーブ発生時にはクリーンアップスタックに詰まれた全てのオブジェクトがデストラクトされます。つまり、

リーブが発生する可能性が無くなった時点でクリーンアップスタックから取り除く

必要があります。将来、全く別の理由でリーブが発生した場合、問題のないオブジェクトまでデストラクションする可能性があるためです。

次回の予定

 次回は、非同期の処理を見て生きたいと思います。Series60でもスレッドの利用は可能ですが、リソースの消費用の大きさから異なるフレームワークを使用します。この点について確認していく予定です。
posted by シンビアン at 20:58| Comment(0) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

ドメインの再利用

ドメインとは、ある立場から見た場合のシステムの見え方のことでした。ドメインは独立した単位ですので再利用ができます。

ドメインの再利用は何も難しい事ではなく、言ってみれば当たり前の話です。例を挙げると、あるシステムをWebベースで作成した後で、画面や操作はまったく同じにしてJavaで作り直して欲しいと言われた時のことです。こんな時にはデータベースや画面の設計をそのまま持ってきてコーディングだけやり直せばいい、というだけの話です。

しかし、言うだけなら簡単ですが、実際にやってみるとそんなに簡単ではないことがわかるでしょう。ドメインを意識した設計でないと、仕様書のそこかしこにWebの言葉が出てきてしまっているでしょう。「FORMタグを使う」とか「URLに○○のパラメータを含めて……」とか。このようにWebベースであることを前提とした仕様書になっていると再利用することが難しくなります。

こうした場合、本来ならアプリケーション本来の動作を表現するアプリケーションドメインとそれをWeb上で閲覧/操作するためのユーザインターフェースドメインに分けるべきなのです。アプリケーションドメインではURLとかタグとか CGIというような用語は一切使ってはいけません。なぜならそれは本来のアプリケーション(経理とか在庫管理とか)とは一切関係がないからです。そしてしっかりドメイン分けされていれば、関係ない部分が変わっても問題はない、というわけです。
posted by シンビアン at 20:45| Comment(0) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

メタモデル

「メタモデル」とは、モデルをモデル化したものです。「CDには複数の曲が入っていて、曲にはタイトルと作曲者がある」というのがCDというものをモデル化したものだとすると、「オブジェクトには属性があり、それぞれのオブジェクトは関係しあっている」というのがモデルをモデル化したものです。

メタモデルというのは案外いろいろな場所で顔を出します。データベースのテーブル設計がモデル化であるとすると、テーブルの定義方法を設計するのがメタモデルです。プログラムそのものに対して「プログラムには関数と変数があって……」というのがメタモデルです。

メタモデルというのは一種のパターンです。メタモデルはただシステムを開発するだけならさほど意識しなくてもすむのですが、開発スタイルや開発ツールを作成しようとすると目を向けなくてはならなくなります。
posted by シンビアン at 20:43| Comment(0) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

コンベンション

「コンベンション」とは、システム設計に関する規則のことです。「コードコンベンション」という単語が一番馴染みがあるでしょう。コードコンベンションとはコードを書く時の規則のことです。(例えば)Java言語の構文規則のことではありません。それにのっとった上で、例えば関数のコメントはどこに書くかとか、関数名の決め方などを決めたのがコードコンベンションです。当然のことながら、こうした取り決めはいったん決めてしまえばいろんなシステムに適用できます。

コンベンションはコードだけに限りません。仕様書や設計書の書き方、そしてそもそもどんな仕様書を用意するのかなど、システム開発のやり方を規定する様々な規則がコンベンションです。システム開発のモデル化とも言えるでしょう。「システム開発=モデル化」という考え方からすればこれはメタモデルです。

開発するシステムの種類によって適切なコンベンションは変わってきます。小規模システムと大規模システムでは違いますし、データベース系と組み込みリアルタイム系でもまた違うでしょう。その場にあったコンベンションを開発し、それは同じようなシステムで使い回すというのがよいやり方でしょう。
posted by シンビアン at 20:42| Comment(0) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

例外処理(リーブ)

つぎに例外処理を見ていきましょう。また、例外処理に関連して2フェーズコンストラクションについて見ていきたいと思います。このセクションをお読みになるにあたり、しつこいですが「携帯環境のリソースは非常に限られたものを想定している」というのを頭の片隅において置いてください。

 Series60では通常のC++によるtry-catch構文とは異なる例外処理の機構を利用します。これは通常のtry-catchによる例外処理が結果としてコードサイズの肥大化を招くためです。ですから、通常のtry-catchによる例外処理は採用されていません。

 そこで、try-catchの代用として出てくるのが「リーブ(Leave)」という考え方(機構、仕組みといっても良いかもしれません)です。リーブは、従来の例外と概念的にはほぼ同一です。以下にリーブについて概略をまとめます。

* C++の例外の代わりに使用される
* リソースエラー等が発生すると、コードはリーブする
* トラップハーネス(TRAPマクロやTRAPDマクロ)でリーブが処理されるまで関数のスタックをさかのぼる

リーブによる例外処理の例

 それでは、次のサンプルを見てみましょう(LeaveSample.zip)。このサンプルでは、メニューから「リーブのテスト」を選ぶと、意図的にメモリ不足のリーブが発生するようになってます。

// コード例(A)
void CLeaveSampleAppUi::HandleCommandL(TInt aCommand)
{
switch ( aCommand )
{
/// 中略….
case ELeaveSampleCmdAppTest:
{
// 次の関数の呼び出しはリーブを発生させる
this->iAppContainer->TestLeaveL();
break;
}
// 後は省略….

このTestLeaveL()関数の実装は次の通りです。

// コード例(B)
void CLeaveSampleContainer::TestLeaveL()
{
TInt reason = KErrNoMemory; // 勝手にメモリがないことにする
User::Leave(reason); // (1)例外を発生
this->iToDoLabel->SetTextL(_L("問題なく終了しました"));
// 上の一行は実行されない
}

 このサンプルでは、(1)においてメモリ不足のリーブを意図的に発生させています(User::Leave関数でリーブを発生)。このアプリケーションをそのまま実行すると次のような画面がでてきます。

それでは、このアプリケーションに対して例外処理のコードを加えたいと思います。上記コード例(A)を次のように書き換えてみてください。ボールドになっている箇所が書き換えた箇所です。

// コード例(C)
void CLeaveSampleAppUi::HandleCommandL(TInt aCommand)
{
switch ( aCommand )
{
/// 中略….
case ELeaveSampleCmdAppTest:
{
// TRAPDマクロで例外を捕捉
// error に例外コードが入ってくる
TRAPD( error,
this->iAppContainer->TestLeaveL(); );
if( error != KErrNone ){ // 例外が発生しているかを検査
this->iAppContainer->UpdateMessage();
}
break;
}
// 後は省略…

TRAPDマクロの第一引数にあるerrorに例外が発生した理由が格納されます。これをKErrorNone(つまり例外が発生しなかったことを表す)と比較しているわけです。

 もう少しリーブについて見ていきましょう。例外処理自体は、このような形で行うことができます。次に残る問題は「作成した関数、またはAPIで提供されている関数がリーブを発生させるか否か、コンパイラレベルでは判断できない」点にあります。リーブの機構はC++の言語ではなく、Series60で採用されているものです。つまり、例外を適切に処理するのであれば、先のコード例(A)を本来はコード例(C)のようにしなくてはいけません。しかし、コード例(A)のままでも問題なくコンパイルが可能ですし、警告も表示しません。

 そこで出てくるのが関数のコードコンベンションです。今回はコード例(B)で定義した関数は最後に「L」の付く関数になっていますが、一般にリーブを発生させる可能性のある関数はその名前の最後に「L」を付ける習慣になっています。このような「L」の付く関数に関しては(製品としてプログラムを出す場合は)必ずTRAPマクロや今回の様にTRAPDで処理をする必要があります。

補足:今回はリーブによる例外処理を見ていきましたが、一部のAPIには戻り値によって正常/異常終了を表すもあります。
posted by シンビアン at 20:40| Comment(0) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

2006年02月11日

RクラスとMクラス

Rクラス

 Rクラスはシステムリソースを利用するためのクラスになります。SymbianはマイクロカーネルOSですから、システムリソースはサーバアプリケーション(ネットワーク上でのサーバではないことに注意してください)から提供されます。そして、このRクラスがシステムリソースを操作/利用する際のインターフェースになります。従って、通常のクラスとは異なり生成と破棄の方法が異なります。

タイマを使ったサンプル

 ここでサンプルを見てみましょう(RTimerTest.zip)。

ビルド方法:RTimerTest.zipを展開していただくと、次のようなディレクトリ構造になっています。


この中のgroupディレクトリに移動して、
> bldmake bldfiles
> abld build [wins|winscw] udeb

とコマンドを発行してください。winsとwinscwはダウンロードしたSDKにあわせて選択してください。

その後は[スタート]→[全てのプログラム]→[Series60 Developer Tool]から[Emulator (Debug)]を選択してください。

エミュレータを起動した後は


のアイコンを探して、プログラムを起動します。

 このサンプルを起動すると、次のような画面になります。
次に、「オプション」のメニューから「カウント」を選びます。


 すると、次の画面のように右上で5回、カウントを行います。


タイマを使ったサンプルの解説
サンプル1では、Rクラス(つまりOSのサービス)を利用しています。実際に利用している箇所はCRTimerTestContainerクラスのcountというメンバ関数です。
#include
void CRTimerTestContainer::count(){
RTimer timer;
if (KErrNone == timer.CreateLocal())
{
TRequestStatus status;
const TTimeIntervalMicroSeconds32 KTimerInterval = 1000000;
// 一秒間隔でラベルの書き換え
TBuf<8> text;
for( int i = 0 ; i < 5 ; i++ )
{
_LIT(KCount, "Count:%d");
text.Format(KCount, i+1);
CEikonEnv::Static()->InfoMsg(text);
// 文字列のセットと書き込み

timer.After(status, KTimerInterval); // タイマーを開始し
User::WaitForRequest(status); // タイマーの終了を待つ
}
timer.Close(); // timerリソースのクローズ(1)
}
}
ここで重要な点は、RクラスはClose()関数(コメント(1)で書いてある箇所など)で、必ず終了処理を行う必要がある点です。通常のデストラクタ以外に処理が必要です。

Mクラス

 Mクラスは「純粋仮想関数のみを提供するクラス」であり、APIなどに関数等の名前解決を提供するクラスになります。換言すれば、Javaのインターフェースとほぼ同じ役割を持ちます。Mクラスに関しては、後の連載で出てくるサンプルで確認していきたいと思います。
posted by シンビアン at 09:15| Comment(0) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

Mach

読み方 : マーク
別名 : Machマイクロカーネル, マークマイクロカーネル, Mach micro-kernel
分野 : OS

 カーネギーメロン大学のRichard Rashid教授らのグループが米国防総省の援助を受けて開発したマイクロカーネル型のOS。1985年に最初のバージョンが公開され、その後、改良が進められると同時に、商用・非商用を含め多くのOSの基盤部分として採用された。

 Mach自体は、従来のOSの中核部分を抜き出して再構成したようなソフトウェアになっており、その上で動作する多くのサブシステムをあわせて、OSとしての機能を発揮するようになっている。

 また、複数のCPUを動作させるマルチプロセッサ機能や、ネットワーク上に分散したコンピュータが連携するための機能、巨大なメモリ空間のサポートなどが盛り込まれている。

 それまでのUNIX系OSのカーネルは、多くの機能を抱え込んで複雑になりすぎ、また、機種依存コードとそうでないものが混在していたため、他機種への移植に莫大な労力が必要だった。

 これに対し、MachはOSとして必要最低限の機能だけを提供し、また、ハードウェアの設計に依存する部分を自身の内部に隠蔽した。このような設計は、従来の「複雑な」OS基盤(モノリシックカーネル)と区別して「マイクロカーネル」と呼ばれる。

 Machを基盤に採用したOSは、サブシステムを追加することでOSの機能を拡張することができ、また、最小限の労力で他機種へ移植できる。ただし、マイクロカーネルと上位層の通信にかかるオーバーヘッドのせいで、処理速度が向上させにくいという欠点もある。

 当初のMachはまだ多くの機能を自身の中に抱え込んでおり、事実上モノリシックカーネルだったが、バージョンアップするたびに機能を外部に出し、そのたびにパフォーマンスが下がっていった。このため、いったん外に出した機能をカーネル空間に戻したり、ライブラリでエミュレートしたりすることを余儀なくされた。

 こうしたマイクロカーネルの欠点はいまだ克服されておらず、実用性やパフォーマンスを重視する多くのOSはモノリシックカーネルを採用している。

 Machを採用しているOSには、IBM社のOS/2やOSFのOSF/1、Apple社のMac OS X、NeXT社のNEXTSTEPとOPENSTEPなどがある。
posted by シンビアン at 08:35| Comment(0) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

広告


この広告は60日以上更新がないブログに表示がされております。

以下のいずれかの方法で非表示にすることが可能です。

・記事の投稿、編集をおこなう
・マイブログの【設定】 > 【広告設定】 より、「60日間更新が無い場合」 の 「広告を表示しない」にチェックを入れて保存する。


×

この広告は1年以上新しい記事の投稿がないブログに表示されております。