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

Vivaldi 2.6.1566.49 - An advanced browse...
Vivaldi is a browser for our friends. We live in our browsers. Choose one that has the features you need, a style that fits and values you can stand by. From the look and feel, to how you interact... Read more
Capo 3.7.4 - Slow down and learn to play...
Capo lets you slow down your favorite songs so you can hear the notes and learn how they are played. With Capo, you can quickly tab out your songs atop a highly-detailed OpenCL-powered spectrogram... Read more
Daylite - Dynamic business organ...
Daylite helps businesses organize themselves with tools such as shared calendars, contacts, tasks, projects, notes, and more. Enable easy collaboration with features such as task and project... Read more
Firefox 68.0.1 - Fast, safe Web browser.
Firefox offers a fast, safe Web browsing experience. Browse quickly, securely, and effortlessly. With its industry-leading features, Firefox is the choice of Web development professionals and casual... Read more
Dashlane 6.1927.0 - Password manager and...
Dashlane is an award-winning service that revolutionizes the online experience by replacing the drudgery of everyday transactional processes with convenient, automated simplicity - in other words,... Read more
BetterTouchTool 3.153 - Customize multi-...
BetterTouchTool adds many new, fully customizable gestures to the Magic Mouse, Multi-Touch MacBook trackpad, and Magic Trackpad. These gestures are customizable: Magic Mouse: Pinch in / out (zoom)... Read more
calibre 3.46.0 - Complete e-book library...
Calibre is a complete e-book library manager. Organize your collection, convert your books to multiple formats, and sync with all of your devices. Let Calibre be your multi-tasking digital librarian... Read more
DEVONthink Pro 3.0beta4 - Knowledge base...
DEVONthink Pro is your essential assistant for today's world, where almost everything is digital. From shopping receipts to important research papers, your life often fills your hard drive in the... Read more
Adobe Creative Cloud - Access...
Adobe Creative Cloud costs $20.99/month for a single app, or $52.99/month for the entire suite. Introducing Adobe Creative Cloud desktop applications, including Adobe Photoshop CC and Illustrator CC... Read more
Chromium 75.0.3770.142 - Fast and stable...
Chromium is an open-source browser project that aims to build a safer, faster, and more stable way for all Internet users to experience the web. Version 75.0.3770.142: Release notes were... Read more

Latest Forum Discussions

See All

Void Tyrant guide - Tips and tricks for...
Void Tyrant continues to get a lot of play in these parts. Probably because the game is just so deep and varied. The next stop on our guide series for Void Tyrant is class-specific guides. First up is the Knight, as it’s the first class anyone has... | Read more »
Summon beasts and battle evil in epic re...
Imagine a tale of conlict between factions of good and evil, where rogueish heroes summon beasts to aid them in them in warfare and courageously battle dragons over fields of scorched earth and brimstone - that's exactly the essence of epic fantasy... | Read more »
Upcoming visual novel Arranged shines a...
If you’re in the market for a new type of visual novel designed to inform and make you think deeply about its subject matter, then Arranged by Kabuk Games could be exactly what you’re looking for. It’s a wholly unique take on marital traditions in... | Read more »
TEPPEN guide - The three best decks in T...
TEPPEN’s unique take on the collectible card game genre is exciting. It’s just over a week old, but that isn’t stopping lots of folks from speculating about the long-term viability of the game, as well as changes and additions that will happen over... | Read more »
Intergalactic puzzler Silly Memory serve...
Recently released matching puzzler Silly Memory is helping its fans with their intergalactic journeys this month with some very special offers on in-app purchases. In case you missed it, Silly Memory is the debut title of French based indie... | Read more »
TEPPEN guide - Tips and tricks for new p...
TEPPEN is a wild game that nobody asked for, but I’m sure glad it exists. Who would’ve thought that a CCG featuring Capcom characters could be so cool and weird? In case you’re not completely sure what TEPPEN is, make sure to check out our review... | Read more »
Dr. Mario World guide - Other games that...
We now live in a post-Dr. Mario World world, and I gotta say, things don’t feel too different. Nintendo continues to squirt out bad games on phones, causing all but the most stalwart fans of mobile games to question why they even bother... | Read more »
Strategy RPG Brown Dust introduces its b...
Epic turn-based RPG Brown Dust is set to turn 500 days old next week, and to celebrate, Neowiz has just unveiled its biggest and most exciting update yet, offering a host of new rewards, increased gacha rates, and a brand new feature that will... | Read more »
Dr. Mario World is yet another disappoin...
As soon as I booted up Dr. Mario World, I knew I wasn’t going to have fun with it. Nintendo’s record on phones thus far has been pretty spotty, with things trending downward as of late. [Read more] | Read more »
Retro Space Shooter P.3 is now available...
Shoot-em-ups tend to be a dime a dozen on the App Store, but every so often you come across one gem that aims to shake up the genre in a unique way. Developer Devjgame’s P.3 is the latest game seeking to do so this, working as a love letter to the... | Read more »

