TweetFollow Us on Twitter

Odds and Ends
Volume Number:10
Issue Number:8
Column Tag:Getting Started

Odds and Ends

Universal headers and code building under THINK & CodeWarrior for 68K and PowerPC

By Dave Mark, MacTech Magazine Regular Contributing Author

Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.

Last month’s column walked through our MDEF and the test program that brought it to life. When I first wrote the MDEF and Tester, I did all my work using THINK C, version 6. Before the column went to press, I installed THINK C 7, as well as MetroWerks CodeWarrior, Developer Release 2 (more commonly referred to as DR/2). My goal was to develop a single set of source code that would build properly in both environments. I learned a lot along the way. That’s what this month’s column is all about.

We’ll look at porting code from THINK C 6 to THINK C 7. Next, we’ll go over the changes you’ll need to make to get all the Primer, Volume I programs running under both THINK C 7 and CodeWarrior. Finally, we’ll walk through the process of creating projects using CodeWarrior. We’ll generate both 68K and PowerPC versions of last month’s MDEF and Tester programs.

Moving From THINK C 6
to THINK C 7

Seems like every time Symantec comes out with a new version of their C compiler, I end up having to make a bunch of changes to my source code.

In writing a book, that can be a real pain, since I have no way of getting the source code changes to the people who have already purchased the book and then upgrade their compiler. Bottom line, if you know someone who has a copy of the Primer, please pass this column along to them. Better yet, help support MacTech and tell them to get a subscription...

The move from THINK 6 to THINK 7 was no exception. In the past, the changes were usually brought about by changes in the level of type checking mandated by the compiler. When I moved my code from THINK 5 to THINK 6, I ended up adding a bunch of typecasts to various Toolbox routines, casting the parameters so they matched the function prototypes in the Toolbox include files. Adding the typecasts was a pain, but it definitely made my code much better. Strict type checking is important. If your parameter types don’t match, strict type checking forces you to explicitly cast your parameters to the right type if they aren’t already of the right type. This helps you avoid a lot of simple mistakes, like passing a pointer to a short when you meant to pass a pointer to a long.

THINK 7 kept the strict type checking imposed by THINK 6. Much more importantly, THINK 7 replaced the THINK 6 Toolbox include files with Apple’s standardized universal headers.

Under THINK 6, you’d get to the Toolbox include files by diving into the THINK C folder, then into the folder Mac #includes, then into the folder Apple #includes. There you’ll see files like Dialogs.h and Quickdraw.h. Under THINK 7, the folder Apple #includes has been renamed (Apple #includes), the parentheses hiding the folder from the normal search path. In the Mac #includes folder, there is now a folder named Universal Headers, which contains most of the same files as (Apple #includes), but with some important changes, which we’ll get to in a bit.

Under CodeWarrior, the universal headers are found in the MetroWerks C/C++ ƒ folder, inside Headers ƒ, inside Universal Headers ƒ. Note that while THINK C keeps all its headers in the same folder, CodeWarrior splits some of their headers off into a folder called System Extras ƒ, which is in the MetroWerks C/C++ ƒ folder. I prefer the THINK C method, since I only have to go to one folder when I want to browse through a header file. On the other hand, maybe there’s a benefit to the CodeWarrior approach. Anyone have any opinions on this? Just curious...

One of the benefits of the universal headers is that, by using them, you’ll have a much easier time writing source code that compiles under all three of the major C compilers: THINK C, CodeWarrior, and MPW.

But a far more important benefit of the universal headers is the leg up it gives you on PowerPC compatibility. This compatibility assistance occurs in two different areas. First, the universal headers offer a set of macros you’ll use for accessing all low-memory globals.

These macros are found in the file <LowMem.h>. For example, here are two macros that let you get and set the height of the menu bar, which (in a highly irreverent earlier life) you might have set by directly touching (gasp!) the low-memory global MBarHeight:


/* 1 */
extern short LMGetMBarHeight(void);
extern void LMSetMBarHeight(short MBarHeightValue);

<LowMem.h> bottlenecks access to low-memory globals. By using these bottleneck macros, you’ll remove any dependencies in your code on a specific low-memory global model. The macros handle the differences in implementation by 68K and PowerPC machines. Use the macros and you’ll be fine.

