TweetFollow Us on Twitter

2001 A Space Odyssey Volume Number: 17 (2001)
Issue Number: 1
Column Tag: QuickTime Toolkit

2001: A Space Odyssey

By Tim Monroe

Writing Cross-Platform QuickTime Code


As you know, QuickTime was originally developed to run on Macintosh computers. In the mid 1990's, Apple released a version of QuickTime (called "QuickTime for Windows") that provided support for playing QuickTime movies on Windows computers. While it was a significant step forward, this version had some severe limitations. Most importantly, it provided a playback engine only; there was no API for creating QuickTime movies on the Windows platform. Also, many of the APIs for playing movies back differed from their Macintosh counterparts. For instance, on the Mac, NewMovieController is declared essentially like this:

MovieController NewMovieController (Movie theMovie, 
                        const Rect *movieRect, long someFlags);

But under QuickTime for Windows, it had this declaration:

MovieController NewMovieController (Movie theMovie, 
                        const LPRECT lprcMovieRect, long someFlags, 
                        HWND hWndParent);

You'll notice that the Windows version took an additional parameter (hWndParent) and that the type of the second parameter was a pointer to the standard Windows rectangle type (RECT), not the Macintosh rectangle type (Rect).

Personally, I never wrote any code using the original QuickTime for Windows APIs, but I can imagine that it was something of a headache for experienced Macintosh programmers. Just flipping casually through the documentation is enough to convince me that it was probably impossible to develop any very interesting QuickTime code that would compile and link on both platforms. My guess is that anyone developing a QuickTime-savvy application for both Mac and Windows platforms probably had to maintain two separate source code bases.

QuickTime 3.0 changed all that. It provided, for the first time, a set of APIs that were virtually identical - in both parameter lists and feature completeness - on Macintosh and Windows platforms. In other words, it is now possible to write Mac and Windows applications that use the same source code, at least for the QuickTime-specific portions of the application. If you've been following this series of articles for the past year, then you know that I've placed a great deal of emphasis on developing code that is completely cross-platform. In this article, we'll take a more in-depth look at some of the platform-specific issues that we've considered only in passing up to now (such as working with multi-byte data and resources).

The magic provided by the Windows version of QuickTime 3.0 was accomplished principally by a library called the QuickTime Media Layer (or, more briefly, QTML). The QuickTime Media Layer provides an implementation of a number of the parts of the Macintosh Operating System (including the Memory Manager and the File Manager) and the Macintosh User Interface Toolbox (including the Dialog Manager, the Control Manager, and the Menu Manager). QuickTime, of course, also runs on Mac OS X, which is a UNIX-based operating system. In this case, the implementation of the Macintosh Operating System and Toolbox managers is provided by a library called Carbon. It should come as no surprise to learn that Carbon originally grew out of the existing work on QTML.

We're going to approach this article in this way: let's suppose that we've got a QuickTime-savvy application that runs under the "classic" Macintosh operating systems (that is, MacOS 8.x and 9.x). We want to see what we need to do to get it to run also under the various flavors of Windows (namely, Windows 95/98/ME and Windows NT/2000) and under Mac OS X.

Endian Issues

Let's begin this odyssey at the lowest (and sometimes most vexing) level, by considering the memory storage of multi-byte data. Motorola processors in the 680x0 family expect multi-byte data to be stored with the most significant byte lowest in memory. This is known as big-endian byte ordering (because the "big" end of the data value comes first in memory). Intel and other processors used for Windows machines expect multi-byte data to be stored with the least significant byte lowest in memory; this is known as little-endian byte ordering. (See Figure 1, which shows the value 0x12345678 stored in both Motorola 680x0 and Intel formats.) The PowerPC family of processors supports both big- and little-endian byte orderings, but uses big-endian when running Macintosh software.

Figure 1. Big- and little-endian data storage

This difference is important only in one instance, namely when a stream of data is transferred between the computer's memory and some external container (such as a file). For instance, if we open a file that has its data stored in big-endian format and read a segment of it into memory, we should not operate upon that data without first making sure that we convert the data into the proper endian format for the processor we're running on. This conversion is known as byte swapping. Let's consider a few real-life cases where byte swapping comes into play.

Working with Movie User Data

A movie's user data is stored inside of atoms in a QuickTime file. When we call the function GetMovieUserData to get a movie's user data, the Movie Toolbox reads that atom into memory and returns a handle to that data to us. We can get a specific piece of movie user data by calling GetUserDataItem and passing it the type of data we want. For instance, we can retrieve the stored window location of a movie by reading the user data item of type 'WLOC'. Listing 1 defines the function QTUtils_GetWindowPositionFromFile, which returns the stored location of the top-left corner of the movie window.

Listing 1: Getting the stored position of a movie window