Price Scanner via

Weekend Deal: 2018 13″ MacBook Airs starting...
B&H Photo has clearance 2018 13″ MacBook Airs available starting at only $999 with all models now available for $200 off Apple’s original MSRP. Overnight shipping, or expedited shipping, is free... Read more
Apple has clearance 10.5″ iPad Pros available...
Apple has Certified Refurbished 2017 10.5″ iPad Pros available starting at $469. An Apple one-year warranty is included with each iPad, outer shells are new, and shipping is free: – 64GB 10″ iPad Pro... Read more
Apple restocks refurbished iPad mini 4 models...
Apple has restocked Certified Refurbished 32GB iPad mini 4 WiFi models for $229 shipped. That’s $70 off original MSRP for the iPad mini 4. Space Gray, Silver, and Gold colors are available. Read more
Apple, Yet Again, Is Missing An Ultraportable...
EDITORIAL: 07.19.19 Prior to the decision made by Apple earlier this month to retire the thin and light MacBook model with a 12-inch retina display, the Cupertino, California-based company offered,... Read more
Verizon is offering a 50% discount on iPhone...
Verizon is offering 50% discounts on Apple iPhone 8 and iPhone 8 Plus models though July 24th, plus save 50% on activation fees. New line required. The fine print: “New device payment & new... Read more
Get a new 21″ iMac for under $1000 today at t...
B&H Photo has new 21″ Apple iMacs on sale for up to $100 off MSRP with models available starting at $999. These are the same iMacs offered by Apple in their retail and online stores. Shipping is... Read more
Clearance 2017 15″ 2.8GHz Touch Bar MacBook P...
Apple has Certified Refurbished 2017 15″ 2.8GHz Space Gray Touch Bar MacBook Pros available for $1809. Apple’s refurbished price is currently the lowest available for a 15″ MacBook Pro. An standard... Read more
Clearance 12″ 1.2GHz MacBook on sale for $899...
Focus Camera has clearance 12″ 1.2GHz Space Gray MacBooks available for $899.99 shipped. That’s $400 off Apple’s original MSRP. Focus charges sales tax for NY & NJ residents only. Read more
Get a new 2019 13″ 2.4GHz 4-Core MacBook Pro...
B&H Photo has new 2019 13″ 2.4GHz MacBook Pros on sale for up to $150 off Apple’s MSRP. Overnight shipping is free to many addresses in the US: – 2019 13″ 2.4GHz/256GB 6-Core MacBook Pro Silver... Read more
AirPods with Wireless Charging Case now on sa...
Amazon has extended their Prime Day savings on Apple AirPods by offering AirPods with the Wireless Charging case for $169.99. That’s $30 off Apple’s MSRP, and it’s the cheapest price available for... Read more

Jobs Board

Geek Squad *Apple* Master Consultation Agen...
**702908BR** **Job Title:** Geek Squad Apple Master Consultation Agent **Job Category:** Services/Installation/Repair **Location Number:** 000360-Williston-Store Read more
Best Buy *Apple* Computing Master - Best Bu...
**711023BR** **Job Title:** Best Buy Apple Computing Master **Job Category:** Sales **Location Number:** 000012-St Cloud-Store **Job Description:** **What does a Read more
*Apple* Systems Architect/Engineer, Vice Pre...
…its vision to be the world's most trusted financial group. **Summary:** Apple Systems Architect/Engineer with strong knowledge of products and services related to Read more
Best Buy *Apple* Computing Master - Best Bu...
**696259BR** **Job Title:** Best Buy Apple Computing Master **Job Category:** Store Associates **Location Number:** 001076-Temecula-Store **Job Description:** The Read more
Business Development Manager, *Apple* Globa...
Business Development Manager, Apple Global Tampa, FL, US Requisition Number:73805 As a Global Apple Business Development Manager at Insight, you proactively Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.