TweetFollow Us on Twitter

PowerPlant Tips

Volume Number: 15 (1999)
Issue Number: 4
Column Tag: PowerPlant Workshop

PowerPlant Tips, Tricks, and Gotchas

by John C. Daub, Austin, Texas USA

Tidbits of knowledge to improve your use of PowerPlant

Hello, Again

I hope you've been enjoying my "series" of articles on PowerPlant as they've appeared here in MacTech Magazine. I've enjoyed writing it. Just about a week before I started writing this article, one of the hard drives on my Mac at home starting making a strange clicking noise, so I've disabled it - there went my development drive. So since I don't have all my normal resources at my immediate disposal, I thought this month I'd turn to the Internet for some input and posted a question to the comp.sys.mac.oop.powerplant newsgroup. I asked the readers to post their favorite PowerPlant "tip, trick, or gotcha". I received a few, but one reader had a lot to say. I've dedicated a special section of this article to his experiences :-)

This is the sort of stuff that you learn through experience. As much as Carl tries to document PowerPlant, most of these things you just don't appreciate until you get bitten, and especially if they save your hide or find that niggling bug that you haven't been able to squash for the past three months. This isn't to say I'm the most experienced programmer out there, but rather to try to put some of those experiences of mine and your fellow PowerPlant-users down in print to document them. A magazine article's a good medium for this purpose. So I tried to collect from various peoples and sources and put down what's relevant today. Some of this might still be relevant years from now, others obsolete, or maybe all of this moot.

But still, I remember what it was like when I started. It's a steep learning curve to get into programming, and definitely into PowerPlant. Once you're there it's not always easy, but it's certainly not as difficult as when you started. Hang in there. I remember all of the wonderful people that helped me when I was starting (Marco, Dan, Tim, Eric, Greg) and still help me today (especially you, Greg) and so I'm just trying my best to return the favor. It's this really cool Mac developers community that we have full of openness and helpfulness. Maybe that's why Mac folk seem to find linux-ppc really cool (and can't wait for OS X). And that's why I love to stick with Mac. If we can keep this kind of community of sharing going on, that'll benefit us in the long run.

So read on. I hope you'll learn something new. I know that I did.

Tips

Let's start out with some general tips for using PowerPlant. These should remain valid for a while as they are more principles of PowerPlant's design and implementation style than matters of code. After Tips will come the Tricks, and following that the Gotchas. The numbers are present for ease of reference. I present these in no particular ordering, except for this first one:

1. Always read the release notes (or, RTFM)

This cannot be stressed enough. PowerPlant is a very dynamic framework that is ever-evolving to suit the needs of Mac OS software developers. It's certainly frustrating at times to have to keep up with each release, but you must. Reading the release notes is the shortcut to seeing what changed and learning how to quickly update your project to suit.

There are sections in the release notes that deal with new features, a detailed change log, possible referrals to documents and PowerPlant TechNotes (yes, we have them too). And most importantly, we try to catch all of the places where existing projects will have to tweak something and report those first in the notes file. Heed them and it should eliminate most of your upgrading pains and posting to the newsgroups (or emailing Metrowerks Support). You'll get faster turnaround if you RTFM first.

And if you do have trouble, of course email Metrowerks Support at <support@metrowerks.com> and/or post to the comp.sys.mac.oop.powerplant newsgroup. There's usually someone just a keyboard away that can help.

2. Check the Reference CD

On your Reference CD in the PowerPlant Examples folder we keep a great deal of information about PowerPlant. Not only examples, but also archives of obsolete code, TechNotes, and archived release notes. Yes, all PowerPlant release notes archived back to DR/1. Eases those upgrades when you jump a couple releases.

