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 */
}