(note : これは OpenWatcom 0.8.4 のソースアーカイブ中に含まれている、OS/2 非公開 API に関するテキストを無断翻訳したものです。原文に対する整合性とかはあんまり期待しないほうがいいかも)

荒野を探検する

Open Watcom デバッガの OS/2 版は、プレゼンテーションマネージャ(PM)プログラムの効果的なデバッグを可能とするため、あまり知られていないいくつかの制約を突破する必要がある。

問題は同期式入力キュー(SIQ : Synchronous Input Queue)メカニズムにある。 PM はメッセージをきっちりと生成順に処理する。 これでメッセージ処理から多少の曖昧さが除去されるわけだが、べつの(おそらくは余計に厄介な)問題を引き起こす。 そのひとつが、(たとえば、ブレークポイントにぶつかって)アプリケーションがブロックされたとき、アプリケーションがメッセージに応答できず PM 全体が影響をこうむることである。

この事態を打開するため、IBMは(あるいは、暗黒時代にあったかつてのマイクロソフトかも)きわめて特殊な API をいくつか実装した。 悲しいことだが、API のいくつかはそもそもの性質からして不明瞭であるのみならず、全くもってドキュメント化されていない。

これらの API の研究は、かなり荒野の探検と似てくる。 どこに向かっているのかも定かではなく(できればここで終りにしたい、という場所ならいくつかあるが)、わかることといえば、次に猛獣に出っくわしたら血みどろの結果になるいうことぐらい。 当人たちにとってはたとえ話でなく、それは幾度もリブートしなければならないことを意味するのだ。

これが API の一覧である。
引数の合計バイト数 PMWIN PMMERGE
WinLockInput 6 308 3306
WinThreadAssocQueue 8 304 3296
WinQueueFromID 8 305 3220
WinReplyMsg 16 307 3233
WinQuerySendMsg 16 306 3201

これら API のすべては16ビットと32ビットのエントリーポイントを持っており、16ビットのものはかつての OS/2 1.x インポートライブラリ(OS2.LIB)に存在していたが、OS/2 2.x Toolkit バージョン(OS2286.LIB と OS2386.LIB)からは除去された。 DDK の「プライベート」なインポートライブラリ(OS2286P.LIB と OS2386P.LIB)には依然存在するが。

それでは API を掘り返そう。 情報の大部分を提供してくれた Dave Blaschke に多謝。

BOOL APIENTRY WinLockInput( HMQ hmqQueue, BOOL fLock );
fLock フラグが TRUE の場合、WinLockInput は hmqQueue で指定されるメッセージキューの入力を、fLock を FALSE にセットした呼び出しでアンロックするまでの間、ロックする。 指定キューで入力がロックされたとき、マウスとキーボードの入力はアンロックされるまでキューに与えられない。 hmqQueue が NULL の場合、現在動作中のもの以外すべてのキューがロック/アンロックされる。
成功時は TRUE、そうでなければ FALSE を返す。

BOOL APIENTRY WinThreadAssocQueue( HAB habAnchor, HMQ hmqQueue );
hmqQueue で指定されるメッセージキューを現行スレッドに結びつける。 habAnchor は呼び出し元のアンカーブロック。
成功時は TRUE、そうでなければ FALSE を返す。

この API で、呼び出し元はメッセージキューを本質的に「ハイジャック」できる。

HMQ APIENTRY WinQueueFromID( HAB habAnchor, USHORT usPid, USHORT usTid );
この API は任意のプロセス/スレッドのプロセス ID(usPid)とスレッド ID(usTid)を入力に用い、関連付けられたメッセージキューハンドルを返す。
usPid/usTid に関連付けられたメッセージキューのハンドルを、ハンドルが存在しないときは NULL を返す。

この API で、デバッガはデバック対象のメッセージキューがどのハンドルなのかを確定する。

BOOL APIENTRY WinReplyMsg( HAB habAnchor, HMQ hmqSender, HMQ hmqReceiver, MRESULT result );
正確な機能の全貌は明らかでない。 引数は呼び出し元アンカーブロック(habAnchor)、送り手のメッセージキュー(hmqSender)、受け手のメッセージキュー(hmqReceiver)、そしてメッセージ結果(result)。 この API は送り手から受け手へ送付されているメッセージを横取りし、結果を代返してやるものだろう。 hmqSender は見たところ NULL にできそうであり、こうするとどの送り手からのメッセージも横取りすることになる。
成功時は TRUE、そうでなければ FALSE を返す。

これが、(ブロックされているはずの)デバッグ対象に成り代わって、デバッガがメッセージを返信する方法である。

BOOL APIENTRY WinQuerySendMsg( HAB habAnchor, HMQ hmqSender, HMQ hmqReceiver, PQMSG pqmsgMsg );
WinReplyMsg と対を成すものである。 引数は呼び出し元アンカーブロック(habAnchor)、送り手のメッセージキュー(hmqSender)、受け手のメッセージキュー(hmqReceiver)、そしてメッセージ構造体(pqmsgMsg)。 成功した場合、API は送り手から受け手へ送ることになるメッセージでメッセージ構造体を埋めるようだ。 hmqSender が NULL ならば、すべての送り手からのメッセージを受信するようである。
成功時は TRUE、そうでなければ FALSE を返す。

