==============================================================================
C-Scene Issue #05
UNIX Programmer's Introduction to Macintosh Programming
Platform: MacOS
Paul Brannan
==============================================================================

UNIX Programmer's Introduction to Macintosh Programming


Not too many years ago, almost every school in the nation that had a computer had a lab of Macintoshes. The Macintosh itself symbolized the day when computers would be usable by the average Joe. Before SoundBlaster compatibility was virtually mandatory on the PC, while PC games had their blips and bleeps coming out of the PC speaker, the Mac sported their fancy 8-bit sound and much more. Today the Mac is all but history, but as anyone in IT knows, there are still a multitude of users who will swear by their Macs forever.

We can't, unfortunately, always ignore these hard heads (I really do mean that in the nicest way), and every so often it may be useful to port a Unix program over to the Mac. It's not an easy task, but believe it or not, it isn't as difficult as people might have you believe.

Know your tools

Before beginning anything, be familiar with the resources that are available to you. The single greatest resource available on the planet for Macintosh development is Inside Macintosh, known as IM by Mac developers. You'll want to check this resource frequently when starting out, since it will tell you everything you want to know, and more, about all the API's and especially about the Macintosh toolbox. The URL for IM is http://gemma.apple.com/techpubs/mac/mac.html.

Another resource you'll want to check is the listing of Mac error codes. In the old days, when RAM was expensive, PC users didn't always load COMMAND.COM's message table into memory. Whenever an error would occur, they would get an error code rather than a message. Macs are the same way; unless you have a program that will intercept errors, you will get lots of error numbers, especially negative ones. There are a number of lists of these numbers; one good one is at http://www.cs.cmu.edu/~lenzo/mac_errors.html.

If you are an IRC user, you may want to check out the #macdev channel. There are lots of knowledgeable and friendly people t ere who, when not idle, are glad to give a helping hand. The comp.sys.mac* Usenet groups are also very helpful when trying to track down a problem.

Finally, if you are the book type, public libraries generally carry at least one or two Mac programming books. It's not often that these books are written for C, though, but there is at least one good book for learning Mac programming with C: Macintosh C Programming by Example by Kurt W. G. Matthies and Thom Hogan. IM isn't always easy reading, and this book really makes Mac programming a lot more fun.

Choose your weapon

There are a number of different compilers for the Mac. For a more complete comparison, see the Mac Programming FAQ. Here's a summary: Here we'll be focusing on MPW. It's not really known for optimizing code too well, but it's not all that bad either, especially considering the price tag.

If you are going to be doing any serious work, though, you will probably want to get a copy of CodeWarrior. Most Mac developers would swear by this tool. If you are a casual programmer, though, MPW will probably suit your needs just fine.

Installing MPW for the first time

You can get the MPW etc. folder from http://gemma.apple.com/dev/tools/. You'll want to start off getting the About_MPW_etc.sit.hqx file (which is in PDF format, so you'll also need Acrobat Reader). You'll also need the MPW shell, MrC/MrCpp for PowerPC development, SC/SCpp for 68k development, Make, Commando and CreateMake (for making makefiles), Link, and anything else that looks useful. All of these utilities go in the "User Commands" folder under the MPW Shell folder.

Under the Interfaces&Libraries directory, you'll need CIncludes, CLibraries, and SharedLibraries at the absolute minimum. You may also need some of the other interfaces/libraries. Put the CIncludes folder in the Interfaces folder under the MPW Shell folder, and put the libraries under the Libraries folder, also in the MPW Shell folder.

That should save you at least a little bit of trouble getting things set up. If you have a Mac that predates System 7.6, you'll also need to get the StdCLibInit in order to run the MPW shell and utilities. The reason for this is that the C library that comes with Macs has bugs, and needs to be patched at boot time to the latest version; if you don't do this you will get an error about StdCLib not being found.

That should be enough to get you running. Once you have everything installed, you should get a screen like this:



