alain@atr-la.atr.co.jp (Alain de Cheveigne) (12/23/89)
Some time ago I posted an article claiming that some ROM calls leave handles unlocked (causing a bug if code expects a locked handle to stay locked after the call). Larry Rosenstein asked me to produce an example. I set traps in my code to catch the bug, and after 3 weeks it still hasn't turned up. I guess I misinterpreted some other bug. Here are my apologies for maligning the ROM. Luckily, a posting by shebanow@Apple.COM (Andrew Shebanow) offers me an honorable way out: >As far as it goes, there is (should be) very little code left in >the system which goes around unlocking things on you. Almost >everything has been fixed to use HGetState/HSetState. Which implies that it hasn't always been so. So maybe it wasn't my imagination after all... My original posting had another aim: find the wisest way to deal with locking and unlocking handles, especially in code that contains many nested subroutines. Here's a synthesis of the follow-ups and e-mail I received. First, why lock a Handle? Because the Memory Manager might move the block it contains. The MM will update the master pointer, so the Handle is still valid, but it but won't update the pointers that you (or the compiler) might be using to access the data. This happens in seemingliy harmless code. Tim Maroney gives an example (marsup is an unlocked handle): >(*marsup)->wombat = NewHandle(WOMBAT_SIZE); > >The compiler is free to evaluate the address of the left-hand side >before it performs the right-hand side. If NewHandle moves memory, >then its return value will be assigned into outer space. There's a tech note that says that code like that needs to be preceded by MoveHHi(marsup); HLock(marsup), and followed by HUnlock(marsup). The trouble with this is that you use four operations instead of one. Your code gets longer, wastes time, and becomes pretty unreadable. So it's natural to try to cut down on the locking. When not to lock? Ordinary code that does't a) call the ROM or b) call routines in another segment is safe. Furthermore, IM-IV and V contain lists of Routines that May Move or Purge Memory, implying that all other ROM routines are safe. However, as pete@hub.UUCP (Pete Gontier) writes: >DTS' official position now is that there are so many patched traps running >around calling the Memory Manager that you need to lock and unlock handles >around ANY trap call. The list, in other words, is no longer valid. The follow-up on this boiled down to: From: shebanow@Apple.COM (Andrew Shebanow): >Anyhow, this statement of DTS's position is a bit of an oversimplification. > >It is true that the lists of traps that may move or purge memory are out >of date, and that traps which currently are considered "safe" may become >"unsafe" sometime in the future. Our recommendation is that developers >err on the side of caution: if you are passing a dereferenced field >of a handle by address, either use a temporary or lock the handle. I >do feel that there are some calls which are "safe" to use dereferenced >handles with, like BlockMove, some string manipulation calls, etc. Use >your better judgment (or "Toolbox Karma", if you will). Which is pretty lame. Either a call is safe or it isn't. If DTS can't guarantee it, who am I to judge, and how? Trial and error? Other (non-ROM) subroutines are supposedly safe if they don't call the ROM. But to be sure of this, you have to know what's inside a routine. This defeats the principle of information hiding, so the code tends to be unreliable (even Apple can't seem to keep track of such side effects). In addition, a call to a "harmless" subroutine can move memory if the subroutine is in an unloaded segment. Bottom line: don't trust *any* routine, either Apple's or your own. Another way to avoid locking is to use a temp variable, as recommends tim@hoptoad.uucp (Tim Maroney): >Handle h = NewHandle(WOMBAT_SIZE); >(*marsup)->wombat = h; > >Slightly more stack overhead, but you avoid trap overhead for locking >and unlocking, and also avoid all the state-restoration hassles that >have been mentioned lately here. This scheme was recommended by many, and emerged as the best solution for short fields. But it isn't obvious what to do if the field is large: a string, or an array, or data for a BitMap or PixMap. Temp variables for big data fields require space (stack? heap?) and copying takes time. In the case of a string, copying with strcpy() might move memory if strcpy() happened to be in an unloaded segment. BlockMove() is said to be safe (but then who knows...). It seems better to lock a handle to access big fields. This brings us to the question of state restoring. Suppose you have a long piece of code that needs a pointer to data in a handle. You don't want to keep locking and unlocking, so you call Hlock() once at the beginning, and HUnlock() at the end. Now, if your code calls a routine that also locks and unlocks the handle, then that handle is left *unlocked* in the following code. This situation is difficult to identify. Avoiding it, if you only have HLock() and HUnlock(), requires some coordination between sub-routine implementation and calling code (for example the routine relies on the calling code to lock). This runs counter to the principle of information hiding, and makes for fragile code. That's where HGetState() and HSetState() come in handy. When locking can't be avoided, precede HLock() with a call to HGetState(), and replace HUnlock() with HSetState(). Your code is guaranteed to leave the state as it found it. Some last advice: From: earleh@eleazar.dartmouth.edu (Earle R. Horton): > One thing to remember when using HGetState and HSetState is that these >are not available on early (64k ROM) machines, so you have to test for >machine type or trap availability before using them. For an application >program which probably owns all the Handles it deals with, HLock and HUnlock >are appropriate nearly all the time. For code resources which manipulate >data owned by applications or the system, H[GS]etState is a more application- >friendly technique, if available. For 64k ROM machines you could save the high-order byte of the handle. Code that does either this or calls HGetState() according to a flag should fit ok in a macro. From: lippin@ronzoni.berkeley.edu (The Apathist) (Tom Lippincott0): >Finally, a note on MoveHHi -- this call is easy to overuse. It's only >an advantage if a block is going to remain locked for a respectable >length of time; in particular, only when there's a chance of >significant memory allocation while the block is locked. If used when >not necessary, it wastes time with frivolous heap shuffling. Since >I avoid keeping handles locked for such periods, I use MoveHHi very >sparingly. From: tim@hoptoad.uucp (Tim Maroney): >>HLock and HUnlock are probably the best solution in this case, or the >>HGetState and HSetState calls. > >Nope, there's more overhead involved in the trap calls than in the >copying of eight bytes. And there's also less "cognitive overhead" >if you avoid locking handles, since you never have to keep track >of when things are supposed to be locked. Reducing "cognitive overhead" is *the* idea. Alain de Cheveigne alain@atr-la.atr.co.jp
beard@ux1.lbl.gov (Patrick C Beard) (12/27/89)
In article <3875@atr-la.atr.co.jp> alain@atr-la.atr.co.jp (Alain de Cheveigne) writes: >My original posting had another aim: find the wisest way to deal with >locking and unlocking handles, especially in code that contains many >nested subroutines. [discussion of ways to avoid locking deleted] >Another way to avoid locking is to use a temp variable, as recommends >tim@hoptoad.uucp (Tim Maroney): > >>Handle h = NewHandle(WOMBAT_SIZE); >>(*marsup)->wombat = h; >> >>Slightly more stack overhead, but you avoid trap overhead for locking >>and unlocking, and also avoid all the state-restoration hassles that >>have been mentioned lately here. > >This scheme was recommended by many, and emerged as the best solution >for short fields. But it isn't obvious what to do if the field is >large: a string, or an array, or data for a BitMap or PixMap. Now wait one second. We are talking about _Handles_. The overhead required to keep a temporary copy of a Handle, regardless of how large the allocated memory block is, is 4 bytes! Therefore, your analysis is incorrect. You would never need more than one temporary handle variable to keep track of any size field (if that field is allocated as a Handle). Thus, Tim and everybody else who said this are correct. This is the best way to avoid locking handles. > >Temp variables for big data fields require space (stack? heap?) and >copying takes time. In the case of a string, copying with strcpy() >might move memory if strcpy() happened to be in an unloaded segment. >BlockMove() is said to be safe (but then who knows...). > >It seems better to lock a handle to access big fields. > Bogus conclusion. >This brings us to the question of state restoring. Suppose you have a >long piece of code that needs a pointer to data in a handle. You >don't want to keep locking and unlocking, so you call Hlock() once at >the beginning, and HUnlock() at the end. Now, if your code calls a >routine that also locks and unlocks the handle, then that handle is >left *unlocked* in the following code. This situation is difficult to >identify. Why are you passing this Handle around between more than a few routines? If you are writing fairly modular code, you should be able to minimize the number of places this is a problem. I've been programming the Mac for a couple of years and I've never thought memory management was so hard. I've just followed these simple rules: 1. Never lock a handle if possible. 2. Never, never, keep a copy of a dereferenced handle around, you might be tempted to use it. These cause a whole new breed of dangling references. 3. Allocate memory that is never going to be relocated as a pointer. For example, allocate lots of WindowRecords and GrafPorts early in a program as a single NewPtr call. This will keep the heap clean, and you don't have to worry about Handle states, and your code will be faster. With virtual memory on the horizon, Handles may not be long for this world. ------------------------------------------------------------------------------- - Patrick Beard, Macintosh Programmer (beard@lbl.gov) - - Berkeley Systems, Inc. ".......<dead air>.......Good day!" - Paul Harvey - -------------------------------------------------------------------------------
alain@atr-la.atr.co.jp (Alain de Cheveigne) (12/27/89)
In article <4525@helios.ee.lbl.gov>$@$G(Jbeard@ux1.lbl.gov (Patrick C Beard) writes: >In article <3875@atr-la.atr.co.jp> alain@atr-la.atr.co.jp (Alain de Cheveigne) writes: [...] >>Another way to avoid locking is to use a temp variable, as recommends >>tim@hoptoad.uucp (Tim Maroney): >>>Handle h = NewHandle(WOMBAT_SIZE); >>>(*marsup)->wombat = h; [...] >>This scheme was recommended by many, and emerged as the best solution >>for short fields. But it isn't obvious what to do if the field is >>large: a string, or an array, or data for a BitMap or PixMap. > >Now wait one second. We are talking about _Handles_. The overhead required >to keep a temporary copy of a Handle, regardless of how large the allocated >memory block is, is 4 bytes! Therefore, your analysis is incorrect. You >would never need more than one temporary handle variable to keep track of >any size field (if that field is allocated as a Handle). Thus, Tim and >everybody else who said this are correct. This is the best way to avoid >locking handles. Groan! Why on earth should one want to copy the _Handle_ itself? I must not have made myself clear: what needs copying is not the 4 byte Handle, but the *contents* of the block. That's the stuff that can move. But I admit that Tim's example was unfortunate, as it involves two Handles: wombat, that needs locking, and h, the 4 byte value of which needs copying because HewHandle() moves memory. >>It seems better to lock a handle to access big fields. >> > >Bogus conclusion. Please read more carefully. >3. Allocate memory that is never going to be relocated as a pointer. For >example, allocate lots of WindowRecords and GrafPorts early in a program >as a single NewPtr call. This will keep the heap clean, and you don't have >to worry about Handle states, and your code will be faster. What you're telling me is that the safe way to use Handles is to use Pointers instead. This far from covers all needs. All depends on the order in which you allocate and release memory. Schematically: - if you need memory throughout the program, you can use global space (though you might not want to, for other reasons), - if memory allocated in a routine is allways released on exit (meaning that memory is released in strictly the inverse order - if
alain@atr-la.atr.co.jp (Alain de Cheveigne) (12/27/89)
Ooops! Butter fingers! My last message escaped unfinished. Sorry. Here is the end of what I had to say: [...] This far from covers all needs. All depends on the order in which you allocate and release memory. Schematically: - if you need memory throughout the program, you can use global space (though you might not want to, for other reasons), - if memory allocated in a routine is always released on exit (meaning that memory is released in strictly the inverse order in which it was allocated), it can go on the stack, - if you want memory to stay around after a routine returns (say, a window creation routine) and you're unlikely to need to deallocate it, then use a Pointer, - if you need to allocate and deallocate in any order, then you must use Handles to avoid heap fragmentation. By this reasoning, WindowPtrs should have been Handles, since the user can open and close them in any order. The choice between Handle and Pointer in a program depends on the program's overall structure, and the kind of user interaction you wish to allow. If you can get away with Pointers, then by all means do. Alain de Cheveigne, alain@atr-la.atr.co.jp
d88-jwa@nada.kth.se (Jon Watte) (12/28/89)
In article <3889@atr-la.atr.co.jp> alain@atr-la.atr.co.jp (Alain de Cheveigne) writes: >By this reasoning, WindowPtrs should have been Handles, since the user >can open and close them in any order. Well, for "historical reasons" they aren't... A good thing to do is to ... tempH = NewHandle(sizeof(WindowRecord)); MoveHHi(tempH); HLock(tempH); theWindow = GetNewWindow(4711, (GrafPtr) *tempH, 0L); SetWRefCon(theWindow, tempH); ... tempH = GetWRefCon(theWindow); CloseWindow(theWindow); DisposHandle(tempH); But, alas, that means you can't use the WRefCon for something else... It DOES avoid cluttering the heap, though. If you don't intend to close the window, you may ignore putting the handle into the WRefCon. >The choice between Handle and Pointer in a program depends on the >program's overall structure, and the kind of user interaction you wish >to allow. If you can get away with Pointers, then by all means do. Pointers should be allocated before anything else, or at least when all handles are unlocked. And they DO fragment the heap too often to be very popular with me. -- --- Stay alert ! - Trust no one ! - Keep your laser handy ! --- h+@nada.kth.se == h+@proxxi.se == Jon Watte longer .sig available on request
tim@hoptoad.uucp (Tim Maroney) (12/28/89)
In article <3875@atr-la.atr.co.jp> alain@atr-la.atr.co.jp (Alain de Cheveigne) writes: >>>Another way to avoid locking is to use a temp variable, as recommends >>>tim@hoptoad.uucp (Tim Maroney): >>>>Handle h = NewHandle(WOMBAT_SIZE); >>>>(*marsup)->wombat = h; >>>This scheme was recommended by many, and emerged as the best solution >>>for short fields. But it isn't obvious what to do if the field is >>>large: a string, or an array, or data for a BitMap or PixMap. In article <4525@helios.ee.lbl.gov>$@$G(Jbeard@ux1.lbl.gov (Patrick C Beard) writes: >>Now wait one second. We are talking about _Handles_. The overhead required >>to keep a temporary copy of a Handle, regardless of how large the allocated >>memory block is, is 4 bytes! Therefore, your analysis is incorrect. You >>would never need more than one temporary handle variable to keep track of >>any size field (if that field is allocated as a Handle). In article <3888@atr-la.atr.co.jp> alain@atr-la.atr.co.jp (Alain de Cheveigne) writes: >Groan! Why on earth should one want to copy the _Handle_ itself? > >I must not have made myself clear: what needs copying is not the 4 >byte Handle, but the *contents* of the block. That's the stuff that >can move. You still haven't made yourself clear; I really haven't the faintest idea what you're talking about. Could you provide a code example? >But I admit that Tim's example was unfortunate, as it involves two >Handles: wombat, that needs locking, and h, the 4 byte value of which >needs copying because HewHandle() moves memory. Well, it's big of you to "admit" this. However, you are wrong. That was what the example was all about -- assigning into a relocatable structure can fail if the assignment may have the side effect of moving memory. If the "copying" does not involve anything that moves memory -- for instance, if you are doing a BlockMove on a string, or a conventional "=" assignment on a structure -- then you don't have to either lock the handle or use a temporary variable. And after reading the above quoted paragraph five times, I have to wonder if *you* know what you mean. wombat does not require locking. You can't lock a structure field; that makes no sense. h does not "require copying because NewHandle moves memory"; I don't even know what that is supposed to mean. It is there because the statement (*marsup)->wombat = NewHandle(WOMBAT_SIZE); may be evaluated left to right. First, the address of (*marsup)->wombat is calculated. Second, NewHandle() is called. This second step may cause the first step's address to become invalid, since (*marsup) may change. The way around this is to use a temporary variable, because this inverts the order. First NewHandle() is called and its result saved on the stack; then the address of (*marsup)->wombat is taken and the saved value is assigned into it. >>3. Allocate memory that is never going to be relocated as a pointer. For >>example, allocate lots of WindowRecords and GrafPorts early in a program >>as a single NewPtr call. This will keep the heap clean, and you don't have >>to worry about Handle states, and your code will be faster. >What you're telling me is that the safe way to use Handles is to use >Pointers instead. No he isn't. WindowRecords and GrafPorts *have* to be pointers in the Mac OS. -- Tim Maroney, Mac Software Consultant, sun!hoptoad!tim, tim@toad.com "God must be a Boogie Man." -- Joni Mitchell
alain@atr-la.atr.co.jp (Alain de Cheveigne) (12/28/89)
>>I must not have made myself clear: what needs copying is not the 4 >>byte Handle, but the *contents* of the block. That's the stuff that >>can move. > >You still haven't made yourself clear; I really haven't the faintest idea >what you're talking about. Could you provide a code example? OK, here's a piece of code: /* saveBits is a Handle containing a BitMap record followed by the bit image data. It needs locking because CopyBits can move memory. */ state = HGetState((Handle) saveBits); HLock((Handle) saveBits); fixBitMap(saveBits); /* the baseAddr field is incorrect if the block has moved, fixBitMap() corrects it */ CopyBits(&(w->portBits),*saveBits, &(w->portRect),&(**saveBits).bounds,srcCopy,NULL); HSetState((Handle) saveBits, state); /* restore state */ The "contents of the block" in the quotation above would refer here to the BitMap record and its associated bit image data. Using the "copy to temp" method to avoid locking would require a big piece of stack or non-relocatable heap, time to copy, and the faith that whatever you use to copy won't scramble the heap. BlockMove() is said to be safe, but I'm less sure about calling strcpy() on a string in a Handle. I'm not saying one shouldn't copy to temps, even in this case. Maybe it's the cleanest way. I don't know. I'm here to learn. The reason for putting the offscreen bitmaps in Handles is that they are allocated and released as the user creates and disposes windows. As the user is expected to use many windows (up to say, 50 in a session), using Pointers instead would badly fragment the heap. I'm less worried about WindowPtrs because they are all the same size, and I expect new ones to tend to occupy the slots left by old ones. >>But I admit that Tim's example was unfortunate, as it involves two >>Handles: wombat, that needs locking, and h, the 4 byte value of which >>needs copying because HewHandle() moves memory. [...] >And after reading the above quoted paragraph five times, I have to >wonder if *you* know what you mean. wombat does not require locking. >You can't lock a structure field; that makes no sense. h does not >"require copying because NewHandle moves memory"; I don't even know >what that is supposed to mean. It is there because the statement > >(*marsup)->wombat = NewHandle(WOMBAT_SIZE); > >may be evaluated left to right. First, the address of Mea culpa. I mistyped wombat for marsup. The posting escaped me before I could check. The phrasing wasn't too good either. It's indeed not _wombat_ that might need locking, but _marsup_. It should make sense this way: "Tim's example was unfortunate because it involves two Handles: one whose fields might move in memory (marsup), and another returned by NewHandle(). The latter is the example of a 4 byte value that is copied to temp h, to avoid having to lock marsup." A different choice for that value (say, a Pointer returned by NewPtr()) would have made the example clearer. >>>3. Allocate memory that is never going to be relocated as a pointer. For >>>example, allocate lots of WindowRecords and GrafPorts early in a program >>>as a single NewPtr call. This will keep the heap clean, and you don't have >>>to worry about Handle states, and your code will be faster. > >>What you're telling me is that the safe way to use Handles is to use >>Pointers instead. > >No he isn't. WindowRecords and GrafPorts *have* to be pointers in the >Mac OS. I *know* that. Presenting as a choice something the OS enforces anyway wouldn't make sense anyhow. Recall that the discussion is about Handles. I interpret Patrick's point as saying one should plan out memory needs as far as possible, and allocate memory early, in non-relocatable blocks. Good point, but not relevant to Handle locking in cases where Handles are called for. Anyhow, my apologies again for posting a badly written article. Alain de Cheveigne, alain@atr-la.atr.co.jp
tim@hoptoad.uucp (Tim Maroney) (12/30/89)
In article <3899@atr-la.atr.co.jp> alain@atr-la.atr.co.jp (Alain de Cheveigne) writes: > /* saveBits is a Handle containing a BitMap record > followed by the bit image data. It needs locking > because CopyBits can move memory. */ > > state = HGetState((Handle) saveBits); > HLock((Handle) saveBits); > fixBitMap(saveBits); > CopyBits(&(w->portBits),*saveBits, > &(w->portRect),&(**saveBits).bounds,srcCopy,NULL); > HSetState((Handle) saveBits, state); /* restore state */ > >The "contents of the block" in the quotation above would refer here to >the BitMap record and its associated bit image data. Using the "copy >to temp" method to avoid locking would require a big piece of stack or >non-relocatable heap, time to copy, and the faith that whatever you >use to copy won't scramble the heap. BlockMove() is said to be safe, >but I'm less sure about calling strcpy() on a string in a Handle. A bitmap is only 14 bytes. The compiler can move it using three MOVE.Ls and a MOVE.W. I'm pretty sure that's faster than calling a trap -- that is, even the overhead of trap dispatch is more expensive than that, to say nothing of what the trap does once you get to it. I see what you mean now, and of course some locking does need to be done. For instance, if the baseAddr field of your bitmap is made relocatable, as you seem to be doing and as some code I'm now working on does, then you want to lock it, extract the pointer, stick it in the bitmap, and then revert to the previous state after CopyBits. Not only is it big, but it's variable-sized. But a BitMap structure is cheaper to copy into a temp variable than to save/lock/restore, e.g., BitMap map = **saveBits; /* four instructions to copy */ >The reason for putting the offscreen bitmaps in Handles is that they >are allocated and released as the user creates and disposes windows. >As the user is expected to use many windows (up to say, 50 in a >session), using Pointers instead would badly fragment the heap. Right. Good choice. >I'm less worried about WindowPtrs because they are all the same size, and >I expect new ones to tend to occupy the slots left by old ones. Well, maybe and maybe not. I throw caution to the winds and go ahead and allocate all my windows in the heap, but it's just bad design in the MacOS that they aren't relocatable, and they *can* cause heap fragmentation. You can't touch GrafPort's at the interrupt level anyway, because of the clipRgn and visRgn fields, so there's no reason the whole structure shouldn't be relocatable. No doubt by the time the designers realized this, they had all this hand-optimized assembly language code in the OS already written and it would have been impossible to modify. Another reason to use C to write operating systems. (I understand the MacOS was written largely in Lisa Pascal, then hand-optimized because the compiler was too stupid to do it. Changing a data structure from pointer to handle is not too hard in a compiler, because the compiler will pick up all the places that need to be changed, but a naive Pascal compiler won't put out fast enough code for many OS modules, or small enough code to put an entire OS in 64K. So all the code would need to be re-hand-optimized, a process which is likely to introduce many new errors and take a very long time. A naive C compiler can be fast enough with careful register declarations and expression phrasing. Optimizing compilers for either language should work just about as well.) -- Tim Maroney, Mac Software Consultant, sun!hoptoad!tim, tim@toad.com "The Diabolonian position is new to the London playgoer of today, but not to lovers of serious literature. From Prometheus to the Wagnerian Siegfried, some enemy of the gods, unterrified champion of those oppressed by them, has always towered among the heroes of the loftiest poetry." - Shaw, "On Diabolonian Ethics"
alain@atr-la.atr.co.jp (Alain de Cheveigne) (12/30/89)
In article <9433@hoptoad.uucp> tim@hoptoad.uucp (Tim Maroney) writes: >In article <3899@atr-la.atr.co.jp> alain@atr-la.atr.co.jp (Alain de Cheveigne) >writes: >> /* saveBits is a Handle containing a BitMap record >> followed by the bit image data. It needs locking >> because CopyBits can move memory. */ >> >> state = HGetState((Handle) saveBits); >> HLock((Handle) saveBits); >> fixBitMap(saveBits); >> CopyBits(&(w->portBits),*saveBits, >> &(w->portRect),&(**saveBits).bounds,srcCopy,NULL); >> HSetState((Handle) saveBits, state); /* restore state */ >> >>The "contents of the block" in the quotation above would refer here to >>the BitMap record and its associated bit image data. Using the "copy >>to temp" method to avoid locking would require a big piece of stack or >>non-relocatable heap, time to copy, and the faith that whatever you >>use to copy won't scramble the heap. BlockMove() is said to be safe, >>but I'm less sure about calling strcpy() on a string in a Handle. > >A bitmap is only 14 bytes. The compiler can move it using three >MOVE.Ls and a MOVE.W. I'm pretty sure that's faster than calling a >trap -- that is, even the overhead of trap dispatch is more expensive >than that, to say nothing of what the trap does once you get to it. If you read carefully the comment that opens the code, you'll see that the Handle saveBits contains a bitmap *and* the associated bit image data, pointed to by the baseAddr field. This is much larger than 14 bytes. The purpose of the fixBitMap() routine is to make sure the baseAddr field is correct once the Handle is locked. I might have added that this code illustrates a point I tried to make earlier. Suppose that the implementation of fixBitMap() requires locking saveBits (it doesn't really, but we're supposing), and uses HLock() and HUnlock(): void fixBitMap(bitMapH) BitMap **bitMapH; { HLock(bitMapH); [do stuff] HUnlock(bitMapH); /* don't do this */ } In the calling code, saveBits remains unlocked for CopyBits(), and we die. This is an insidious bug, because the function call hides the unlocking from the calling code. This is why I advocate the use of HGetState() and HSetState(), in *all* cases where locking/unlocking is called for. You never know where your code will be called from. And I wish someone had told me when I started out programming the Mac. >>I'm less worried about WindowPtrs because they are all the same size, and >>I expect new ones to tend to occupy the slots left by old ones. > >Well, maybe and maybe not. I throw caution to the winds and go ahead >and allocate all my windows in the heap, but it's just bad design in >the MacOS that they aren't relocatable, and they *can* cause heap >fragmentation. I think WindowPtrs are the only Pointers I allocate dynamically (and DialogPtrs, but dialogs usually go away soon). I read somewhere that the Memory Manager puts pointers as far down in memory as possible. So I think that that part of the heap should consist mainly of WindowPtrs, or holes of the same size. But I haven't checked. I wonder if there's a better way. Alain de Cheveigne, alain@atr-la.atr.co.jp
alain@atr-la.atr.co.jp (Alain de Cheveigne) (12/31/89)
In article <2632@draken.nada.kth.se>, d88-jwa@nada.kth.se (Jon Watte) writes: >In article <3889@atr-la.atr.co.jp> alain@atr-la.atr.co.jp (Alain de Cheveigne) writes: >>By this reasoning, WindowPtrs should have been Handles, since the user >>can open and close them in any order. To be precise, I should have written, "allocate and deallocate" instead of "open and close", since a "closed" window (e.g. hidden with HideWindow())can still be allocated. Mais vous m'aviez compris... >Well, for "historical reasons" they aren't... > >A good thing to do is to > >... > tempH = NewHandle(sizeof(WindowRecord)); > MoveHHi(tempH); > HLock(tempH); > theWindow = GetNewWindow(4711, (GrafPtr) *tempH, 0L); > SetWRefCon(theWindow, tempH); > >... > > tempH = GetWRefCon(theWindow); > CloseWindow(theWindow); > DisposHandle(tempH); > >But, alas, that means you can't use the WRefCon for something else... Wow! I always wanted to put a window record in a Handle, but never dared. Do you actually sometimes unlock the Handle and let it float? If you don't, I fail to see the advantage of a non-relocatable block at the top of the heap over one at the bottom. If you do unlock, don't you risk confusing the Window Manager or Multifinder when the block actually moves? Using the refcon is not a big problem, because you can make it point to a structure (preferably relocatable) that contains a field that points back to your Handle, along with other fields for whatever else you need to associate with the window. Alain de Cheveigne atr-la.atr.co.jp
tim@hoptoad.uucp (Tim Maroney) (01/01/90)
In article <3902@atr-la.atr.co.jp> alain@atr-la.atr.co.jp (Alain de Cheveigne) writes: >> tempH = NewHandle(sizeof(WindowRecord)); >> MoveHHi(tempH); >> HLock(tempH); >> theWindow = GetNewWindow(4711, (GrafPtr) *tempH, 0L); >> SetWRefCon(theWindow, tempH); >> >> tempH = GetWRefCon(theWindow); >> CloseWindow(theWindow); >> DisposHandle(tempH); >> >>But, alas, that means you can't use the WRefCon for something else... > >Wow! I always wanted to put a window record in a Handle, but never >dared. Do you actually sometimes unlock the Handle and let it float? That's expressly forbidden. Some people were doing this in their grow zone routines before the II, and their code broke under color systems. The auxiliary window list counts on the pointers staying the same. I suppose you could fix up the auxiliary window list pointers now, but then Apple could institute yet another table that stashes window pointers (perhaps in the Layer Manager) and you'd be SOL again. >If you don't, I fail to see the advantage of a non-relocatable block >at the top of the heap over one at the bottom. I don't understand that either. In fact, it looks as if you're likely to be creating big holes at the top of the heap. I don't see why it's better to have them at the top. >If you do unlock, don't you risk confusing the Window Manager or >Multifinder when the block actually moves? Spot on, old chap. >Using the refcon is not a big problem, because you can make it point >to a structure (preferably relocatable) that contains a field that >points back to your Handle, along with other fields for whatever else >you need to associate with the window. I don't know why your correspondent said that you can't use the window refCon for anything else if you use it to store this handle. First, you don't have to store the handle at all; you can use RecoverHandle on the window pointer to find the handle. And as you pointed out, if you do store it, it can be stored in a struct stored in the refCon. -- Tim Maroney, Mac Software Consultant, sun!hoptoad!tim, tim@toad.com These are not my opinions, those of my ex-employers, my old schools, my relatives, my friends, or really any rational person whatsoever.
casseres@apple.com (David Casseres) (01/03/90)
In article <3875@atr-la.atr.co.jp> alain@atr-la.atr.co.jp (Alain de Cheveigne) writes: > From: shebanow@Apple.COM (Andrew Shebanow): > >It is true that the lists of traps that may move or purge memory are out > >of date, and that traps which currently are considered "safe" may become > >"unsafe" sometime in the future. Our recommendation is that developers > >err on the side of caution: if you are passing a dereferenced field > >of a handle by address, either use a temporary or lock the handle. I > >do feel that there are some calls which are "safe" to use dereferenced > >handles with, like BlockMove, some string manipulation calls, etc. Use > >your better judgment (or "Toolbox Karma", if you will). > > Which is pretty lame. Either a call is safe or it isn't. If DTS > can't guarantee it, who am I to judge, and how? Trial and error? A big part of the problem is patches. Apple can (in principle) tell you exactly which traps are safe, but this doesn't help if your code winds up running on a system where safe traps have been patched by inits or other third-party code, and the patches are not safe. Personally, I assume that no trap call is "safe." > Other (non-ROM) subroutines are supposedly safe if they don't call the > ROM. But to be sure of this, you have to know what's inside a > routine. This defeats the principle of information hiding, so the > code tends to be unreliable (even Apple can't seem to keep track of > such side effects). > > In addition, a call to a "harmless" subroutine can move memory if the > subroutine is in an unloaded segment. Bottom line: don't trust *any* > routine, either Apple's or your own. That's the bottom line, all right, but I want to point out that a great deal of pain can be avoided by a style of code design that finesses the problem by passing around handles, not pointers. The whole point of a handle is that it is always valid. Pass handles, and dereference them only when you have to; then you only have to lock occasionally. Design your heap data structures accordingly. In the kind of code you're worrying about, I'd say the overhead of dereferencing a handle many times instead of just once is going to be less than the overhead of a great deal of locking and unlocking. Certainly the cognitive overhead is going to be less, and readability will be much better. It's true that the Toolbox, especially older parts like QuickDraw, doesn't always encourage this style of coding, but this is not a major problem. David Casseres Exclaimer: Hey!