Another PowerPC compatibility issue addressed by the universal headers concerns procedure pointers passed to the Toolbox. In a nutshell, the PowerPC uses a different mechanism to call routines than a 68K-based Mac does. Part of this is due to the PowerPC’s hardware architecture, and part is due to the fact that the PowerPC’s awesome 68K emulator. In simple terms, on a 68K, the Toolbox knows it is calling 68K code. There are no other options. On a PowerPC, the Toolbox (which might be PowerPC code or 68K code running in emulation) needs to know whether the code it is about to call is 68K code or PowerPC code. To learn more about this, read about the Mixed Mode Manager and the Code Fragment Manager, written about extensively in the last eight issues of MacTech magazine.

If you haven’t already, go dig out all your MacTech issues from January till now and read the ongoing Powering Up column, written by Richard Clark and Jordan Mattson. These columns will give you an overview of the now-and-future Macintosh architecture. You might also want to check out two new Inside Macintosh volumes. One is called PowerPC System Software and the other is called PowerPC Numerics. The first one is of more general interest, but both have some pretty essential information.

Here’s the declaration of ModalDialog() I pulled from THINK C 6:


/* 2 */
pascal void ModalDialog(ModalFilterProcPtr filterProc,short *itemHit)

Notice that the first parameter, filterProc, is declared as a ModalFilterProcPtr. Basically, you could pass any function pointer as the first parameter to ModalDialog(), as long as it was typecast to a ModalFilterProcPtr. In order for the routine to be called properly, you must use the pascal keyword when you declare the function being pointed to.

Now here’s the universal headers version of ModalDialog():


/* 3 */
pascal void ModalDialog( ModalFilterUPP modalFilter, short *itemHit )

Notice that the first parameter (now called modalFilter) was declared as a ModalFilterUPP. The key to this name change are the letters UPP, which stand for Universal Procedure Pointer. A UPP is a data structure that, in addition to the procedure pointer, contains information about the nature of the function being pointed to. If you would normally pass nil as a procedure pointer, you can continue to pass nil under the universal headers. If you want to pass a procedure pointer, you’ll have to create a UPP, fill the appropriate field with the desired function pointer, then pass the UPP instead of the function pointer. Finally, you’ll dispose of the UPP when the function pointer is no longer needed. Here’s how this works.

Every Toolbox routine that takes a function pointer now has a corresponding set of macros that make building a UPP a snap. I’ll walk through the macros for ModalDialog(). Once you see how this works, you should be able to figure out how to build a UPP for any other Toolbox routine.

First, declare a UPP variable. If you know the UPP will only be needed within a limited scope, declare the variable within that scope. For example, if you have a routine that handles a dialog box, and you know that there is no chance that the UPP will be called from outside the routine, declare the UPP local to that routine. If you are not sure, make the UPP a global, just to be safe. They’re not so big that they’d be wasting lots of space. Here’s a global UPP definition for ModalDialog():


/* 4 */
ModalFilterUPP   gMyModalFilterUPP;

Next, create and fill in the UPP by calling a macro named NewXXXX(), where XXXX is the UPP type you want to create:


/* 5 */
gMyModalFilterUPP = NewModalFilterUPP( MyFilterProc );

If you are not sure about the macro name, you can find it in the same include file that contains the Toolbox routine’s function prototype. For example, NewModalFilterUPP() is defined in <Dialogs.h>. NewModalFilterUPP() takes a single parameter, the procedure pointer you normally would have passed directly to ModalDialog(). The macro returns the UPP.

Once the UPP is created, your next step is to call the Toolbox routine, just as you would have before (only this time you’ll be passing in a UPP instead of a procedure pointer):


/* 6 */
ModalDialog( gMyModalFilterUPP, &itemHit );

Do not dispose of the UPP until you are completely done with it. For example, if you are calling AEInstallEventHandler(), you better make sure the AEEventHandlerUPP stays alive as long as that handler is valid. A UPP is not just a pointer passed by value. It is a data structure which you are passing to the Toolbox. If you are absolutely certain that you are done with the UPP, you can dispose of it by passing the UPP to the macro DisposeRoutineDescriptor(). If you do this while the UPP is still “live”, your code will bark like a wounded seal, spin around a few times, then shuffle off to MacsBug. Be safe. Use a global if you are not sure.

Note that a UPP is not necessary if you are generating 68K code. On the other hand, why not fix it now so you won’t have to make any changes when you recompile for the PowerPC? You can use exactly the same source code for 68K as you do for PowerPC, and the universal headers generate the appropriate code for whichever target you’re building for.