One last thing: if you plan on running your program on System 7.6 and above machines, you'll need to get a different StdCLib stub library than ships with MPW. The reason for this is that the StdCLib that ships with System 7.6 is version 3.4.3, and the version that comes with MPW is version 3.4.4. If you link against version 3.4.4, you won't be able to run your program on any systems that have an earlier version of StdCLib (and copying the file doesn't help). The older stub libraries are available at dev.apple.com. The 3.4.3 library is fairly safe, and is only missing "long long" support.

Languages that just won't die

The first thing that non-Mac programmers have to get used to is that Macs were based on Pascal. Most of the example code on IM is in Pascal. In addition, all of the operating system calls and the toolbox functions are Pascal functions. That means that all strings are Pascal strings, and function arguments are passed left-to-right. The order of arguments is handled for us in the headers, but the strings are a different matter.

A Pascal string differs from a C string in the way the length of the string is stored. Recall that in C, a string is an arbitrary number of non-null characters, followed by a null terminator. In Pascal, on the other hand, the first character of the string is the length of the string, and the string is stored in the 255 characters that follow:

Index01234567891011
Pascal String11Hello World
C StringHello World0x00

On the Mac, there is a predefined type for Pascal strings, namely, Str255. Conversion to and from this type is simple:
char *pas2cstr(char *str) {
	int j, len;
	len = str[0];			
	for(j = 1; j <= len; j++)	/* memcpy() works for this too */
		str[j - 1] = str[j];
	str[len] = 0;			/* Store the null terminator */
	return str;
}

char *c2passtr(char *str) {
	int j, len;
	len = strlen(str);
	if(len > 255) len = 255;
	for(j = 1; j <= len; j++)	/* memcpy() works for this too */
		str[j] = str[j - 1];
	str[0] = len;			/* Record the string length */
	return str;
}

main() {
	Str255 pas_string = "\pString";	/* \p means Pascal string */
	puts(pas2cstr(pas_string));	/* Print the string */
}

These two functions are really very simple, and will work almost 100% of the time. Note, however, that there is one thing that Pascal strings can do that C strings can't: it is impossible for a C string to contain a null character (obviously, since the null character represents the end of the string!). At the same time, Pascal strings can't be longer than 255 characters (the maximum value for an unsigned char).

A simple Mac program

Now it's time to start writing our first Mac program. This program will open a window, and display that phrase that we all love to hate, "Hello, world!" The first step is to initialize the toolbox. If you don't initialize the toolbox, you will get an error of type 1 if you start it from the Finder, or the computer may simply lock up if you start if from within the MPW shell.
	InitGraf(&qd.thePort);
	InitFonts();
	InitWindows();
	InitMenus();
	TEInit();
	InitDialogs(NULL);
	InitCursor();
Notice the variable in the first line, qd. This is a variable that is defined globally, at the top of the program. Old libraries used to define this variable for you, but it is now necessary to define it ourselves:
	QDGlobals qd;
The next step is to open a window, and set the graph port.
	WindowPtr window;
	Rect windRect;

	windRect = qd.screenBits.bounds;
	InsetRect(&windRect, 50, 50);
	window = NewWindow(NULL, &windRect, "\phello.c", true, documentProc
		(WindowPtr)(-1), false, 0);
	SetPort(window);
The two lines define our variables. The next line sets windRect to be the size of the screen. This is kind of large, so we call InsetRect() to make the window 50 pixels smaller on each side. We then create a new window with the dimensions specified in windRect, the title "hello.c" (sent as a Pascal string), and with the visible attribute. Finally, we set the graph port to the window we created.

Next we display something on the screen. This part is easy:
	TextSize(48);
	MoveTo(windRect.left, WindRect.top);
	DrawString("\pHello, World!");
And wait until the mouse button is clicked:
	while(!Button()) ;
Notice the space between the while() and the semicolon. The MPW compiler will generate a warning if this space is missing. This is definitely not what Unix programmers are used to, but it's not a bad idea, since it keeps the programmer from accidentally creating a null loop.

While this is a very simple way to pause a program, it really isn't very practical. In fact, this loop is just as bad as the classic while(!kbhit());. A much better way to write it is this:
	EventRecord myEvent;
	for(;;) {
		SystemTask();
		if(WaitNextEvent(everyEvent, &myEvent, 5L, NULL))
			if(myEvent.what == mouseDown) break;
	}
Finally, a screen shot:



The source code is in hello.c.

The standard file interface

If you want to do any sort of file i/o, you'll need to use the Mac's standard file package (don't ask why, Mac users just don't like to type filenames the way Unix and PC users do). If your program is only going to run on System 7 or later (chances are this is the case), you can use the StandardGetFile and StandardPutFile routines.
	StandardFileReply r_in;
	OSType typeList[4] = {0};
	StandardGetFile(NULL, -1, typeList, &r_in);
This code will open a window that lets you select a file to open for reading:



The first arguement to StandardGetFile is a pointer to a function that can filter out certain files, so they won't be displayed. You can also filter certain types by putting up to four types to be displayed in the typeList variable. The second argument specifies how many of these types there are; a -1 means to display all types.

You can use similar code to get the name of a file to open for writing:
	StandardFileReply r_out;
	Str255 putfile_prompt = "\pFilename: ";
	Str255 default_name = "\pmailbox";
Don't forget the \p at the beginning of the string or you might get unexpected results! You should get a window like this:



Finally, if you want to use C's stdio.h functions with these filenames, you have to do a little bit of translation. Mac's haven't always had the ability to have folders within folders, and so it's necessary to change to the proper volume/folder before opening a file. We also have to convert the string to a C string before calling fopen():
/* This is a modified version of the function found in the Mac
 * Programmer's FAQ
 */
FILE *doOpen(FSSpec sfFile, char *mode) {
	short oldVol, aVol;
	long aDir, aProc;

	if(GetVol(NULL, &oldVol)) return NULL;
	if(GetWDInfo(oldVol, &aVol, &aDir, &aProc)) return NULL;
	if(HSetVol(NULL, sfFile.vRefNum, sfFile.parID)) return NULL;

	/* Note: this will mangle sfFile.name for subsequent calls */
	pas2cstr(sfFile.name);
	
	return fopen((const char *)sfFile.name, mode);
}
You can also move the pas2cstr() call outside of the doOpen function if you are planning on reusing the FSSpec struct.

Converting UNIX programs to the Mac

The first thing you will want to do is to make sure you create an SIOW (Single Input/Output Window) app rather than a standard application. If you don't do this, all output to stdout will go to a file called stdout, and all output to stderr will go to a file called stderr. An SIOW application can output to a window and even get input from the keyboard using standard C I/O routines. (Note: you may need to get the MPW-PR version of the RIncludes Interface to make this work).

The second thing to keep in mind is translation of end-of-line characters. As anyone who transfers files from the DOS to Unix knows, DOS and Unix use different ways of specifying end of line; DOS uses a CR/LF combination, while Unix uses a single line feed (LF). Macs do something different too; the end of line on a Mac is represented by a carriage return (CR). Even if you open a file as binary, you don't always get a the right character. We can write our own fputs that outputs the proper character if we don't get the right output (such as when writing to a network drive):
int macfputs(const char *s, FILE *stream) {
	int j;
	for(j = 0; s[j] != 0; j++) {
		switch(s[j]) {
			case '\r': fputc('\n', stream); break;
			case '\n': fputc('\r', stream); break;
			default: fputc(s[j], stream);
		}
	}
	return j;
}

Go ye therefore...

That should be everything you need to get started programming on the Mac. Programming the Mac may not be the most exciting thing in the world, nor the most rewarding, but like it or not these machines are here to stay. Of course, we can also pull for Bill Gates....
This page is Copyright © 1998 By C Scene. All Rights Reserved