[comp.sys.mac.programmer] VBL tasks

mikeoro@hubcap.UUCP (Michael K O'Rourke) (02/13/89)

I have a question about VBL Tasks. I looked in IM-2 and read about using        
VInstall and i understand about setting up a VBLTask record.  However, what
if the procedure I install needs to be called only when a certain key combo
is hit? Does it have to scan the event queue or something? How can it be called
only when this key combo is hit?  I am assuming I must install it as a VBL
Task because I want it running all the time in the background.

Also, what attributes must I set on the CODE? I would assume system heap and
locked.  Is that correct?

Thanx,
Michael O'Rourke

sl161022@silver.bacs.indiana.edu (02/13/89)

"VInstall and i understand about setting up a VBLTask record.  However, what
"if the procedure I install needs to be called only when a certain key combo
"is hit? Does it have to scan the event queue or something?

"I want it running all the time in the background.

Actually, another way to do this (and the way I use to do a similar thing)
is to install a patch for _GetOSEvent, which is called quite frequently.
In fact, whenever the Finder, the running application, or any DA is ready
to respond to an event, it will call either _GetNextEvent (which goes
through the Toolbox Event Manager) or _GetOSEvent (which goes directly
through the Operating System.)  Since _GetNextEvent calls _GetOSEvent,
you can patch _GetOSEvent and you'll be essentially guaranteed access
to any keypress, and you can then do whatever you want.

Of course, I do program in assembly language, where one has direct control
of the registers.  (_GetOSEvent passes and returns parameters in A0 and D0.)
I am not certain how a high level language gets access to the same
parameters.  Anyway, in assembly language, it looks something like. . .

; the initial code to install your background routine:

         push.l    A4             ;we'll be using this, so save it

         push.l    #'blah'        ;fetch the background routine
         push.w    #0             ;(push = move ?,-(SP))
         _GetResource             ;resource handle still on stack

         move.l    (SP),A4        ;let A4 point to our routine, and
         move.l    (A4),A4        ; detach resource, just to keep it safe
         _DetachResource

         move.w    #$A031,D0      ;$A031 is the trap code of _GetOSEvent
         _GetTrapAddress          ;A0 <- address of "old" GetOSEvent routine
         move.l    A0,OldGetOS(?) ;save this address, we'll need it later
         move.w    #$A031,D0      ;and now we'll redirect _GetOSEvent to
         move.l    A4,A0          ; my replacement routine
         _SetTrapAddress          ;ta da

         pop.l     A4             ;restore old value of A4

         . . .

; the replacement routine, which is a resource ('blah' #0) with its
; resSysHeap and resLocked attributes set (important!!)

Beginning
         push.l    A0             ;we'll need pointer to event record later
         move.l    OldGetOS(?),A1 ;call original routine (it's as if we're
         jsr       (A1)           ; not even here)
         pop.l     A0             ;we now have access to event
         push.l    D0             ;better save D0, caller is expecting it

         . . .                    ;do whatever you want based on the event

         pop.l     D0             ;restore D0
         rts


The chore of translating this into your language of choice is up to you,
but I think this will accomplish what you're after.
__________________________________________________________________

"May the forces of evil become confused on the way to your house."

Sincerely,                                        -- George Carlin
           Phaedrus
           (aka Colin Klipsch)
           sl161022@silver.bacs.indiana.edu
           Indiana University at Bloomington

morourk@hubcap.UUCP (michael orourke) (02/14/89)

In article <99000007@silver>, sl161022@silver.bacs.indiana.edu writes:
> 
> "VInstall and i understand about setting up a VBLTask record.  However, what
> "if the procedure I install needs to be called only when a certain key combo
> "is hit? Does it have to scan the event queue or something?
> 
> "I want it running all the time in the background.
> 
> Actually, another way to do this (and the way I use to do a similar thing)
> is to install a patch for _GetOSEvent, which is called quite frequently.
> In fact, whenever the Finder, the running application, or any DA is ready
> to respond to an event, it will call either _GetNextEvent (which goes
> through the Toolbox Event Manager) or _GetOSEvent (which goes directly
> through the Operating System.)  Since _GetNextEvent calls _GetOSEvent,
> you can patch _GetOSEvent and you'll be essentially guaranteed access
> to any keypress, and you can then do whatever you want.

Sounds great. Only one problem. There followed after this response a lot of
assembly language code examples. But I don't know assembly language so I couldntmake heads or tail of what to do.

How do I install a patch to a routine like GetOSEvent?

Michael O'Rourke

dwb@Apple.COM (David W. Berry) (02/14/89)

In article <4418@hubcap.UUCP> morourk@hubcap.UUCP (michael orourke) writes:
>In article <99000007@silver>, sl161022@silver.bacs.indiana.edu writes:
>> 
>> "VInstall and i understand about setting up a VBLTask record.  However, what
>> "if the procedure I install needs to be called only when a certain key combo
>> "is hit? Does it have to scan the event queue or something?
>> 
>> "I want it running all the time in the background.
>> 
>> Actually, another way to do this (and the way I use to do a similar thing)
>> is to install a patch for _GetOSEvent, which is called quite frequently.
>> In fact, whenever the Finder, the running application, or any DA is ready
>> to respond to an event, it will call either _GetNextEvent (which goes
>> through the Toolbox Event Manager) or _GetOSEvent (which goes directly
>> through the Operating System.)  Since _GetNextEvent calls _GetOSEvent,
>> you can patch _GetOSEvent and you'll be essentially guaranteed access
>> to any keypress, and you can then do whatever you want.
>
>Sounds great. Only one problem. There followed after this response a lot of
>assembly language code examples. But I don't know assembly language so I couldntmake heads or tail of what to do.
>
>How do I install a patch to a routine like GetOSEvent?
	By taking your life in your hands.  Routines like GetOSEvent,
GetNextEvent, etc should be patched only if absolutely necessary.  In
this case it isn't, Apple has thoughtfully provided a hook through
which all events are filtered.  What you want to do can be done by
installing a pointer to a routine in jGNEFilter (0x????).  For more
details see Tech Note #85.
>
>Michael O'Rourke


Opinions:  MINE, ALL MINE! (greedy evil chuckle)

David W. Berry		(A/UX Toolbox Engineer)
apple!dwb@sun.com	dwb@apple.com	973-5168@408.MaBell

alexis@ccnysci.UUCP (Alexis Rosen) (02/17/89)

There is no way to do this patch stuff without access to registers, which
in practice means assembler (you could do in-line asm from pascal, but that's
the same thing). If you intend to muck around on this low level, though, you
have to at least be able to read assembler. It's really not all that
difficult (though coding significant programs in it ain't my idea of fun).
If you're not willing, give it up. You won't be able to do it, I think.
Anyway, the amount of assembly required is truly minimal.

The assembly code posted for patching GetOSEvent is great except for one
little problem- it is a tail patch. Tail patches are expressly forbidden,
and this routine seems more likely than most to be susceptible to major
trouble.

On the other hand, my little scheme for legal tail patches would probably
make it work.

Another problem with this is that it might not work with apps which call
WaitNextEvent (I don't know if WNE calls GetOSEvent, but I sort of doubt it).

In any event (:-) there is no need to patch the trap. There is a hook already
in place. I think it is a low-mem global called jGNEFilter or some such which
takes a pointer to a routine. Check IM for more info. There has also been
a tech note or two on the subject. I seem to recall that apple now discourages
all use of this hook in the latest relevant technote, but that no alternatives
are offered.

Given this, I would say that the legal tail patch is the most future-
compatible way of doing things, but that it may well impair performance
(doing four traps + misc. stuff for every GetOSEvent). jGNEFilter will
probably stick around for awhile (at least within an app's multifinder
partition) so it's probably safe to use it for now.

On the other hand, who the hell knows? Not me, that's for damn sure...

Alexis Rosen
alexis@ccnysci.uucp

cheshire@neon.Stanford.EDU (Stuart David Cheshire) (05/13/91)

Quick question / plea for help:

Does anybody know how to stop Multifinder turning off my VBL task when my
application is switched to the background?

The technical notes proudly explain this wonderful feature of Multifinder
which means you don't have to set up A5 to point to your globals -- but I
am using the VBL task for timing and I do set up A5 correctly and I don't
want it to slow down whenever my app is in the background (major OR minor
context switches have this effect, so even when the app is 'in the fore-
ground' the speed is erratic).

I tried doing as they say and installing the vbl task in the system heap
which is supposed to stop this, but it doesn't seem to work:

task = (ex_VBLTask*) NewPtrSysClear(sizeof(ex_VBLTask));
task->task.qType    = vType;
task->task.vblAddr  = (ProcPtr)Tick;
task->task.vblCount = 1;
task->regA5         = Application_A5;
VInstall(task);

Please e-mail respones to cheshire@cs.stanford.edu

jmunkki@hila.hut.fi (Juri Munkki) (05/13/91)

In article <1991May13.014725.5238@neon.Stanford.EDU> cheshire@neon.Stanford.EDU (Stuart David Cheshire) writes:
>Does anybody know how to stop Multifinder turning off my VBL task when my
>application is switched to the background?
>
>I tried doing as they say and installing the vbl task in the system heap
>which is supposed to stop this, but it doesn't seem to work:

You got it almost right. The task procedure has to be in the system heap,
the queue entry can be anywhere.

I use a resource with the system heap bit set and a single JMP instruction
that I patch to point to my code in the application.

   ____________________________________________________________________________
  / Juri Munkki	    /  Helsinki University of Technology   /  Wind  / Project /
 / jmunkki@hut.fi  /  Computing Center Macintosh Support  /  Surf  /  STORM  /
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

cheshire@neon.Stanford.EDU (Stuart David Cheshire) (05/15/91)

Many thanks to everyone who responded to my question.

The answer is that the vblAddr has to point into the System heap AS WELL as the
queue element itself residing in the system heap. I found an easy way to put a
piece of code in the system heap which calls my routine. It may look dangerous,
but it seems to work as long as you are not stupid and deliberately call UnLoad
Seg to throw away the code it is trying to call.

Sample code follows:

/* define a structure which is a VBLTask queue element with four bytes to
stash A5, our application global pointer, appended to the end */

typedef struct { VBLTask task; long *regA5; } ex_VBLTask;
local ex_VBLTask *task_qelem;

/* define a 'jump instruction' structure to put on the system heap. It is
six bytes -- a two byte opcode and a four byte absolute address. The code
that address points to must already be in memory and we must not call
UnLoadSeg or it will vanish under us. */

typedef struct { short jmp_opcode; void (*codeptr)(void); } jumpcode;
local jumpcode *task_code;

/* code starts here*/

	{
	/* allocate structures in System Heap */
	task_qelem = (ex_VBLTask*) NewPtrSysClear(sizeof(ex_VBLTask));
	task_code  = (jumpcode *)  NewPtrSysClear(sizeof(jumpcode));
	
	/* fill in opcode for jump and the address to jump to */
	task_code->jmp_opcode     = 0x4EF9;
	task_code->codeptr        = &MacTick;
	
	/* set up the queue element and install it */
	task_qelem->task.qType    = vType;
	task_qelem->task.vblAddr  = (ProcPtr)task_code;
	task_qelem->task.vblCount = 1;
	task_qelem->regA5         = Application_A5;
	VInstall(task_qelem);
	}

local void MacTick(void)
	{
	asm	{	move.l	a5, -(sp)
			move.l	OFFSET(ex_VBLTask,regA5)(a0), a5
		}
	
	/* do stuff here. Globals may be accessed if required. */
	
	asm {	move.l (sp)+, a5 }
	}

ech@cbnewsk.att.com (ned.horvath) (05/15/91)

From article <1991May15.081149.4579@neon.Stanford.EDU>, by cheshire@neon.Stanford.EDU (Stuart David Cheshire):
> Sample code follows:
> 
> /* define a structure which is a VBLTask queue element with four bytes to
> stash A5, our application global pointer, appended to the end */
> 
> typedef struct { VBLTask task; long *regA5; } ex_VBLTask;
> local ex_VBLTask *task_qelem;
> 
> /* define a 'jump instruction' structure to put on the system heap. It is
> six bytes -- a two byte opcode and a four byte absolute address. The code
> that address points to must already be in memory and we must not call
> UnLoadSeg or it will vanish under us. */
> 
> typedef struct { short jmp_opcode; void (*codeptr)(void); } jumpcode;
> local jumpcode *task_code;
> 
> /* code starts here*/
> 
> 	{
> 	/* allocate structures in System Heap */
> 	task_qelem = (ex_VBLTask*) NewPtrSysClear(sizeof(ex_VBLTask));
> 	task_code  = (jumpcode *)  NewPtrSysClear(sizeof(jumpcode));
> 	
> 	/* fill in opcode for jump and the address to jump to */
> 	task_code->jmp_opcode     = 0x4EF9;
> 	task_code->codeptr        = &MacTick;
> 	
> 	/* set up the queue element and install it */
> 	task_qelem->task.qType    = vType;
> 	task_qelem->task.vblAddr  = (ProcPtr)task_code;
> 	task_qelem->task.vblCount = 1;
> 	task_qelem->regA5         = Application_A5;
> 	VInstall(task_qelem);
> 	}
> 
> local void MacTick(void)
> 	{
> 	asm	{	move.l	a5, -(sp)
> 			move.l	OFFSET(ex_VBLTask,regA5)(a0), a5
> 		}
> 	
> 	/* do stuff here. Globals may be accessed if required. */
> 	
> 	asm {	move.l (sp)+, a5 }
> 	}

Perhaps you didn't receive my email on this subject.  While your code
will work, and work correctly, it will create problems on a Mac running
under VM: there is no guarantee that your MacTick() routine, or your
below-A5 (globals) area will be in memory.  If they aren't, you are
going to page-swap at VBL-interrupt time (not cool).

I haven't given the VM section of IM-VI a careful reading, but I'd guess
that what you propose will WORK.  However, it's quite possible that the
jerkiness it introduces will be perceptible to the user...

A way to avoid the problem is to check for VM enabled (using Gestalt)
and make the appropriate locking calls to keep your routine and the vars 
it needs in memory.  Another way is to load the MacTick code, and the
variables it needs, into the System heap (which doesn't swap).  Your app,
since it does the allocation, can continue to access the vars needed by
the VBL task.

Finally, as always with a VBL task, remember that you are running at
interrupt time: all the memory-moving traps are off limits, and you should
do the minimum possible processing in the VBL itself.
-- 

=Ned Horvath=
ehorvath@attmail.com