リソースの取り扱い

1. まえがき

 前回はGUI構造体へのアクセスに関する説明を行いました。CPUが変わったことによってEndian、バイトアライメントが変わり、それを解消するためにShadow Structureが導入され、結果としてアクセスの方法を変えなければならなくなったことを理解していただけたでしょうか。

 今回もShadow Structureに関する注意点、変更点について解説します。

2. サンプル プログラム

 今回のサンプル プログラムは簡単なペイント プログラムです。 OS4対応版 OS5対応版

図 1

図1のようにペンでノートを取ることができ、SAVEボタンを押すと保存されます。LOADボタンを押すと最後にSAVEした画像が画面に現れます。なおこのプログラムは、ハイレゾ、ハイデンシティ画面に対応していませんのでSAVE/LOADすると画像は標準解像度(160x160)になってしまいます。ご容赦ください。

 サンプルプログラムを解説します。ただし、画面に描画する部分については今回の内容とは直接関係ありませんので、詳細な説明は省略します。

 まずはMainFormHandleEventです。

static Boolean MainFormHandleEvent(EventPtr eventP)
{
  Boolean handled = false;
  FormPtr frmP;

  switch (eventP->eType)
    {
  .
  .
  .
  .
      
    case penMoveEvent:
    case penDownEvent:
      if (( eventP->screenY > 15 ) && ( eventP->screenY <140)) {
        HandlePenDown ( eventP->screenX, eventP->screenY);
        handled = true;
      }
      break;

    case ctlSelectEvent:
      switch ( eventP->data.ctlSelect.controlID){
        case MainSaveButton:
          SaveScreen();
          handled = true;
          break;
                
        case MainLoadButton:
          LoadScreen();
          handled = true;
          break;
      }
      break;
      
  .
  .
  .

    default:
      break;
    
    }
  
  return handled;
}

行っている処理は3つです。画面内の描画エリアで、ペン関連のイベントが発生したら軌跡を表示するHandlePenDownを呼び出します。そして、SAVEボタンが押されたときは画面の保存を行うSaveScreen()、LOADボタンが押されたときは保存された描画データを読み込むLoadScreen()を呼び出しています。

 次はHandlePenDownです。画面からペンが離されるまでペンの座標をTrackし、画面に線を表示します。

static void HandlePenDown( Coord x, Coord y)
{
  Int16 new_x, new_y;
  Boolean pen_down;
    
  while ( true ){
    EvtGetPen ( & new_x , &new_y, &pen_down);
    if ( pen_down == false) // Pen removed from screen
      break;
    
    WinDrawLine( x,y, new_x, new_y );
    x = new_x;
    y = new_y;
  }

}

次が画面の保存を行うSaveScreen()です。

// 画面内の描画エリア
RectangleType CANVAS = { { 0,15} , { 160,125 } };

static void SaveScreen(void)
{
  WindowType *save_win;
  BitmapType *bmp;
  LocalID dbID;
  DmOpenRef db;
  MemHandle h;
  MemPtr p;
  UInt16 at;
  Err err;

// Bitmapを作成し、画面をコピーする。
// これにより画面のフォーマット(ローレゾ、ハイレゾ、カラー)に関わらず
// 決まったデータ フォーマット(ローレゾ、8Bit)が保証される

// まず8bitカラーでBitmapを作成する。BmpCreateで作ったものは必ずローレゾになる。
  bmp = BmpCreate( CANVAS.extent.x, CANVAS.extent.y, 8, 0, &err);

// Bitmapに対する描画をするためにOffscreenWindowを作る。
  save_win = WinCreateBitmapWindow( bmp, &err);

// 画面をコピー。ここで解像度、カラーの変換が自動的に行われる
  WinCopyRectangle( WinGetDrawWindow(), save_win, &CANVAS, 0,0 , winPaint);

// 保存用のデータベースを作成する。
// すでに存在する場合はまずそれを消す
  dbID = DmFindDatabase( 0, "InkerDatabase");
  if ( dbID )
    DmDeleteDatabase( 0, dbID);

// データベースを作成し、オープンする
  DmCreateDatabase( 0, "InkerDatabase", appFileCreator, 'DATA', false);
  dbID = DmFindDatabase( 0, "InkerDatabase");
  db= DmOpenDatabase( 0, dbID, dmModeReadWrite );

// 保存用のレコードを作製する
// レコードのサイズはBitmapチャンクのサイズをMemPtrSizeで取得している
  at = 0;
  h = DmNewRecord( db, &at, MemPtrSize( bmp ));

// Bitmapリソースをレコードに書き込む
  p = MemHandleLock (h);
  DmWrite( p, 0, (MemPtr) bmp, MemPtrSize( bmp));
  MemHandleUnlock (h);

// Databaseのクローズ
  DmCloseDatabase( db);

// 作成したWindow,Bitmapを開放する
  WinDeleteWindow( save_win, false);
  BmpDelete( bmp);

}

 処理内容についてはコメントをご覧いただければ分かると思いますので詳細は省略しますが、注目していただきたいのはBitmapリソースをそのままデータベースに保存していることです。リソースのFormatはOSのバージョンとともに変更されることがありますので、あまり良い方法とはいえませんが(たとえば、新しいOS上で作ったデータを古いOS上で開くと動かない可能性がある等)、OS4.1以前では一応動作します。

 次は保存した画面を呼び出すLoadScreen()です。

