 |
Technote 1118
Unlocking GDHandles Considered Harmful
目次
症状
問題
解決策
要約
|
最近アップルコンピュータではMac OSや一部のサードパーティのコードがクラッシュの要因となっていることを確認しました。
この現象は特定のQuickDrawデータ構造のロックを解除した時に起こります。その後、ロックが解除されたデータ構造にアクセスしようとするコードがあると、データ構造がロックされている前提であるため、Macintoshがクラッシュしてしまうことがあります。
このテックノートはGDHandleデータ構造を直接参照するデベロッパの皆様と、この問題について情報を求めているユーザの方が対象です。 |
症状
多くの場合、この問題によってStdTextがクラッシュします(MacsBugではNQDStdTextと報告されることがありますが、NQDStdTextはStdTextのPowerPCバージョンです)。クラッシュが起こるのは、メモリを動かすような関数が呼び出されたあとにGDHandleの内容が参照された時です。
注意が必要なのは、この問題によるクラッシュが必ずしも原因となるコードの実行時ではなく、その後様々な所で起こる可能性があることです。例えば、割り込み時に実行されるカーソル描画関数がクラッシュする可能性があります。さらに、メモリマネージャがGDHandleのメモリブロックを移動している最中にアクセスされ、クラッシュが起こる可能性もありますが、これは非常に稀なケースと考えて良いでしょう。
問題
NewGDevice ()を使ってGDHandleを作成すると、GDHandleはロックされた状態で返されます。QuickDrawはGDHandleが常にロックされている前提で動作をします。問題の要因は、サードパーティアプリケーションとアップルのモニタ&サウンドコントロールパネル(Mac OS 8のバージョンを含む)が以下のようなコードを実行していることです。
// 問題のコード開始
GDHandle gdh = GetMainDevice ();
HLock ((Handle) gdh);
// gdhが移動されるようなコードを実行する
HUnlock ((Handle) gdh);
// ここが問題のコードです
// 問題のコード終了
|
上記のコードは実行の際にGDHandleがロックされていることが前提で書かれており、最終的にGDHandleのロックを解除してしまいます。(皮肉なことに、GDHandleをロックする必要も最初からありませんでした。)
註:
モニタ&サウンドコントロールパネルの次期バージョンはこのような問題を起こさないよう修正されます。
システムのGraphic Deviceリストに含まれるGDHandleは常にロックされていることが必須です。この条件はColor QuickDrawの初期リリース(Mac IIのROM)から変わっていません。また、当初のソースコードでもシステムのGraphic Deviceリスト内のGDHandleはロックを解除してはならないと記述されていました。
つまり、症状が把握されたのは最近のことですが、問題の要因は常に存在していました。メモリマネージャのメモリブロック管理アルゴリズムは非常に複雑で、問題を起こすようなアプリケーションを走らせても症状が出たり、出なかったり、問題の再現性は低かったです。アプリケーション側で問題を起こしていることに気が付かない場合が多かったです。
アップルコンピュータの技術文献を検索したところ、システムGraphic Deviceリストに含まれているGDHandleのロックの解除については触れていません。Macintoshのプログラミングでは、他のコードが管理するハンドルの属性を変更する場合は特に気を付けることが常識でしたが、この度はこのルールを明確に提示します(以下のデベロッパが取れる対応策をご覧下さい)。 |
解決策
解決策は2つあります。1つ目はデベロッパが取れる対応策です。2つ目はユーザ(デベロッパを含む)が取れる回避策です。
デベロッパが取れる対応策
まず、システムのGraphic Deviceリストに含まれるようなGDHandleはロックを解除しないことが一番重要です。GDHandleを確実にロックする場合は以下のようなコードを実行して下さい。
GDHandle gdh = GetMainDevice ();
SInt8 hState = HGetState ((Handle) gdh);
HLock ((Handle) gdh);
// gdhが移動されるようなコードを実行する
HSetState ((Handle) gdh, hState);
|
上記のコードはGDHandleの状態(ロックされているかどうか)を確認してからハンドルをロックし、最終的にハンドルを元の状態に戻します。つまり、GDHandleは上記のコードの実行前と実行後では属性が変わりません。
理想的な環境においては、GDHandleは常にロックされているはずなので、GDHandleをロックする必要さえありません。しかし、他のアプリケーションやソフトウェアがシステムのGraphic DeviceリストのGDHandleのロックを誤って解除することも考えられますので、上記のようなコードを実行することが一種の保険です。
もう1つの対処方法はGDHandleを使う代わりに、GDHandleの必要フィールドをすべてローカル変数にコピーすることです。GDHandleのフィールドの多くは小さいので、それほどの負担にならないはずです。
アップルコンピュータでは、システムGraphic Deviceリストに含まれていないGDHandleをロックする必要はないと見ています。例えば、NewGWorld ()で作成されるGDHandleはロックが解除された状態で返されます。
GDHandleをロックする必要がある場合、もっとも安全かつ容易な方法はGDHandleの状態を保存して、処理後に元に戻すことです。これはGDHandleがシステムのGraphic Deviceリストに含まれているかどうかに関わらず、問題を起こしません。
註:
上記の対応策はバイナリで既に提供されているコードには適応できません。このようなバイナリがGDHandleのロックを解除してしまうとクラッシュの要因となります。NQDStdTextなどのシステム内部の関数はGDHandleを予めロックするようにはなりません。上記の対応策を取ったアプリケーションはクラッシュを免れて、クラッシュの要因となることはありません。しかし、他のバイナリがGDHandleのロックを解除してしまうと、システムがクラッシュする可能性はあります。
アップルコンピュータが発行している文献にはHUnlock ()やHGetState ()とHSetState ()の様々な条件での使い分け方があまり解説されていませんでしたので、DTSはこの話題について近日中にテックノートを発行する予定です。 |
ユーザが取れる回避策
このテックノートの以前のバージョンではこの問題を回避するための機能拡張を配付すると記述されていましたが、このような機能拡張は配付されません。
要約
システムのGraphic Deviceリストに含まれているGDHandleは常にロックされていることが必須です。一部のサードパーティアプリケーションとアップルコンピュータのモニタ&サウンドコントロールパネルはGDHandleのロックを解除してしまい、クラッシュの要因となっています。システムのGraphic Deviceリストに含まれるGDHandleを取り扱う場合、デベロッパはHGetState ()やHSetState ()を使ってGDHandleの属性を保つことが重要です。また、ユーザ(デベロッパを含む)は機能拡張(テックノートを参照)をインストールすることでこの問題を回避することができます。
参考文献
変更履歴
1998年2月 テクニカル・ノート発行
1998年6月 「ユーザが取れる回避策」の内容を削除
|