Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
VBLSnippet.c
/* |
File: VBLSnippet.c |
Contains: A simple example of a persistent VBL written in C that |
works with 68K or PowerPC. |
Written by: Jim Luther (Based on the VBL code from the Technical Note |
"TB 35 - MultiFinder Miscellanea".) |
Copyright: Copyright © 1995-1999 by Apple Computer, Inc., All Rights Reserved. |
You may incorporate this Apple sample source code into your program(s) without |
restriction. This Apple sample source code has been provided "AS IS" and the |
responsibility for its operation is yours. You are not permitted to redistribute |
this Apple sample source code as "Apple sample source code" after having made |
changes. If you're going to re-distribute the source, we require that you make |
it clear in the source that the code was descended from Apple sample source |
code, but that you've made changes. |
Change History (most recent first): |
7/27/1999 Karl Groethe Updated for Metrowerks Codewarror Pro 2.1 |
*/ |
#include <Types.h> |
#include <Memory.h> |
#include <QuickDraw.h> |
#include <Fonts.h> |
#include <Windows.h> |
#include <Menus.h> |
#include <TextEdit.h> |
#include <Dialogs.h> |
#include <Events.h> |
#include <TextUtils.h> |
#include <Retrace.h> |
#include <LowMem.h> |
#include <Gestalt.h> |
/*----------------------------------------------------------------------------*/ |
/* |
** Define a struct to keep track of what we need in the VBL. Put theVBLTask |
** into the struct first because its address will be passed to our VBL task |
** in A0. |
*/ |
struct VBLRec |
{ |
VBLTask theVBLTask; /* the VBL task itself */ |
#if !GENERATINGCFM |
long VBLA5; /* saved CurrentA5 where we can find it for 68K code */ |
#endif |
}; |
typedef struct VBLRec VBLRec; |
typedef struct VBLRec *VBLRecPtr; |
/*----------------------------------------------------------------------------*/ |
/* |
** Constants used in sample |
*/ |
enum |
{ |
kInterval = 6, /* VBL interval */ |
rInfoDialog = 140, /* DLOG resource ID */ |
rStatTextItem = 1 /* item number of counter field in dialog */ |
}; |
/*----------------------------------------------------------------------------*/ |
/* |
** Prototypes |
*/ |
void DoVBL(VBLRecPtr recPtr); |
#if GENERATINGCFM |
void StartVBL(VBLTaskPtr vblTaskPtr); /* Under CFM, we're passed the VBL task pointer */ |
#else |
void StartVBL(void); /* Otherwise, we'll have to get it out of register A0 */ |
#endif |
OSErr NewPersistentVBLProc(ProcPtr userRoutine, VBLUPP *theVBLUPP); |
void DisposePersistentVBLProc(VBLUPP theVBLUPP); |
void main(void); |
/*----------------------------------------------------------------------------*/ |
/* |
** A global which will be referenced from our VBL Task and the test program |
*/ |
long gCounter; /* Counter incremented each time our VBL gets called */ |
/*----------------------------------------------------------------------------*/ |
/* |
** DoVBL is called only by StartVBL() |
*/ |
void DoVBL(VBLRecPtr recPtr) |
{ |
gCounter++; /* Show we can set a global */ |
recPtr->theVBLTask.vblCount = kInterval; /* Set ourselves to run again */ |
} |
/*----------------------------------------------------------------------------*/ |
#if !GENERATINGCFM |
/* |
** GetVBLRec returns the address of the VBLRec associated with our VBL task. |
** This works because on entry into the VBL task, A0 points to the theVBLTask |
** field in the VBLRec record, which is the first field in the record and that |
** is the address we return. Note that this method works whether the VBLRec |
** is allocated globally, in the heap (as long as the record is locked in |
** memory) or if it is allocated on the stack as is the case in this example. |
** In the latter case this is OK as long as the procedure which installed the |
** task does not exit while the task is running. This trick allows us to get |
** to the saved A5, but it could also be used to get to anything we wanted to |
** store in the record. |
*/ |
extern VBLRecPtr GetVBLRec(void) |
= 0x2008; /* MOVE.L A0,D0 */ |
#endif |
/*----------------------------------------------------------------------------*/ |
/* |
** This is the actual VBL task code. It uses GetVBLRec to get our VBL record |
** and properly set up A5 (non-CFM only). |
** Because of the vagaries of C optimization, it calls a separate routine to |
** actually access global variables. See "OV 10 - Setting and Restoring A5" |
** for the reasons for this, as well as for a description of SetA5. |
*/ |
#if GENERATINGCFM |
void StartVBL(VBLTaskPtr vblTaskPtr) |
{ |
DoVBL((VBLRecPtr)vblTaskPtr); /* Call another routine to do actual work */ |
} |
#else |
void StartVBL() |
{ |
long curA5; |
VBLRecPtr recPtr; |
recPtr = GetVBLRec(); /* First get our record */ |
curA5 = SetA5(recPtr->VBLA5); /* Get the saved A5 */ |
/* Now we can access globals */ |
DoVBL(recPtr); /* Call another routine to do actual work */ |
(void) SetA5(curA5); /* Restore old A5 */ |
} |
#endif |
/*----------------------------------------------------------------------------*/ |
/* |
** NewPersistentVBLProc allocates the VBLUPP in the System heap so the VBL |
** will be persistent. |
*/ |
OSErr NewPersistentVBLProc(ProcPtr userRoutine, VBLUPP *theVBLUPP) |
{ |
#if GENERATINGCFM |
OSErr result; |
THz savedZone; |
savedZone = GetZone(); |
SetZone(SystemZone()); |
*theVBLUPP = NewVBLProc(userRoutine); |
result = MemError(); |
SetZone(savedZone); |
return ( result ); |
#else |
enum |
{ |
kJMPInstr = 0x4ef9, |
kJMPSize = 6 |
}; |
OSErr result; |
Ptr sysHeapPtr; |
sysHeapPtr = NewPtrSys(kJMPSize); |
result = MemError(); |
if ( result == noErr ) |
{ |
*(short *)sysHeapPtr = kJMPInstr; |
*(ProcPtr *)(sysHeapPtr+2) = userRoutine; |
FlushCodeCacheRange(sysHeapPtr, kJMPSize); |
*theVBLUPP = (VBLUPP)sysHeapPtr; |
} |
return ( result ); |
#endif |
} |
/*----------------------------------------------------------------------------*/ |
/* |
** DisposePersistentVBLProc frees up the memory used for the VBLUPP. |
*/ |
void DisposePersistentVBLProc(VBLUPP theVBLUPP) |
{ |
#if GENERATINGCFM |
THz savedZone; |
savedZone = GetZone(); |
SetZone(SystemZone()); |
DisposeRoutineDescriptor(theVBLUPP); |
SetZone(savedZone); |
#else |
DisposePtr((Ptr)theVBLUPP); |
#endif |
} |
/*----------------------------------------------------------------------------*/ |
/* |
** Create a dialog just to demonstrate that the global variable |
** is being updated by the VBL Task. Before installing the VBL, we store |
** our A5 in the actual VBL Task record, using SetCurrentA5 described in |
** OV 10 - Setting and Restoring A5. We'll run the VBL, showing the counter |
** being incremented, until the mouse button is clicked. Then we remove |
** the VBL Task, close the dialog, and remove the mouse down events to |
** prevent the application from being inadvertently switched by MultiFinder. |
*/ |
void main (void) |
{ |
VBLRec theVBLRec; |
DialogPtr infoDPtr; |
DialogRecord infoDStorage; |
Str255 numStr = "\pTest"; |
OSErr theErr; |
Handle theItemHandle; |
short theItemType; |
Rect theRect; |
long lastCount = 0; |
InitGraf(&qd.thePort); |
InitFonts(); |
InitWindows(); |
InitMenus(); |
TEInit(); |
InitDialogs(NULL); |
InitCursor(); |
MaxApplZone(); |
#if !GENERATINGCFM |
/* Store the current value of A5 in the VBLA5 field if not CFM. */ |
theVBLRec.VBLA5 = SetCurrentA5 (); |
#endif |
gCounter = 0; /* Initialize our global counter */ |
/* Put up the dialog */ |
infoDPtr = GetNewDialog (rInfoDialog, (Ptr) &infoDStorage, (WindowPtr) -1); |
DrawDialog (infoDPtr); |
GetDialogItem (infoDPtr, rStatTextItem, &theItemType, &theItemHandle, &theRect); |
/* Set the address of our routine */ |
theErr = NewPersistentVBLProc((ProcPtr)StartVBL, &theVBLRec.theVBLTask.vblAddr); |
theVBLRec.theVBLTask.vblCount = kInterval; /* Frequency of task, in ticks */ |
theVBLRec.theVBLTask.qType = vType; /* qElement is a VBL task */ |
theVBLRec.theVBLTask.vblPhase = 0; |
/* Now install the VBL task */ |
theErr = VInstall((QElemPtr)&theVBLRec.theVBLTask); |
/* Display the counter until the mouse button is pushed */ |
if ( theErr == noErr ) |
{ |
do |
{ |
if (gCounter != lastCount) |
{ |
lastCount = gCounter; |
NumToString(gCounter, numStr); |
SetDialogItemText(theItemHandle, numStr); |
} |
} while ( !Button () ); |
theErr = VRemove((QElemPtr)&theVBLRec.theVBLTask); /* Remove it when done */ |
DisposePersistentVBLProc(theVBLRec.theVBLTask.vblAddr); /* Dispose of the memory */ |
} |
/* Finish up */ |
CloseDialog (infoDPtr); /* Get rid of our dialog */ |
FlushEvents (mDownMask, 0); /* Flush all mouse down events */ |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-10-30