上記と同様、これでデバッガがデバッグ対象に成り代われる。


とりあえず原文も貼っとこうかと。

   Exploring the Wilderness
   ========================

 The OS/2 version of the Open Watcom debugger needs to jump through some
rather obscure hoops in order to be able to effectively debug Presentation
Manager (PM) programs.

 The problem lies in the Synchronous Input Queue (SIQ) mechanism. PM processes
messages strictly in the order they were generated. This removes some
ambiguity from message processing but causes other (quite possibly much worse)
problems. One of them is that if an application is blocked (for instance
because it hit a breakpoint), it can't respond to messages and the entire
PM will be affected.

 To overcome this issue, IBM (or more likely Microsoft way back in the Dark
Ages) implemented several highly specialized APIs. Sadly, some of the APIs
are not only obscure by their very own nature but also completely undocumented.

 Which makes working with these APIs a lot like exploring the wilderness.
You don't know where exactly you're going (though hopefully you have some idea
where you want to end up) and the only thing you can tell about the next
wildlife encounter is that the results will be messy and gory. For those who
don't appreciate metaphors, that means you'll have to reboot a lot.

 Here is a list of the APIs:

                        parm wds   PMWIN    PMMERGE
 WinLockInput              6        308       3306
 WinThreadAssocQueue       8        304       3296
 WinQueueFromID            8        305       3220
 WinReplyMsg              16        307       3233
 WinQuerySendMsg          16        306       3201

 All of these APIs have 16 and 32-bit entry points. Curiously enough, the
16-bit ones were all present in the old OS/2 1.x import library (OS2.LIB) but
were removed from the OS/2 2.x Toolkit versions (OS2286.LIB and OS2386.LIB).
They are still present in the "private" DDK import libraries (OS2286P.LIB
and OS2386P.LIB) however.

 And here's the scoop on these APIs. Big thanks to Dave Blaschke for providing
the bulk of this information.


BOOL APIENTRY WinLockInput(HMQ hmqQueue, BOOL fLock);

  If the fLock flag is TRUE, WinLockInput will lock input to the message
  queue specified in hmqQueue until it is unlocked by a call with fLock
  set to FALSE. When input is locked for a given queue, mouse and keyboard
  input will not be given to the queue until input is unlocked. If hmqQueue
  is NULL, input to all message queues _except_ the currently running one
  is locked/unlocked.
  Returns TRUE if successful, FALSE otherwise.

  Prescription use only! If you manage to lock input to all other queues
  except yours and then do something stupid, you're hosed.


BOOL APIENTRY WinThreadAssocQueue(HAB habAnchor, HMQ hmqQueue);

  Associates the message queue specified in hmqQueue with the currently
  running thread. The habAnchor parameter is the anchor block of the
  caller.
  Returns TRUE if successful, FALSE otherwise.

  This API allows the caller to essentially "hijack" a message queue.


HMQ APIENTRY WinQueueFromID(HAB habAnchor, USHORT usPid, USHORT usTid);

  This API takes the process ID (usPid) and thread ID (usTid) of any
  process/thread in the system as input and returns the associated message
  queue handle.
  Returns handle of message queue associated with usPid/usTid or NULL if
  there is none.

  This API allows the debugger to determine what the handle to debuggee's
  message queue(s) is.


BOOL APIENTRY WinReplyMsg(HAB habAnchor, HMQ hmqSender,
                          HMQ hmqReceiver, MRESULT result);

  The exact functionality is not entirely clear. The parameters are the
  caller's anchor block (habAnchor), handle to sender's message queue
  (hmqSender), receiver's message queue (hmqReceiver) and message result.
  This API should intercept a message being sent from sender to receiver and
  return 'result' instead. The hmqSender parameter can apparently be NULL,
  resulting in the interception of messages from any sender.
  Returns TRUE if successful, FALSE otherwise.

  This is how a debugger can reply to messages on behalf of the (presumably
  blocked) debuggee.


BOOL APIENTRY WinQuerySendMsg(HAB habAnchor, HMQ hmqSender,
                              HMQ hmqReceiver, PQMSG pqmsgMsg);

  This is a counterpart to WinReplyMsg. The parameters are the caller's
  anchor block (habAnchor), handle to sender's message queue (hmqSender),
  receiver's message queue (hmqReceiver) and a pointer to the message
  structure (pqmsgMsg). If successful, this API should fill in the message
  structure with a message being sent from sender to receiver. If hmqSender
  is NULL, messages from all senders are apparently received.
  Returns TRUE if successful, FALSE otherwise.

  Similar to the above, this lets a debugger act on behalf of the debuggee.