Updating Your Primer Code

If you’ve been through Volume I of the Macintosh C Programming Primer, this section will help you get your code running under THINK C 7 and CodeWarrior DR/3. If you’ve never read the Primer, you might want to read through these changes anyway, just to get an idea of what I had to go through to get my code running native on a PowerPC.

These changes follow a definite pattern:

• Programs that passed procedure pointers to the Toolbox were modified to take advantage of the Universal Procedure Pointer mechanism.

• References to QuickDraw globals (screenBits.bounds, for example) were prefaced with the three characters “qd.”. THINK C is nice enough to add the “qd.” for you; other environments need you to be explicit about it.

• Some include files changed names in the move to the universal headers.

• All low-memory globals are now accessed via calls to the macros in <LowMem.h>.

• For some reason, InitDialogs() doesn’t like nil as a parameter in all compilers. Passing 0L seems to work just fine.

Here are the changes to the Primer programs, broken down by program:

Hello2

1) In the routine ToolBoxInit(), change:

 InitGraf( &thePort );
 to
 InitGraf( &qd.thePort );

2) In the routine ToolBoxInit(), change:

 InitDialogs( nil );
 to
 InitDialogs( 0L );

Mondrian

1) In the routine ToolBoxInit(), change:

 InitGraf( &thePort );
 to
 InitGraf( &qd.thePort );

2) In the routine ToolBoxInit(), change:

 InitDialogs( nil );
 to
 InitDialogs( 0L );

3) In the routine MainLoop(), change:

 GetDateTime( (unsigned long *)(&randSeed) );
 to
 GetDateTime( (unsigned long *)(&qd.randSeed) );

ShowPICT

1) In the routine ToolBoxInit(), change:

 InitGraf( &thePort );
 to
 InitGraf( &qd.thePort );

2) In the routine ToolBoxInit(), change:

 InitDialogs( nil );
 to
 InitDialogs( 0L );

FlyingLine

1) In the routine ToolBoxInit(), change:

 InitGraf( &thePort );
 to
 InitGraf( &qd.thePort );

2) In the routine ToolBoxInit(), change:

 InitDialogs( NULL );
 to
 InitDialogs( 0L );

3) In the routine WindowInit(), get rid of the unused local variable totalRect.

4) In the routine WindowInit(), change:

 gOldMBarHeight = MBarHeight;
 MBarHeight = 0;
 to
 gOldMBarHeight = LMGetMBarHeight();
 LMSetMBarHeight( 0 );

5) In the routine WindowInit(), change:

 window = NewWindow( nil, &(screenBits.bounds),
 kEmptyTitle, kVisible, plainDBox, kMoveToFront,
 kNoGoAway, kNilRefCon );
 to
 window = NewWindow( nil, &(qd.screenBits.bounds),
 kEmptyTitle, kVisible, plainDBox, kMoveToFront,
 kNoGoAway, kNilRefCon );

6) In the routine WindowInit(), change:

 SetRect( &mBarRect, screenBits.bounds.left,
 screenBits.bounds.top,
 screenBits.bounds.right,
 screenBits.bounds.top+gOldMBarHeight );
 to
 SetRect( &mBarRect, qd.screenBits.bounds.left,
 qd.screenBits.bounds.top,
 qd.screenBits.bounds.right,
 qd.screenBits.bounds.top+gOldMBarHeight );

7) In the routine WindowInit(), change:

 FillRect( &(window->portRect), black );
 to
 FillRect( &(window->portRect), &qd.black );

8) In the routine LinesInit(), change:

 GetDateTime( (unsigned long *)(&randSeed) );
 to
 GetDateTime( (unsigned long *)(&qd.randSeed) );

9) In the routine MainLoop(), change:

 MBarHeight = gOldMBarHeight;
 to
 LMSetMBarHeight( gOldMBarHeight );

EventTracker

1) In the routine ToolBoxInit(), change:

 InitGraf( &thePort );
 to
 InitGraf( &qd.thePort );

2) In the routine ToolBoxInit(), change:

 InitDialogs( nil );
 to
 InitDialogs( 0L );

3) Change the #include:

 #include <Values.h>
 to
 #include <limits.h>

4) Change the #define:

 #define kSleep  MAXLONG
 to
 #define kSleep  LONG_MAX

