Retrocoding on Macintosh System 7.5, Think C and ResEdit

Most modern programmers are accustomed to using code completion tools and newfangled AI things a la Copilot. They have become the norm.

These tools are so convenient that we have begun to forget about the pain and struggle that accompanied the development process in the distant past. Unless you, like me, are not into computer history and retro programming in particular.

Classic Macintosh computers are becoming increasingly popular among collectors. My personal collection includes the iMac G3, my beloved Macintosh SE 30, and the Apple Newton. I don’t collect them for fun – I feel how quickly the world is losing knowledge of the history of computing. This is especially true for programming. It’s incredible how much effort one has to put into finding all the necessary development tools and documentation from those years.

Why and who needs it

The original Macintosh and its OS went on sale in January 1984. The Macintosh OS will live for 17 long years until it is replaced by Mac OS X (NeXTSTEP) in 2001. With MacOS 9.2, the latest version of the Macintosh system, thousands of application programs and development tools disappeared.

Macintosh development environment

Macintosh development environment

Why would anyone need retroprogramming today?

Here are some reasons:

  • Preserve the software development culture, its historical heritage, and learn from past successes and failures.

  • Understand why certain practices, tools and development methodologies are still used today.

  • Draw inspiration from the experience of past generations, preserve their knowledge, practices and ideas. And so move on.

History and authenticity

The history of software development allows developers to appreciate the contribution of those who stood at its origins. Realize yourself as part of a community with rich experience in solving complex problems, an engine of technological progress. This is especially true for beginners, who already find it difficult to understand the huge number of stacks, tool chains, nuances of operating systems, APIs and programming paradigms.

Intro of the HyperCard presentation at WWDC '91 (ISO is in the archive)

Intro of the HyperCard presentation at WWDC ’91 (ISO is in the archive)

Samples of automotive or, say, industrial technologies, after the end of their support period, sometimes end up in museums, while computer hardware and software often end up in a landfill. We, the software development community, should not accept this. WWDC ’91 records should not be allowed to be buried in the bowels of the Stanford Library, accessible by personal request.

I hope this article does a little bit of historical context for programmers, especially those currently working with macOS, iOS, iPadOS, watchOS or tvOS.

Preparing to Launch Macintosh 7

Before you get started, you’ll need a number of tools. First, Macintosh System 7 itself. Second, ResEdit (version 2.1.3) and Think C (I recommend version 5). There are also various ways to run System 7 in a virtual environment. And to work with a real, physical OS, you will already need an old Macintosh.

You might find these links useful:

All necessary applications can be found:

Books on programming are freely distributed on Vintage Apple.

My personal favorite is “The Macintosh C Programming Primer 1992

You can figure out how ResEdit works with “ResEdit Complete 1991

ResEdit 2.1.3 and Think C 5 on Infinite Mac's System 7

ResEdit 2.1.3 and Think C 5 on Infinite Mac’s System 7

Once all the tools are ready, you can start creating your Macintosh applications. It wouldn’t hurt to create a folder on your desktop where you can later save projects. Both ThinkC and ResEdit allow the user to create a mess and do not enforce rules and regulations.

The simplest program: Hello Macintosh

You can check whether the toolkit is functioning correctly by writing a simple “Hello Macintosh” program for the command line. Since there is virtually no command line on the Macintosh, we will output the text to the Output Window. For Think C projects the file extension is .∏. On a Mac, you can enter the ∏ symbol, using the combination option + shift + p. If there is no extension, the project file will not open. Additionally, the ResEdit file should ideally have the same name as the project itself. For the HelloMacintosh.∏ project resource file, this would be HelloMacintosh.∏.rsrc. Also make sure that all files are in the same folder.

Adding a file to a Think C project

Adding a file to a Think C project

If you create a “HelloMacintosh.c” file, it will be a plain text file and will not be associated with the project. It will have to be added to the project separately. If you try to run the project now, it will fail with a linker error because you did not import the ANSI C library.

ANSI C library included with Think C

ANSI C library included with Think C

Once the ANSI library is connected to the project, you can run it and a window with the text output “Hello Macintosh” will appear on the screen. Thus, we verified that the system works and basic C applications can be compiled.

Running the humble

Running the humble “Hello Macintosh” with ThinkC

This is certainly not the most inspiring start. I would like to create something more complete, preferably with a graphical interface. To do this, you need to create a resource file using ResEdit and add the appropriate elements to it.

ResEdit: windows, dialogs and buttons

ResEdit is an independent program that is not tied to Think C. It will work with other applications, such as Apple’s Macintosh Programmer’s Workshop (MPW for short). In ResEdit you can add elements and build a graphical user interface.

ResEdit 2.1.3 on Macintosh System 7.5

ResEdit 2.1.3 on Macintosh System 7.5

IDs are used in ResEdit as a reference for code to access resources. An approach that was shared by all operating systems at the time, including Palm OS and Windows. I won’t go into all the details of working with ResEdit and will instead advise you to read the books mentioned earlier. They both describe ResEdit and working with it in detail.

Simple conversational application

