問題は同期式入力キュー(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 に多謝。
この API で、呼び出し元はメッセージキューを本質的に「ハイジャック」できる。
この API で、デバッガはデバック対象のメッセージキューがどのハンドルなのかを確定する。
これが、(ブロックされているはずの)デバッグ対象に成り代わって、デバッガがメッセージを返信する方法である。
上記と同様、これでデバッガがデバッグ対象に成り代われる。
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.