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.
Switch Stack.c
/* |
** File: Switch Stack.c |
** |
** Contains: A simple 68K example of a VBL written in C that runs on a |
** private stack. This won't compile for PowerPC, so don't try it. |
** This is a 68K-only thing. |
** |
** Written by: Jim Luther (Based on the VBL code from the Technical Note |
** "TB 35 - MultiFinder Miscellanea".) |
** |
** Copyright: © 1993-1995 by Apple Computer, Inc., all rights reserved. |
** |
** Change History (most recent first): |
** |
** <8> 3 Feb 98 Quinn Expanded comment about holding the private stack. |
** <7> 05/30/95 JML Added check for VM before using HoldMemory and UnholdMemory. |
** <6> 02/18/95 JML Added HoldMemory and UnholdMemory calls to keep stack in |
** physical memory. |
** <5> 01/27/95 JML Added .r file for resources |
** <4> 01/27/95 JML Got rid of the THINK C-isms. Now builds with THINK C, MPW C, and Metrowerks C |
** <3> 11/02/93 JML Added reentrancy comment to VBL |
** <2> 10/13/93 JML Minor cleanup |
** <1> 10/08/93 JML First pass |
** |
*/ |
#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 to swap stacks. |
** |
** WARNING: Do not change this structure. The assembly inlines |
** SwitchtoPrivateStack and RestoreStack depend on the |
** exact order of the fields in this record! |
*/ |
struct StackRec |
{ |
Ptr ourStackBottom; /* saved stack bottom where we can find it */ |
Ptr ourStackTop; /* saved stack top where we can find it */ |
Ptr savedA7; /* place where VBL saves current A7 */ |
Ptr savedStkLowPt; /* place where VBL saves StkLowPt */ |
Ptr savedHiHeapMark; /* place where VBL saves HiHeapMark */ |
}; |
typedef struct StackRec StackRec; |
typedef StackRec *StackRecPtr; |
/* |
** 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 */ |
long VBLA5; /* saved CurrentA5 where we can find it */ |
StackRec stackRecord; |
}; |
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 */ |
kStackSize = 0x4000 /* 16K */ |
}; |
/*----------------------------------------------------------------------------*/ |
/* |
** Prototypes |
*/ |
pascal void DoVBL (VBLRecPtr recPtr); |
void StartVBL(void); |
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 */ |
long gVMOn = false; /* true if System 7 VM is turned on */ |
/*----------------------------------------------------------------------------*/ |
/* |
** 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 */ |
/* |
** SwitchtoPrivateStack and RestoreStack are assembly language inlines |
** that perform the stack switch using the StackRec passed as a parameter |
** to SwitchtoPrivateStack. |
*/ |
#pragma parameter SwitchtoPrivateStack(__A0) |
extern void SwitchtoPrivateStack(StackRec *sr) |
= { /* ; A0 = the StackRecPtr parameter */ |
0x2178, 0x0BAE, 0x0010, /* MOVE.L HiHeapMark,StackRec.savedHiHeapMark(A0) ; save HiHeapMark */ |
0x2178, 0x0110, 0x000C, /* MOVE.L StkLowPt,StackRec.savedStkLowPt(A0) ; save StkLowPt */ |
0x214f, 0x0008, /* MOVE.L SP,StackRec.savedA7(A0) ; save current system stack */ |
0x42b8, 0x0110, /* CLR.L StkLowPt ; disable the stack sniffer */ |
0x21D0, 0x0BAE, /* MOVE.L StackRec.ourStackBottom(A0),HiHeapMark ; set HiHeapMark to bottom of our stack */ |
0x2e68, 0x0004, /* MOVEA.L StackRec.ourStackTop(A0),SP ; switch stacks */ |
0x2f08 /* MOVE.L A0,-(SP) ; save A0 (*sr) on top of private stack */ |
}; |
extern void RestoreStack(void) |
= { |
0x205F, /* MOVE.L (SP)+,A0 ; restore A0 (*sr) from private stack */ |
0x2E68, 0x0008, /* MOVEA.L StackRec.savedA7(A0),A7 ; restore system stack */ |
0x21E8, 0x0010, 0x0BAE, /* MOVE.L StackRec.savedHiHeapMark(A0),HiHeapMark ; restore HiHeapMark */ |
0x21E8, 0x000C, 0x0110 /* MOVE.L StackRec.savedStkLowPt(A0),StkLowPt ; restore the sniffer */ |
}; |
/*----------------------------------------------------------------------------*/ |
/* |
** DoVBL is called only by StartVBL() |
** |
** WARNING: It MUST be declared "pascal" so the function will remove the |
** parameters itself! |
*/ |
pascal void DoVBL (VBLRecPtr recPtr) |
{ |
gCounter++; /* Show we can set a global */ |
recPtr->theVBLTask.vblCount = kInterval; /* Set ourselves to run again */ |
} |
/*----------------------------------------------------------------------------*/ |
/* |
** This is the actual VBL task code. It uses GetVBLRec to get our VBL record |
** and properly set up A5. Having done that, switches to a private stack and |
** then calls DoVBL to increment a global counter and sets itself to run again. |
** 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. |
** I can switch to my private stack without checking to make sure I haven't |
** already done so because VBLs don't have to worry about begin reentrant. |
*/ |
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 */ |
/* Switch to private stack */ |
SwitchtoPrivateStack(&recPtr->stackRecord); |
/* Now we're running on our private stack */ |
/* |
** Make sure any functions called at this point use pascal calling conventions |
** so the functions clean up the stack before returning! |
*/ |
DoVBL(recPtr); /* Call another routine to do actual work */ |
/* Switch back to original stack */ |
RestoreStack(); |
(void) SetA5(curA5); /* Restore old A5 */ |
} |
/*----------------------------------------------------------------------------*/ |
/* |
** 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 also store the location of our |
** private stack which the VBL code will run under. 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; |
long response; |
InitGraf(&qd.thePort); |
InitFonts(); |
InitWindows(); |
InitMenus(); |
TEInit(); |
InitDialogs(NULL); |
InitCursor(); |
MaxApplZone(); |
/* check for VM */ |
if ( Gestalt(gestaltVMAttr, &response) == noErr ) |
{ |
gVMOn = ((response & (1L << gestaltVMPresent)) != 0); |
} |
/* Store the current value of A5 in the VBLA5 field. */ |
theVBLRec.VBLA5 = SetCurrentA5 (); |
/* Allocate memory for our private stack and store it's location in ourStackBottom */ |
theVBLRec.stackRecord.ourStackBottom = NewPtrSys(kStackSize); |
if ( theVBLRec.stackRecord.ourStackBottom != NULL ) |
{ |
if ( gVMOn ) |
{ |
/* Make sure the stack is held in physical memory. Otherwise we could |
end up running interrupt time code on a pageable stack. This would |
be bad, because a page fault allocating stack space would attempt |
to create a exception frame on the same interrupt stack, which would |
cause a double bus fault. See DTS Technote 1094 for details. |
*/ |
theErr = HoldMemory(theVBLRec.stackRecord.ourStackBottom, kStackSize); |
} |
else |
{ |
theErr = noErr; |
} |
if ( theErr == noErr ) |
{ |
/* Store location of our stack's top in ourStackTop */ |
theVBLRec.stackRecord.ourStackTop = |
(Ptr)((unsigned long)theVBLRec.stackRecord.ourStackBottom + kStackSize); |
gCounter = 0; /* Initialize our global counter */ |
/* Put up the dialog */ |
infoDPtr = GetNewDialog (rInfoDialog, (Ptr) &infoDStorage, (WindowPtr) -1); |
DrawDialog (infoDPtr); |
GetDItem (infoDPtr, rStatTextItem, &theItemType, &theItemHandle, &theRect); |
/* Set the address of our routine */ |
theVBLRec.theVBLTask.vblAddr = NewVBLProc(StartVBL); |
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); |
SetIText(theItemHandle, numStr); |
} |
} while ( !Button () ); |
theErr = VRemove((QElemPtr)&theVBLRec.theVBLTask); /* Remove it when done */ |
} |
/* Finish up */ |
CloseDialog (infoDPtr); /* Get rid of our dialog */ |
FlushEvents (mDownMask, 0); /* Flush all mouse down events */ |
if ( gVMOn ) |
{ |
/* Release the hold on our stack */ |
theErr = UnholdMemory(theVBLRec.stackRecord.ourStackBottom, kStackSize); |
} |
else |
{ |
theErr = noErr; |
} |
} |
/* Dispose of the private stack */ |
DisposePtr(theVBLRec.stackRecord.ourStackBottom); |
} |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-01-14