Retrocoding on Macintosh System 7.5, Think C and ResEdit

Retrocoding on Macintosh System 7.5, Think C and ResEdit

Most modern programmers are used to using auto-completion tools and new-fangled AI gadgets a la Copilot. They have become the norm.

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

Classic Macintosh computers are becoming increasingly popular among collectors. My personal collection includes an iMac G3, my favorite Macintosh SE 30, and an Apple Newton. I don’t collect them for fun – I feel how rapidly the world is losing knowledge of the history of computing. This is especially true of programming. It’s mind-boggling how much effort it takes to find all the necessary development tools and documentation of those years.

Why and who needs it

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

Macintosh development environment

Why would anyone need retro-programming today?

Here are a few reasons:

  • To preserve the culture of software development, its historical heritage, to learn from past successes and failures.

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

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

History and authenticity

The history of software development allows developers to properly appreciate the contributions 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, toolchains, nuances of operating systems, APIs and programming paradigms.

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

Samples of automotive or, say, industrial technologies after the end of the support period sometimes end up in museums, and computer equipment and software often end up in landfills. We, the software development community, should not put up with this. WWDC ’91 records cannot be allowed to be buried in the bowels of Stanford’s private library.

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 starting work, you will 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 different 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 may find these links useful:

  • Infinite Mac System 7 works in the browser and already has everything you need on board.

  • Basilisk II – Runs on a modern Mac.

  • UTM with QEMU – Runs on a modern Mac.

All necessary programs can be found:

Programming books are freely distributed on Vintage Apple.

My personal favorite is “The Macintosh C Programming Primer 1992“.

You can understand how ResEdit is arranged with “ResEdit Complete 1991“.

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

With all the tools ready, you can start creating your Macintosh programs. It will not hurt to create a folder on the desktop, in which you can later save projects. Both Think C and ResEdit allow the user to make a mess and do not follow rules and regulations.

The simplest program: Hello Macintosh

You can check whether the toolkit is working correctly by writing a simple Hello Macintosh program for the command line. Since there is actually no command line on the Macintosh, we will output the text to the Output Window. For Think C projects, the file extension is used .∏. On a Mac, you can type ∏, using the option+shift+p combination. Without the extension, the project file will not open. Also, 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 all the files are in the same folder.

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 a project. It will have to be added to the project separately. If you try to run the project now, it will exit with a linker error because you haven’t imported the ANSI C library.

ANSI C library as part of Think C

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

Running the humble Hello Macintosh with Think C

This, of course, is not the most inspiring start. I want to create something full-fledged, preferably with a graphical interface. To do this, you need to use ResEdit to create a resource file 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 also 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

IDs are used by ResEdit as references for code to access resources. An approach shared by all operating systems, including Palm OS and Windows. I won’t go into all the details of how to use ResEdit and instead recommend you read the books mentioned earlier. They are both detailed in and with ResEdit.

A simple dialog program

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

// 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 desktop (Gestalt). After compilation, the program will display a window with a button and issue a NoticeAlert after clicking on it.

A simple dialog application on System 7.5.3

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

Networking and MacTCP

Here is another important feature. All of these books cover a wide variety of topics, but none of them touch 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 difficult enough to get the SE 30 online, but even more difficult to write a program for it to support 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 Spring 1991

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

I will show a complete example of Steve’s implementation of 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 something more. 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 MacTCP knowledge for everyone.

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

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

We are creating a “release” version of the program for Macintosh

We have studied the process of writing a program and running a test build. The older generation will now smile, and I will describe the process of creating a “release” version of the program. What is a release? In fact, it is a ready-made diskette with a program. The compiled binary produced by Think C can be dumped onto a floppy disk.

I program in CodeWarrior 8 Gold on my iMac G3 with macOS 9.2

Insert the diskette into the Macintosh and copy the binary file into the applications folder. Actually, that’s all. At the dawn of computers, Macintosh programs were distributed by mail. People sent money and received an envelope with a floppy disk in return. Alternatively, you could go to a computer store or download the program via the Internet. But this method appeared much later.

If you have a more modern computer, like my iMac G3, 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

Look at ResEdit, the interface of early Macintosh 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. Macintosh was released on January 24, 1984. That’s 444 days before I was born. This is further than 2060 years from now. On Macintosh systems of the early 1990s, performing a simple HTTP GET method over the network using a web server was at least a few 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 with SwiftUI, remember where it all started. You are building 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 the 215 machines of the past (the iPhone 15 is a multi-core 2.4 GHz CPU versus the single-core Mac SE 30 at 16 MHz).

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

Related posts