static void LoadScreen(void)
{
  LocalID dbID;
  DmOpenRef db;
  MemHandle h;
  MemPtr p;

// データベースを探す。
  dbID = DmFindDatabase( 0, "InkerDatabase");

// データベースがない(まだ保存されていない)場合はそのままリターン
  if ( dbID ==0 ) // Not save yet
    return;

// データベースを開く    
  db= DmOpenDatabase( 0, dbID, dmModeReadOnly );

// データベースからoffset 0のレコードを取得
  h = DmQueryRecord( db, 0);
  p = MemHandleLock (h);

// レコードの内容はBitmapそのものなので、そのままWinDrawBitmapで描画する。
  WinDrawBitmap( (BitmapType *)p , CANVAS.topLeft.x,CANVAS.topLeft.y);

// 後片付け
  MemHandleUnlock (h);
  DmCloseDatabase( db);
}

 特に説明は不要でしょう。データベースから該当のレコードを取得し、それをWinDrawBitmapで画面に表示しています。

3. Palm OS 5での問題点

 このプログラムはPalm OS 5搭載機では正常に動作しません。その理由はShadow Structureにあります。

図 2

 図2に示すように、Palm OS 4以前ではOSが利用するBitmap構造体および画像データに、Memory Chunkとしてそのままアプリケーションからアクセスすることができましたので、上記のようなコーディングが可能でした。

 しかしPalm OS 5では、アプリケーションから直接Bitmap構造体にアクセスすることはできず、アクセスできるのはShadow Structureだけです。しかもBitmap構造体のShadow Structureには画像データが含まれていません。

 もしOS内部のBitmap構造体へのポインタが取得できたとしても、それはARM形式のデータになるため、保存はできても、呼び出しができないことになります。Plam OS 5では、アプリケーションがARM形式のBitmapをパラメータとして渡すことを想定していないためです。
 またShadow StructureはOS4以前とは異なり、Memory Chunkとしてあつかうことはできません。

 Palm OS 5上では、まず以下の部分で問題が起きます

  h = DmNewRecord( db, &at, MemPtrSize( bmp ));

 アプリケーションがbmpで指し示しているのはShadow Structureです。そしてShadow StructureはMemory Chunkではないため、MemPtrSizeを適用した時点でFatal Errorが発生します。

 これを避けるために、

  h = DmNewRecord( db, &at, BmpSize( bmp ));

と書き直すとこの時点でエラーは出なくなりますが、書き込みの時にまた問題が起こります。
※ BmpSizeはBitmapのヘッダー、カラーテーブル、画像データ全てを合計したサイズを返却します。

  DmWrite( p, 0, (MemPtr) bmp, BmpSize( bmp));

 ここで、Shadow Structureおよびそれに続くゴミデータを書き込んでしまうことになります。

 Saveが正しく行われていないために、当然Loadも動かないことになります。

4. Palm OS 5への対応方法

 Palm OS 5対応させるにはいくつかの方法が考えられますが、今回はデータのフォーマットを変更することでPalm OS 5への対応を行います。フォーマットが変わりますので、Palm OS 4対応版との間でのデータの互換性はなくなってしまいます。
 ※ フォーマットを変更したくない場合の対応については後述します。

 今回の対応はBitmapリソースではなく、Bitmap内のデータのみを保存する方法です。OS 5ではBitmap構造体は変化してしまいましたが、Bitmap内のBitsと呼ばれるBitデータ自体は変化していないため、OS 4とOS 5でBitmap構造体が変わった問題を避けることができます。

 では、以下変更後のプログラムを見てみましょう。

 まずはMainFormHandleEvent及びHandlePenDownには変更はありません。

 SaveScreenです。