Here is an application that shows a window (resource “WIND”) and closes it when the user presses any button. As soon as the window is closed, a dialog will appear on the screen (resources “DLG” and “DITL”) with a message.

// references the resource id 128
#define kBaseResID 128
#define kMoveToFront (WindowPtr)-1L
#define kHorizontalPixel 30
#define kVerticalPixel 30

// definition of the functions
void ToolBoxInit(void);
void WindowInit(void);
void ShowDialog(void);

// our main function
void main(void){
ToolBoxInit();
WindowInit();

// closes the window when any button is pressed
while(!Button());

// show the DLG and DITL when a button is pressed.
// WARNING: if the DITL doesn't have
//             any button, it may freeze Basilisk II
NoteAlert(kBaseResID, nil);
}

// initialises the Graf of the Gestalt ;)
// meaning the graphics of the machine
void ToolBoxInit(void){
InitGraf(&thePort);
InitFonts();
InitWindows();
InitMenus();
TEInit();
InitDialogs(nil);
InitCursor();
}

// actually runs our main window
void WindowInit(void){
WindowPtr window;       // window pointer for our window
ControlHandle button;   // control handle that is the button
Rect buttonPos;         // rectangle defining where the button is

// gets the window from the resources using the id of 128
window = GetNewWindow(kBaseResID, nil, kMoveToFront);

// beeps and crashes if the window resource isn't there
if(window == nil){
 SysBeep(10);
 ExitToShell();
}

// shows our window
ShowWindow(window);
SetPort(window);

MoveTo(kHorizontalPixel,kVerticalPixel);
DrawString("\pHello Medium reader. Subscribe to my articles!");

// setting the position of our button
buttonPos.top = 60;
buttonPos.left = 23;
buttonPos.bottom = 90;
buttonPos.right = 140;

button = NewControl(window, &buttonPos, "\pSubscribe now!", true, 0, 0, 0, pushButProc, 0);
if(button == nil){
 SysBeep(10);
 ExitToShell();
}

ShowControl(button);
}

In addition to the resource file, you will need to import the MacTraps library into the project. It contains header files and information for graphics (Graf) and the desktop environment (Gestalt). After compilation, the application will display a window with a button and issue a NoticeAlert after clicking on it.

A simple conversational application on System 7.5.3

A simple conversational application on System 7.5.3

This is a simple application that clearly demonstrates how the user interface works on the Macintosh. The programming books found on Vintage Apple also include examples of working with graphics, printers, and many other peripherals and system libraries.

Network Applications and MacTCP

Here’s another very important feature. All of these books cover a wide variety of topics, but none of them touches on programming applications that use the network or the Internet. My Mac SE 30 and iMac G3 are connected to a local network and, accordingly, to the Internet. It was quite difficult to get the SE 30 online, but even more difficult to write an application for it that supports Internet access. Networking on a Mac is done using a library called MacTCP. In the early 90s, this library cost hundreds of dollars.

Steve Falkenburg's article on MacTCP, published in the spring of 1991

Steve Falkenburg’s article on MacTCP, published in the spring of 1991

I highly recommend Steve Falkenburg’s article “MacTCP Cookbook: Constructing Network-Aware Applications” in Develop Issue 6.

I will show Steve’s complete example of implementing the protocol Finger using MacTCP.

I took the liberty of tweaking the formatting a bit to match the modern syntax highlighting scheme.

/* MacTCP finger client */
/* written by Steven Falkenburg */
/* */
#include <CursorCtl.h>
#include <Memory.h>
#include <Packages.h>
#include <String.h>
#include <Types.h>

#include "CvtAddr.h"
#include "MacTCPCommonTypes.h"
#include "TCPHi.h"
#include "TCPPB.h"

/* constants */
#define kFingerPort 79  /* TCP port assigned for finger protocol */
#define kBufSize 16384  /* Size in bytes for TCP stream buffer and receive buffer */
#define kTimeOut 20     /* Timeout for TCP commands (20 sec. pretty much arbitrary) */

/* function prototypes */
void main(int argc, char *argv[]);
OSErr Finger(char *userid, char *hostName, Handle *fingerData);
OSErr GetFingerData(unsigned long stream, Handle *fingerData);
void FixCRLF(char *data);
Boolean GiveTime(short sleepTime);

/* globals */
Boolean gCancel = false; /* This is set to true if the user cancels an operation. */

/* main entry point for finger */
/* */
/* usage: finger <user>@<host> */
/* */
/* This function parses the args from the command line, */
/* calls Finger() to get info, and prints the returned info. */

void main(int argc, char *argv[]) {
 OSErr err;
 Handle theFinger;
 char userid[256], host[256];
 if (argc != 2) {
   printf("Wrong number of parameters to finger call\n");
   return;
 }

 sscanf(argv[1], "%[^@]@%s", userid, host);
 strcat(userid, "\n\r");
 err = Finger(userid, host, &theFinger);
 if (err == noErr) {
   HLock(theFinger);
   FixCRLF(*theFinger);
   printf("\n%s\n", *theFinger);
   DisposHandle(theFinger);
 } else {
   printf("An error has occurred: %hd\n", err);
 }
}