5) Near the top of the file, just after the declaration of gDone, add the global declaration:

 AEEventHandlerUPP gDoOpenAppUPP,
 gDoOpenDocUPP,
 gDoPrintDocUPP,
 gDoQuitAppUPP;

6) In the routine WindowInit(), delete the unused local variable windRect.

7) In the routine EventInit(), change:

 err = AEInstallEventHandler( kCoreEventClass, kAEOpenApplication,
  DoOpenApp, 0L, false );
 to
 gDoOpenAppUPP = NewAEEventHandlerProc( DoOpenApp );
 err = AEInstallEventHandler( kCoreEventClass, kAEOpenApplication,
  gDoOpenAppUPP, 0L, false );

8) In the routine EventInit(), change:

 err = AEInstallEventHandler( kCoreEventClass, kAEOpenDocuments,
 DoOpenDoc, 0L, false );
 to
 gDoOpenDocUPP = NewAEEventHandlerProc( DoOpenDoc );
 err = AEInstallEventHandler( kCoreEventClass, kAEOpenDocuments,
 gDoOpenDocUPP, 0L, false );

9) In the routine EventInit(), change:

 err = AEInstallEventHandler( kCoreEventClass, kAEPrintDocuments,
 DoPrintDoc, 0L, false );
 to
 gDoPrintDocUPP = NewAEEventHandlerProc( DoPrintDoc );
 err = AEInstallEventHandler( kCoreEventClass, kAEPrintDocuments,
 gDoPrintDocUPP, 0L, false );

10) In the routine EventInit(), change:

 err = AEInstallEventHandler( kCoreEventClass, kAEQuitApplication,
 DoQuitApp, 0L, false );
 to
 gDoQuitAppUPP = NewAEEventHandlerProc( DoQuitApp );
 err = AEInstallEventHandler( kCoreEventClass, kAEQuitApplication,
 gDoQuitAppUPP, 0L, false );

11) In the routine HandleMouseDown(), change:

 DragWindow( window, eventPtr->where, &screenBits.bounds );
 to
 DragWindow( window, eventPtr->where, &qd.screenBits.bounds );

Updater

1) In the routine ToolBoxInit(), change:

 InitGraf( &thePort );
 to
 InitGraf( &qd.thePort );

2) In the routine ToolBoxInit(), change:

 InitDialogs( nil );
 to
 InitDialogs( 0L );

3) Change the #include:

 #include <Values.h>
 to
 #include <limits.h>

4) Change the #define:

 #define kSleep  MAXLONG
 to
 #define kSleep  LONG_MAX

5) In the routine HandleMouseDown(), change:

 growRect.bottom = MAXSHORT;
 growRect.right = MAXSHORT;
 to
 growRect.bottom = INT_MAX;
 growRect.right = INT_MAX;

6) In the routine HandleMouseDown(), change:

 DragWindow( window, eventPtr->where, &screenBits.bounds );
 to
 DragWindow( window, eventPtr->where, &qd.screenBits.bounds );

EventTrigger

1) In the routine ToolBoxInit(), change:

 InitGraf( &thePort );
 to
 InitGraf( &qd.thePort );

2) In the routine ToolBoxInit(), change:

 InitDialogs( nil );
 to
 InitDialogs( 0L );

WorldClock

1) In the routine ToolBoxInit(), change:

 InitGraf( &thePort );
 to
 InitGraf( &qd.thePort );

2) In the routine ToolBoxInit(), change:

 InitDialogs( nil );
 to
 InitDialogs( 0L );

3) In the routine HandleMouseDown(), delete the unused local variable oldPort.

4) In the routine HandleMouseDown(), change:

 DragWindow( window, eventPtr->where, &screenBits.bounds );
 to
 DragWindow( window, eventPtr->where, &qd.screenBits.bounds );

Reminder

1) Delete this struct definition (it’s built-in now):

 struct PopupPrivateData
 {
   MenuHandle  mHandle;
 short       mID;
 char        mPrivate[1];
 };

2) Delete these function prototypes (they’re built-in now):

pascal OSErr SetDialogDefaultItem(DialogPtr theDialog, short newItem) 

 = { 0x303C, 0x0304, 0xAA68 };        
pascal OSErr SetDialogCancelItem(DialogPtr theDialog, short newItem)
   = { 0x303C, 0x0305, 0xAA68 };
