The Macintosh One-Liners are intended to condense into a small space
information about some of the most common Macintosh problems and programming
pitfalls. Each one-liner is a single line of text, shorter than 80 characters,
which informs about one aspect of Macintosh use or programming.
One-liners give either facts or advice. The facts may be obvious to some
people and obscure to others but are important for all. The advice is intended
to help keep people from running into the most common nontrivial problems.
Like proverbs, the advice may not be absolute and may sometimes be more
conservative than is strictly necessary. However, I have found that a little
constructive paranoia can go a long way toward avoiding problems, and more than
once I have taken a precaution which seemed extreme at the time but which saved
my skin later on.
The one-liners started as a list I made for myself of things to remember while
writing programs. I have augmented them with my condensed records of several
years of Info-Mac, Usenet, and Delphi digests and one year of Usenet reading.
People who have contributed to the list since its first release are mentioned
at the end. The result is very much a gestalt of the Macintosh lore I have
seen and depends on the wisdom and efforts of many people. If I have forgotten
to include your name, I apologize.
The one-liners are organized into two sections. The first lists the one-liners
under broad categories without explanation. This is good for cutting out and
sticking on the wall. The second section gives some extra information on each
one-liner.
All references given are to Apple documentation. In some cases, the references
explain the problem, but in most, they just describe the parts of the Toolbox
you need to use. Most of the problem fixes are a result of my experience and
the experience of those people who are listed below. The acronym IM means
Inside Macintosh; the page numbers refer to the Addison-Wesley trade paperback
version. TN means Technical Note. I use the HyperCard stack, available from
sumex-aim.stanford.edu, apple.com, and no doubt other places as well. I have
avoided referring to any third-party software tools and only refer to a minimal
set of Apple tools. This is not to endorse Apple in any way. It's just that I
don't want to get into the product comparison business. I use a minimal set of
ubiquitous tools, and I describe fixes in terms of those tools. If you have
different tools, you can translate. I have also tried to avoid using
language-specific statements where possible, but all my example code is in C.
Again, you can translate.
Compiled by Eric Pepke
Additional material by Steve Maker, Keith Rollin, Gregory Dudek, Brian
Bechtel,
Henry Minsky, Carl C. Hewitt, Jim Lyons, Alex Lau, Kent Borg, Peter W.
Poorman,
Ross Yahnke, Mark Fleming, Mark Anderson.
Send suggestions to pepke@gw.scri.fsu.edu on the
Internet or PEPKE@FSU on BITNET. Have fun.
Call MaxApplZone and MoreMasters when the application starts up.
If you call SetApplLimit, do it before calling MaxApplZone.
Use HT in MacsBug while running to estimate how many times to call MoreMasters.
Don't use SetEventMask to disable mouseUp events. Better not to use it at all.
SetPort to a known good GrafPort once every time through the event loop.
Calling WaitNextEvent with more than 50 ticks will fail on some systems.
Set the cursor on suspend and resume events.
Call GetDblTime to get the maximum time for a double click.
Measure double-click time from mouse up to mouse down.
Call either WaitNextEvent or both GetNextEvent and SystemTask.
Call IsDialogEvents and DialogSelect even if GetNextEvent returns false.
Use SetItem to include meta characters literally in menus.
Never make MENU resources purgeable.
GetResource never produces resNotFound. Check for a NIL handle instead.
To create a resource file, call Create, then CreateResFile.
To open a resource file read-only for shared access, use OpenRFPerm.
Don't leave ResLoad set to false.
GetResource on a dctb may return a non-resource copy of the dctb.
Drag windows to the bounding box of GetGrayRgn.
Hide scroll bars when deactivating a window.
Call DrawGrowIcon when activating or deactivating a window with a grow region.
DrawGrowIcon does not check to see if the window has a grow region.
Call PenNormal before calling DrawGrowIcon.
ItemHit will not be set when a dialog filter is called.
Use a disabled UserItem to draw the roundrect outline around the OK button.
ModalDialog assumes the dialog is already visible and in the front.
Use screenBits.bounds to center dialogs, alerts, etc. below the menu bar.
If you save window locations in files, they may not be valid for all monitors.
DragWindow expects startPt in boundsRect; if not it may not call SelectWindow.
SelectWindow does not automatically call SetPort. You must do that yourself.
DialogSelect responds to activate events but ignores suspend/resume events.
Call PenNormal before calling DrawControls.
The Control Manager only works when the origin of a window is at (0, 0)
To find the position of a window, use LocalToGlobal on the origin.
Always set the VisRgn and ClipRgn of offscreen ports.
Set the ClipRgn first when making a picture.
Don't make rowBytes in bitMaps greater than 8191.
To dim text, draw a rectangle with penPat=gray and penMode=patBic over it.
To draw rotated text, draw to an offscreen bitmap, rotate it, and CopyBits it.
Don't use picSize to determine the size of a picture. Check the handle size.
Never draw outside a window except in XOR mode for temporary effects like drag.
To avoid animation flicker, synchronize drawing to the vertical retrace.
Lock handles to pictures before calling DrawPicture.
CopyBits on more than 3Kb data will work, but it might have to allocate memory.
The small Mac screen is 512 pixels wide by 342 pixels high including menu bar.
Don't call any Memory Manager routines during an interrupt.
Unlocked handles may not be valid during an interrupt.
To synchronize to the vertical retrace on Macs with slots, use SlotVInstall.
Don't write in the application file. This will fail with read-only devices.
Use PBGetVInfo to convert a VRefNum to a volume name.
Delete uses the Poor Man's Search Path, so don't delete blindly.
File Manager routines with dirID=0 use the Poor Man's Search Path.
Truncate and reallocate files before overwriting to reduce fragmentation.
Check/change the creator and type of Save As... files before overwriting.
If you rewrite files by deleting and creating, copy all Finder information.
Directory ID's are longs, not shorts. Shorts work ALMOST all the time.
If a file version number appears in a file manager call, always set it to 0.
To convert a pathRefNum to a name or file number, use PBGetFCBInfo.
Prevent the creation of files with names that begin with a period.
Write/update the Finder info before writing data or resource forks.
Lock handles before passing their dereferenced pointers to any routine.
Lock handles before setting referenced data to expressions containing functions
Put an odd long at location zero on a 68000 to help find NIL handle references.
Call MoveHHi before locking a handle to avoid memory fragmentation.
Use HGetState/HLock/HSetState to lock a handle then put it back as it was.
Always use unsigned characters within text and Pascal-format strings.
Save application preferences in a folder named Preferences in the System Folder
Use SysEnvirons to find the System (Blessed) Folder.
Use GetAppParms to get the name of the application.
The high bit of SysParam . volClik enables the alarm clock.
Check the application name at $910 before exiting with ES within MacsBug.
To exit to shell in the mini-debugger, enter SM 0 A9 F4 and then G 0.
In Pascal, don't nest procedures to be passed by procedure pointer.
In Pascal, "with theHandle^^" is unsafe if memory compaction can occur.
Avoid writing tail patches for traps.
There is no official way to tell if MultiFinder is running or not.
[This section was created by Eric after my futile attempt at
trying to explain his one-liners. I showed him my idea and he was real
interested and expanded on it. The idea was that his one-liners was very
helpful at remembering the correct things to do but to help some beginning
programmers , I thought that an explanation would help them understand why.
Also I added a few comments and they are shown by square brackets. MXM
]
---
References: IM II-30, IM II-31, TN 53
A call to MaxApplZone is needed for an application to get as large a heap
as it can under all systems. Usually, this is what you want to do. Call
MoreMasters early in the application to get enough blocks of master pointers
for the anticipated number of relocatable memory blocks to be allocated. [You
should also call MoreMasters from code segment 1 and not from your
intialization segment.]
References: IM II-30
MaxApplZone expands the application zone to the heap limit set by
SetApplLimit. It doesn't make any sense to call MaxApplZone first. Most
applications will not need SetApplLimit at all, but the few that do should call
it before MaxApplZone.
References: IM II-22, IM II-31, MacsBug 6.0, TN 53
There is an art to esimating how many times to call MoreMasters. If you
call it too few times, the Memory Manager will be forced to call it later on in
the application, which can fragment the heap. If you call it too many times,
you will be wasting master pointers that you do not need. It's usually better
to call MoreMasters too many times than too few. To estimate the number of
times, you need to have some idea of how many relocatable blocks your
application typically allocates. A good way of finding out is to run the
application for a while, exercising all its features. Then find out how many
relocatable memory blocks have been allocated. The HT command in MacsBug will
provide this information. Once you have this number, divide by 64, add some
slop (maybe 25%), and that's the number of times you need to call MoreMasters.
References: IM II-70, TN 202
SetEventMask should only be used to enable KeyUp events if an application
needs them. Some programmers, especially in the early days, used SetEventMask
to disable MouseUp events, which caused a lot of problems. One particularly
strange one is that, after running them, double-clicking would no longer work
in the Finder. Because the Finder could not see the MouseUp, it could not
detect a double-click.
References: IM I-165
You never know if a desk accessory is playing by the rules. Even if you do
things absolutely correctly in your application, there is a bizarre set of
circumstances which can (and has) resulted in crashes. Say desk accessory A
plays hard and fast with the current GrafPort. Say it has the current GrafPort
set to a window, and it closes the window. The current GrafPort becomes
invalid. Now, desk accessory B does something which requires the current
GrafPort to be valid, but does not explicitly set it first. This is common
because there are calls that expect some GrafPort to be current, but don't
really care which one, and some programmers are sloppy. When B tries this,
kabloom! Even though your application is perfectly innocent, it will probably
be blamed anyway. If you do a SetPort to a known good GrafPort every time
through your event loop, this will not happen.
References: TN 177
MultiFinder 1.0 had a bug which caused it to hang if you called
WaitNextEvent from the background with more than about 50 ticks. The bug has
since been fixed, but you never know what weird system will be used to run your
application.
References: IM I-167, Programmer's Guide to MultiFinder
When you receive a resume event, another application may have changed the
cursor, so set it to whatever you need it to be, even if it is just an arrow.
When you receive a suspend event, set the cursor to an arrow as a courtesy to
other applications whose programmers are not as well informed as you.
References: IM I-261
The double-click time can be set by the user via the Control Panel. Use
this value to determine whether two sequential clicks formed a double click.
People have different capabilities and natural speeds, and some users may be
physically unable to make a double click at a certain speed. Also remember
that the value of GetDblTime can change at any time, because the user can pull
down the Control Panel at any time.
The Finder measures a double click from a MouseUp event to a MouseDown
event. This seems to be a very natural way of doing it.
References: Programmer's Guide to Multifinder
WaitNextEvent is not just a direct replacement for GetNextEvent. It
performs the function of SystemTask as well.
[ Example Below is from Apple's TESample Source Code ]
procedure EventLoop;
{Get events forever, and handle them by calling DoEvent.}
{ Also call AdjustCursor each time through the loop.}
var
cursorRgn: RgnHandle;
gotEvent: BOOLEAN;
event: EventRecord;
mouse: Point;
begin
cursorRgn := NewRgn; {we'll pass an empty region to WNE the first time thru}
repeat
if gHasWaitNextEvent then
begin {put us 'asleep' forever under MultiFinder}
GetGlobalMouse(mouse); {since we might go to sleep}
AdjustCursor(mouse, cursorRgn);
gotEvent := WaitNextEvent(everyEvent, event, GetSleep, cursorRgn);
end
else
begin
SystemTask; {must be called if using GetNextEvent}
gotEvent := GetNextEvent(everyEvent, event);
end;
if gotEvent then
begin
AdjustCursor(event.where, cursorRgn); {make sure we have the right cursor}
DoEvent(event); {Handle the event only if for us.}
end
else
DoIdle;
{If you are using modeless dialogs that have editText items,}
{you will want to call IsDialogEvent to give the caret a chance}
{to blink, even if WNE/GNE returned FALSE. However, check FrontWindow}
{for a non-NIL value before calling IsDialogEvent.}
until AllDone
end; {EventLoop}
References: IM I-416
In addition to handling dialog events, these routines also control cursor
flashing in dialog EditText items. They need to be called even as a result of
null events to make sure the flashing occurs.
References: IM I-346, IM I-357
AppendMenu recognizes certain characters as meta characters to control the
text style, keyboard equivalents, and so on. If you want to display any of
these meta characters within the text in the menu, you will need to use SetItem
instead.
References: IM I-119
[ quick example ]
id:=1000;
myResHandle:=GetResource('ICON', id);
if myResHandle<>nil then
PlotIcon(r, myResHandle);
else
SysBeep(duration);
References: IM I-114, TN 101
CreateResFile will first check to see if the file exists, and if it does,
it will add a resource fork to that file. CreateResFile uses the Poor Man's
Search Path, so if you call it by itself, you may find that it actually
modifies a file in the system folder rather than creating a file where you want
it. To avoid this, call Create first to make sure a file exists where you want
it.
References: IM IV-17, TN 116, TN 185
References: IM I-118
The SetResLoad routine should only be used in very special circumstances to
prevent the automatic loading of resources into memory. Parts of the Toolbox
require ResLoad to be true, so ResLoad should only be false for a very short
time.
When
GetResource is called on a dctb, it may return a copy of the dctb rather than a
handle to the resouce itself. This was done as a kludge to fix a bizarre
problem. Of course, if you aren't aware of it, the kludge itself can cause
some bizarre problems in programs that try to edit dctb resources.
References: IM I-282, IM I-289, IM V-208
DragWindow requires a bounds rectangle to specify the area over which the
window can be dragged. Normally, it should be possible to drag a window
anywhere. Once upon a time applications were recommended to use
screenBits.bounds as the drag rectangle. Many pieces of Apple sample code
recommend this to this day. However, on systems with multiple monitors,
screenBits.bounds only enclose the screen with the menu bar. What you really
want is to be able to drag the window within a rectangle that encloses all
screens. The best one to use is the bounding box of the return value of
GetGrayRgn. Because the GrayRgn covers all monitors, the bounding box is at
least large enough. If the system is too old to have the GetGrayRgn function,
you can use the GrayRgn global variable or just assume that screenBits.bounds
is good enough. The following sample THINK C code drags theWindow around in
response to an EventPtr called theEvent:
Rect boundsRect;
boundsRect = (*GetGrayRgn()) -> rgnBBox;
DragWindow(theWindow, theEvent -> where, &boundsRect);
References: IM I-46
The entire scroll bar of an inactive window, including the arrows at the
top and bottom, is supposed to be hidden. Few applications pay attention to
this, but it is one of those little touches that really cleans up the feel of a
program.
References: IM I-287
DrawGrowIcon is not smart enough to know if the window has a grow region,
so don't assume that you can always call it in a generic window handler. On
the other hand, it is smart enough to know if the window is active or inactive,
so it always does the right thing. It assumes certain things about the
QuickDraw environment, such as a pen size of (1, 1), so it is safest to call
PenNormal before every call to DrawGrowIcon.
References: IM I-415, TN 112
This is one of the trickiest pitfalls to using dialogs. Dialog filters are
nearly always used to provide interactions in userItems and are specific to
those userItems. Thinking about that task, it is reasonable to assume that the
filter can just test itemHit to see if the action is appropriate to the
userItem. Though reasonable, the assumption is wrong. A dialog filter gets
the raw event before any decisions are made. It is entirely responsible for
determining where the click was and setting itemHit. An easy way to do this
for a userItem is to do a PtInRect with the rectangle around the userItem. You
can also use FindDItem.
[ Sample below is from an old program that had a list manager in the dialog
MXM].
function MyFilter (theDialog: DialogPtr; var theEvent: EventRecord; var itemHit: integer): Boolean;
var
key: SignedByte;
Itype: Integer;
iBox, r: Rect;
iHdl: Handle;
MouseLoc: Point;
isDbl: boolean;
myList: ListHandle;
begin
Setport(theDialog);
MyFilter := False;
MyList := ListHandle(GetWRefCon(theDialog));
MyList^^.port^.txSize := 9;
MyList^^.port^.txfont := 4;
case theEvent.what of
keyDown, autoKey:
begin
key := theEvent.message;
if key in [13, 3] then
begin
MyFilter := true;
itemHit := 9; {Set Itemhit}
end;
end;
mouseDown:
begin
mouseLoc := theEvent.where;
GlobalToLocal(mouseLoc);
GetDItem(theDialog, 3, iType, ihdl, ibox);
r := Ibox;
r.right := r.right + 16;
if PtInRect(mouseLoc, r) then
begin
isDbl := LClick(mouseLoc, theEvent.modifiers, Mylist);
MyFilter := true;
itemHit := 3;
end;
end;
UpdateEvt:
begin
BeginUpdate(theDialog);
LUpdate(theDialog^.visrgn, Mylist);
MyList^^.port^.txSize := 9;
MyList^^.port^.txfont := 4;
DrawDialog(theDialog);
EndUpdate(theDialog);
end;
end;
end;
References: IM I-421, TN 34, default cdev
It seems strange that there is no trivial way to do such a basic feature of
the Macintosh interface, but that's the truth. How does one make the roundrect
outline around the default button that we all love so much? Just drawing it
there after showing the dialog is not good enough, because if something happens
to cover up the dialog, the outline will be erased. Some people use disabled
PICT items containing roundrects, but you really have to make a custom PICT for
every size of button to get the thickness of the line just right. A better way
is to use a userItem that draws the outline. Add to the dialog a disabled
userItem that is larger than the OK button by 4 pixels on each side. Make the
dialog hidden, so that you will have to do a ShowWindow to show it. Then
install a procedure for the userItem which draws the outline within its
rectangle. Here are some THINK C code fragments:
pascal void DrawOKOutline(theDialog, whichItem)
DialogPtr theDialog;
INTEGER whichItem;
/*Draws an OK outline in a userItem*/
{
INTEGER itemType; /*The type of the item in GetDItem*/
Handle theItem; /*The handle to the item*/
Rect itemBox; /*The box containing the item*/
PenState savedPenState; /*The old pen state saved and later restored*/
GetDItem(theDialog, whichItem, &itemType, &theItem, &itemBox);
GetPenState(&savedPenState);
PenNormal();
PenSize(3, 3);
FrameRoundRect(&itemBox, 16, 16);
SetPenState(&savedPenState);
}
...and when you want do use this userItem...
#define MyDlgRSRC whatever /*Number of the dialog resource*/
#define MyDlgOutline whatever /*The number of the userItem with the outline*/
INTEGER itemType; /*The type of the item in GetDItem*/
Handle theItem; /*The handle to the item*/
Rect itemBox; /*The box containing the item*/
/*Get the dialog box*/
theDialog = GetNewDialog(MyDlgRSRC, (DialogPtr) 0, (WindowPtr) -1);
/*Add the OK button outline*/
GetDItem(theDialog, MyDlgOutline, &itemType, &theItem, &itemBox);
SetDItem(theDialog, MyDlgOutline, itemType, &DrawOKOutline, &itemBox);
/*Everything is set; show the dialog*/
ShowWindow(theDialog);
If you want to get really fancy you can have the userItem procedure first look at the rectangle of the OK button, and then set its own rectangle accordingly.
[For
an automatic way of doing this look for the Default cdef article by Lloyd Lim]
References: IM I-415
A dialog has to be visible and above all other windows before you call
ModalDialog. If you call ModalDialog without ensuring this, there will be big
trouble.
References: IM I-163, IM I-527
screenBits.bounds is the bounding rectangle of the screen that has the menu
bar. This is the most convenient place to put dialogs and alerts. With some
simple calculation, knowing screenBits . bounds and the width and height of
your window, you can center it. You can find out the width and height of the
standard file dialogs by examining their DLOG templates.
Refernces: IM V-208
Saving the locations of windows between sessions is a nice thing for an
application to do, but what if the user opens the document on a system with a
very different screen setup? The window might not appear where it can be
manipulated. So, if you do this, be sure to test to see if a window will
appear in an accessible place on the screen, and if not, put it in a default
location. You can do this by checking to see if a rectangle inset a few pixels
within the title bar intersects the grayRgn.
References: IM I-289
If the startPt you pass to DragWindow is not within the boundsRect, a click
in the title bar may not call SelectWindow and bring the window to the front.
Dragging will still work, but clicking in place won't. The easiest fix is just
to make sure boundsRect is big enough. You can call SelectWindow yourself, but
this affects the feel of window dragging.
References: IM I-284
Selection and drawing are independent of each other, although most of the
time you want to do both. You can draw into windows that are not selected.
QuickDraw does not set the port behind your back, which has the advantage of
giving you flexibility. It has the disadvantage that you must remember to set
the port yourself.
References: IM I-417
If you get suspend and resume events, make sure to activate or deactivate
dialogs on the screen in response to them.
References: IM I-322
DrawControls assumes several things about the QuickDraw environment.
Calling PenNormal is the best way to be sure that it does not mess up.
References: IM I-322
If you use SetOrigin to set the origin of a window, you must call
SetOrigin(0, 0) before doing anything that can affect or draw controls.
References: IM I-165, IM I-193
The safest way to find the position of a window is to do a SetPort to the
window, get the origin of the window, and do a LocalToGlobal on that point.
References: IM I-149
The assertion in Inside Macintosh that the VisRgn has no effect on images
that are not on the screen is wrong. To be safe, always set both regions of
offscreen GrafPorts.
References: TN 59
The default ClipRgn for pictures is the largest possible region. If you
create a picture using the default, as soon as you try to scale or move it
using DrawPicture, the edges can overflow, causing all sorts of nasty things to
happen. To avoid this, set the ClipRgn as the first thing when you create a
picture.
References: IM V-53
The top three bits of rowBytes are used by the first release of Color
QuickDraw to keep information about the bitmap. More recent versions of Color
Quickdraw only use the top two bits, but do you want to gamble that everybody
in the world has upgraded? I thought not.
Dim text is another of those things which is strangely absent from
QuickDraw. You can dim text or anything else by just painting over it with a
gray penPat in patBic mode.
Although there are ways to rotate text at any angle in PostScript,
QuickDraw is only able to draw text in one orientation. The only way to get
rotated onscreen text is to rotate the bitmap. There is no call to do this;
you have to do it yourself.
References: IM V-87
The picSize field of a picture is only 16 bits wide. This is O.K. for most
black-and-white pictures, but when Color QuickDraw was invented, it was
discovered that this limitation was way too small. The actual length of a
picture is now determined by the size of the handle. Although picSize is
maintained for compatibility in version 1 pictures, don't count on it.
Drawing outside a window is very dangerous. For one thing, historically,
most programs which have done this broke when extensions were made to
QuickDraw. For another thing, in most cases, the user does not expect anything
except the Finder to draw on the desktop. If you need to draw in a window the
size of the screen, why not open a window the size of the screen and use that?
In general, only DragGrayRgn and XOR lines for zooms should be done outside a
window. These are temporary effects which always go away, so they are not so
bad.
References: IM II-350, IM V-567
The video hardware is constantly sweeping through the screen memory
displaying the bytes on the screen. If you try to access some memory during a
QuickDraw operation that is currently being swept, there will be a conflict.
Usually, QuickDraw is so fast that you will not notice it, but if you are doing
animation using CopyBits, you might notice some flicker. You can try to avoid
this by synchronizing the beginning of the CopyBits to the vertical retrace.
The idea is always to have the drawing occur just ahead of the sweep so that no
conflict occurs. A good way of finding out when the vertical retrace occurs is
to install a VBL task on a small Macintosh, or use SlotVInstall on a Macintosh
with slots. The routine will be called as a result of the vertical retrace, so
it can signal another portion of the program to begin drawing. You will have
to experiment with timing for your particular application.
References: IM I-190
Rumor has it there is a bug in DrawPicture which involves following a
pointer to the picture data rather than a handle and an offset. As DrawPicture
can at times cause a memory compaction, this is unsafe. I find this bug hard
to believe, as it would be a monstrous oversight, but better safe than sorry.
Lock the picture.
References: IM I-188
On old systems, CopyBits had a problem with copying a picture bigger than
about 3Kb. This was because CopyBits uses temporary memory from the stack.
The bug required some programs to break down CopyBits calls into a number of
thin wide strips, which was not very convenient. The bug has for a long time
been fixed. What happens now is that CopyBits will first try to get enough
memory from the stack and, if that is not enough, will allocate blocks using
Memory Manager calls.
Count 'em.
References: IM II-195
An interrupt may interrupt anything, including a memory compaction. During
a memory compaction, parts of the heap may be in an indeterminate state.
Calling any Memory Manager routines could destroy the heap. Also, it is unsafe
to look at unlocked handles. The memory in the handle may have been in the
process of moving when the interrupt occurred.
References: IM V-567
References: TN-115,TN-116
You never know where the file containing your application resides. It
could be on a CD-ROM, or it could be on a shared volume. Applications that
write to themselves, for example to set user preferences, will fail under these
circumstances. Besides, there is a better way to save preferences, described
in another one-liner.
References: IM IV-129
References: IM IV-113, IM IV-147
As the high level FSDelete is an old MFS call, it will use the Poor Man's
Search Path. This means that, if it does not find a file of the right name in
the current directory to delete, it will try to delete such a file in the
System Folder. This can be disastrous. Before you call Delete, make
absolutely sure that it will delete the correct file by checking to see if such
a file exists where you expect it.
Actually, every File Manager routine with dirID=0 or no dirID specified
uses the Poor Man's Search Path. Most of the time this just makes things more
convenient, as it allows the System File to be searched by default after the
current directory. Beware of what is happening, though.
References: IM IV-111, IM IV-112
As you overwrite a file again and again with sometimes less and sometimes
more data, the file can become fragmented. Lots of fragmented files will
worsen the performance of the entire file system. To help avoid this
fragmentation, every time you need to overwrite a file from the beginning to
the end, first truncate it to zero bytes using the SetEOF function and then
allocate the number of bytes you expect to write. The FileManager will try to
allocate a nice contiguous chunk of blocks.
References: IM IV-113
Just checking for the existence of a file with the same name is not enough.
You must make sure that the file type and creator are correct as well.
Otherwise, the file will not appear correct to the Finder, and you may not be
able to read it in the future. There are a variety of strategies to do this.
Some programs simply don't let you Save As over a file of a different type or
creator. Some quietly change the type and creator. Some put up an alert. You
decide.
References: IM IV-113
Deleting and recreating is not the best way to rewrite a file, but if you
must do it, be sure to copy all the Finder information. This means the entire
contents of the FInfo record. One more piece of information that needs to be
copied is the GetInfo comment. I don't think there is a standard way of doing
this, which is one of the reasons deleting and recreating is a bad thing to
do.
References: IM IV-92
References: IM IV-90
The version number of a file was an old mechanism to distinguish between
two files with the same name, for example to use one as a backup for the other.
It was a good idea, but it was never completely implemented. The problem is
that different bits of software do different things with the version number.
The Finder ignores the version number, but the standard file routines only show
files with version 0. This is the most common cause of bugs involving a file
that you can see in the Finder but not within an application. To prevent this
from happening, if a version number appears in a call, always explicitly set it
to 0 before you make that call.
References: IM IV-179
References: IM II-175, IM II-245
The File Manager interprets any file name beginning with a period as the
name of a device driver. Unfortunately, it is also possible to create real
files with names that begin with a period, causing no end of confusion. To
protect your users from this, add a check to your Save As routines to reject
file names that begin with a period.
References: IM IV-113
References: IM XREF
It is well known that some Toolbox routines are known to change memory.
The Inside Macintosh X-Ref has a list of such routines, which it claims is
complete. Hah! The list is really growing all the time, and you cannot count
on any routine's being safe any more. Even if you could, you run the risk that
your language accesses that routine through glue which can be loaded the first
time you call the routine, thus changing memory. The best way to be safe from
this is to lock all handles before dereferencing their pointers and passing
them to any routine, however benign the routine may seem.
This is a nasty one. Let us say you have a C statement like
(*aHandle)[5] = foo(3); in other words, aHandle is a handle to an
array, and you want to set element number 5 of that array to the return value
of foo. This is unsafe! The reason is that C will calculate the pointer to
the fifth element of aHandle before it calls foo. If foo compacts memory, this
pointer will no longer be valid. This problem is not limited to C; it can
occur with any language that allows similar constructs. Don't count on a
certain order of evaluation. Lock the handle, or use an intermediate
variable.
Whenever you use a NIL handle or pointer, it will look at whatever is
stored in location 0. The long word at location 0 could conceivably point
anywhere, causing all sorts of indirect problems to result from nil handle
references. If you suspect your application has NIL handle references, you can
track them down by using a Macintosh that uses the old 68000 processor. If you
put an odd value at location 0, whenever your application tries to use a NIL
handle, you will get an exception immediately, and you will break into the
debugger.
References: IM II-44
If you lock a handle, it may be in the middle of the heap. If you lock
several handles, they may fragment memory and affect subsequent memory
allocations. You can avoid this by first calling MoveHHi on the handle before
locking it. MoveHHi will take some time, but it will try to move the handle as
high in memory as possible before you lock it.
References: IM IV-79
Most of the time, the best way of using handles is always to keep them
unlocked and only lock them when it is absolutely necessary for a short time.
However, sometimes you have a handle that you don't know is locked and you need
to lock it and then set it back to the previous state. To do this, use
HGetState to get the state of the handle before locking it. Then, instead of
using HUnlock, use HSetState to restore the handle to its previous state.
The first character of a Pascal format string is its length in bytes, from
0 to 255. This length only makes sense if you are using unsigned characters.
If you are using signed characters, such as the default C char, numbers above
127 will be interpreted as negative numbers. This is very dangerous,
especially when you use packages such as TextEdit, which take a length. Most
languages will quietly promote a signed character to a signed short or long and
will happily pass this value to TextEdit, which will interpret it as a VERY
LARGE length, whereupon TextEdit will crash and burn. To avoid this, always
use unsigned characters.
As it is a bad idea to keep application preferences in the file itself,
where do you keep them? After a discussion of this matter on Usenet, the
consensus seemed to be that there should be a folder named Preferences in the
System Folder and each application should have file with an
application-specific name in that folder. To make it as general as possible,
the algorithm should work like this: First look in the current directory for
the preferences file. If it is found, use that. The Poor Man's Search Path
will ensure that any file in the System Folder will be found as well. If no
file is found, look for a Preferences folder in the System Folder and look for
the file there. If none is found, search all folders in the System Folder for
the file. (This allows the user to rename the Preferences folder.) If it is
not found, create a Preferences folder and save a file with the default
preferences in that folder.
References: IM V-5
References: IM II-58
References: IM II-370
Hitting the programmer's interrupt switch and doing an ES within MacsBug is
a good way to stop a runaway application. Unfortunately, with MultiFinder, you
never know just what is running when you hit that switch. To find out, look at
location $910 to see the name of the current application. If it is not the one
you want to stop, enter G and try again.
The mini-debugger has very few commands. One of the most useful functions,
exit to shell, is not provided. There are a lot of ways of doing an exit to
shell, but most of them involve remembering magic numbers of addresses for
different systems. The way presented here should work on any machine. A9F4 is
the code for the ExitToShell trap. SM 0 A9 F4 puts that instruction in the RAM
at location 0. G 0 jumps to that instruction and executes it.
Because a with statement dereferences a handle at the beginning of the
block of code it controls, if the code causes a memory compaction, this handle
may no longer be valid. Lock the handle first.
References: TN 212
Some Apple patches to traps check the stack to see who called them and have
some special cases. I know that this is not very sociable of them, but the
upshot is that your application will get blamed if you use a tail patch and
this causes one of Apple's bug fixes to fail.
I wish there were a way to tell this, because it affects the behavior of
Launch, but there isn't. There have been a variety of unofficial ways of
telling, but most of them have failed as the system was changed. The only
absolutely safe way to tell seems to be to assume that you are not running
under MultiFinder until you receive a MultiFinder event.