UI構造体へのアクセス

1. まえがき

前回解説したように、(※第1回へ)Palm OS 5ではFom、Control、Window、Bitmap等、各種GUI構造体のメンバに直接アクセスすることは推奨されておらず、アクセス用に用意されたPalm OS関数を使うことになっています。また、Palm OS 5 SDKの標準状態でGUI構造体のメンバへ直接アクセスはできません。今回はPalm OS 5 の構造体へのアクセスについて、次の2つの段階で行う対応の方法を説明します。

  • Palm OS 5 SDKでとりあえずコンパイルを通す
  • 正式なAPIを使って構造体にアクセスする

2. サンプルプログラム

今回のサンプルは、非常に簡単なアプリケーションです。起動すると画面1のように2つのボタンが表示されます。

"I will Move"と表示されたボタンは押しても何も起こりません。"Move Button"と書かれたボタンを押すともう一方のボタンが移動し、同時にボタン内のフォントがスタンダードとラージの間で交互に変わります。このようにButton Controlの位置とフォントを変更するのが主な内容になります。


ソースコードを見てみましょう。まずMainFormのハンドラです。

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

    switch (eventP->eType) {
      .
      .
      .
    case ctlSelectEvent:
      if (eventP->data.ctlSelect.controlID == MainMoveButton){
        MoveButton();
        handled = true;
        break;
      }
      break;
    default:
      break;
    }
    return handled;
  }

Move Buttonが押された場合にMoveButton()を呼び出すだけのものです。次にMoveButton()を見てみます。

static void MoveButton(void) {
  ControlType *button;
  RectangleType button_region;

  // ボタンのPointerを取得し、一旦非表示にする。
  button = GetObjectPtr( MainMovingButton);
  CtlHideControl( button);

  // ボタンの座標を取得
  button_region = button->bounds;

  // ボタンの座標を(+3,+3)ピクセル移動する
  RctOffsetRectangle( &button_region, 3,3);

  // 変更した座標をボタン構造体へ反映
  button->bounds = button_region;

  // フォントをstdFont<->largeFontで交互に変更
  if( button->font == stdFont)
    button->font = largeFont;
  else
    button->font = stdFont;

  // 変更が完了したので、再度表示する。ここで変更が反映される。
  CtlShowControl( button);
  }

このようにMoveButton()ではいったんボタンを非表示にし、座標とフォントを変更したあとで再度表示しています。ここではControl構造体のメンバのうちboundsとfontにアクセスし、変更しています。こういった手法はPalm OS 4までは問題なかったのですが、Palm OS 5では問題となります。

3. Palm OS 5 SDKでコンパイル

まずこのソースコードをPalm OS 5 SDKでコンパイルしてみます。図1のようにエラーが出てきます。

なぜエラーになってしまうのでしょうか? これを調べるためにPalm OS Support\Incs\Core\UI\にあるControl.hを見てみましょう。ControlTypeの定義は以下のようになっています。

typedef struct ControlType
  #ifdef ALLOW_ACCESS_TO_INTERNALS_OF_CONTROLS // These fields will not be available in the next OS release!
  {
    UInt16 id;
    RectangleType bounds;
    Char * text;
    ControlAttrType attr;
    ControlStyleType style;
    FontID font;
    UInt8 group;
    UInt8 reserved;
  }
  #endif
  ControlType;

このようにALLOW_ACCESS_TO_INTERNALS_OF_CONTROLSが定義されていない限り、構造体のメンバにアクセスできないようになっています。次にPalm OS Support\IncsにあるBuildDefaults.hを見てみます。

  #define DO_NOT_ALLOW_ACCESS_TO_INTERNALS_OF_STRUCTS

  #ifndef DO_NOT_ALLOW_ACCESS_TO_INTERNALS_OF_STRUCTS

  #define ALLOW_ACCESS_TO_INTERNALS_OF_CLIPBOARDS
  #define ALLOW_ACCESS_TO_INTERNALS_OF_CONTROLS
  #define ALLOW_ACCESS_TO_INTERNALS_OF_FIELDS
  #define ALLOW_ACCESS_TO_INTERNALS_OF_FINDPARAMS
  #define ALLOW_ACCESS_TO_INTERNALS_OF_FORMS
  #define ALLOW_ACCESS_TO_INTERNALS_OF_LISTS
  #define ALLOW_ACCESS_TO_INTERNALS_OF_MENUS
  #define ALLOW_ACCESS_TO_INTERNALS_OF_PROGRESS
  #define ALLOW_ACCESS_TO_INTERNALS_OF_SCROLLBARS
  #define ALLOW_ACCESS_TO_INTERNALS_OF_TABLES
  #define ALLOW_ACCESS_TO_INTERNALS_OF_BITMAPS
  #define ALLOW_ACCESS_TO_INTERNALS_OF_FONTS
  #define ALLOW_ACCESS_TO_INTERNALS_OF_WINDOWS
  #endif