static void SaveScreen(void)
{
  WindowType *save_win;
  BitmapType *bmp;
  LocalID dbID;
  DmOpenRef db;
  MemHandle h;
  MemPtr p;
  UInt16 at;
  Err err;
  UInt32 bits_size;
  UInt32 size;

// Bitmapを作成し、画面をコピーする。
// これにより画面のフォーマット(ローレゾ、ハイレゾ、カラー)に関わらず
// 決まったデータ フォーマット(ローレゾ、8Bit)が保証される

// まず8bitカラーでBitmapを作成する。BmpCreateで作ったものは必ずローレゾになる。
  bmp = BmpCreate( CANVAS.extent.x, CANVAS.extent.y, 8, 0, &err);

// Bitmapに対する描画をするためにOffscreenWindowを作る。
  save_win = WinCreateBitmapWindow( bmp, &err);

// 画面をコピー。ここで解像度、カラーの変換が自動的に行われる
  WinCopyRectangle( WinGetDrawWindow(), save_win, &CANVAS, 0,0 , winPaint);

// 保存用のデータベースを作成する。
// すでに存在する場合はまずそれを消す
  dbID = DmFindDatabase( 0, "InkerDatabase");
  if ( dbID )
    DmDeleteDatabase( 0, dbID);

// データベースを作成し、オープンする
  DmCreateDatabase( 0, "InkerDatabase", appFileCreator, 'DATA', false);
  dbID = DmFindDatabase( 0, "InkerDatabase");
  db= DmOpenDatabase( 0, dbID, dmModeReadWrite );


// 保存用のレコードを作製する
// レコードのサイズはBitmapチャンクのサイズをMemPtrSizeで取得している
  at = 0;
  h = DmNewRecord( db, &at, MemPtrSize( bmp ));


// 保存用のレコードを作製する
// レコードのサイズはBitmapのデータサイズをBmpBitsSizeによって取得している
  at = 0;
  size = BmpBitsSize( bmp);
  h = DmNewRecord( db, &at, size);

// Bitsデータをレコードに書き込む。Bitsの先頭アドレスはBmpGetBitsで取得する
  p = MemHandleLock (h);
  DmWrite( p, 0, BmpGetBits( bmp ),size );
  MemHandleUnlock (h);
  
  DmCloseDatabase( db);

  WinDeleteWindow( save_win, false);
  BmpDelete( bmp);

}

 従来はBitmap構造体をそのまま書き込んでいたのを、Bitsのみを書き込むように変更します。

 次は画面を読み込むLoadScreenです。

static void LoadScreen(void)
{
  WindowType *save_win;
  LocalID dbID;
  DmOpenRef db;
  MemHandle h;
  MemPtr p;
  UInt16 at;
  Err err;
  BitmapType *bmp;

// まず、データを読み込むためのBitmapを作成する。
  bmp = BmpCreate( CANVAS.extent.x, CANVAS.extent.y, 8, 0, &err);

// データベースを探す。
  dbID = DmFindDatabase( 0, "InkerDatabase");

// データベースがない(まだ保存されていない)場合はそのままリターン
  if ( dbID ==0 ) // Not save yet
    return;

// データベースを開く    
  db= DmOpenDatabase( 0, dbID, dmModeReadOnly );

// データベースからoffset 0のレコードを取得  
  h = DmQueryRecord( db, 0);
  p = MemHandleLock (h);

// データベース内の情報を作成したBitmapのBits内にコピーする。
// 注意!!:この方法はSAVEした情報とLOADする情報のBitmapサイズがまったく同じ場合にのみ動作します!!!
  MemMove( BmpGetBits( bmp ), p, BmpBitsSize( bmp));

// Bitmapを画面に表示
  WinDrawBitmap( bmp, CANVAS.topLeft.x,CANVAS.topLeft.y);
  MemHandleUnlock (h);

// データベースをClose
  DmCloseDatabase( db);
// Temporaryに作ったBitmapを解放
  BmpDelete( bmp);
}

 この方法ではBitsデータのみを保存していますので、Bitmapのサイズ、カラー数などの情報がまったく含まれていないことに注意しなければなりません。今回のサンプルでは描画エリアのサイズを固定としましたが、サイズ、カラー数などが可変の場合はそれらの情報を別途格納する必要があるでしょう。

5. データ互換性が必要な場合

 旧バージョンとの間でデータの互換性が必要な場合、問題は多少複雑になります。旧バージョンで作成したビットマップリソースをPalm OS 5上のアプリケーションが読み込むことは標準で可能ですが、Palm OS 5上のアプリケーションでPalm OS 4以前のフォーマットのビットマップリソースを作ることができないためです。
 どうしてもということであれば、旧バージョンのビットマップリソースをアプリケーションで作成することになります。具体的には説明しませんが、Bitmap.hで定義されているBitmapTypeV2構造体を用いて、自ら構造体のフィールドを埋めていくことになります。各フィールドにどのような値が入るのかはPalm OS 4以前で作成したビットマップデータを参考にすることになるでしょう。
 ただ、この方法はOSへの依存度が高いためあまりお勧めできる方法ではありません。OSには依存しない形式で情報を格納するほうが望ましいでしょう。


 従来のPalm OSでは、処理の効率化のために実行時のフォーマット(メインメモリー上の構造体フォーマット)とデータ交換用のフォーマット(リソース等)が同じになっていることが多いのですが、Palm OS 4からOS 5への移行のように、アーキテクチャの変更が必要になる際にはそれが大きな足かせとなってきます。ARMネイティブな開発環境が用意されるPalm OS 6以降では、実行用のフォーマットと交換用のフォーマットが区別されるようになるのではないでしょうか。



copyright(C)2002 Hacker Dude-san all rights reserved.