PP TechNotes is our folder of PowerPlant Technical Notes (just like Apple's TechNotes). This is a good place to go for explanation on greater concepts like the design of the Appearance Classes, or understanding the Networking and Internet Classes, docs for Andy Dent's More Table Classes, and many other items of interest. Worth checking out.

3. Keep up with OS evolution

Although PowerPlant is written in C++ and is an application framework (see my "What is PowerPlant?" article , December, 1998 MacTech), it remains very close to the Toolbox. A lot of people are after higher levels of abstraction and encapsulation these days, mostly because it aids porting to other platforms. PowerPlant doesn't want to prevent you from going cross-platform, but it'd rather focus on the Mac. Do one thing really well instead of trying to do more and failing.

And so PowerPlant must keep pace with the Mac OS as it evolves. ATSUI, Carbon, OS X, OpenGL. There are some great things in store for the future of Mac. PowerPlant will be there, we all have to keep up.

4. This is C++. Take advantage of what the language has to offer

As the C++ language has evolved and compilers race to implement the standard, PowerPlant has made more and more use of the C++ language. By the time you read this, CodeWarrior Professional 5 should have shipped and hopefully you have a copy on your desk. PowerPlant now uses a real exception class, LException, publicly inheriting from std::exception as the means of providing information about error conditions (instead of the ExceptionCode, which is just a typedef'd long). Templates are being used more (TArray, I'm working on a template version of UMemoryMgr for post-Pro 5). RTTI is utilized throughout the framework. C++ is a good language - strive to take advantage of it.

So as well, use classes like TArray to manage your lists of data; use LString and friends LStr255, LStringRef, and TString (see my article on LString in the January, 1999 issue of MacTech). Be typesafe. Learn how great stack-based classes are for resource management and exception safety. Work logically.

5. Use compiler warnings

Compiler warnings are there to help you. They are not errors, which mean something is certainly wrong, but rather they are warnings - informative messages to try to point out potential problems. Turn them on, use them on your code. They can prevent problems.


Figure 1. CW Pro 4 C/C++ Warnings Preference Panel.

As you can see in Figure 1, there is a good deal of coverage performed by the Metrowerks C/C++ compiler. I'd like to talk about three warnings: Implicit Arithmetic Conversions, Non-Inlined Functions, and Hidden Virtual Functions.

Implicit Arithmetic Conversions is a warning issued "if the destination of an operation isn't large enough to hold all possible results. For example, assigning a value of a variable of type long to a variable of type char will result in a warning if this option is on" (Metrowerks 1998, CCR-26). PowerPlant strives to compile cleanly with this warning on, but some areas do slip through (e.g. Grayscale Implementations of the Appearance Classes). Barring that one point, PowerPlant should compile cleanly with this option.

Non-Inlined Functions is a warning that provides you with information on what you asked the compiler to inline but it did not for some reason (remember, inline is just a suggestion). Aside from the usual guidelines for inlining, the inline depth, a C/C++ compiler setting, does affect the output of this warning. Depending upon your inline setting, you may receive these warnings on PowerPlant code. Play around with the inline levels and see how that affects your resulting binary. Choose what works for you. Generally I leave this option off and just use it when I wish to fine-tune my inline settings for maximum gain from this C++ language feature.

Hidden Virtual Functions is another warning with which PowerPlant should compile cleanly. PowerPlant does compile cleanly with this warning enabled, but not without a little help from a #pragma. TArray hides some inherited virtual functions, but due to the design and intent of the class we can guarantee this hiding is safe; in fact it is needed for the intent of the class. But when you use a TArray, you receive several hidden virtual function warnings. To quiet the compiler perform an explicit instantiation of the template or just at the point of instantiation, wrap it with #pragma warn_hidevirtual off/reset like this:

   #pragma warn_hidevirtual off
         template class TArray<FooData*>;
   #pragma warn_hidevirtual reset

Using a technique like this you are able to ensure quiet and clean compiles with TArray. Look in the PowerPlant sources - this technique is used throughout the framework.

All compiler warnings are useful and generally I leave all on except the non-inline and arithmetic conversions warnings, turning those on as needed. Refer to the C Compilers Reference on your CodeWarrior Reference CD for more information on the warnings and other language preferences.

6. Explore unfamiliar areas of PowerPlant

I do not know all of PowerPlant, but I do my best to at least have a familiarity with all of PowerPlant. It takes time to learn and master every aspect, and with PowerPlant so large it's difficult to know it all. But if you are at least familiar with what PowerPlant has to offer then in the future when you have a new project, by knowing what PowerPlant has to offer you can determine if it's the right tool for your new job. Or at least you can avoid reinventing the wheel. I've seen a lot of people spend hours writing something only later to find that PowerPlant already had something better.

For example, do you know what LSharable is? Have you tried using the AppleScripting support? What about the Networking and Internet Classes? Thread Classes? Debugging Classes? Keep exploring PowerPlant. It has a lot to offer.

6a. Check the Contributed Class Archives

Located off Metrowerks' website you will find the PowerPlant Contributed Class Archives, sometimes called the "contrib archives", or some similar abbreviation. This is an archive of PowerPlant classes submitted by your fellow PowerPlant users for your use. The story usually goes that someone had a problem they needed solved, they couldn't find an existing answer so they invented their own. The Warrior believed their situation wasn't a unique experience, so they submitted their code to the archives for public distribution. You can find a lot of great stuff on there.

Some of the code is old, might be out of date, might not even work. Metrowerks can do nothing about this. All Metrowerks can and does do is provide the web and ftp space, and the search engine. Metrowerks cannot vouch for the code on the archive, cannot support, cannot fix, etc. that code. For any issues with items on the contrib archive, you will need to contact the author of the code for support.

And now to speak a bit more on a code level....

7. Never assume the drawing state. Before you perform any operation that draws, always set the drawing state exactly as you need it.

That could be as simple as ensuring FocusDraw() is called (and don't forget that FocusDraw() returns a Boolean if it succeeded in focusing or not - check it), or it might require you to load text traits, ApplyForeAndBackColors, or a host of other things. The key to remember is to never assume the drawing state and always establish it as you need.

One common "rule" has been to save the current state, set it as you need, then restore the original state. Usually this was done because too many assumptions were being made, be it that the state wouldn't change or that nothing else could change the state or people just want to be paranoid and safe. If however you base upon the premise that everything must set their state before they draw, then the save and restore becomes unnecessary as there are no assumptions being made. To then save and restore adds extra overhead and bloat you just don't need.

Of course there are times where you must save and restore, and sometimes this rule has to bend a bit, but those are exceptions and not the rule.

8. Always check return codes

Exceptions are a wonderful part of C++, but the Toolbox doesn't throw exceptions; instead it returns error codes. Sometimes the error codes are a return code (FSWrite), sometimes returned as a parameter (TempNewHandle), sometimes indirectly (NewHandle, returns nil on failure), sometimes signaling to look elsewhere (NewHandle returning a nil Handle tells you to call MemError), sometimes in other places (ResError). But no matter where or how you obtain the error checking, always perform it and handle it as you can. If you can handle and recover, then do so. If you cannot, do what you can, perform any cleanup, and report that error (typically throwing an exception, e.g. ThrowIfOSErr_()).

If you don't check return codes and don't put a mechanism in place to handle those situations, then don't be surprised when you cannot explain the strange new behavior your application has now taken on.

Tricks

9. Option-click to use the color picker in Constructor

The color popup button/menu that you see in Constructor editors such as the Text Traits editor (to edit the color of the 'Txtr') or the LWindow Property Inspector (to edit the 'wctb' resource) normally pops up a menu/palette of colors to choose from. However, if you find using the Color Picker more to your needs, option-click on the button and the Color Picker will appear.

10. Use drag and drop to create instances of widgets not within Views in Constructor

When you create a 'PPob' resource, typically you create a container View (LView, LWindow, LDialogBox, etc.) and place other widgets inside of the container. But what if you want to create a single instance of a widget, like a single LPushButton; perhaps to use as a "template" for some widgets you need to create on the fly. Simple. Drag an instance of the widget from the Catalog window into Constructor's Project Window (not to be confused with the IDE's project window). That will create a 'PPob' resource containing nothing but that particular widget. No container view. Totally independent. You can establish its properties like you normally do in Constructor (double-click, open Resource in the Layout Editor, double-click on what you see to open the Property Inspector window for the widget). To instantiate the widget at run time, use UReanimator::ReadObjects().

11. Use Constructor's hidden preferences

Did you notice that Constructor has no Preferences dialog? Constructor does have a Preferences file and it does have preferences, but no user-accessible means of manipulating those preferences. And with Constructor currently in maintenance mode, it's difficult to justify the effort required to add a Preference dialog. But that doesn't mean there aren't preferences that you can modify - you just have to know where to look.

Open a copy of the Constructor application in your favorite resource editor (I prefer Resorcerer). Look for the 'INSP' Resource. The contents of this Resource (all one byte of it) control the "pinned" state of the Property Inspector window. If the byte of this Resource is zero, there is only one Property Inspector window shared by all objects (of course you can pin individual instances of the window by clicking the "pin" icon). If this byte is a non-zero value, then all Property Inspector windows open "pinned". If you find yourself pinning the Property Inspector window a great deal, you may want to consider turning this byte on to ease your mouse-clicking finger.

Another hidden preference lies in 'STR#' 1099, index 4. The Constructor application ships with this string as "AM". In this mode, Constructor will attempt to use Appearance Implementations when rendering widgets in the Layout Editor (some widgets are hard-coded to use Grayscale Implementations due to Resource Manager limitations). If you change this string to "GA", Grayscale Implementations will be forced in the Layout Editor.

12. Mark your Resources as purgeable

When you access a Resource, the Mac OS Resource Manager first looks to see if that Resource is already in memory. If so, it returns a Handle to that existing instance of the Resource, else it loads a copy from disk. This is a wonderful way of reducing memory footprint as everyone shares a single instance of the Resource data. But what if you wish to have multiple instances of a particular picture in a window, and this picture comes from 'PICT' resource? If they all share the PICT data, if when a window closes it calls ReleaseResource upon its cached PicHandle, the next time the other window needs to redraw and accesses that now-released Resource via its now-stale PicHandle? You'll probably crash. It's not good to pull the rug out from under someone.

So instead, you could DetachResource, but that wastes memory by having multiple instances of the same (and typically read-only) data. Still, it's a way to go. Or, just never assume you have a valid Handle (as someone may pull the rug out from under you) - just GetResource every time you need it and cannot be certain of the Handle state. Then to follow through, never call ReleaseResource on that Handle; instead mark your Resource as purgeable and let the Memory Manager handle releasing it for you. Why release it before you need it? Your app will feel a lot slower if it has to load Resources from disk every time it needs them, so why not let them be cached in memory? Just remove them if you need it, and ensure they are always there when you need them. Again, never assume, always make the state known and certain.

PowerPlant works to practice this in all possible places, like handling 'Txtr' Resources (UTextTraits) and 'PICT' Resources (LPicture::DrawSelf()). Other Resources include: 'STR ', 'STR#', 'ICON', 'icl8' and other icon Resources, etc. It might make reports from tools like Spotlight a little noisy, but that's ok. I consider it a bug in Spotlight ;-)

13. Use the PowerPlant Resources folder

A lot of people do not realize that we do provide the necessary Resources for working in PowerPlant. In fact we do, and they are within the PowerPlant Resources folder. Here you will find all of the necessary resource files you need, like PP Action Strings.rsrc, PP AppleEvents.rsrc, and a host of other files needed for PowerPlant to work correctly. Then also some ones needed to get certain features to work: GetDirectory.rsrc, EmptyMenu.rsrc, CustomTextColorCDEF.rsrc. Finally, some utility files like Resorcerer and ResEdit TMPL (template) files, and PowerPlant.r for you Rez-lovers out there.

13a. Use Rez/DeRez for localization

Use the above PowerPlant.r file and Rez/DeRez your .ppob files to help you localize your PPob's. The IDE does support both Rez and DeRez.

14. The class browser is your friend

Learn what the Class Browser is and learn to love it. It's really great at helping you navigate code. I find it's a useful reference tool (what better way to know what a function or class does than to read source!). I also find looking up information in my project much easier (that Catalog window is wonderful). Class browsing is more effective for C++ (and other OO languages) because it lets you see the natural structure of the class, including what was inherited, overridden, overloaded, etc. Source alone sometimes cannot give you the full picture.

Also, to make life browsing (and debugging, and just in development overall), don't use precompiled header files (.pch, .pch++, etc.). I've found them to be evil. You're welcome to use them if you'd like, but I've found that a good prefix file is all you really need and quite less headache in the long run. Yes the compile speedup from pch's is nice, but hey... gives me time to refill my water mug at work. Use prefix files, don't use pch files.

15. Pay attention to new releases and possible changed code dependencies

PowerPlant is an evolving framework. Occasionally some code falls by the wayside sooner or later for some reason or other (no time to work on it, engineer transferred to another team, no longer a Metrowerker, obsolete technology (PowerTalk, PowerPart/OpenDoc)). To ease transition, any code that is moving obsolete we try to place into a Will Be Obsolete folder. Next release of PowerPlant, code in this folder becomes obsolete. Obsolete code means that it will no longer be actively developed by Metrowerks, no longer supported, no more bugs fixed, no more features added, no more upkeep, nothing. We'll ship it on the Reference CD in the archives and you're welcome to use it, but Metrowerks no longer supports them.

So to help you make the move, shield this folder and reset your access paths. Make. See what happens. Adjust as needed. Refer to Release Notes and Change Logs for complete details and information. Don't forget the Release Note archives on the Reference CD as mentioned in item 2.

Gotchas

16. When storing in an LArray, use TArray instead. Or if you really must use LArray, don't forget to pass by address.

One of the problems with LArray is its use of void*'s to pass arguments of data to and from the Array. This is needed to allow the storing of any data type, but is hardly typesafe - the void* will allow anything through, and there will be no help from the compiler to alert you to a potential problem situation. This is why TArray was created. It's a wrapper for LArray, but provides an Array class that is typesafe. It also simplifies using the Array classes as having type information allows TArray to handle some aspects for you (such as always providing the sizeof the data).

Another benefit of having type information is the elimination of using void* as an argument. Instead the methods of TArray take the data by its type and then by reference. This eases use in code and improves safety. But if you must use LArray, do remember to pass your data by address and not value. The following snippet should illustrate.

   LArray      theLArray(sizeof(int));
   int         theInt = 3;

      // This is wrong
   theLArray.AddItem( theInt, sizeof(int) );

      // This is right
   theLArray.AddItem( &theInt, sizeof(int) );

      // Or using TArray, this is a bit easier
   TArray<int>   theTArray;
   theTArray.AddItem(theInt);

17. Avoid conflicts. Adhere to PowerPlant conventions

Chapter 3 of the PowerPlant Book discusses the PowerPlant conventions. Even if you do not wish to adopt them for yourself, at least be aware of PowerPlant's style so you can know where to look to try to resolve conflict, and/or to avoid conflict altogether. You have a copy of this book on your Reference CD.

18. Erase On Update

Before drawing you ordinarily want to erase what was there before so there is no drawing of ghost and/or incorrect images. As an optimization for most needs, PowerPlant provides the typical topmost view (LWindow) with a feature to Erase On Update. By doing this the entire port is cleaned of artifacts so the subpanes can now draw freely as LWindow::Draw() iterates over them.

If you do not like the flicker this creates, you are welcome to turn the feature off. However, by turning the feature off, you assume the responsibility for erasing the drawing area. This may change the behavior of many Pane classes as they are written upon the assumption of this feature being active.

19. Create Appearance Controls first

With the advent of Themes and the concept of a root control, it is necessary to ensure this root control is truly the first control made in a window. PowerPlant creates a root control in LAMControlImp::MakeMacControl(). Ensure this is called (and hence CreateRootControl) before a call to NewControl is made. If you do not do this, both the call to GetRootControl and subsequently CreateRootControl will fail.

Typically you end up in this problem situation from mixing both Appearance and "Classic" Controls in the same window, e.g. a LWindow with an LStdButton and an LImageWell (respectively, in order of visual hierarchy). More specifically, it is usually because the Standard Control was created before the Appearance Control (hence NewControl is being called before CreateRootControl). This situation often arises if you have an LScroller or LActiveScroller in your PPob and it's created "first" (before any Appearance widgets are). Use LScrollerView instead.

Solutions: 1) Don't mix Control types within a Window. Use all Appearance or all "Classic". 2) Ensure at least an Appearance Control is created first (if you have to, embed a hidden widget like an LSeparatorLine just to force things through). That's about it. We thought through many possible solutions and none were truly optimal, but this worked the best given the nature of the situation (which if you're not familiar with, try DejaNews). IMHO, go with solution one.

20. Use LApplication as it was intended - to manage the Application object

This tip comes from Julien Guimont. He noted that as a beginner, he placed all of his application's behaviors into the Application object. It's certainly very tempting for a beginner to do this as it's very central, provides most of what you need, and you don't really need to branch out much more. Furthermore, if you're coming more from a procedural programming background, this is perhaps a more familiar and comfortable style. Although perhaps effective, it's not very PowerPlant.

Instead, factor your behaviors out to your objects. For example, your windows should be "self-sufficient" and able to manage itself and its own behaviors. Learn to think more about the data and how to manipulate it instead of how to get from point A to point B. If you want some Button to do some thing when some occurrence happens, then have the button Listen for that occurrence instead of some totally unrelated object listening for it and then dispatching to the Button. This is what object-oriented programming is all about.

21. Let LPane come first in class declarations

Greg Dow wrote: "When deriving from LPane (or any subclass of LPane) and one or more other mix-in classes, make LPane (or the LPane subclass) the first base class in the class declaration. For example:

   class MyWindow : public LWindow, public LListener // Right
   class MyWindow : public LListener, public LWindow // WRONG

When creating objects from a PPob resource, UReanimator and URegistrar cast pointers to LPane objects to/from (void*). If the LPane class or subclass isn't the first base c lass, these casts may not work properly."

This is less of an issue today since RTTI is used throughout PowerPlant, but some people still avoid RTTI and use unsafe C-style casts. Be safest and adhere to this rule.

David Phillip Oster

David Phillip Oster I have never met in person, but have interacted with a great deal on comp.sys.mac.oop.powerplant. David's always been one of the active and helpful members of our community of PowerPlant users and readers/posters to the PowerPlant newsgroup. When I posted to the newsgroup asking for contributions to this article, David responded with three lengthy and detailed posts. I was quite happy to have elicited this sort of response (and wondered just how much free time David must have).

With David's permission I am reprinting the full text of his posts. I did perform some editing for typos and layout, but the meat of the following text is all David's. These posts were made to the comp.sys.mac.oop.powerplant newsgroup on 16 January and 18 January, 1999. Although not all of the items pertain directly to PowerPlant, they certainly will aid the PowerPlant developer. David, thanx a bunch.

Here are a few, off the top of my head:

DebugNew is your friend

DebugNew.cp is your friend. Read it, use it. Configure it in your debug builds to do full checking. Learn to interpret its leaks report. Use "NEW" instead of "new".

Assert_() is your friend

Know when to use Assert_() (to catch programmer-time errors) versus when to call Throw_, or one of its variants (to catch run-time errors). Sprinkle your code with Assert_() sanity checks:

   LPane* thePane = someView->FindPaneByID(pane_TheFooPane);
   Assert_(thePane != nil);

This will fire if you munged your resources in Constructor (a programmer-time error). We use Assert_() because if thePane is nil here, then we've got a wildly inappropriate resource and we need to get the programmer's attention to fix it now. But we don't necessarily need to check this in the release build (where debugging, including Assert_(), is turned off) because this error can only happen in the release build if the program is corrupt on disk, and if we are corrupt on disk, all bets are off. Its like checking to see if the power is on. Versus:

   ThrowIfOSErr_(FSMakeFSSpec(0, 0, "\pFooFile", &fooFS));

which is a run time error. The user deleted the FooFile, which we were expecting to be present. We use (a variant of) Throw_ so the program can detect the error, report and recover as appropriate, and keep going.

[The Debugging Classes have a nifty FindPaneByID_() macro that simplifies the former situation.]

Spotlight is your friend

Test with Spotlight. It will show you bugs that nothing else will.

Fat binaries are your friend

Build both 68K and PPC versions of your app. Test them both. This roots out bugs that just happen to work on one architecture or the other because of random chance and the memory layout.

[And do test your 68K binary on a real 68K machine, if you have access to one. I have seen a 68K binary run perfectly under emulation on a PPC but crash horribly on a real 68K. It turned out to be user coding errors, but they would never have been noticed if 68K was not tested under real 68K conditions.]

Scripting is your friend

Since you need to test them both anyway, make your app scriptable, and run it through a test script. When you find a bug, augment your test script so it tests that case and the result. That keeps the bug from creeping back in.

Avoid allocation in constructors. Where it is unavoidable, preflight

Avoid allocation in constructors. Where it is unavoidable, preflight. The Metrowerks C++ runtime doesn't gracefully handle a throw from a constructor. (Since an object is partially built the corresponding destructor isn't called, so that owned sub-objects can't be deleted. Not Metrowerks fault. It is a flaw built in to the C++ language.

  class Ca{
   public:
      Ca() { mAp = nil; mAp = NEW LArray; }
      ~Ca() { delete mAp; }

      LArray*   mAp;
   };

   class Cb : public Ca{
      Cb() { mB2p = mB1p = nil; mB1p = NEW LArray;
               mB2p = NEW LArray; }
      ~Cb() { delete mB1p; delete mB2p; }

      LArray*   mB1p;
      LArray*   mB2p;
   }

Suppose we say:

   {
      Cb b;
   }

First the constructor of Ca is called, allocating mAp. Then the constructor for Cb is called, allocating a mBp. The destructors reverse the process.

Now, let's try it again. Only this time, as we allocate mB2p, we run out of RAM, and NEW throws an exception. The destructor ~Cb can't be called, because the run-time system knows that it never finished constructing Cb. So, the super-class destructor is called, mAp is freed. mB1p is never freed. Since we threw because we are low on memory, it is sad that the memory is never freed.

How does PowerPlant handle this problem? It doesn't. Instead it has an LGrowZone class that reserved a 20K emergency buffer. If NEW eats in to the buffer, then the next time WaitNextEvent is called, you'll get a "Memory is Low. Please close some open documents" Alert. The buffer allows PowerPlant applications to eat in the reserve and still successfully allocate. Note that 20K isn't very much, so it is wise to check the free space in the heap before, say, opening a major window.

[There are ways around the situation David illustrated above, such as creating the LArray's as local objects instead of pointers to objects allocated on the heap, performing the allocation in an Init() function that users must call after object creation, or using a class like StDeleter.]

Try to use TArray<LType*> rather than LArray

LArray is sometimes confusing [see item 16 above]. Use the type-safe variant instead.

Use stack based classes like StValueChanger for exception safety

StValueChanger cleanly sets temporarily changed values back to their original value. When you can't use it, consider writing your own stack based class along the same lines, rather than cluttering code with an inline try/catch block. One advantage of the stack-based classes is that you don't need to know what type of exception to catch, cleanup after, and then re-throw. [files such as UMemoryMgr.cp/.h contain many stack-based classes].

Avoid calls to NewRoutineDescriptor(), use BUILD_ROUTINE_DESCRIPTOR

NewRoutineDescriptor() allocates memory, and if called in a loop can be a source of memory leaks. Build your own macros like this:

#if TARGET_RT_MAC_CFM
#define DeclareModalFilterUPP_(var, proc) \
   static RoutineDescriptor   var##RD = \
   BUILD_ROUTINE_DESCRIPTOR
                           (uppModalFilterProcInfo, (proc));\
   ModalFilterUPP             var = (ModalFilterUPP) &var##RD

#define DeclareModalFilterYDUPP_(var, proc) \
   RoutineDescriptor          var##RD = \
      BUILD_ROUTINE_DESCRIPTOR(uppModalFilterYDProcInfo,
                                                    (proc));\
   ModalFilterYDUPP         var = (ModalFilterYDUPP) &var##RD

#else
#define DeclareModalFilterUPP_(var, proc)     \
            ModalFilterUPP     var = proc
#define DeclareModalFilterYDUPP_(var, proc)     \
            ModalFilterYDUPP   var = proc
#endif

Call them like this:

DeclareModalFilterUPP_(ucStandardFileModalFilterUPP, StandardModalFilter);

DeclareModalFilterYDUPP_(ucStandardFileModalFilterYDUPP, StandardModalYDFilter);

This defines variables named ucStandardFileModalFilterUPP and ucStandardFileModalFilterYDUPP which you can use in either 68K or PPC code, that are initialized at run time to either a procedure pointer or a universal proc pointer. Putting the macro definitions in a header file keeps the clutter out of your code.

Ignore constructor, use Rez.(or: #define is your friend)

If your define all your resources except pictures in Rez, then you get access to the C preprocessor. This gives you a number of advantages:

  1. You get consistent symbols between your PPobs and C++ code. Just include a header file written in the common subset of both languages and your paneIDs will be consistent, from C++ to Rez.
  2. You can use search and replace and spell checkers on your strings.
  3. You can parameterize your resources by target. (I build both the Pro and Regular versions of ImageAXS from the same source. In the header of each target I #define TARGET PRO or #define TARGET REGULAR and include Header.h. It has:
   #if PRO == TARGET
      #define programS "ImageAXS Pro"
   #elif REGULAR == TARGET
      #define programS "ImageAXS"
   #else
      #error unkown target
   #endif

Then, in all my dialogs, strings, and menus, I build up the strings I need with expressions like:

   resource 'MENU' (kAppleMenu show_Apple) {
      kAppleMenu,
      textMenuProc,
      kAllItems - (kItem2) ,
      enabled,
      apple,
      {   /* array: 2 elements */
          /* [1] */   "About " kProgramS "S∨", kMenuItemStuff,
          /* [2] */   "-", kMenuItemStuff
      }
   };

That way, if the program name changes during development, I just change it in the header, and know the change is correctly applied through the whole program.

A similar trick separates out language dependent information (mostly strings and the screen rects that need to grow to accommodate long ones.) Another similar trick separates out style guides: you want a gutter of 4 pixels between items, and a 10 pixel border around all the items of the window? Just specify their positions using a #define macro that is an arithmetic expression of appropriate constants. Once you get in to this mode of thinking, you can design your own resource types that are compatible with Reanimator, and use it for persistent storage.

Make PowerPlant read-only

Use a version control tool, or a tool like File Buddy to set all the source code of PowerPlant (headers and .cp files) to be read-only. That way, you won't accidentally modify it. This will save you trouble when a new version of PP comes out and you want to upgrade.

[I prefer a version control tool as Finder locks are annoying. Furthermore, having PowerPlant in version control helps ensure your PowerPlant stays in sync, tracks any changes you might make to the PowerPlant sources, and if you're working in a multi-developer situation, ensures all team members are using the exact same PowerPlant.]

Adding your own code to a basic PowerPlant class with mix-in classes

Balloon Help is great for letting a user know what some graphical object on one of your windows does. It is even better for letting the user know why some object is disabled. Unfortunately, the Mac Toolbox support for Balloon Help only allows for a single disable string, so programmers generally put in a catch-all string listing all of the reasons that an object might be disabled.

A better way is, in your Balloon help support, try to dynamic_cast the pointer to the object that the mouse is currently over to CBalloonHelpMixIn. Recurse up the view hierarchy trying to dynamic_cast, until you get a non-nil pointer. If you get a non-nil pointer, call the BalloonCommandStatus() method, passing in the paneID. Then, in your classes, mix-in CBalloonHelpMixIn as an super-class, as appropriate, and implement BalloonCommandStatus(). This lets your window, or some portion of it, return a Balloon Help Record, referencing a 'STR#' and strID, so that the Balloon Help system can explain to the user exactly why he can't do that right now.

By implementing this as a mix-in class, you write functionality once, where you need it, and mix it in to all of your windows and views that need it. An alternative would be to actually go into the source for LPane or LView. But, then you'd have trouble moving your changes to the next version of PowerPlant.

Adding your own code to a basic PowerPlant class by editing a PowerPlant file

If you really need to change something in PowerPlant, create a PP Overrides folder, and put it ahead of the PowerPlant folder in the Access Paths panel of the Target(s)' Settings. That way, your modified version of LPane.cp will be found before the official one. It will be in a separate folder, so that when you go to a new version of PowerPlant you won't accidentally overwrite it, and you'll be able to quickly compare your file with the official one to see if your changes are still appropriate.

Read the source code for PowerPlant

Often, when you have a design problem, you'll find that the designers of PowerPlant have already coped with that problem or a similar one, so you'll find a debugged pattern for solving your problem.

For example, in ImageAXS Pro an ImageAXS Pro document has many windows, and when the last window is closed, the document should close. But, when the app is closed, it closes all open documents, and when the document is closed, it closes all open windows. If you just wrote the code naively to handle the first case, you'd get in to a situation where the destructor of the window called the destructor of the document and the destructor of the document calls the destructor of the window. This is not only an infinite loop, but it is a call to a destructor of an object that has already been deleted (so DebugNew helps you crash early, while enough of the environment remains so you can still see what is going on). It's bad.

Let's look inside the source code of PowerPlant to see how it handles an analogous problem: Listeners and Broadcasters similarly keep track of each other, also LArrays and LArrayIterators. By making the window a Broadcaster and broadcasting the message msg_BroadcasterDied just before it dies (with itself in the refCon of the message), the document can clear out its pointer to the window and decide whether to die too (since it clears its pointer, its destructor will no longer try to destruct the now dead window.)

LSharable is another choice: if you re-write the way you handle assignments to variables, LSharable implements reference counting with deletion when the reference count is zero.

The Broadcaster/Listener pattern was a better fit for my application.

Read it again

When you get a new release from Metrowerks, read the source again. Metrowerks often adds new methods to existing classes and entirely new classes, and the best way to find out what is new is to read the source code.

Hope This Helped

What you've read here may or may not be news to you. If it is new, I'm glad that you've learned something new. If it's all old-hat to you, consider sharing some of your knowledge and experience with others on forums like comp.sys.mac.oop.powerplant to help further the community. And if you already share on the newsgroups, I'll see you there.

I'd like to thank DejaNews for its services; David Phillip Oster for his permission to reprint his postings; Greg Dow for correcting me (a lot), and letting me reprint some of his tips (read the TechNotes, you'll see).

If there's any single item I'd like you to remember from this article, not only should you read the PowerPlant source, but just make sure you read the Release Notes.

And then read them again. :-)

Happy programming!

Bibliography

  • Daub, John. "Arrays, Iterators, and Comparators... Oh My!" MacTech Magazine. Vol 15, No. 2. February, 1999.
  • Daub, John. "The Ultra-Groovy LString Class" MacTech Magazine. Vol 15, No. 1. January, 1999.
  • Daub, John. "What Is PowerPlant?" MacTech Magazine. Vol. 14, No. 12. December, 1998.
  • Metrowerks Corporation. Constructor Manual. 1998.
  • Metrowerks Corporation. PowerPlant Book. 1998.
  • Metrowerks Corporation. PowerPlant Reference. 1998.
  • Metrowerks Corporation. C Compilers Reference. 1998.
  • <http://www.dejanews.com/>
  • <http://www.onyx-tech.com/>
  • <http://www.dascorp.com/>

John C. Daub is one of Metrowerks Corporation's PowerPlant engineers. He finds he enjoys framework authoring a little too much to be considered healthy. You can reach John via email at hsoi@metrowerks.com.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Tokkun Studio unveils alpha trailer for...
We are back on the MMORPG news train, and this time it comes from the sort of international developers Tokkun Studio. They are based in France and Japan, so it counts. Anyway, semantics aside, they have released an alpha trailer for the upcoming... | Read more »
Win a host of exclusive in-game Honor of...
To celebrate its latest Jujutsu Kaisen crossover event, Honor of Kings is offering a bounty of login and achievement rewards kicking off the holiday season early. [Read more] | Read more »
Miraibo GO comes out swinging hard as it...
Having just launched what feels like yesterday, Dreamcube Studio is wasting no time adding events to their open-world survival Miraibo GO. Abyssal Souls arrives relatively in time for the spooky season and brings with it horrifying new partners to... | Read more »
Ditch the heavy binders and high price t...
As fun as the real-world equivalent and the very old Game Boy version are, the Pokemon Trading Card games have historically been received poorly on mobile. It is a very strange and confusing trend, but one that The Pokemon Company is determined to... | Read more »
Peace amongst mobile gamers is now shatt...
Some of the crazy folk tales from gaming have undoubtedly come from the EVE universe. Stories of spying, betrayal, and epic battles have entered history, and now the franchise expands as CCP Games launches EVE Galaxy Conquest, a free-to-play 4x... | Read more »
Lord of Nazarick, the turn-based RPG bas...
Crunchyroll and A PLUS JAPAN have just confirmed that Lord of Nazarick, their turn-based RPG based on the popular OVERLORD anime, is now available for iOS and Android. Starting today at 2PM CET, fans can download the game from Google Play and the... | Read more »
Digital Extremes' recent Devstream...
If you are anything like me you are impatiently waiting for Warframe: 1999 whilst simultaneously cursing the fact Excalibur Prime is permanently Vault locked. To keep us fed during our wait, Digital Extremes hosted a Double Devstream to dish out a... | Read more »
The Frozen Canvas adds a splash of colou...
It is time to grab your gloves and layer up, as Torchlight: Infinite is diving into the frozen tundra in its sixth season. The Frozen Canvas is a colourful new update that brings a stylish flair to the Netherrealm and puts creativity in the... | Read more »
Back When AOL WAS the Internet – The Tou...
In Episode 606 of The TouchArcade Show we kick things off talking about my plans for this weekend, which has resulted in this week’s show being a bit shorter than normal. We also go over some more updates on our Patreon situation, which has been... | Read more »
Creative Assembly's latest mobile p...
The Total War series has been slowly trickling onto mobile, which is a fantastic thing because most, if not all, of them are incredibly great fun. Creative Assembly's latest to get the Feral Interactive treatment into portable form is Total War:... | Read more »

Price Scanner via MacPrices.net

Early Black Friday Deal: Apple’s newly upgrad...
Amazon has Apple 13″ MacBook Airs with M2 CPUs and 16GB of RAM on early Black Friday sale for $200 off MSRP, only $799. Their prices are the lowest currently available for these newly upgraded 13″ M2... Read more
13-inch 8GB M2 MacBook Airs for $749, $250 of...
Best Buy has Apple 13″ MacBook Airs with M2 CPUs and 8GB of RAM in stock and on sale on their online store for $250 off MSRP. Prices start at $749. Their prices are the lowest currently available for... Read more
Amazon is offering an early Black Friday $100...
Amazon is offering early Black Friday discounts on Apple’s new 2024 WiFi iPad minis ranging up to $100 off MSRP, each with free shipping. These are the lowest prices available for new minis anywhere... Read more
Price Drop! Clearance 14-inch M3 MacBook Pros...
Best Buy is offering a $500 discount on clearance 14″ M3 MacBook Pros on their online store this week with prices available starting at only $1099. Prices valid for online orders only, in-store... Read more
Apple AirPods Pro with USB-C on early Black F...
A couple of Apple retailers are offering $70 (28%) discounts on Apple’s AirPods Pro with USB-C (and hearing aid capabilities) this weekend. These are early AirPods Black Friday discounts if you’re... Read more
Price drop! 13-inch M3 MacBook Airs now avail...
With yesterday’s across-the-board MacBook Air upgrade to 16GB of RAM standard, Apple has dropped prices on clearance 13″ 8GB M3 MacBook Airs, Certified Refurbished, to a new low starting at only $829... Read more
Price drop! Apple 15-inch M3 MacBook Airs now...
With yesterday’s release of 15-inch M3 MacBook Airs with 16GB of RAM standard, Apple has dropped prices on clearance Certified Refurbished 15″ 8GB M3 MacBook Airs to a new low starting at only $999.... Read more
Apple has clearance 15-inch M2 MacBook Airs a...
Apple has clearance, Certified Refurbished, 15″ M2 MacBook Airs now available starting at $929 and ranging up to $410 off original MSRP. These are the cheapest 15″ MacBook Airs for sale today at... Read more
Apple drops prices on 13-inch M2 MacBook Airs...
Apple has dropped prices on 13″ M2 MacBook Airs to a new low of only $749 in their Certified Refurbished store. These are the cheapest M2-powered MacBooks for sale at Apple. Apple’s one-year warranty... Read more
Clearance 13-inch M1 MacBook Airs available a...
Apple has clearance 13″ M1 MacBook Airs, Certified Refurbished, now available for $679 for 8-Core CPU/7-Core GPU/256GB models. Apple’s one-year warranty is included, shipping is free, and each... Read more

Jobs Board

Seasonal Cashier - *Apple* Blossom Mall - J...
Seasonal Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Seasonal Fine Jewelry Commission Associate -...
…Fine Jewelry Commission Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) Read more
Seasonal Operations Associate - *Apple* Blo...
Seasonal Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Read more
Hair Stylist - *Apple* Blossom Mall - JCPen...
Hair Stylist - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.