このように標準の状態ではALLOW_ACCESS_TO_INTERNALS_OF_CONTROLSが定義されていないために構造体のメンバにアクセスすることができず、エラーがでていると考えられます(ちなみに以前のSDKではALLOW_ACCESS_TO_INTERNALS_OF_CONTROLSが定義されていたためにエラーが出なかったことになります)。
このエラーを回避するためには独自にALLOW_ACCESS_TO_INTERNALS_OF_CONTROLSを定義してやればよいことになります。サンプルのターゲットのソースコードの先頭を以下のように変更します。

#define ALLOW_ACCESS_TO_INTERNALS_OF_CONTROLS

#include <PalmOS.h>
#include <PalmOSGlue.h>
#include "StarterRsc.h"

#include <PalmOS.h>の前で定義する必要がありますので、注意してください。これで無事コンパイルが通るようになります。前述のようにControl、Field、Form等構造体ごとに定義が必要になりますので、ここで必要なだけ#define分を並べることになります。

大量のソースコードがあり、大量の構造体にアクセスしている場合は、この変更だけでも大変なことがあります。この場合はあまり良いことではありませんが、前述のBuildDefaults.hを変更してしまうことも可能です。#define DO_NOT_ALLOW_ACCESS_TO_INTERNALS_OF_STRUCTSをコメントアウトしてしまえば、既存のプロジェクトに変更を加えることなくコンパイルはできるようになります。
また、あとで述べるようにGlueライブラリーを使いますのでそのためのヘッダもインクルードしておきます。
ただ、前回説明したように必ずしも構造体の全てのメンバをShadow Structureがサポートしているわけではないため、コンパイルが通ったからといって動作するとは限りません。きちんと試験されることをお奨めします。

4. 正規のAPIに変更

これで、とりあえずコンパイルが通るようになりました。アプリケーションの利用しているライブラリがShadow Structureでサポートされており、正常に動作すれば一安心ですが、あくまでも暫定的な対応に過ぎません。いつShadow Structureのサポートが停止されるかわかりませんので、正規の対応を行いましょう。
対応方法は簡単です。各種メンバに直接アクセスせずに、対応するPalm OS関数を使えばよいのです。ただ各メンバの取得・設定にどのPalm OS関数を使えばいいかを調べるのにかなり時間がかかると思われますので、実行はそれなりに大変でしょう。
たとえばControlTypeには以下のようなメンバがあります。

  UInt16 id;
  RectangleType bounds;
  Char * text;
  ControlAttrType attr;
  ControlStyleType style;
  FontID font;
  UInt8 group;
  UInt8 reserved;

これらのメンバ情報の取得および設定を行うPalm OS関数は以下のようになります。

 取得設定
idFrmGetObjectIdなし
boundsFrmGetObjectBoundsFrmSetObjectBounds
textCtlGetLabelCtlSetLabel
attrなし
(一部はCtlGetEnabled、CtlGetUsable等で取得可)
なし
(一部はCtlSetEnabled、CtlSetUsable等で設定可)
styleCtlGlueGetControlStyleなし
fontCtlGlueGetFontCtlGlueSetFont
groupなしなし

このように関数がForm, Control,とGlue Libraryに分散されているため、探すのはなかなか大変です。id、boundsなどのGUI系で共通のものはFrm*を利用し、GUI部品に特有のものは各部品のManagerとGlueLibraryにあると思えばよいでしょう。
また、必ずしも全てのメンバに対応したPalm OS関数が用意されているわけでもありません。中にはidのようにそもそも設定する必要がないものもありますが、attrのように手段が用意されていないものもあり、これらを操作するアプリケーションでは、異なったプログラミングが必要になります(Palm Sourceは必要性があるもの、高いものに対して順に用意しているようですので、必要な関数がなければPalm Sourceにレポートすると良いでしょう)。
では、これらのPalm OS関数を使って書き直してみます。MoveButton関数は以下のようになります。

