Feb 90 Letters
Volume Number: | | 6
|
Issue Number: | | 2
|
Column Tag: | | Letters
|
CheckAbort() Revised
By David E. Smith, Editor & Publisher, MacTutor
Lets Doodat!
Greg King, Ph.D.
SCIEX
Ontario, Canada
This letter is to point out 2 bugs in Lee Neuses check_abort routine (Three Doodats, Sept. 89).
The first bug occurs when the command period event is the last event in the queue. Mr. Neuses code removes the event and then tests to see if it was the last event. This will never succeed because the event has been removed. The result is usually a bus error because the qLink field of the last event contains a -1. The funny thing is that the routine will not crash if the command-period is the only event in the queue.
The second bug is more subtle. Dequeue does not de-allocate the memory for the event entry being removed from the queue, this is the responsibility of the program calling it (IM2-383). Mr. Neuse doesnt do this so every time an event is removed it hangs around the heap.
Below is my version of a command-period handler that addresses the above stated problems. It has been tested for the past week in the print and update loops of a 400K application. It is a scientific application that often has to plot many thousands of data points on the screen, and it is often desirable to be able to both abort long updates and queue up multiple keystroke commands. Please excuse my style, I was originally a Pascal programmer.
/* 1 */
Boolean CmdPeriod()
{
EvQElPtreq_p, theEntry;
Boolean f_found, last, dispose_entry;
f_found = FALSE;
last = FALSE;
/* Start at head of internal queue. */
eq_p = (EvQElPtr) (EventQueue.qHead);
do {
/* Test for end of queue first. */
if (eq_p == (EvQElPtr) EventQueue.qTail)
last = TRUE;
/* Is this a cmd-period event. */
if ((eq_p->evtQWhat == keyDown
|| eq_p->evtQWhat == autoKey)
&& (eq_p->evtQModifiers & cmdKey)
&& (eq_p->evtQMessage & charCodeMask) == .)
{
/* Remove the event from the queue. */
Dequeue((QElemPtr) eq_p, &EventQueue);
f_found = TRUE;
/* Setup for disposal */
dispose_entry = TRUE;
/* Point to the beginning of entry. */
theEntry = (EvQElPtr) ((long) eq_p - 4);
}
else
dispose_entry = FALSE;
if (!last)
/* Continue with next queue entry. */
eq_p = (EvQElPtr) (eq_p->qLink);
/* Dequeue doesnt deallocate entry */
if (dispose_entry)
DisposPtr((Ptr) theEntry);
}
while (!last);
return (f_found);
}
On the whole, I found Mr. Neuses article well written and informative.
SCIEX is a Canadian company that produces high end mass spectrometers for environmental, security, biotech, and research applications. Our latest product uses a Macintosh II for data aquisition and processing. We are dedicated to using the Macintosh for scientific applications.
Lets Doodat Again
J. Peter Alfke
Tucson, AZ
The idea of doodats as presented by Lee A. Neuse (Three Doodats, September 1989, p. 82) is a good one. Unfortunately, the checkAbort() doodat (which checks for Cmd-.) has at least two bugs, one apparently harmless, the other fatal.
The harmless bug concerns the most common case: an empty event. As written, checkAbort() charges ahead and examines the event-queue element pointed to by EventQueue.qTail. Unfortunately, if the event queue is empty this value is NIL. Fortunately, whatever data is at NIL doesnt usually look like a Cmd-. keypress, and the routine exits with false.
But consider another case: there are at least two entries in the queue, the last of which is a bona-fide Cmd-.. The last queue entry gets dequeued and then checked to see if it matches EventQueue.qTail. Of course, it can never match since it was just removed from the queue. So the routine always goes around again to the next queue element. But the next element is reference through the old ones qLink field, which is NIL since it was the last entry in the queue.
Now we are looking at the queue entry at NIL, just like in the other bug, But this time EventQueue.qTail isnt conveniently NIL (its pointing to the last remaining event) so we jump to the next entry instead of exiting. This involves dereferencing NIL and we are on a runaway journey to Bus Error Land.
After sitting in the THINK C debugger for a while figuring all this out. I came up with a revised version, as shown below:
/* 2 */
bool
checkAbort(void)
{
EvQElPtr q, nextq;
bool last;
bool found = false;
q = (EvQElPtr) EventQueue.qHead;
if (q) /* Ignore empty event queue */
do {
last = (q == (EvQElPtr) EventQueue.qTail); /* Last event? */
nextq = (EvQEl*) q->qLink; /* the next one */
if ((q->evtQWhat == keyDown || q-> evtQWhat == autoKey) && q->evtQModifiers
& cmdKey
&& (q->evtQModifiers & shiftKey) == 0
&& (q->evtQMessage & charCodeMask) == .) {
Dequeue(q, &EventQueue);
found = true;
}
q = nextq;
} while (!last);
return found;
}
My routine also ignores Cmd-Shift-. (i.e. Cmd->), which many applications (such as mine, along with Microsoft Word) uses as an Increment-Point-Size command.
Doodat One More Time
Joe Rice
Doodat #1 in your C Workshop feature in the September issue encouraged me to implement a solution to a problem we were having in the application on which Im currently working. In some areas of our application, were running real-time loops that cannot afford to be interrupted by Multifinder yet need to be able to test for several events, not just Cmd-Period. While the sample code provided an excellent starting, it had a few bugs.
First, it doesnt check for an empty queue before plunging into the loop.
Second, it retrieves a value (qLink) from the queue element pointed to by eq_p after the element has potentially been dequeued.
Third, dequeuing elements from the EventQueue confuses the Event Manager big time. My guess is that when the Event Manager dequeues an element, it probably enqueues that element outside onto a queue of free elements so that it can be reused. Dequeuing the element outside of the Event Manager appears to shorten the EventQueue permanently causing the application to grind to a halt within short order. The solution is to change the event to a null event rather than dequeuing it.
The code that we implemented is below. Its organized somewhat differently than the example code in MacTutor so that it would be a bit more general.
/* 3 */
/**********************
Name: CheckForEvent
Purpose: check the event queue for an event without calling Get/WaitNextEvent.
Note that this routine removes all events which pass the test from the
Event queue, not just the first.
TestMDown and TestCmdPeriod are two TestProcs for CheckForEvent.
CheckForEvent((ProcPtr) TestMDown) will return TRUE if the mouse has
been clicked.
CheckForEvent((ProcPtr) TestCmdPeriod) will return TRUE if a Cmd-Period
has been typed.
**********************/
Boolean CheckForEvent(TestProc)
ProcPtr TestProc;
{
EvQElPtreqPtr, nextEqPter;
Boolean found;
OSErr status;
found = FALSE;
eqPtr = (EvQElPtr) (EventQueue.qHead);
while (eqPtr != 0) {
if ((*TestProc) (eqPtr)) {
eqPtr->evtQWhat = nullEvent;
found = TRUE;
}
if (eqPtr == (EvQElPtr) EventQueue.qTail)
break;
eqPtr = (EvQEl *) (eqPtr->qLink);
}
return found;
}
Boolean TestCmdPeriod (eqPtr)
EvQElPtreqPtr;
{
if ((eqPtr->evtQWhat == keyDown
|| eqPtr->evtQWhat == autoKey)
&& (eqPtr->evtQModifiers & cmdKey)
&& (eqPtr->evtQMessage & charCodeMask) == .)
return TRUE;
return FALSE;
}
Boolean TestMDown (eqPtr)
EvQElPtreqPtr;
{
if ((eqPtr->evtQWhat == mouseDown)
return TRUE;
return FALSE;
}
[Although each of the three previous letters deal with the bug in the original article. I thought their solutions were interesting in their own right to be printed here. The first letter dealt with deallocation of memory. The second deals with Cmd-. over Cmd->. The third takes a unique approach by setting the event type to null rather than removing the event. It also gives a mouse down event remover.-ed]
Ken Manly
Buffalo Chip Software
Buffalo, NY
Your readers may be amused by an undocumented feature that Forrest Tanaka (MacDTS) and I discovered. If you create an Apple menu using ResEdit, there are two ways you can type the apple symbol into the menu title field. If you type control-T, the value stored will be $14, and all will be well.
If you type shift-command-K (which is what Key Caps suggests) you will get a menu which looks and acts like an Apple menu, but it is not recognized by Notification Manager routines (in System 6.x). The first symptom is that when your alarm clock goes off while your application is running, the alarm icon (which should alternate with the apple) never appears. Thanks to Forrest for guessing there was something wrong with my apple, although he had no way of knowing what.
ADB??
Kirk Chase
Anaheim CA
It seems someone did not know what the acronym ADB meant in a recent article. ADB stands for Apples Desktop Bus. It was introduced with the new keyboard. It allows the chaining of serial devices such as keyboards, mice, tablets, and so on to the Mac.
If you would like more information on ADB, there is an article by Dave Kelly and David Smith in the March '89 issue. It explains some of the concepts. It also references Tech Note #206. You can also find information on the Apple Desktop Bus in Inside Macintosh Vol. V.
MacFortran subroutines from MPW Assembler
Bob Robinson
Plainfield NJ 07060
One of the first things I needed after buying MPW was a way to get the files from the assembler into a format that could be used as a subroutine by Absoft MacFortran (chemists arent instructed in C in college or grad school.) MacFortran subroutines can be speeded up dramatically (also true of other languages) by hand-tuning the compilers assembly language output. The Fortran program listed below strips out the unneeded bytes from the MPW assembler object file. The resulting file is callable from MacFortran as a subroutine, which can be loaded dynamically or linked in with the main program. Also listed below is a short assembly language Fortran function for compilation by MPW. The function locks down a Mac memory manager handle and returns a pointer. MacFortran expects function results to be returned in register D0, so the subroutine must save the result in D0 before exiting (the toolbox traps used here happen to use D0.) If its to be loaded dynamically by MacFortran, the subroutine must preserve A0. Note: makesub displays the subroutine name in the menu bar, to avoid the Fortran TTY window (compile with O option.)
*4
*****************************************************************************************
program makesub
* R.S. Robinson 6/12/89
* Converts MPW Asm .a.o files into MacFortran .sub files.
* File name is obtained from clipboard.
* Removes 1st 36 bytes, and last 8 bytes (44 bytes smaller)
implicit none ! always a good idea
include toolbx.par! MacFortran toolbox definitions
integer i,j,toolbx,htoptr,length,scrap_h,scrapptr
integer*1 subr(16384) ! can make bigger if needed
character*6 fname ! subroutine names always ¾ 6 chars long plus .sub
logical*4 exists! error checking
scrap_h=toolbx(NEWHANDLE,0)! needs a dummy handle
length=toolbx(GETSCRAP,scrap_h,TEXT,i) ! we dont use i
* funct htoptr locks handle & returns 32bit comptble ptr;
* or use toolbox calls: call toolbx(HLOCK,scrap_h);scrapptr=LONG(scrap_h)
scrapptr=htoptr(scrap_h)
fname=;if (length>6) length=6
do (i=1,length);fname(i:i)=CHAR(BYTE(scrapptr+i-1));repeat
call toolbx(HUNLOCK,scrap_h) ! finished with the scrap, release the
handle
inquire (file=TRIM(fname)//.a.o,exist=exists)
if (.NOT.exists) stop
call toolbx(INSERTMENU,toolbx(NEWMENU,20,char(length)//fname),0)
call toolbx(DRAWMENUBAR) ! show that we found the file
open(20,file=fname//.a.o,form=unformatted,recl=1)
do (i=1,36);read(20,end=100) subr(i);repeat ! skip 1st 36 bytes
do (i=1,16384);read(20,end=100) subr(i);repeat ! read the .a.o file
100close(20);i=i-9 ! ignore last 8 bytes plus loop overrun
open(20,file=fname//.sub,status=new,form=unformatted,recl=1)
do (j=1,i);write(20) subr(j);repeat;close(20) ! write .sub file
end
*****************************************************************************************
* Assemble the code below with MPW Asm, then run makesub after copying
htoptr
* to the clipboard in MPW. The makesub program will produce a MacFortran-compatible
* subroutine from the MPW a.o file. As set up here, makesub must
be in the same
* folder as the .a.o file. The MPW command sequence is:
*
*(copy htoptr to clipboard, then)
*Asm [pathname:]htoptr.a
*[pathname:]makesub (must remove apl extension from Fortran program)
*
;integer*4 function htoptr(handle)
; R.S. Robinson 6/12/89
; Takes handle as argument, returns locked pointer as function result.
; Function results are obtained by MacFortran from register D0.
;
INCLUDE Traps.a ; MPW equates
Start PROC; needed for MPW
HTOPTR: MOVE.L A0,A2 ; preserve A0 for MacFortran
MOVEA.L4(A7),A0 ; load pointer to handle argument
MOVE.L (A0),A0 ; load handle
_MoveHHi ; move handle to top of heap zone
_HLock ; lock it
MOVE.L (A0),D0 ; convert to pointer, ready to strip
_StripAddress ; its now 32-bit clean; result is in D0
MOVE.L A2,A0 ; restore A0
RTS ; all done; return to Fortran
END