==============================================================================
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:
- Symantec's Think C - no longer supported, but you can order it
for about $20 or so. Most books focus on Think C.
- Symantec C++ - Now in version 8.0, it's a great compiler, and has
neat browsing features. It has a decent optimizer, but uses lots of RAM.
Its biggest downside is that it doesn't support 68k code.
- Codewarrior - Even with its late start in the market in 1994,
Metrowerks Codewarrior is probably the most powerful and most popular
compiler available for the Mac. It sports a low price, fast compile time,
great optimization, and good tech support.
- MPW - This is Apple's compiler, it's free, and there's even a gcc
port. It has it's roots in Unix, and even has a nice command-line shell
(though you have to get used to pressing Enter rather than Return to
end a line).
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:
Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
Pascal String | 11 | H | e | l | l | o | | W | o | r | l | d |
C String | H | e | l | l | o | | W | o | r | l | d | 0x00 |
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