OSErr QTUtils_GetWindowPositionFromFile (Movie theMovie, 
                                                            Point *thePoint)
   UserData      myUserData = NULL;
   Point         myPoint = {kDefaultWindowX, kDefaultWindowY};
   OSErr         myErr = paramErr;

   // make sure we've got a movie
   if (theMovie == NULL)
      goto bail;
   // get the movie's user data list
   myUserData = GetMovieUserData(theMovie);
   if (myUserData != NULL) {
      myErr = GetUserDataItem(myUserData, &myPoint, 
                        sizeof(Point), FOUR_CHAR_CODE('WLOC'), 0);
      if (myErr == noErr) {
         myPoint.v = EndianS16_BtoN(myPoint.v);
         myPoint.h = EndianS16_BtoN(myPoint.h);

   if (thePoint != NULL)
      *thePoint = myPoint;


With only rare exceptions, movie user data is stored in big-endian format. So once we've read in the data stored in the 'WLOC' user data item, we need to convert that data from big-endian format into native-endian format (that is, the native format of the processor we're running on). As you can see, QTUtils_GetWindowPositionFromFile uses the macro EndianS16_BtoN to convert the signed 16-bit data from big- to native-endian format. This macro is defined in the file Endian.h, like this:

   #define EndianS16_BtoN(value)            (value)
   #define EndianS16_BtoN(value)            EndianS16_BtoL(value)

In other words, if we are running on a big-endian processor, then EndianS16_BtoN does nothing. But on a little-endian processor, EndianS16_BtoN is replaced by the macro EndianS16_BtoL, which in turn is replaced by the macro Endian16_Swap:

#define EndianS16_BtoL(value)  ((SInt16)Endian16_Swap(value))

Finally, Endian16_Swap is defined like this:

#define Endian16_Swap(value)                 \
        (((((UInt16)value)<<8) & 0xFF00)   | \
         ((((UInt16)value)>>8) & 0x00FF))

If you work through this, you'll see that Endian16_Swap simply swaps the high-order byte with the low-order byte, which is just what we expected. The macros for handling 32- and 64-bit values are of course more complicated, but do pretty much the same thing. Here are a couple of things to keep in mind to help you decide when you need to worry about endian issues:

  • Byte swapping is required only for multi-byte data (that is, data that's 16 bits or larger). For smaller chunks of data, we don't need to worry about this issue. So, for example, if we retrieve a user data item whose data is a Boolean value, we don't need to swap any bytes.
  • Byte swapping is not required for C or Pascal strings, even though these are typically multi-byte. Strings are interpreted as arrays of single-byte characters, so they are identical on big- and little-endian architectures.
  • Byte swapping is required only when data is to be transferred between RAM and some external container (such as reading data from or writing data to a file). For in-memory calculations or moving data between buffers, we don't need to worry about the endianness of the data.
  • Note that the Endian16_Swap macro evaluates its argument several times. It would be a bad idea, therefore, to use increment (++) or decrement (-) operators in these macros. (To be safe, don't use these operators in any macros, since the definitions of the macros may change. Believe me; this has bitten me more than once.)

In general, any data stored in a QuickTime file is in big-endian format. This includes movie user data, data in atoms and atom containers, stored sample descriptions, and media data itself. In some rare cases, media data may be stored in little-endian format, but the appropriate media handler should insulate us from having to worry about that.

Reading and Writing Resource Data

Data stored in Macintosh resource files is always big-endian. There is, however, an important difference between the data returned to us by the File Manager and the data returned to us by the Resource Manager. To wit: the Resource Manager always gives us native-endian data. In other words, if we use Resource Manager calls to open (for instance) a 'pnot' resource, we can read the fields of that resource (which, as we've seen in earlier articles, has the structure of a preview resource record, of type PreviewResourceRecord) without having to flip any multi-byte data. The same holds true of writing data to a resource: we give the Resource Manager a handle to a block of native-endian data and it will ensure that the data actually written to the resource file is in big-endian format.

This automatic byte swapping is accomplished by a piece of code called a resource flipper. A resource flipper takes a handle of data and flips the multi-byte data fields in that handle, in place. Of course, a resource flipper needs to have intimate knowledge of the structure of the resource we're handing it. QTML includes resource flippers for these resource types: 'ALRT', 'BNDL', 'cdci', 'cicn', 'clut', 'CNTL', 'cpix', 'crsr', 'CURS', 'DITL', 'DLOG', 'FREF', 'fttl', 'gnht', 'icl4', 'icl8', 'ICN#', 'ICON', 'ics4', 'ics8', 'MBAR', 'MENA', 'MENU', 'nini', 'pnot', 'qfmt', 'qmtr', 'qtet', 'snd ', 'stg#', 'stgp', 'STR ', 'STR#', 'styl', 'TEXT', 'thar', 'thga', 'thnd', 'thng', 'thnr', 'vers', 'WIND', and 'wxim'. (This list is current as of version 4.1.2.)

But what if we want to work with a resource that isn't one of these types (say a custom resource used only by our application)? Here we have at least two choices. First, we can just make a local copy of the fields of the data in the resource handle returned by the Resource Manager whenever we need to access them and then flip those fields ourselves. For instance, let's suppose that we have a custom resource of type 'CHAP' that consists of a signed 16-bit count followed by that number of signed 32-bit integers (perhaps to record the start times of the chapters of our movie). We might use this data structure to help us read and write the resource data:

struct ChapterResourceRecord {
   SInt16                fNumChapters;
   SInt32                fChapters[1];
typedef struct ChapterResourceRecord   ChapterResourceRecord;
typedef ChapterResourceRecord *ChapPtr **ChapHdl;

We can open a resource of type 'CHAP' and get the number of chapters in the resource like this:

ChapHdl      myChapters = NULL;

myChapters = (ChapHdl)GetResource(FOUR_CHAR_CODE('CHAP'), 
if (myChapters != NULL)
   myNumChaps = EndianS16_BtoN((**myChapters).fNumChapters); 

Another way to handle this is to tell the Resource Manager how to flip the data in our custom resource. Then it can do all the necessary work for us whenever we read or write a resource of that type, leaving us to work always with native-endian data. To do this, we install a resource flipper for our custom resource type, by calling RegisterResourceEndianFilter, which has this declaration:

OSErr RegisterResourceEndianFilter 
   (ResType theType, ResourceEndianFilterPtr theFilterProc);

As you can see, we pass RegisterResourceEndianFilter the type of our custom resource and a pointer to our resource flipper. The data type ResourceEndianFilterPtr is declared like this:

typedef OSErr (*ResourceEndianFilterPtr)
      (Handle theResource, Boolean currentlyNativeEndian);

For our custom 'CHAP' resource, we could write our resource flipper as shown in Listing 2.

Listing 2: Flipping the data in a custom resource type


OSErr QTXP_ChapterResourceFlipper (Handle theHandle, 
                                          Boolean currentlyNativeEndian)
   Ptr         myDataPtr = NULL;
   short      myNumChaps = 0;
   short      myCount;
   if ((theHandle == NULL) || (*theHandle == NULL))

   // see how many longs are in this resource data
   myDataPtr = *theHandle;
   myNumChaps = *(short *)myDataPtr;
   // if we're flipping from big- to native-endian, we need to flip myNumChaps
   // or else our loop limit will be terribly wrong
   if (!currentlyNativeEndian)
      myNumChaps = EndianS16_BtoN(myNumChaps);
   // first, flip the fNumChapters field.
   *(short *)myDataPtr = Endian16_Swap(*(short *)myDataPtr);
   myDataPtr += sizeof(short);
   // now flip the chapter data
   for (myCount = 0; myCount < myNumChaps; myCount++) {
      *(long *)myDataPtr = Endian32_Swap(*(long *)myDataPtr);
      myDataPtr += sizeof(long);


The second parameter passed to our resource flipper, currentlyNativeEndian, is a Boolean value that indicates whether the data in the handle specified by the first parameter is in native-endian format. If it's native-endian, then we can read the data in that handle and use it without first converting it. Otherwise, we need to convert any of that data that we use in our flipper. In QTXP_ChapterResourceFlipper, for instance, we need to flip the 16-bit count field if it's not already in native-endian format, since we use that value in the for loop. We can install our custom resource flipper like this:

myErr = RegisterResourceEndianFilter(FOUR_CHAR_CODE('CHAP'), 

Keep in mind that resource flippers are necessary only on non-Macintosh platforms, because there is no need to byte swap resource data on the Mac. (In other words, the resource storage format and the memory storage format are the same on Macintosh computers.)

Occasionally we might need to know whether a resource flipper is installed for a particular resource type. We can determine this by calling RegisterResourceEndianFilter to try to install a new resource flipper for that type. If a resource flipper is already installed, RegisterResourceEndianFilter returns the value 1; otherwise, it returns noErr.

The QuickTime Media Layer

As we learned earlier, the QuickTime Media Layer is a Windows implementation of those parts of the Macintosh Operating System and the Macintosh User Interface Toolbox that are used by QuickTime. In effect, QTML provides a sizable chunk of a Macintosh ROM cleverly packaged into a dynamic-linked library (QuickTime.qts). This is required to run QuickTime on Windows computers because QuickTime APIs are riddled through-and-through with Macintosh data types such as handles, resources, and Pascal strings. Also, QuickTime internally calls a large number of Mac OS and Toolbox routines, such as NewHandle, GetResource, NewControl, and the like. As we might say, you can take QuickTime out of the Mac, but you can't take the Mac out of QuickTime; so we need to drag a good bit of the Mac OS and Toolbox anywhere we want to run QuickTime. That's what QTML provides on Windows (and what Carbon provides on Mac OS X).

QTML is a rock-solid product. The most important evidence for this is that it's possible to write large chunks of QuickTime code on the Mac that also compile, link, and run on Windows (assuming you've paid attention to issues like endianness of multi-byte data). It just works! It's important to understand, however, that there are limitations to what you can do with QTML, and we'll consider some of those limitations in this section. The main thing to keep in mind is that the QuickTime Media Layer is not designed as a general-purpose tool for getting any and all Mac OS or Toolbox functions to run on Windows. Rather, it's designed to support those parts of the Mac OS and Toolbox that are needed to run QuickTime. If you remember nothing else from this article, at least remember this: QTML is not a porting layer.

Avoiding Namespace Collisions

One of the first issues we run into when porting our Macintosh-based QuickTime code to Windows is a small number of collisions in the function namespace. That is to say, some of the Mac OS and Toolbox functions have the same names as similar Windows functions. For instance, both Mac and Windows provide functions named ShowWindow. If we try to compile our Macintosh code unchanged, we'll get a compiler error like this:

Error   : cannot convert
'struct GrafPort *' to
'struct HWND__ *'
QTTimeCode.c line 426   ShowWindow(myDialog);

Luckily, the Mac header files now contain alternate names for these functions for non-Mac targets. In general, the new forms of the names simply contain the prefix "Mac", to signal that they are the Macintosh versions of these functions. For instance, the Mac version of ShowWindow has been renamed as MacShowWindow. Some other renamed functions are MacSetPort, MacInsertMenu, MacSetRect, MacOffsetRect, MacPtInRect, MacSetCursor, and MacCheckMenuItem. (This list is by no means exhaustive.) As a rule of thumb, if you get a compiler error where the compiler tries to convert Mac data types to Windows data types, try adding the prefix "Mac" to the function name.

There were also collisions in the names of header files. The Mac OS file originally called Windows.h (which contains the public API for the Macintosh Window Manager) was renamed as MacWindows.h (to avoid conflict with the Windows header file windows.h). Similarly, the file Types.h was renamed as MacTypes.h, Errors.h was renamed as MacErrors.h, Memory.h was renamed as MacMemory.h, and Help.h was renamed as MacHelp.h. (I believe that this list is exhaustive.)

To my knowledge, the only data types renamed to accommodate the Windows APIs were the QuickDraw types Polygon and Region (to MacPolygon and MacRegion, respectively), and I have found only one constant in the Mac headers that causes problems when compiling code under Windows. The file MoviesFormat.h contains these constants:

enum {
   MOVIE_TYPE               = FOUR_CHAR_CODE('moov'),
   TRACK_TYPE               = FOUR_CHAR_CODE('trak'),
   MEDIA_TYPE               = FOUR_CHAR_CODE('mdia'),
   VIDEO_TYPE               = FOUR_CHAR_CODE('vide'),
   SOUND_TYPE               = FOUR_CHAR_CODE('soun')

Unfortunately, the Windows header file winioctl.h contains an enumerated type called MEDIA_TYPE (for various types of device media). Compiling with both these header files results in an "illegal name overloading" error. When building code using Microsoft Developer Studio on Windows, I usually don't include the file winioctl.h. But when using CodeWarrior on the Mac with the precompiled Windows headers, the easiest way to avoid the collision is to edit MoviesFormat.h to comment out the definition of MEDIA_TYPE. Cheesy, but true.

Working with Files

Windows, of course, provides its own functions for opening files (for instance, CreateFile). When working with movie files on Windows, however, it's usually easier to use the routines provided by the Macintosh Standard File Package and File Manager. The main reason is that these functions use file system specifications (of type FSSpec) to refer to files, which are also used by QuickTime functions like CreateMovieFile and OpenMovieFile. For instance, we can use the following lines of code to elicit a movie file from the user and open the selected file:

 // prompt the user for a file
myTypeList[0] = MovieFileType;
StandardGetFilePreview(NULL, 1, myTypeList, &myReply);
if (!myReply.sfGood)
// make an FSSpec record
FSMakeFSSpec(0, 0L,, &myFSSpec);
OpenMovieFile(&myFSSpec, &myRefNum, fsRdWrPerm);

This snippet of code also reveals that the FSSpec data type is defined differently on Mac and Windows. On Windows, the name field contains a full pathname of a file, and the parID and vRefNum fields are ignored. Also, like the Mac version, the name field should contain a Pascal string, not a C string. QTML provides a useful function for converting a C-style pathname into an FSSpec:

NativePathNameToFSSpec("c:\\", &myFSSpec, 

We can also use the File Manager to open, create, and manipulate files that are not movie files. You may recall that in an earlier article ("In and Out" in MacTech, May 2000), we defined a function QTDX_GetExporterSettingsFromFile that used another function, QTDX_ReadHandleFromFile, to read the data in a file into a handle. QTDX_ReadHandleFromFile is defined in Listing 3.

Listing 3: Reading a file's data into a handle


Handle QTDX_ReadHandleFromFile (FSSpecPtr theFSSpecPtr)
   Handle         myHandle = NULL;
   short         myRefNum = 0;
   long            mySize = 0;
   OSErr         myErr = noErr;

   // open the file
   myErr = FSpOpenDF(theFSSpecPtr, fsRdWrPerm, &myRefNum);
   if (myErr == noErr)
      myErr = SetFPos(myRefNum, fsFromStart, 0);

   // get the size of the file data
   if (myErr == noErr)
      myErr = GetEOF(myRefNum, &mySize);
   // allocate a new handle
   if (myErr == noErr)
      myHandle = NewHandleClear(mySize);
   if (myHandle == NULL)
      goto bail;

   // read the data from the file into the handle
   if (myErr == noErr)
      myErr = FSRead(myRefNum, &mySize, *myHandle);

   if (myRefNum != 0)      


This is all pretty straightforward File Manager (and Memory Manager) code, and it works as well on Windows as it does on Macintosh.

Working with Resources

The Resource Manager, which reads typed data from a resource file, also works very well on Windows. Perhaps the biggest annoyance when working with resource files on Windows machines is getting them there in the first place. I've found that some means of transferring resource files from Macintosh computers to Windows computers don't work very well or require additional steps. For example, if I copy the Macintosh resource file MacApplication.rsrc onto a floppy disk and then mount that floppy disk on a Windows machine, the file MacApplication.rsrc is usually 0 bytes in size. To find the actual resource data, I need to look inside a folder called Resource.frk, where the resource file now has the name Macapp~1.rsr. Nor is copying resource files across a network much better. If I mount a Windows disk on my Macintosh computer and drag the file MacApplication.rsrc to the Windows disk, two files are created on the Windows machine, MacApplication.rsrc (which again is 0 bytes in size) and MacApplication.rsrc.#Res (which is the actual resource file).

Perhaps the most reliable course is simply to build the resource file on Windows from a .r file (which, being a normal text file, is easy to transfer from machine to machine). The QuickTime SDK includes the tool Rez for converting .r files into resource files. We can execute this line of code in the DOS Console to create a resource file:

               -i "QuickTimeSDK\QTDevWin\RIncludes" -i . 
               QTShell.r -o QTShell.qtr

Here, Rez converts the resource descriptions in the file QTShell.r into resources in the resource file QTShell.qtr (looking in the current directory and in the directory QuickTimeSDK\QTDevWin\RIncludes for any included .r files).

The output of the Rez tool is typically a file with the extension ".qtr", which is the preferred extension for resource files that are to be handled using QTML. As we see, an application whose name is QTShell.exe would have a resource file whose name is QTShell.qtr. On the Macintosh, when an application is launched, the application's resource file is opened automatically and added to the resource chain. But on Windows this is not the case. So if our Windows application uses any Macintosh-style resources, we need to explicitly open the application's resource file, as illustrated in Listing 4.

Listing 4: Opening an application's resource file


myLength = GetModuleFileName(NULL, myFileName, MAX_PATH);
if (myLength != 0) {
   NativePathNameToFSSpec(myFileName, &gAppFSSpec, 

   gAppResFile = FSpOpenResFile(&gAppFSSpec, fsRdWrPerm);
   if (gAppResFile != kInvalidFileRefNum)

Here, we retrieve the name of the file that holds the application, create an FSSpec for that file, and then pass that FSSpec to the Resource Manager function FSpOpenResFile. Once this is accomplished, we can use other Resource Manager calls (like GetResource) to use the resources in that file.

There is one final issue to consider. On Macintosh systems, a data fork and its associated resource fork have the same name and appear in the Finder as a single file. On Windows, our application and its resources have different names and appear to the user as two different files. When we ship an application to the user, it's better to combine both of these files into a single file. The QuickTime SDK includes the tool RezWack, which appends the resource file to the executable file (and also appends some additional data to alert QTML to the fact that the .exe file contains the resource file). We can use RezWack to combine our .exe and our .qtr files like this:

QuickTimeSDK\QTDevWin\Tools\RezWack -d QTShell.exe 
                  -r QTShell.qtr -o TempName.exe
del QTShell.exe
ren TempName.exe QTShell.exe

RezWack does not allow us to overwrite either of the two input files, so we need to save the output file under a temporary name, delete the original .exe file, and then rename the output file to the desired name.

Even if we have Rezwack'ed our application's resource file into the executable file, we still need to explicitly open the resource file; so our applications should include the code in Listing 4, whether the resource file is a separate file or is included in the executable file.

Working With Modal Dialog Boxes

Okay, it's perhaps not all that surprising that the File Manager and the Resource Manager run pretty well on Windows under QTML. But, for me at least, it is fairly surprising that QTML provides extensive support for key parts of the Macintosh User Interface Toolbox, including the Window Manager, the Dialog Manager, the Control Manager, and the Menu Manager. Certainly, if the goal is to support running QuickTime applications on Windows computers, then there needs to be support for user interaction, since QuickTime and its components can display dialog boxes to show information to or get information from the user. Let's see what issues arise when porting our Macintosh dialog box code to Windows.

A dialog box typically contains some small number of controls (buttons, check boxes, edit-text boxes, pop-up menus, and so forth). Controls in modal dialog boxes work very well, with minimal cross-platform dependencies. For example, in the previous article ("Timecode", last month) we displayed the modal dialog box shown in Figure 2.

Figure 2. QTTimecode's Timecode Options dialog box (Macintosh).

The code for displaying this dialog box and for handling events in this box works unchanged on Windows. Figure 3 shows the Windows version of this dialog box.

Figure 3. QTTimecode's Timecode Options dialog box (Windows).

Nonetheless, I did need to make a few changes in the original Macintosh source code to achieve this cross-platform parity. First of all, I needed to adjust the way in which the plus sign (+) and the rectangle surrounding it were drawn. The code I originally inherited did something like this:

Listing 5: Drawing a user item (original version)


do {   
   ModalDialog(gModalFilterUPP, &myItem);
   switch (myItem) {
      ...   // lots of lines omitted here   
      case kItemIsNeg:
         gIsNeg = !gIsNeg;
         GetDialogItem(myDialog, myItem, NULL, NULL, &myRect);
         MoveTo(myRect.left + 2, + 17);
         MacInsetRect(&myRect, 1, 1);
         MacInsetRect(&myRect, -1, -1);

         if (gIsNeg) 

} while ((myItem != kStdOkItemIndex) && 
               (myItem != kStdCancelItemIndex));

As you can see, this code draws into the dialog box from within the ModalDialog loop. That is to say, each time the user clicks in the rectangle of the kItemIsNeg dialog item, this code erases that rectangle and redraws the '+' or '-' symbol. This happens to work just fine on Macintosh computers, but it doesn't work quite right on Windows computers. (The symbols draw okay when the user clicks in the item rectangle, but they are not redrawn if the dialog box is covered up and then uncovered.) Instead, we need to make sure we use the method recommended by Inside Macintosh, which is to define a user item callback procedure, as shown in Listing 6.

Listing 6: Drawing a user item (revised version)


PASCAL_RTN void QTTC_OptionsUserItemProcedure 
                        (DialogPtr theDialog, short theItem)
   Handle                  myItemHandle = NULL;
   Rect                     myRect;
   ControlHandle      myControl = NULL;
   if (theItem != kItemIsNeg)


   GetDialogItem(theDialog, theItem, NULL, NULL, &myRect);
   MoveTo(myRect.left + 2, + 17);
   MacInsetRect(&myRect, 1, 1);
   MacInsetRect(&myRect, -1, -1);

   if (gIsNeg) 

Then, before we display our dialog box, we install the callback procedure by calling GetDialogItem and 

gOptionsUserItemProcUPP = 
GetDialogItem(myDialog, kItemIsNeg, &myKind, &myHandle, 
SetDialogItem(myDialog, kItemIsNeg, myKind,
                  (Handle)gOptionsUserItemProcUPP, &myRect);

(In fact this has always been the recommended way to draw user items in dialog boxes, but you never know when shortcuts have been taken.)

There is one final step we need to take here, which is to have the Dialog Manager call our user item callback procedure whenever the user clicks in the user item rectangle. We can do this by calling InvalRect or InvalWindowRect. Now the code for the kItemIsNeg case should look like this:

case kItemIsNeg:
   gIsNeg = !gIsNeg;
   GetDialogItem(myDialog, myItem, NULL, NULL, &myRect);
   InvalWindowRect(GetDialogWindow(myDialog), &myRect);
   DrawDialog(myDialog);      // force a redraw (necessary on Windows)

I've found that, under Windows, we also need to call DrawDialog to get the appropriate parts of the dialog box to redraw.

Now look again at Figure 2. You'll notice that it contains a pop-up menu control that allows the user to select a font. When I was adding the font pop-up menu to QTTimeCode, I diligently followed Inside Macintosh, according to which we can specify a resource type in the control reference constant field of a control resource and add the value popupUseResMenu (0x0004) to the control definition ID. In other words, I constructed the control resource shown in Figure 4.

Figure 4. ResEdit definition of a pop-up menu control.

(Here, 1179602516 is 0x464F4E54, or 'FONT' in ASCII.) The Control Manager will add the names of all resources of the specified type to the pop-up menu when it creates that menu.

Unfortunately, I couldn't get this to work on Windows. Instead, I needed to programmatically add the names of the available fonts to the pop-up menu control, as shown in Listing 7:

Listing 7: Adding font names to a pop-up menu control


myControl = QTTC_GetDItemRect(myDialog,
                      kFontPopUpMenuControl, &myRect);
if (myControl != NULL) {
   myMenu = MacGetMenu(kFontPopUpResID);
   if (myMenu != NULL) {
      // insert the menu into the menu list
      MacInsertMenu(myMenu, kInsertHierarchicalMenu);
      SetControlPopupMenuHandle(myControl, myMenu);
      SetControlPopupMenuID(myControl, kFontPopUpResID);
      PopupPrivateData      myPrivateData;

      // insert the menu into the menu list
      MacInsertMenu(myMenu, kInsertHierarchicalMenu);
      myPrivateData.mHandle = myMenu;
      myPrivateData.mID = kFontPopUpResID;
                  (**myControl).contrlData) = myPrivateData;
      // clean out existing menu
      while (CountMenuItems(myMenu))
         DeleteMenuItem(myMenu, 1);
      // add in the available fonts
      AppendResMenu(myMenu, FOUR_CHAR_CODE('FONT'));
      SetControlMaximum(myControl, CountMenuItems(myMenu));
      SetControlValue(myControl, gFontIndex);

It would have been nice if QTML had correctly interpreted the original control resource, but the workaround is straightforward and completely cross-platform.

Working With Modeless Dialog Boxes

As we've seen, modal dialog boxes (that is, dialog boxes whose event handling is accomplished using the ModalDialog function) work well on Windows, with very minor adjustments to our existing Macintosh source code. Working with modeless dialog boxes requires a bit more work, however. The main reason for this is that, because our Windows application is not driven by a Macintosh-style event loop, we cannot use the IsDialogEvent and DialogSelect functions to detect and handle events for modeless dialog boxes. Instead, we need to install a callback function to handle Windows messages generated by the main message loop.

Let's suppose that we want our application to display the modeless dialog box shown in Figure 5 (Macintosh version) and Figure 6 (Windows version).

Figure 5. A modeless dialog box (Macintosh)

Figure 6. A modeless dialog box (Windows)

This dialog box contains three radio buttons for dynamically setting the looping state of the frontmost movie window. In our Macintosh code, we can display the dialog box like this:

gLoopDlg = GetNewDialog(kLoopDLOGResID, NULL, 

And then we can intercept events targeted for this dialog box in our main event loop, like this:

Listing 8: Handling events in a modeless dialog box (Macintosh)

WaitNextEvent(everyEvent, &myEvent, kWNEDefaultSleep, NULL);
if (IsDialogEvent(&myEvent))
   if (DialogSelect(&myEvent, &myDialog, &myItemHit)) {
      if (myDialog == gLoopDlg)
         QTXP_DoLoopDialogEvent(&myEvent, myDialog, myItemHit);

Here, we call IsDialogEvent to determine whether the event is targeted at a dialog box. Then we call DialogSelect to get the dialog pointer and the number of the item hit. Finally, we call the application function QTXP_DoLoopDialogEvent to handle the event. (We won't bother to consider QTXP_DoLoopDialogEvent here; it just sets the control values of the radio buttons as appropriate and changes the looping mode of the frontmost movie.)

On Windows, as I've said, there is no event loop, so we can't call IsDialogEvent and DialogSelect. Instead, we need to install a modeless dialog box callback procedure, by calling the SetModelessDialogCallbackProc function:

gLoopDlg = GetNewDialog(kLoopDLOGResID, NULL, 

QTML calls a modeless dialog box callback procedure whenever it processes a message that's targeted at an item in the modeless dialog box. QTML first translates that message into a Macintosh-style event, which it passes as a parameter to the callback procedure (along with the dialog pointer and the number of the affected dialog item). Our callback procedure, defined in Listing 9, is quite simple. It just calls the application function QTXP_DoLoopDialogEvent to handle the event.

Listing 9: Handling events in a modeless dialog box (Windows)

void QTXP_LoopingDialogCallback (EventRecord *theEvent, 
               DialogPtr theDialog, DialogItemIndex theItemHit)
   if (theDialog != NULL)
      QTXP_DoLoopDialogEvent(theEvent, theDialog, theItemHit);

Once we've added a modeless dialog box callback procedure in this way, the dialog box shown in Figure 6 will work as expected on Windows. (You might be wondering why we didn't just install QTXP_DoLoopDialogEvent as our modeless dialog box callback procedure, since all QTXP_LoopingDialogCallback really does is call QTXP_DoLoopDialogEvent. The answer is that, in the future, our callback procedures will need to be more complicated; so I've opted for illustrating the more general case here, even though it isn't strictly necessary.)

Handling Strings

Let's take a look at how we might need to adjust our handling of strings (that is, sequences of individual characters). Strings passed to Macintosh APIs are generally expected to be Pascal strings, which consist of a length byte followed by that number of characters. Most Macintosh C compilers recognize the '\p' escape sequence for constructing Pascal strings. For instance, Listing 5 contains this line:


The parameter to DrawString is a string constant, which our compiler formats as a Pascal string. In memory, this string occupies two bytes, having the value 0x012B. Similarly, the string "hello, world" occupies 13 bytes (12 characters plus a length byte). Because the length of a Pascal string is specified by a single byte, a Pascal string can contain at most 0xFF (that is, 255) characters.

By contrast, the C programming language supports C strings, which consist of some number of characters followed by a termination character (the byte 0x00, or '\0'). There is no length byte in a C string, so C strings can be arbitrarily long. Many Macintosh programmers - myself included - prefer to work primarily with C strings; as a result, when we want to call a function like DrawString that requires a Pascal string, we need to convert the C string to a Pascal string. In the past, I've used the function c2pstr, which converts its argument in place:

char         myString[] = "hello, world";

The C string "hello, world" looks like this in memory:


After the call to c2pstr, that same block of memory looks like this:


As you can see, the characters in the C string have been shifted to the right, and the length byte (here, 0x0C) has been inserted before the first character; the byte formerly occupied by the termination character is now occupied by the last character in the string.

To avoid this string conversion, we can use special glue functions that take C strings as parameters, rather than Pascal strings. For instance, the file QuickdrawText.h declares the function drawstring, which takes a C string:

char         myString[] = "hello, world";

So far, so good. But several problems arise when we try to make our string-handling code work under QTML and under Carbon. First of all, these glue functions are not supported under Carbon, so we need to restrict ourselves to those functions that work with Pascal strings. Further, the functions c2pstr and p2cstr are likewise not supported under Carbon. (Instead, Carbon supports several similar functions, c2pstrcpy and p2cstrcpy.)

Another problem is that not all Windows compilers support the '\p' escape sequence for creating Pascal strings. Microsoft Developer Studio Visual C/C++ versions 5 and earlier did support it, but that support was dropped in version 6. In that case, the string "\phello, world" would be misinterpreted as the C string "phello, world". To help prevent such unexpected results, QTML examines Pascal strings whose length byte is 'p' (that is 0x70 or 112) to see if they are really misgenerated C strings; if they are, it calls c2pstr to convert them in place to bona fide Pascal strings.

This is wonderful, unless our strings happen to be located in read-only memory (in which case having QTML call c2pstr on them would cause an access violation). The safest course of action is probably to avoid using the '\p' escape sequence entirely (so that Windows compilers don't ever get the chance to misinterpret it). Instead, we'll create all strings as C strings and then convert them to Pascal strings whenever we need to pass them to a function that requires Pascal strings.

But which functions shall we use to make this conversion? Early on in my efforts to port Macintosh code to Windows, I decided it was simplest just to define my own functions for converting strings from one form into another. That way, we don't need to worry about whether any functions have been deprecated by the move to Carbon, or QTML, or whatever. Listing 10 defines the function QTUtils_ConvertCToPascalString, which we've used many times in this series of articles for converting C strings to Pascal strings.

Listing 10: Converting a C string into a Pascal string


StringPtr QTUtils_ConvertCToPascalString (char *theString)
   StringPtr   myString = malloc(min(strlen(theString) + 1,
   short         myIndex = 0;

   while ((theString[myIndex] != '\0') && (myIndex < 255)) {
      myString[myIndex + 1] = theString[myIndex];
   myString[0] = (unsigned char)myIndex;

There's no magic here: we just allocate a buffer large enough to hold the characters in the C string (up to a maximum 255 characters) and the length byte of the Pascal string, copy the characters from the existing C string into the new buffer, and then finish up by prepending the length byte. Listing 11 defines the QTUtils_ConvertPascalToCString function, which performs the reverse conversion.

Listing 11: Converting a Pascal string into a C string


char *QTUtils_ConvertPascalToCString (StringPtr theString)
   char       *myString = malloc(theString[0] + 1);
   short      myIndex = 0;

   for (myIndex = 0; myIndex < theString[0]; myIndex++)
      myString[myIndex] = theString[myIndex + 1];
   myString[theString[0]] = '\0';

Whenever we use these functions in our code, we need to make sure to free the buffer allocated by malloc, by calling free once we're done using the string.

Let's sum this up. Our preferred method for working with strings will be to create all strings as C strings. When we need a Pascal string, we'll explicitly create a new Pascal string by calling QTUtils_ConvertCToPascalString. To illustrate, reconsider these lines from Listing 5:

if (gIsNeg) 

We'll need to rewrite them now, like this:

StringPtr      mySign = NULL;
if (gIsNeg) 
   mySign = QTUtils_ConvertCToPascalString("-");   
   mySign = QTUtils_ConvertCToPascalString("+");   

Updating last month's QTTimeCode sample code is left as an exercise for the reader.

Converting Data Types

Occasionally we need to convert between similar Macintosh and Windows data types. One case that pops up often enough to warrant attention is the conversion between the Macintosh data type Rect and the Windows data type RECT. The fields of these structures have the same names, but in a Rect, the fields are of type short, while in a RECT they are of type long. (Also, for what it's worth, the fields are not in the same order in these two structures.) QTML knows how to do this conversion, but it does not provide a public API for it; so I wrote the function QTFrame_ConvertMacToWinRect defined in Listing 12.

Listing 12: Converting a Macintosh rectangle into a Windows rectangle


void QTFrame_ConvertMacToWinRect (Rect *theMacRect, 
                                                RECT *theWinRect)
   theWinRect->top = (long)theMacRect->top;
   theWinRect->left = (long)theMacRect->left;
   theWinRect->bottom = (long)theMacRect->bottom;
   theWinRect->right = (long)theMacRect->right;

We've bumped into this function previously (in "Word Is Out", MacTech, November 2000) but haven't discussed it explicitly. It's really rather simple, of course. (So much so that I'll leave the companion function QTFrame_ConvertWinToMacRect as an easy exercise for the reader.)

QTML does provide some useful functions for converting other kinds of structures. For instance, we can convert between Macintosh regions (of type MacRegion) and Windows regions (of type Region) by calling the MacRegionToNativeRegion and NativeRegionToMacRegion functions. See the "QuickTime For Windows Programmers" document referenced at the end of this article for exact details on these and other conversion functions.

Handling Text

Dialog boxes, both modal and modeless alike, often contain fields where the user can enter and edit text. The dialog boxes shown in Figures 2 and 3 use the Control Manager's edit-text control. Some Macintosh applications use TextEdit for more complicated text support. QTML does not support TextEdit. If you need simple text entry and editing services, use the edit-text control (in a Mac-style dialog box) or use the Windows native edit control (in a Windows window). If you need more complicated text-editing services, you'll have to do a bit of programming.


Unlike QTML, Carbon is a porting layer. In particular, Carbon is a set of programming interfaces and a run-time library that together define a subset of Macintosh APIs that are supported both on "classic" Mac operating systems and on the new Mac OS X. By writing our code to conform to the Carbon standard, we can ensure that our compiled applications will run on both Mac platforms.

In general, it's easier to port existing QuickTime code to Carbon than it is to port it to QTML. There are just a few issues we need to pay attention to when reworking some existing Mac code to run under Carbon (that is, when Carbonizing our application). First, we need to make sure that all the OS and Toolbox functions we call are part of the Carbon standard. This is because some existing functions have been dropped (usually in favor of better technologies). Second, we need to make sure that we use accessor functions whenever necessary. This is because many of the data structures that hitherto were public are now opaque; we cannot directly read or write the data in their fields.

At times Carbon and QTML seems to be working at cross-purposes, but it turns out that it's fairly easy to support both Carbon and Windows with a single set of source code files. In this section, I want to focus on the kinds of changes we need to make to our QuickTime-savvy code to get it to run on Carbon, while maintaining our Windows compatibility. (General information on porting to Carbon can be found elsewhere.)

Accessing Fields of Data Structures

Under Carbon, many of the key data structures used by the OS and Toolbox managers have been privatized (made opaque). For instance, in the not-too-distant past, the standard way of getting the menu ID of the menu associated with the menu handle myMenu was like this:

myID = (**myMenu).menuID;

Nowadays, when we're targeting Carbon, we must instead use the accessor function GetMenuID, like this:

myID = GetMenuID(myMenu);

It turns out, however, that GetMenuID (like most of the new accessor functions) is not supported by QTML. So we might conditionalize our code, like this:

   myID = GetMenuID(myMenu);
   myID = (**myMenu).menuID;

Alternatively, we could just stick the following lines somewhere in one of our project's header files:

#define GetMenuID(mHdl)      (**mHdl).menuID

In this case, we can call GetMenuID without worrying whether we're building a Carbon application or not.

Which of these (or still other) options you adopt is largely a matter of taste, I suppose. Personally, I generally opt for the former approach. Partly that's because it's not always so easy to concoct a suitable #define. Ideally, however, the Apple-supplied header files should contain those defines, or else QTML should implement the accessor functions.

Replacing Unsupported Functions

The really troubling problem in writing Carbon- and QTML-compatible code concerns Mac OS and Toolbox functions that have been dropped entirely from Carbon. A good case in point is the ICM function StandardGetFilePreview. Not too terribly long ago, we used StandardGetFilePreview in both Mac and Windows code to elicit files from the user. StandardGetFilePreview relies on the services of the Standard File Package, which is not supported under Carbon. So we need to rework our file-opening and -saving code to use the Navigation Services APIs.

In this case, I decided to create wrapper functions that internally call either the Standard File Package or Navigation Services, depending on our target runtime environment. For instance, to elicit a file from the user, I wrote the QTFrame_GetOneFileWithPreview function, shown in Listing 13. It may be a bit lengthy, but it does the trick.

Listing 13: Eliciting a file from the user


OSErr QTFrame_GetOneFileWithPreview (short theNumTypes, 
         QTFrameTypeListPtr theTypeList, FSSpecPtr theFSSpecPtr, 
         void *theFilterProc)
   StandardFileReply      myReply;
   NavReplyRecord         myReply;
   NavDialogOptions      myDialogOptions;
   NavTypeListHandle      myOpenList = NULL;
   NavEventUPP            myEventUPP = 
   OSErr                     myErr = noErr;
   if (theFSSpecPtr == NULL)
   // deactivate any frontmost movie window

   // prompt the user for a file
      theNumTypes, (ConstSFTypeListPtr)theTypeList, &myReply);
   if (!myReply.sfGood)
   // make an FSSpec record
   myErr = FSMakeFSSpec(myReply.sfFile.vRefNum, 
      myReply.sfFile.parID,, theFSSpecPtr);

   // specify the options for the dialog box
   myDialogOptions.dialogOptionFlags -= kNavNoTypePopup;
   myDialogOptions.dialogOptionFlags -= 
   BlockMoveData(gAppName, myDialogOptions.clientName, 
                           gAppName[0] + 1);
   // create a handle to an 'open' resource
   myOpenList = (NavTypeListHandle)QTFrame_CreateOpenHandle(
               kApplicationSignature, theNumTypes, theTypeList);
   if (myOpenList != NULL)
   // prompt the user for a file
   myErr = NavGetFile(NULL, &myReply, &myDialogOptions, 
         myEventUPP, NULL, (NavObjectFilterUPP)theFilterProc, 
            myOpenList, NULL);
   if ((myErr == noErr) && myReply.validRecord) {
      AEKeyword      myKeyword;
      DescType         myActualType;
      Size               myActualSize = 0;
      // get the FSSpec for the selected file
      if (theFSSpecPtr != NULL)
         myErr = AEGetNthPtr(&(myReply.selection), 1, typeFSS, 
                           &myKeyword, &myActualType, theFSSpecPtr, 
                              sizeof(FSSpec), &myActualSize);

   if (myOpenList != NULL) {

Working with Universal Procedure Pointers

There is one set of unsupported functions that is relatively easy to deal with, namely the three functions NewRoutineDescriptor, DisposeRoutineDescriptor and CallUniversalProc. On "classic" Macintosh systems, a universal procedure pointer (UPP) is a pointer to a routine descriptor, which is a structure that occupies memory (and hence must be allocated and disposed of). Under Carbon, the UPP data type is opaque, and might or might not require memory allocation. So we need to use a new creation and deletion function for each specific type of UPP we want to use. For instance, in Listing 13, we create a UPP for a Navigation Services event procedure by calling NewNavEventProc. To dispose of this UPP, we call DisposeNavEventUPP. Similarly, we can create a UPP for a modal dialog event filter by calling NewModalFilterProc. To dispose of this UPP, we call DisposeModalFilterUPP. If we ever needed to call this procedure ourselves, we would use the function InvokeModalFilterUPP.

The good news here is that the header files for Macintosh APIs provide definitions of these new UPP functions for non-Carbon targets. For instance, the header file Dialogs.h contains some lines like this:

   EXTERN_API(void) DisposeModalFilterUPP
                                       (ModalFilterUPP userUPP);
   #define DisposeModalFilterUPP(userUPP)

This means that we can revise our code to use (for instance) DisposeModalFilterUPP and the resulting source code will compile and run on Windows, classic Macintosh, and Mac OS X.


The QuickTime Media Layer is a rock-solid implementation of key parts of the Macintosh Operating System and the Macintosh User Interface Toolbox for Windows computers. We've considered a number of changes that we might need to make to our existing QuickTime code to get it working on Windows. The changes are, all things considered, relatively straightforward. We occasionally need to byte swap data read from or written to a file. We need to ferret out the Mac APIs that have the same names on Mac and Windows and rename the Mac versions. We need to explicitly open our application's resource fork on Windows if we use any Mac-style resources. And we need to install a callback procedure if we want to work with modeless dialog boxes on Windows. Otherwise, things work pretty much the same on both platforms. QTML is not a porting layer, but it is amazingly good at supporting a large set of Mac APIs. There is, unfortunately, no definitive documentation on which functions are available and which are not. My advice is to experiment; if a Mac function compiles, links, and runs on Windows, that's great.

Acknowledgements and References

Many thanks are due to Greg Chapman and Tom McHale for reviewing this article and providing some helpful comments. The sample resource flipper is based on code in the Letter from the Ice Floe, Dispatch 25 (found at The information on strings is based on information posted by Sam Bushell to the QuickTime API discussion list. If you are serious about developing QuickTime applications, you should definitely subscribe to this list; visit to sign on. If you're a Windows programmer who wants to learn more about Macintosh data types and APIs, then you might want to take a look at "QuickTime For Windows Programmers" at

Tim Monroe is pleased to report that Libra, his home-hatched baby lizard, is doing well and is happily gobbling up flies, spiders, and crickets. You can reach him at


Community Search:
MacTech Search:

Software Updates via MacUpdate

Luminar 3.1.4 - Powerful, adaptive, conf...
Luminar is the new full-featured image editor that adapts to the way you edit photos. Over 300 essential tools to fix, edit, and enhance your photos with comfort. The future of photo editing is here... Read more
SketchUp 19.3.252 - Create 3D design con...
SketchUp is an easy-to-learn 3D modeling program that enables you to explore the world in 3D. With just a few simple tools, you can create 3D models of houses, sheds, decks, home additions,... Read more
VirtualBox 6.0.14 - x86 virtualization s...
VirtualBox is a family of powerful x86 virtualization products for enterprise as well as home use. Not only is VirtualBox an extremely feature rich, high performance product for enterprise customers... Read more
TinkerTool 7.42 - Expanded preference se...
TinkerTool is an application that gives you access to additional preference settings Apple has built into Mac OS X. This allows to activate hidden features in the operating system and in some of the... Read more
Paperless 3.0.7 - $69.95
Paperless is a digital documents manager. Remember when everyone talked about how we would soon be a paperless society? Now it seems like we use paper more than ever. Let's face it - we need and we... Read more
Yojimbo 4.1.3 - Data organizer.
Yojimbo empowers Mac users to manage, effortlessly and securely, the onslaught of information encountered every day at work and at home, even across multiple computers. Yojimbo stores different data... Read more
Ableton Live 10.1.3 - Record music using...
Ableton Live lets you create and record music on your Mac. Use digital instruments, pre-recorded sounds, and sampled loops to arrange, produce, and perform your music like never before. Ableton Live... Read more
Tinderbox 8.1.0 - Store and organize you...
Tinderbox is a personal content management assistant. It stores your notes, ideas, and plans. It can help you organize and understand them. And Tinderbox helps you share ideas through Web journals... Read more
Microsoft Office 365, 2019 16.30 - Popul...
Microsoft Office 365. The essentials to get it all done. Unmistakably Office, designed for Mac Get started quickly with new, modern versions of Word, Excel, PowerPoint, Outlook and OneNote-... Read more
RoboForm 8.6.5 - Password manager; syncs...
RoboForm is a password manager that offers one-click login, mobile syncing, easy form filling, and reliable security. Password Manager. RoboForm remembers your passwords so you don't have to! Just... Read more

Latest Forum Discussions

See All

Alluris is a choose-your-own adventure g...
Alluris is an RPG that the developer's are calling a swipe-your-own adventure game. This is because the game incorporates a Reigns-style - swiping left or right - selection mechanic to make all the decisions you'd usually expect to make across... | Read more »
Hello Hero All Stars receives update wit...
The first Hello Hero game hit global platforms in 2013 and proved a huge success, with developer Fincon adding two more entries to this popular series of casual RPG games since. Released in June this year, Hello Hero All Stars brings many of the... | Read more »
Zombieland: Double Tapper, a cartoon idl...
Zombieland: Double Tapper is the idle RPG tie-in to the upcoming Zombieland: Double Tap. Oddly, it's one of two different Zombieland games launching today, with the other being the Switch title Zombieland: Double Tap - Road Trip. [Read more] | Read more »
Rusty Lake's The White Door launche...
Rusty Lake and Second Maze's intriguing point-and-click adventure game, The White Door, is now up for pre-order on the App Store. This one sees you playing as Robert Hill, a mental health patient who is suffering from severe memory loss. The game... | Read more »
Hellrule is an auto-runner inspired by G...
Hellrule is an upcoming auto-runner game from independent developer Pedrocorp where players will take control of a dapperly dressed gentlemen who comes equipped with a razor-sharp umbrella for slicing up his foes. The game will be available for... | Read more »
Grobo is a gravity bending puzzle platfo...
Grobo is a 2D puzzle platformer that marks the first release from developers Hot Chocolate Games. You'll find yourself manipulating gravity as you make your through this title that's available now for iOS and Android. [Read more] | Read more »
Adrenaline, Compulsive Entertainment’s h...
Compulsive Entertainment’s high-octane arcade racer, Adrenaline, has now made its way to the App Store following a successful launch on Google Play. It’s a ton of challenging, fast-paced fun, boasting easy-to-learn controls and a varied selection... | Read more »
Mario Kart Tour is adding Super Mario Ga...
Earlier today on Twitter, Nintendo announced that Mario Kart Tour is getting a new racer and track. Fans of Super Mario Galaxy will be pleased to hear that Rosalina is the first post-launch character being added, while the iconic Rainbow Road is... | Read more »
$100,000 up for grabs at World of Tanks...
The fourth annual Blitz Twister Cup will be held in Minsk (Belarus) on November 9th. For those not in the know, the Blitz Twister Cup is an eSports championship for the hugely popular World of Tanks Blitz. [Read more] | Read more »
Brown Dust’s crossover event with That T...
Brown Dust, Neowiz’s epic fantasy RPG, is no stranger to special events, though its latest crossover might be its most exciting yet. On top of a challenging new dungeon, fan-favourite characters from the hit anime series That Time I Got... | Read more »

Price Scanner via

Lease one iPhone Xs, Xr, or X at Sprint and g...
Purchase an Apple iPhone X, Xr, Xs, or Xs Max at Sprint, and get a 64GB iPhone Xr for free. Requires 2 new lines or 1 upgrade-eligible line and 1 new line. The fine print: “iPhone Xs (64 GB) $37.50/... Read more
How to use your Apple Education discount to s...
Purchase a new MacBook Pro or MacBook Air using Apple’s Education discount, and take up to $200 off MSRP. All teachers, students, and staff of any educational institution with a .edu email address... Read more
Base 2019 13″ 1.4GHz 4-Core MacBook Pro on sa...
Amazon has the new 2019 13″ 1.4GHz/128GB 4-Core Space Gray Touch Bar MacBook Pro on sale for $100 off Apple’s MSRP. This is the same MacBook Pro sold by Apple in its retail and online stores: – 2019... Read more
Save up to $30 on Apple’s AirPods at these re...
Amazon is offering discounts on new 2019 Apple AirPods ranging up to $30 off MSRP. Shipping is free: – AirPods with Charging Case: $144 $15 off MSRP – AirPods with Wireless Charging Case: $169 $30... Read more
Save $15 on Apple Watch Series 5 models on Wa...
Walmart has new Apple Watch Series 5 models on sale for $15 off Apple’s MSRP on their online store. Choose free shipping or free local store pickup (if available). Sale prices for online orders only... Read more
Save $750 on the base 8-Core 27″ iMac Pro wit...
Apple has Certified Refurbished 27″ 3.2GHz 8-Core iMac Pros available for $4249 including free shipping. Their price is $750 off the cost of new models. A standard Apple one-year warranty is included... Read more
Apple continues to offer 11″ iPad Pros, refur...
Apple has Certified Refurbished 11″ iPad Pros available on their online store for up to $220 off the cost of new models. Prices start at $679. Each iPad comes with a standard Apple one-year warranty... Read more
Get Beats Solo3 Wireless Headphones today for...
Amazon has Beats Solo3 Wireless On-Ear Headphones in stock and on sale today for $139.99 shipped. Their price is $60 off Apple’s MSRP, and it’s the lowest price available for this model from any of... Read more
Sale! 2019 13″ MacBook Airs for $200 off Appl...
Amazon has new 2019 13″ MacBook Airs on sale for $200 off Apple’s MSRP, with prices starting at $899, each including free shipping. Be sure to select Amazon as the seller during checkout, rather than... Read more
Verizon offers $150 discount on iPhone 8, 8 P...
Use code SMART150 at checkout at Verizon to take $150 off the price of an iPhone 8, iPhone 8 Plus, or iPhone X. New line of service required. Their discount reduces the price of a 64GB iPhone 8 to $... Read more

Jobs Board

Best Buy *Apple* Computing Master - Best Bu...
**730598BR** **Job Title:** Best Buy Apple Computing Master **Job Category:** Store Associates **Location Number:** 001129-Wheaton-Store **Job Description:** The Read more
Best Buy *Apple* Computing Master - Best Bu...
**741153BR** **Job Title:** Best Buy Apple Computing Master **Job Category:** Sales **Location Number:** 000411-Flint-Store **Job Description:** **What does a Best Read more
Surgical Technologist III, *Apple* Hill Sur...
Surgical Technologist III, Apple Hill Surgical Center - Full Time Tracking Code APPHILLST Job Description Surgical Technologist III Apple Hill Surgical Center 25 Read more
Geek Squad *Apple* Master Consultation Agen...
**741154BR** **Job Title:** Geek Squad Apple Master Consultation Agent **Job Category:** Services/Installation/Repair **Location Number:** Read more
Best Buy *Apple* Computing Master - Best Bu...
**740900BR** **Job Title:** Best Buy Apple Computing Master **Job Category:** Store Associates **Location Number:** 000387-Randall Road-Store **Job Description:** Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.