/* Finger() */
/* This function converts the host string to an IP number, */
/* opens a connection to the remote host on TCP port 79, sends */
/* the id to the remote host, and waits for the information on */
/* the receiving stream. After this information is sent, the */
/* connection is closed down. */
OSErr Finger(char *userid, char *hostName, Handle *fingerData) {
 OSErr err;
 unsigned long ipAddress;
 unsigned long stream;

 /* open the network driver */
 err = InitNetwork();
 if (err != noErr) return err;

 /* get remote machine's network number */
 err = ConvertStringToAddr(hostName, &ipAddress);
 if (err != noErr) return err;

 /* open a TCP stream */
 err = CreateStream(&stream, kBufSize);
 if (err != noErr) return err;
 err = OpenConnection(stream, ipAddress, kFingerPort, kTimeOut);

 if (err == noErr) {
   err = SendData(stream, userid, (unsigned short)strlen(userid), false);
   if (err == noErr) err = GetFingerData(stream, fingerData);
   CloseConnection(stream);
 }

 ReleaseStream(stream);
 return err;
}

OSErr GetFingerData(unsigned long stream, Handle *fingerData) {
 OSErr err;
 long bufOffset = 0;
 unsigned short dataLength;
 Ptr data;
 *fingerData = NewHandle(kBufSize);
 err = MemError();
 if (err != noErr) return err;
 HLock(*fingerData);
 data = **fingerData;
 dataLength = kBufSize;
 do {
   err = RecvData(stream, data, &dataLength, false);
   if (err == noErr) {
     bufOffset += dataLength;
     dataLength = kBufSize;
     HUnlock(*fingerData);
     SetHandleSize(*fingerData, bufOffset + kBufSize);
     err = MemError();
     HLock(*fingerData);
     data = **fingerData + bufOffset;
   }
 } while (err == noErr);
 data[0] = '\0';
 HUnlock(*fingerData);
 if (err == connectionClosing) err = noErr;
}

/* FixCRLF() removes the linefeeds from a text buffer. This is */
/* necessary, since all text on the network is embedded with */
/* carriage return linefeed pairs. */
void FixCRLF(char *data) {
 register char *source, *dest;
 long length;
 length = strlen(data);
 if (*data) {
   source = dest = data;

   while ((source - data) < (length - 1)) {
     if (*source == '\r') source++;
     *dest++ = *source++;
   }
   if (*source != '\r' && (source - data) < length) *dest++ = *source++;
   length = dest - data;
 }
 *dest="\0";
}

/* This routine would normally be a callback for giving time to */
/* background apps. */
Boolean GiveTime(short sleepTime) {
 SpinCursor(1);
 return true;
}

Steve’s article is a good way to play around with MacTCP, but for a deep dive you’ll need more than that. MacTCP Developer’s Guide. It took me about 2 months to find this manual. It was given to me by an unknown user from the mac68k forum.

I’ve uploaded it to the Internet Archive to preserve the knowledge of MacTCP for anyone interested.

All necessary libraries are installed by the network driver. For details, please refer to the MacTCP manual.

An example of a complete application on MacTCP can be found in Joshua Stein’s Wikipedia client for classic MacOS.

Creating a “release” version of the application for Macintosh

We studied the process of writing a program and running a test build. The older generation will smile now, and I will describe the process of creating a “release” version of the application. What is a release? In fact, this is a ready-made floppy disk with the program. The compiled binary that ThinkC produces can be thrown onto a floppy disk.

Programming in CodeWarrior 8 Gold on my iMac G3 with macOS 9.2

Programming in CodeWarrior 8 Gold on my iMac G3 with macOS 9.2

Insert the floppy disk into your Macintosh and copy the binary file to your programs folder. That’s all, actually. Applications for the Macintosh were distributed through mail in the early days of computers. People sent money and in return received an envelope with a floppy disk. Alternatively, you could go to a computer store, or download an application via the Internet. But this method appeared much later.

If you have a more modern computer, like my iMac G3, then you can use Rumpus FTP on the iMac G3 and FileZilla on the MacBook Pro to transfer files between your current Mac and the retro machine.

A few words in conclusion

You look at ResEdit, the interface of the early Macintoshes of 1984, and you get goosebumps. We constantly use buttons, checkboxes, text fields and other user interface elements that premiered on the mass market almost 40 years ago. The Macintosh entered the market on January 24, 1984. That’s 444 days before I was born. This is further than 2060 from today. On Macintosh systems in the early 90s, performing a simple HTTP GET method over the network using a web server was at least several hundred lines of code.

You can learn a lot from the old Macintosh, if you are not lazy and try to write something for it. When you develop an iOS app using SwiftUI, remember where it all started. You are creating the dream program of our predecessors, who only dreamed of carrying a Macintosh in their pocket. The iPhone, the successor to this computer, has the power and strength of 215 machines of the past (iPhone 15 – multi-core CPU at 2.4 GHz versus single-core Mac SE 30 at 16 MHz).

We – software engineers, developers, simple coders, inventors – were responsible for making the dreams of our predecessors come true.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *