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

Introduction

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

QTUtils_GetWindowPositionFromFile

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);
      }
   }

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

   return(myErr);
}

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:

#if TARGET_RT_BIG_ENDIAN
   #define EndianS16_BtoN(value)            (value)
#else
   #define EndianS16_BtoN(value)            EndianS16_BtoL(value)
#endif

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'), 
                                                kMyChapResourceID);
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

QTXP_ChapterResourceFlipper

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

   // 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);
   }

   return(noErr);
}

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'), 
                                       QTXP_ChapterResourceFlipper);

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:

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

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:\\myMovie.mov", &myFSSpec, 
                                                   kFullNativePath);

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

QTDX_ReadHandleFromFile

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);

bail:
   if (myRefNum != 0)      
      FSClose(myRefNum);

   return(myHandle);
}

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:

QuickTimeSDK\QTDevWin\Tools\Rez 
               -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

WinMain

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

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

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)

QTTC_GetTimeCodeOptions

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, myRect.top + 17);
         MacFrameRect(&myRect);
         MacInsetRect(&myRect, 1, 1);
         EraseRect(&myRect);
         MacInsetRect(&myRect, -1, -1);

         TextSize(kTextBigSize);
         if (gIsNeg) 
            DrawString("\p-");
         else
            DrawString("\p+");

         TextSize(kTextRegSize);
         break;
      
      default:
         break;
   }
} 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)

QTTC_OptionsUserItemProcedure

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

   MacSetPort(GetDialogPort(theDialog));

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

   TextSize(kTextBigSize);
   if (gIsNeg) 
      DrawString("\p-");
   else
      DrawString("\p+");
   TextSize(kTextRegSize);
}

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

gOptionsUserItemProcUPP = 
                  NewUserItemProc(QTTC_OptionsUserItemProcedure);
GetDialogItem(myDialog, kItemIsNeg, &myKind, &myHandle, 
                  &myRect);                  
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);
#if TARGET_API_MAC_CARBON
   InvalWindowRect(GetDialogWindow(myDialog), &myRect);
#else      
   InvalRect(&myRect);
   DrawDialog(myDialog);      // force a redraw (necessary on Windows)
#endif      
   break;

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

QTTC_GetTimeCodeOptions

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

      // insert the menu into the menu list
      MacInsertMenu(myMenu, kInsertHierarchicalMenu);
      myPrivateData.mHandle = myMenu;
      myPrivateData.mID = kFontPopUpResID;
      (PopupPrivateData)(**(PopupPrivateDataHandle)
                  (**myControl).contrlData) = myPrivateData;
#endif               
      // 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, 
                                                         (WindowPtr)-1);

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)

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

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, 
                                                            (WindowPtr)-1);
#if TARGET_OS_WIN32
SetModelessDialogCallbackProc(gLoopDlg, 
      (QTModelessCallbackUPP)QTXP_LoopingDialogCallback);
#endif

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)

QTXP_LoopingDialogCallback
#if TARGET_OS_WIN32
void QTXP_LoopingDialogCallback (EventRecord *theEvent, 
               DialogPtr theDialog, DialogItemIndex theItemHit)
{
   if (theDialog != NULL)
      QTXP_DoLoopDialogEvent(theEvent, theDialog, theItemHit);
}
#endif

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:

DrawString("\p+");

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";
DrawString(c2pstr(myString));

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

0x68656C6C6F2C20776F726C6400

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

0x0C68656C6C6F2C20776F726C64

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";
drawstring(myString);

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

QTUtils_ConvertCToPascalString

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

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

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

QTUtils_ConvertPascalToCString

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';
   
   return(myString);
}

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) 
   DrawString("\p-");
else
   DrawString("\p+");

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

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

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

QTFrame_ConvertMacToWinRect

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.

Carbon

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:

#if ACCESSOR_CALLS_ARE_FUNCTIONS
   myID = GetMenuID(myMenu);
#else
   myID = (**myMenu).menuID;
#endif

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

#if !ACCESSOR_CALLS_ARE_FUNCTIONS
#define GetMenuID(mHdl)      (**mHdl).menuID
#endif

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

QTFrame_GetOneFileWithPreview

OSErr QTFrame_GetOneFileWithPreview (short theNumTypes, 
         QTFrameTypeListPtr theTypeList, FSSpecPtr theFSSpecPtr, 
         void *theFilterProc)
{
#if TARGET_OS_WIN32
   StandardFileReply      myReply;
#endif
#if TARGET_OS_MAC
   NavReplyRecord         myReply;
   NavDialogOptions      myDialogOptions;
   NavTypeListHandle      myOpenList = NULL;
   NavEventUPP            myEventUPP = 
                  NewNavEventProc(QTFrame_HandleNavEvent);
#endif
   OSErr                     myErr = noErr;
   
   if (theFSSpecPtr == NULL)
      return(paramErr);
   
   // deactivate any frontmost movie window
   QTFrame_ActivateController(QTFrame_GetFrontMovieWindow(), 
                                          false);

#if TARGET_OS_WIN32
   // prompt the user for a file
   StandardGetFilePreview((FileFilterUPP)theFilterProc, 
      theNumTypes, (ConstSFTypeListPtr)theTypeList, &myReply);
   if (!myReply.sfGood)
      return(userCanceledErr);
   
   // make an FSSpec record
   myErr = FSMakeFSSpec(myReply.sfFile.vRefNum, 
      myReply.sfFile.parID, myReply.sfFile.name, theFSSpecPtr);
#endif

#if TARGET_OS_MAC
   // specify the options for the dialog box
   NavGetDefaultDialogOptions(&myDialogOptions);
   myDialogOptions.dialogOptionFlags -= kNavNoTypePopup;
   myDialogOptions.dialogOptionFlags -= 
                                                   kNavAllowMultipleFiles;
   BlockMoveData(gAppName, myDialogOptions.clientName, 
                           gAppName[0] + 1);
   
   // create a handle to an 'open' resource
   myOpenList = (NavTypeListHandle)QTFrame_CreateOpenHandle(
               kApplicationSignature, theNumTypes, theTypeList);
   if (myOpenList != NULL)
      HLock((Handle)myOpenList);
   
   // 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);

      NavDisposeReply(&myReply);
   }
   
   if (myOpenList != NULL) {
      HUnlock((Handle)myOpenList);
      DisposeHandle((Handle)myOpenList);
   }
   
   DisposeNavEventUPP(myEventUPP);
#endif
 
   return(myErr);
}

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:

#if OPAQUE_UPP_TYPES
   EXTERN_API(void) DisposeModalFilterUPP
                                       (ModalFilterUPP userUPP);
#else
   #define DisposeModalFilterUPP(userUPP)
                                       DisposeRoutineDescriptor(userUPP)
#endif

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.

Conclusion

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 http://developer.apple.com/quicktime/icefloe/dispatch025.html). 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 http://lists.apple.com/mailman/listinfo/quicktime-api 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 http://developer.apple.com/techpubs/quicktime/qtdevdocs/RM/tp_rm_qtforwindows.htm.


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 monroe@apple.com.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Aether Gazer unveils Chapter 16 of its m...
After a bit of maintenance, Aether Gazer has released Chapter 16 of its main storyline, titled Night Parade of the Beasts. This big update brings a new character, a special outfit, some special limited-time events, and, of course, an engaging... | Read more »
Challenge those pesky wyverns to a dance...
After recently having you do battle against your foes by wildly flailing Hello Kitty and friends at them, GungHo Online has whipped out another surprising collaboration for Puzzle & Dragons. It is now time to beat your opponents by cha-cha... | Read more »
Pack a magnifying glass and practice you...
Somehow it has already been a year since Torchlight: Infinite launched, and XD Games is celebrating by blending in what sounds like a truly fantastic new update. Fans of Cthulhu rejoice, as Whispering Mist brings some horror elements, and tests... | Read more »
Summon your guild and prepare for war in...
Netmarble is making some pretty big moves with their latest update for Seven Knights Idle Adventure, with a bunch of interesting additions. Two new heroes enter the battle, there are events and bosses abound, and perhaps most interesting, a huge... | Read more »
Make the passage of time your plaything...
While some of us are still waiting for a chance to get our hands on Ash Prime - yes, don’t remind me I could currently buy him this month I’m barely hanging on - Digital Extremes has announced its next anticipated Prime Form for Warframe. Starting... | Read more »
If you can find it and fit through the d...
The holy trinity of amazing company names have come together, to release their equally amazing and adorable mobile game, Hamster Inn. Published by HyperBeard Games, and co-developed by Mum Not Proud and Little Sasquatch Studios, it's time to... | Read more »
Amikin Survival opens for pre-orders on...
Join me on the wonderful trip down the inspiration rabbit hole; much as Palworld seemingly “borrowed” many aspects from the hit Pokemon franchise, it is time for the heavily armed animal survival to also spawn some illegitimate children as Helio... | Read more »
PUBG Mobile teams up with global phenome...
Since launching in 2019, SpyxFamily has exploded to damn near catastrophic popularity, so it was only a matter of time before a mobile game snapped up a collaboration. Enter PUBG Mobile. Until May 12th, players will be able to collect a host of... | Read more »
Embark into the frozen tundra of certain...
Chucklefish, developers of hit action-adventure sandbox game Starbound and owner of one of the cutest logos in gaming, has released their roguelike deck-builder Wildfrost. Created alongside developers Gaziter and Deadpan Games, Wildfrost will... | Read more »
MoreFun Studios has announced Season 4,...
Tension has escalated in the ever-volatile world of Arena Breakout, as your old pal Randall Fisher and bosses Fred and Perrero continue to lob insults and explosives at each other, bringing us to a new phase of warfare. Season 4, Into The Fog of... | Read more »

Price Scanner via MacPrices.net

New today at Apple: Series 9 Watches availabl...
Apple is now offering Certified Refurbished Apple Watch Series 9 models on their online store for up to $80 off MSRP, starting at $339. Each Watch includes Apple’s standard one-year warranty, a new... Read more
The latest Apple iPhone deals from wireless c...
We’ve updated our iPhone Price Tracker with the latest carrier deals on Apple’s iPhone 15 family of smartphones as well as previous models including the iPhone 14, 13, 12, 11, and SE. Use our price... Read more
Boost Mobile will sell you an iPhone 11 for $...
Boost Mobile, an MVNO using AT&T and T-Mobile’s networks, is offering an iPhone 11 for $149.99 when purchased with their $40 Unlimited service plan (12GB of premium data). No trade-in is required... Read more
Free iPhone 15 plus Unlimited service for $60...
Boost Infinite, part of MVNO Boost Mobile using AT&T and T-Mobile’s networks, is offering a free 128GB iPhone 15 for $60 per month including their Unlimited service plan (30GB of premium data).... Read more
$300 off any new iPhone with service at Red P...
Red Pocket Mobile has new Apple iPhones on sale for $300 off MSRP when you switch and open up a new line of service. Red Pocket Mobile is a nationwide MVNO using all the major wireless carrier... Read more
Clearance 13-inch M1 MacBook Airs available a...
Apple has clearance 13″ M1 MacBook Airs, Certified Refurbished, available for $759 for 8-Core CPU/7-Core GPU/256GB models and $929 for 8-Core CPU/8-Core GPU/512GB models. Apple’s one-year warranty is... Read more
Updated Apple MacBook Price Trackers
Our Apple award-winning MacBook Price Trackers are continually updated with the latest information on prices, bundles, and availability for 16″ and 14″ MacBook Pros along with 13″ and 15″ MacBook... Read more
Every model of Apple’s 13-inch M3 MacBook Air...
Best Buy has Apple 13″ MacBook Airs with M3 CPUs in stock and on sale today for $100 off MSRP. Prices start at $999. Their prices are the lowest currently available for new 13″ M3 MacBook Airs among... Read more
Sunday Sale: Apple iPad Magic Keyboards for 1...
Walmart has Apple Magic Keyboards for 12.9″ iPad Pros, in Black, on sale for $150 off MSRP on their online store. Sale price for online orders only, in-store price may vary. Order online and choose... Read more
Apple Watch Ultra 2 now available at Apple fo...
Apple has, for the first time, begun offering Certified Refurbished Apple Watch Ultra 2 models in their online store for $679, or $120 off MSRP. Each Watch includes Apple’s standard one-year warranty... Read more

Jobs Board

DMR Technician - *Apple* /iOS Systems - Haml...
…relevant point-of-need technology self-help aids are available as appropriate. ** Apple Systems Administration** **:** Develops solutions for supporting, deploying, Read more
Omnichannel Associate - *Apple* Blossom Mal...
Omnichannel Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Operations Associate - *Apple* Blossom Mall...
Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
IT Systems Engineer ( *Apple* Platforms) - S...
IT Systems Engineer ( Apple Platforms) at SpaceX Hawthorne, CA SpaceX was founded under the belief that a future where humanity is out exploring the stars is Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.