static void MoveButton(void) {
  ControlType *button;
  RectangleType button_region;

  // ボタンのPointerを取得し、一旦非表示にする。
  button = GetObjectPtr( MainMovingButton);
  CtlHideControl( button);
  // ボタンの座標を取得
  // button_region = button->bounds;
  // 関数を使用
  FrmGetObjectBounds( FrmGetActiveForm(), FrmGetObjectIndex ( FrmGetActiveForm(), MainMovingButton ) , &button_region);

  // ボタンの座標を(+3,+3)ピクセル移動する
  RctOffsetRectangle( &button_region, 3,3);
  // 変更した座標をボタン構造体へ反映
  // button->bounds = button_region;

  // 関数を使用
  FrmSetObjectBounds( FrmGetActiveForm(), FrmGetObjectIndex( FrmGetActiveForm(), MainMovingButton) , &button_region);
  // フォントをstdFont<->largeFontで交互に変更
  // if( button->font == stdFont)
  // button->font = largeFont;
  // else
  // button->font = stdFont;
  // 関数を使用
  {
    FontID font;
    font = CtlGlueGetFont( button );
    if( font == stdFont)
      font = largeFont;
    else
      font = stdFont;
      CtlGlueSetFont( button, font);
    }
  CtlShowControl( button);
  }

GlueLibraryを使っていますので、Palm OS Support\CodeWarrior Libraries\Palm OS Glue\にあるPalmOSGlue.libをTargetに追加しておきます。
フォント変更の部分はControlへのポインタを渡すだけですみますので、かなり容易に書き直すことができました。一方、FrmGetObjectBoundsのように、これまでとは異なるパラメータをとる関数を使わなくてはならない場合があります。FrmGetObjectBounds/FrmSetObjectBoundsはFormへのポインタとオブジェクトインデックスをパラメータとして与える必要があるため、コードが増えているのが分かります。

今回のサンプルのように、対象となるオブジェクトのIDがわかっている場合は比較的簡単にオブジェクトへのポインタ、オブジェクトインデックスを求めることができますが、場合によってはオブジェクトのポインタしか分からない時もあります。例えば、以下のような関数の内部で使われている場合です。

static void MoveButtonPixels( ControlType *buttonP, Coord move_x, Coord move_y) {
  RctOffsetRectangle( &(buttonP->bounds), move_x, move_y);
}

このような場合は、オブジェクトへのポインタからオブジェクトインデックスを求める必要があります。アプリケーションの動作するプラットフォームがPalm OS 4.0以降のみであれば、FrmGetObjectIndexFromPtrを使って以下のように書き直すことができます。

static void MoveButtonPixels( ControlType *buttonP, Coord move_x, Coord move_y) {
  RectangleType rect;
  FormType *formP;

  formP = FrmGetActiveForm();
  FrmGetObjectBounds ( formP, FrmGetObjectIndexFromPtr( formP , buttonP), &rect );
  RctOffsetRectangle( &rect, move_x, move_y);
  FrmSetObjectBounds ( formP, FrmGetObjectIndexFromPtr( formP , buttonP), &rect );
}

PalmOS 3.5以前ではFrmGetObjectIndexFromPtrが使えませんので、以下のような関数を用意して使いましょう(下記関数はPalm OS Referenceからの抜粋です)。

UInt16 MyFrmGetObjectIndexFrmPtr( FormType *formP, MemPtr objectP) {
  UInt16 index;
  UInt16 objIndex = frmInvalidObjectId;
  UInt16 numObjects = FrmGetNumberOfObjects(frmP)

    for (index = 0; index < numObjects; index++) {
      if (FrmGetObjectPtr(index) == myObjPtr) { // Found it
        objIndex = index;

      break;
    }
  }   return objIndex;
}

オブジェクトID,オブジェクトポインタ、オブジェクトインデックスとオブジェクトを識別する3つの用語が出てきたのでここで整理しておきます。

  • オブジェクトID:リソースエディターで各GUI部品に振られる値(例:1000,1010)。設計時に決定される。
  • オブジェクトインデックス:Form内でのGUI部品の番号を表し、0から始まる一連の値。一般的には実行時にこの値が変更されることは無いが、定数で利用するのは危険である。
  • オブジェクトポインタ:メモリー上でGUI構造体が格納されているアドレス。実行事に変化する。

これら3つに識別子は、以下のような方法で相互に変更することができます。

  • ID->インデックス FrmGetObjectIndex()
  • ID->ポインタ FrmGetObjectPtr()とFrmGetObjectIndex()の組み合わせ
  • インデックス->ID FrmGetObjectID()
  • インデックス->ポインタ FrmGetObjectPtr()
  • ポインタ>ID FrmGetObjectID()とFrmGetObjectIndexFromPtr(もしくはMyFrmGetObjectIndexFromPtr)の組み合わせ
  • ポインタ>インデックス FrmGetObjectIndexFromPtr(もしくはMyFrmGetObjectIndexFromPtr)

5. おわりに

構造体メンバへのアクセスをAPIを使って書き直すことは、多少苦痛を伴う作業ですが、やり方の傾向さえつかめてしまえば機械的に進めることができるでしょう。アプリケーションが将来にわたって安全に動作するためにも、ぜひ行いましょう。



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