pascal OSErr SetDialogTracksCursor(DialogPtr theDialog, Boolean tracks)
 = { 0x303C, 0x0306, 0xAA68 };

3) In the routine ToolBoxInit(), change:

 InitGraf( &thePort );
 to
 InitGraf( &qd.thePort );

4) In the routine ToolBoxInit(), change:

 InitDialogs( nil );
 to
 InitDialogs( 0L );

5) Add this line to the global definitions at the top of the source code:

 NMUPP  gLaunchResponseUPP, gNormalResponseUPP;

6) In main(), add this code just before the call to EventLoop():

 gLaunchResponseUPP = NewNMProc( LaunchResponse );
 gNormalResponseUPP = NewNMProc( NormalResponse );

7) In the routine CopyDialogToReminder(), change:

 if( reminder->launch = GetCtlValue( (ControlHandle)itemHandle ) )
 reminder->notify.nmResp = (NMProcPtr)&LaunchResponse;
 else
 reminder->notify.nmResp = (NMProcPtr)&NormalResponse;
 to
 if( reminder->launch = GetCtlValue( (ControlHandle)itemHandle ) )
 reminder->notify.nmResp = gLaunchResponseUPP;
 else
 reminder->notify.nmResp = gNormalResponseUPP;

ResWriter

1) Delete these function prototypes (they’re built-in now):

pascal OSErr SetDialogDefaultItem(DialogPtr theDialog, short newItem) 

 = { 0x303C, 0x0304, 0xAA68 };        
pascal OSErr SetDialogCancelItem(DialogPtr theDialog, short newItem)
   = { 0x303C, 0x0305, 0xAA68 };
pascal OSErr SetDialogTracksCursor(DialogPtr theDialog, Boolean tracks)
 = { 0x303C, 0x0306, 0xAA68 };

2) In the routine ToolBoxInit(), change:

 InitGraf( &thePort );
 to
 InitGraf( &qd.thePort );

3) In the routine ToolBoxInit(), change:

 InitDialogs( nil );
 to
 InitDialogs( 0L );

Pager

1) In the routine ToolBoxInit(), change:

 InitGraf( &thePort );
 to
 InitGraf( &qd.thePort );

2) In the routine ToolBoxInit(), change:

 InitDialogs( nil );
 to
 InitDialogs( 0L );

3) Change the #include:

 #include <Values.h>
 to
 #include <limits.h>

4) Change the #define:

 #define kSleep  MAXLONG
 to
 #define kSleep  LONG_MAX

5) Near the top of the file, just after the declaration of gDone, add the definition:

 ControlActionUPPgActionUPP;

6) In the routine HandleMouseDown(), change:

 else
 thePart = TrackControl( theControl, thePoint, &ScrollProc );
 to
 else
 {
 gActionUPP = NewControlActionProc( ScrollProc );
 thePart = TrackControl( theControl, thePoint, gActionUPP );
 }

7) In the routine HandleMouseDown(), change:

 DragWindow( window, eventPtr->where, &screenBits.bounds );
 to
 DragWindow( window, eventPtr->where, &qd.screenBits.bounds );

ShowClip

1) In the routine ToolBoxInit(), change:

 InitGraf( &thePort );
 to
 InitGraf( &qd.thePort );

2) In the routine ToolBoxInit(), change:

 InitDialogs( nil );
 to
 InitDialogs( 0L );

SoundMaker

1) In the routine ToolBoxInit(), change:

 InitGraf( &thePort );
 to
 InitGraf( &qd.thePort );

2) In the routine ToolBoxInit(), change:

 InitDialogs( nil );
 to
 InitDialogs( 0L );

OpenPICT

1) In the routine ToolBoxInit(), change:

 InitGraf( &thePort );
 to
 InitGraf( &qd.thePort );

2) In the routine ToolBoxInit(), change:

 InitDialogs( nil );
 to
 InitDialogs( 0L );

PrintPICT

1) Change the #include:

 #include <PrintTraps.h>
 to
 #include <Printing.h>

2) In the routine ToolBoxInit(), change:

 InitGraf( &thePort );
 to
 InitGraf( &qd.thePort );

3) In the routine ToolBoxInit(), change:

 InitDialogs( nil );
 to
 InitDialogs( 0L );

HyperCard XCMD

1) You’ll need to move the file HyperXCmd.h from the (Apple #includes) folder into the Universal Headers folder. I’m not sure what THINK 7 intends you to do about XCMDs, but this will work for now. Other than that, the code should compile cleanly.

Getting Started With CodeWarrior

To close out this month’s column, we’re going to build last month’s MDEF and tester projects using CodeWarrior DR/3.

If you are using DR/2, go ahead and upgrade to DR/3. There appears to be a bug in DR/2 that prevents you from building certain kinds of projects (and, as you’ve probably guessed, the MDEF code resource is one of them).

Start off by creating a folder named Metrowerks MDEF. Next, copy the files MDEF.c, Tester.c, and Tester.Π.rsrc from the folder you created last month into the Metrowerks MDEF folder. Change the name of the file Tester.Π.rsrc to Tester.µ.rsrc. By convention, THINK names its project and resource files with the character Π (option-p), while CodeWarrior uses the character µ (option-m).

If you’ve already built a CodeWarrior project using DR/2, drag the project onto the “Project Updater” icon at the top level of the CodeWarrior folder. The “Project Updater” will convert a DR/2 project to a DR/3 project in place. This is a one-way process.

If you’re starting from scratch, you get to decide which flavor compiler you want to work with, 68K or PowerPC. Open the Metrowerks C/C++ ƒ folder and launch either MW C/C++ 68K 1.0 or MW C/C++ PPC 1.0. Either way, when the toolbar appears, select New Project... from the File menu and create a new project named MDEF.µ in the Metrowerks MDEF folder.

When the project window appears, select Add File... from the Project menu and add the file MDEF.c and either MacOS.lib or InterfaceLib to the project. MacOS.lib is in the folder named Libraries ƒ, in the MacOS 68K ƒ folder. InterfaceLib is also in the folder named Libraries ƒ, in the MacOS PPC ƒ folder. Figures 1a and 1b show the project window after these files are added.

Figure 1a. The MDEF.µ project window, 68K version.

Figure 1b. The MDEF.µ project window, PowerPC version.

Next, select Preferences from the Edit menu. Click on the Language icon (Figure 2). Be sure that the Prefix File field agrees with the type of machine you are compiling for, either MacHeaders68K or MacHeadersPPC.

Figure 2. The Language panel.

If you are generating code for the PowerPC, the Linker icon allows you to specify the entry points for your code resource. Since we are programming in C and don’t have C++’s constructor or destructor, we’ll leave the Initialization and Termination fields blank and enter main in the Main field (Figure 3).

Figure 3. The Linker panel in the PowerPC compiler.

Next, click on the Project icon and make sure your settings agree with those shown in Figure 4a or 4b, depending on your target machine. The Merge To File check box asks the compiler to add the resource to the Tester resource file.

Figure 4a. The Project panel, 68K version

Figure 4b. The Project panel, PowerPC version

Click on the OK button to save your changes, then select Make from the Project menu. If all goes well, the MDEF will be built and added to the file Tester.µ.rsrc (you did remember to rename it didn’t you?)...

Creating the Tester Project

Go through a similar process to build a project for the Tester application. Name the project file Tester.µ, to go with the resource file Tester.µ.rsrc. Just like THINK C, CodeWarrior automatically looks for a resource file having the same name as the project file with “.rsrc” added to the name. Figure 5a and 5b show my project windows, one for 68K and one for PowerPC. Notice that the PowerPC version required the addition of the file MWCRuntime.Lib.

Figure 5a. The project window, 68K version

Figure 5b. The project window, PowerPC version

Go into the Preferences... and be sure the project type is set to Application. That should do it. Select Make from the Project menu and take your MDEF for a spin.

Till Next Month

I’m not sure what I’m going to write about next month. If I get the time, I’d like to get into some of the cool tools that you might want to use to ease your Mac development. Till then, I’m going to convince Daniel to come down from the top of my bookshelf, where he has somehow managed to drag all of his trucks, as well as my cordless telephone. See you soon...

Resources Referenced In This Article

Powering Up, MacTech Magazine, ongoing monthly series, January 1994 to present.

Inside Macintosh: PowerPC System Software, Addison Wesley

Inside Macintosh: PowerPC Numerics, Addison Wesley

Macintosh C Programming Primer Volume I, Dave Mark & Cartwright Reed, 672 pgs.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Garmin Express 7.0.0.0 - Manage your Gar...
Garmin Express is your essential tool for managing your Garmin devices. Update maps, golf courses and device software. You can even register your device. Update maps Update software Register your... Read more
ClipGrab 3.8.12 - Download videos from Y...
ClipGrab is a free downloader and converter for YouTube, Vimeo, Facebook and many other online video sites. It converts downloaded videos to MPEG4, MP3 or other formats in just one easy step Version... Read more
VMware Fusion 11.5.5 - Run Windows apps...
VMware Fusion and Fusion Pro - virtualization software for running Windows, Linux, and other systems on a Mac without rebooting. The latest version includes full support for Windows 10, macOS Mojave... Read more
Civilization VI 1.3.0 - Next iteration o...
Civilization® VI is the award-winning experience. Expand your empire across the map, advance your culture, and compete against history’s greatest leaders to build a civilization that will stand the... Read more
Corel Painter 20.1.0.285 - Digital art s...
Corel Painter lets you advance your digital art style with painted textures, subtle glazing brushwork, interactive gradients, and realistic Natural-Media. Easily transition from traditional to... Read more
iTubeDownloader 6.5.19 - Easily download...
iTubeDownloader is a powerful-yet-simple YouTube downloader for the masses. Because it contains a proprietary browser, you can browse YouTube like you normally would. When you see something you want... Read more
OmniFocus 3.8 - GTD task manager with iO...
OmniFocus is an organizer app. It uses projects to organize tasks naturally, and then add tags to organize across projects. Easily enter tasks when you’re on the go, and process them when you have... Read more
Hazel 4.4.5 - Create rules for organizin...
Hazel is your personal housekeeper, organizing and cleaning folders based on rules you define. Hazel can also manage your trash and uninstall your applications. Organize your files using a familiar... Read more
Macs Fan Control 1.5.7 - Monitor and con...
Macs Fan Control allows you to monitor and control almost any aspect of your computer's fans, with support for controlling fan speed, temperature sensors pane, menu-bar icon, and autostart with... Read more
Acorn 6.6 - Bitmap image editor.
Acorn is a new image editor built with one goal in mind - simplicity. Fast, easy, and fluid, Acorn provides the options you'll need without any overhead. Acorn feels right, and won't drain your bank... Read more

Latest Forum Discussions

See All

Steam Link Spotlight - Signs of the Sojo...
Steam Link Spotlight is a feature where we look at PC games that play exceptionally well using the Steam Link app. Our last entry was XCOM: Chimera Squad. Read about how it plays using Steam Link's new mouse and keyboard support over here. | Read more »
Steampunk Tower 2, DreamGate's sequ...
Steampunk Tower 2 is a DreamGate's follow up to their previous tower defence game. It's available now for both iOS and Android as a free-to-play title and will see players defending their lone base by kitting it out with a variety of turrets. [... | Read more »
Clash Royale: The Road to Legendary Aren...
Supercell recently celebrated its 10th anniversary and their best title, Clash Royale, is as good as it's ever been. Even for lapsed players, returning to the game is as easy as can be. If you want to join us in picking the game back up, we've put... | Read more »
Pokemon Go Fest 2020 will be a virtual e...
Niantic has announced that Pokemon Go Fest will still take place this year although understandably it won't be a physical event. Instead, it will become a virtual celebration and is set to be held on 25th and 26th July. [Read more] | Read more »
Marvel Future Fight's major May upd...
Marvel Future Fight's latest update has now landed, and it sounds like a big one. The focus this time around is on Marvel's Guardians of the Galaxy, and it introduces all-new characters, quests, and uniforms for players to collect. [Read more] | Read more »
SINoALICE, Yoko Taro and Pokelabo's...
Yoko Taro and developer Pokelabo's SINoALICE has now opened for pre-registration over on the App Store. It's already amassed 1.5 million Android pre-registrations, and it's currently slated to launch on July 1st. [Read more] | Read more »
Masketeers: Idle Has Fallen's lates...
Masketeers: Idle Has Fallen is the latest endeavour from Appxplore, the folks behind Crab War, Thor: War of Tapnarok and Light A Way. It's an idle RPG that's currently available for Android in Early Access and will head to iOS at a later date. [... | Read more »
Evil Hunter Tycoon celebrates 2 million...
Evil Hunter Tycoon has proved to be quite the hit since launching back in March, with its most recent milestone being 2 million downloads. To celebrate the achievement, developer Super Planet has released a new updated called Darkness' Front Yard... | Read more »
Peak's Edge is an intriguing roguel...
Peak's Edge is an upcoming roguelike puzzle game from developer Kenny Sun that's heading for both iOS and Android on June 4th as a free-to-play title. It will see players rolling a pyramid shape through a variety of different levels. [Read more] | Read more »
Clash Royale: The Road to Legendary Aren...
Supercell recently celebrated its 10th anniversary and their best title, Clash Royale, is as good as it's ever been. Even for lapsed players, returning to the game is as easy as can be. If you want to join us in picking the game back up, we've put... | Read more »

Price Scanner via MacPrices.net

Sams Club Sales Event: $100 off every Apple W...
Sams Club is discounting all Apple Watch Series 5 models by $100 off Apple’s MSRP through June 3, 2020. Choose free shipping or free local store pickup (if available). Sale prices for online orders... Read more
New 16″ MacBook Pros now on sale for up to $2...
Apple reseller DataVision is now offering new 16″ Apple MacBook Pros for up to $255 off MSRP, each including free shipping. Prices start at $2194. DataVision charges sales tax for NY, NJ, PA, and CA... Read more
Apple now offering Certified Refurbished iPho...
Apple is now offering Certified Refurbished iPhone Xr models in the refurbished section of their online store starting at $499. Each iPhone comes with Apple’s standard one-year warranty, ships free,... Read more
Sale! Get a 10.2″ 32GB WiFi iPad for only $27...
Walmart has new 10.2″ 32GB WiFi iPads on sale for $50 off Apple’s MSRP, only $279. These are the same iPads sold by Apple in their retail and online stores. Be sure to select Walmart as the seller... Read more
Apple resellers offer new 2020 Mac minis for...
Apple resellers are offering new 2020 Mac minis for up to $50 off Apple’s MSRP with prices available starting at $759. Shipping is free: (1) B&H Photo: – 2020 4-Core Mac mini: $759 $40 off MSRP... Read more
Sprint is offering the Apple iPhone 11 free t...
Did you miss out on Sprint’s recent free iPhone SE promotion? No worries. Sprint has the 64GB iPhone 11 available for $0 per month for new lines when you trade-in a qualifying phone in any condition... Read more
Apple has clearance 2019 13″ 1.4GHz MacBook P...
Apple has Certified Refurbished 2019 13″ 1.4GHz 4-Core Touch Bar MacBook Pros available today starting at $979 and up to $440 off original MSRP. Apple’s one-year warranty is included, shipping is... Read more
Apple restocks 2019 MacBook Airs starting at...
Apple has clearance, Certified Refurbished, 2019 13″ MacBook Airs available again starting at $779. Each MacBook features a new outer case, comes with a standard Apple one-year warranty, and is... Read more
Apple restocks clearance Mac minis for only $...
Apple has restocked Certified Refurbished 2018 4-Core Mac minis for only $599. Each mini comes with a new outer case plus a standard Apple one-year warranty. Shipping is free: – 3.6GHz Quad-Core... Read more
Apple’s new 2020 13″ MacBook Airs on sale for...
B&H Photo has Apple’s new 2020 13″ 4-Core and 6-Core MacBook Airs on sale today for $50-$100 off Apple’s MSRP, starting at $949. Expedited shipping is free to many addresses in the US. The... Read more

Jobs Board

*Apple* Mac Desktop Support - Global Dimensi...
…Operate and support an Active Directory (AD) server-client environment for all Apple devices operating on the BUMED network + Leverage necessary industry enterprise Read more
Surgical Technologist III, *Apple* Hill Sur...
Surgical Technologist III, Apple Hill Surgical Center - Full Time Tracking Code D5.29.2020 Job Description Surgical Technologist III Apple Hill Surgical Center Read more
Security Officer - *Apple* Store - NANA (Un...
**Security Officer \- Apple Store** **Description** About NMS Built on a culture of safety and integrity, NMSdelivers award\-winning, integrated support services to Read more
Transition Into Practice Program (TIP) - Sept...
…Academy-Transition into Practice (TIP) Residency program at St Mary Medical Center in Apple Valley, CA. **We are seekingRegistered Nurses who are:** + New graduate Read more
Essbase Developer - *Apple* - Theorem, LLC...
Job Summary Apple is seeking an experienced, detail-minded Essbase developer to join our worldwide business development and strategy team. If you are someone who Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.