TweetFollow Us on Twitter

Command Line Porting

Volume Number: 16 (2000)
Issue Number: 7
Column Tag: Programming

Porting Command Line Interface Programs to the Macintosh

by Thurman Gillespy III, M.D.

How To Add a GUI to UNIX Programs with Minimal Changes to the CLI Source Code

Introduction

There is a wealth of source code available for the UNIX (and DOS) OS that uses a command line interface (CLI). Many of these programs have been ported to Windows but not to the Mac OS. This article describes a method for adding a graphic user interface (GUI) to a CLI program with minimal changes to the CLI source code.

Why Port?

Why bother to port a UNIX program to the Macintosh? Most likely, there is a specific program that meets the needs of your workgroup or interest area. However, an additional benefit I would like to emphasize is the opportunity to "brush up" on parts of the Mac OS API. Been too lazy to deal with Navigation Services? Never done a code resource or shared library? Ready to tackle a Carbon compliant app? Porting a CLI program is an excellent opportunity to hone your skills on a small project that you can later apply to your own work. And do you tire of hearing that there is "so much more software available for Windows" than for the Macintosh? Here is one small way you can alleviate the discrepancy!

The Golden Rule of Porting

I propose a simple "golden rule of porting" to guide your porting project.

ALTER THE SOURCE CODE AS LITTLE AS POSSIBLE

At first, this rule appears paradoxical. Doesn't a port from UNIX to Macintosh involve substantial code changes? As you will see, a CLI program can have a GUI added with minimal changes to the original source code. Furthermore, the rule is practical. It's not your code, so leave it alone! By not altering the CLI code, you make the porting project easier, you leave the burden of maintaining the CLI program with the original author and you make it easier to update the Macintosh version of the program when the CLI code is updated.

Anatomy of a CLI Program

Before getting started, let's briefly review the structure of a CLI program. There are several key terms to understand: the standard console, the standard input and output, and how the OS handles the command line arguments.

The Standard Console and the Standard Input and Output

The Standard C Library assumes an interactive input and output environment known as the standard console. The standard console is typically an interactive computer screen where users can enter information from the keyboard and view output on the screen. Many of the C library input and output functions can read from the standard input (input entered into the standard console, also known as stdin) and can write to the standard output (output displayed on the standard console screen, also known as stdout). The standard input and output are collectively referred to as the stdio. Functions that read and write to the stdio include printf, scanf, getchar, putchar and the C++ inserter and extractor operators << and >>. Of course, the C library can read and write to other data streams including files and network connections.

For example, printf writes to the standard output. This code snippet

printf("Hello, world!\n");

will print the words "Hello, world!" (without the quotes) and a newline on the standard console (see Figure 6, below).

The Command Line Interface

CLI programs have a main function with the following prototype:

int main(int argc, char *argv[]);

The compiled CLI program is invoked from the standard console by typing the program name and adding optional parameters after the name which are known as the command line arguments. The arguments are processed by the OS and are passed to the main function as the argc and argv parameters. argc is the number of parameters passed to the application (plus one) and argv is a vector that points to an array of C strings. By convention, argv is structured as follows:

Listing 1. The argv vector

argv[0]				==> program name
argv[1]				==> first parameter
argv[argc - 1]	==> last parameter
argv[argc]			==> NULL pointer

Every command line argument separated by white space is parsed into a separate argv string. For example, here is how the command line arguments for a program named midi2abc are translated into argc and argv.

Listing 2. Converting command line arguments to argc and argv

// invoking the program from the console
midi2abc -f mytune.midi -a 2

// argc and argv
argc = 5

argv[0] ==> "midi2abc"
argv[1] ==> "-f"
argv[2] ==> "mytune.midi"
argv[3] ==> "-a"
argv[4] ==> "2"
argv[5] ==> NULL

Project Overview

To port the CLI code with as few changes as possible, we want our project separated into two components: a Macintosh GUI "front end" program that passes the argc and argv parameters to a separate component that contains the CLI code. We accomplish this separation by compiling the CLI code as a code resource or shared library that is called by the Macintosh GUI program. In addition, we need glue code to handle the C standard library functions that read and write to the stdio. A diagram of the technique is shown in Figure 1.


Figure 1. Porting overview

This porting method will work best on UNIX programs with well defined input and output that are typically invoked from the command line.

Define the Objectives

After you've identified the code to be ported, sketch out your objectives before getting started. Here are some points you should consider.

  • What is the purpose of the ported program?
  • Who is the target audience?
  • What is the basic GUI interface?
  • What are the target platforms? (MacOS X, 68k, CFM68k, etc.)
  • What can I learn from this project?

General Porting Issues

There are some general issues to consider when porting from one computer platform to another, even if the OS is unchanged. Mac programmers had to deal with many of these issues during the conversion from 680x0 to PPC. Some of these porting issues include the following.

  • the size of int
  • bitfields (notoriously non-portable)
  • little endian vs. big endian byte order
  • data alignment
  • exact byte layout of structures
  • implicit type conversion
  • C/C++ language extensions
  • library functions not supported by MSL

For a thorough discussion on these issues, read the white paper published by Metrowerks on porting GNU C programs to CodeWarrior (Thompson 1999) which is included on the CodeWarrior CD. Correcting these general porting issues may require significant code changes. This article focuses on the changes necessary to add a Macintosh GUI to your command line program.

Steps in the Process

After your target code and project objectives have been identified, here is a sequence of steps you can take to port your CLI program.

  • Step 1: Do a minimal Mac port that preserves the command line interface. Understand this program and validate the output, if any. Identify the tools for developing the Mac GUI front end.
  • Step 2: Build a code resource or shared library from the CLI code.
  • Step 3: Build the Mac GUI front end.
  • Step 4: Put it all together.

An Example CLI Program Port

I recently ported a UNIX program that converts midi files to abc format. ABC format is a human readable music notation that is an increasingly popular method of exchanging music over the internet. I'll use this project to illustrate the techniques in this article.

ABC
<http://www.gre.ac.uk/~c.walshaw/abc/>

The target source code is midi2abc, written by James Allwright. The project has been ported to Windows, but only a bare bones Macintosh port had been done.

midi2abc
<http://perun.hscs.wmin.ac.uk/~jra/abcMIDI/>

Here were my project objectives (which changed slightly during the project).

  • Purpose: convert midi files to abc format.
  • Audience: owners of midi files who wish to convert them to abc notation. Many have older Macintoshes.
  • Interface: convert files from application, or by dropping files on application icon from desktop; convert one or more files, or folder full of files; converted output displayed in text window, written to a file, or both; floating dialog to easily set the conversion parameters.
  • Target platforms: Mac OS X (eventually), PPC and 68k Macintoshes with System 7 or higher; support Navigation Services if available.
  • Personal goals: learn NavServices API; improve preference file handling; learn code resource technology.

The name of the ported program is MacMIDI2abc (Figure 2).

MacMIDI2abc
<http://www.dr-razz.com/midi2abc/>


Figure 2. MacMIDI2abc application icon

The CLI Port Demo Program

I found it was useful to first port a very simple UNIX program that printed the command line arguments to stdout (Listing 3). The source code and CodeWarrior project files for this demo program are available on the MacTech web site, and I suggest you refer to these files when reviewing this article.

Listing 3. cl_echo.c

// a simple utility that prints argc and argv to stdout
// it also checks that argv[argc] == NULL
#include <stdlib.h>
#include <stdio.h>

main
int main(int argc, char *argv[])
{
	int	i;

	printf("argc: %d\n", argc);
	
	for (i =0; i < argc; i++)
		printf("argv[%d]: %s\n", i, argv[i]);
	
	if (argv[argc] == NULL)
		printf("argv[argc] == NULL\n");
	else
		printf("error: argv[argc} is not NULL!\n");

	exit(0);
}

Step 1: Macintosh Minimal CLI Port

Early in the project, I recommend a minimal Macintosh port that preserves the command line interface. The minimal CLI port will help you focus on the non-GUI aspects of the project, and helps prevent wasting time on the GUI code if the CLI code can't be ported.

CodeWarrior SIOUX Console

CodeWarrior provides the SIOUX (Simple Input and Output User eXchange) facility which creates a standard console window in a Macintosh GUI application. Here are the steps to create a SIOUX console project.

  • Create a new CodeWarrior project with the MacOS C/C++ stationary (Figure 3).


    Figure 3. Selecting MacOS C/C++ stationary

  • Set the project stationery for the appropriate Standard Console (Figure 4). I use Std C Console PPC for starters. CodeWarrior then sets up a project ready for a standard console application (Figure 5), and includes the correct SIOUX libraries.


    Figure 4. Selecting standard console stationery


    Figure 5. New project for standard console

  • Include the source files for the project. If there are multiple source files and you are uncertain which ones to include, begin with the file that contains the main function.
  • Add the ccommand function to the main statement just below the local variable declarations and include the console.h and SIOUX.h headers (Listing 4).

    Listing 4. Adding the ccommand function to main

    #include <SIOUX.h>
    #include <console.h>
    
    int main(int argc, char *argv[])
    {
    	// local variables defined here
    	// add this statement after local variables and
    	// _before_ anything else
    	argc = ccommand(&argv);
    
  • Compile the project. Use the compiler and linker errors to guide changes to the included header files and project source files. Common header files that need to be included are <stdlib.h>, <stdio.h> and <string.h>.
  • Handle any general porting issues such as byte order, size of int, bit fields, etc.

When completed, you have a Macintosh application ready to accept command line arguments just like the UNIX version. All stdio is routed to the SIOUX console window (Figure 6). At this point, make sure you have made as few changes to the source code as possible in order to support the minimal port.


Figure 6. The SIOUX console window

The ccommand function

The ccommand function displays a dialog with a text field for entering the command line arguments (Figure 7). There are also radio buttons for redirecting the standard input and output to a file or the standard console. When the dialog is closed, the argc and argv variables are correctly configured.


Figure 7. The ccommand dialog

Understanding the application

After compiling the SIOUX console application, be certain you understand all the command line arguments and their effect on any output. Are there certain combinations of arguments that are allowed or make sense? Also try to understand how users will typically use the program.

For my midi2abc port, I spent an afternoon with a musician friend who had midi files he needed to convert to abc format. We played with all the parameters, and tried to understand their affect on the abc output. Key concepts I learned from this session were that converting midi files was likely to be iterative, and that it was desirable to save the conversion parameters from one session to the next.

Validating the Output

If the CLI program produces an output, try to validate the output. In my midi2abc port, we viewed and played the abc output in BarFly, a Macintosh abc viewer and player. The abc files appeared to play correctly. A more rigorous session would have compared the Macintosh abc output with the output from the original UNIX program.

BarFly
<http://rbu01.ed-rbu.mrc.ac.uk/BarFlyStuff/BarFlyPage.html>

Identify Tools for the GUI

By this time, you should have selected the tools you will need to produce the Mac GUI front end. Most likely, this will be the application framework or development system you currently use. If your program processes files, make sure you have good support for opening or converting files and folders dropped on the application icon from the desktop.

For MacMIDI2abc, I chose ToolsPlus for the GUI development because I am most familiar with that environment and it met my requirements (Gillespy 1999).

ToolsPlus
<http://www.interlog.com/~wateredg/>

Step 2: Build the Code Resource

For the next step in our project, we need to compile the CLI code as a code resource or shared library without the SIOUX console. For the midi2abc port, I chose to use a code resource because code resources work with 68k Macintoshes without the Code Fragment Manager. The remainder of this article will only discuss code resources.

Create a new empty project. If you are supporting 68k and PPC code resources, create a second target and name each target appropriately. For an example of the correct project settings, see the CLI Port Demo project.

Tips on Dealing With Code Resources

There are two aspects to programming with code resources: the source code and the project settings. The source code is reasonably straightforward, but getting the project settings correct can be frustrating. After reading Joe Zobqiw's classic book on code resources and shared libraries (Zobqiw 1995), I found the private code resource examples included with Metrowerks CodeWarrior to be extremely helpful.

CodeWarrior Pro 5:CodeWarrior Examples:Mac OS Examples: 
  Code Resource Examples:Private Resources:

Here are a few problem areas to check carefully.

  • correctly exporting and linking with symbols.
  • getting the initialization, main and termination entry points correct.
  • correctly setting the resource header.
  • correctly setting the file name, resource name, resource type and resource ID.

Resource Initialization, Main and Termination Entry Points

In the PPC linker Target Settings Panel, there are fields for Initialization, Main and Termination entry points (Figure 8).


Figure 8. Entry Points (PPC linker panel setting)

The Main entry point is, of course, the main routine of the CLI code. If you enter the name of the initialization and termination routines in the entry point fields, these routines will be called automatically when the code resource is loaded and unloaded.

However, the initialization routine has important work to perform since we don't want to change the CLI code main routine. The initialization routine must import any symbols from the GUI application that the resource requires. If the symbol import fails, calling the code resource will also fail. Therefore, for MacMIDI2abc I chose to manually call the initialization routine from the GUI code, and check an error result for failure. If you manually call the initialization or termination routine, then leave the respective fields in the settings panel blank (Figure 8).

Handling Standard Input and Output without the SIOUX Console

There is a common misconception that you can't use Standard C library functions that use the stdio in a code resource. However, supporting the C library stdio without the SIOUX console is easy in either a Macintosh application or code resource. Instead of the SIOUX libraries, add a copy of the Metrowerks file console.stubs.c to the code resource (or application) project. Then remove the ccommand function and included console.h and SIOUX.h files from the CLI code if you added them for the basic CLI port, and don't include the MSL SIOUX library.

CodeWarrior Pro 5:Metrowerks CodeWarrior:MSL:MSL_C:
	MSL_MacOS:Src:console.stubs.c

The console.stubs.c file provides four stub functions that allow you to intercept calls to the stdio. You can use these functions to create your own custom standard console, or to route the stdio as needed for your project.

Listing 5. Console stubs prototypes (console.stubs.h)

short	InstallConsole(short fd);
void		RemoveConsole(void);
long		WriteCharsToConsole(char *buffer, long n);
long		ReadCharsFromConsole(char *buffer, long n);

Before any reading or writing to the stdio, InstallConsole is called. Then any function that writes to the standard console (e.g., printf) calls WriteCharsToConsole, and any function that reads from the standard console (e.g., scanf) calls ReadCharsFromConsole. Finally, RemoveConsole is called. If you are not creating a custom standard console, the InstallConsole and RemoveConsole functions can be left empty.

After including a local copy of console.stubs.c in the code resource project, we need to connect the stdio in the code resource to the GUI application. Here are the steps to accomplish this task:

  • in the Mac GUI program, create one or more functions that will route the stdio
  • in the code resource, create function pointers that match the GUI functions
  • link the pointers to the GUI functions when the resource is initialized
  • call the function pointers in the WriteCharsToConsole and ReadCharsFromConsole routines

In midi2abc, the abc output is written to the standard output via multiple printf calls. In the Mac GUI application, I wrote a function - CopyBytesToBuffer - that copies n bytes to a buffer. The CopyBytesToBuffer function keeps track of how many bytes are in the buffer and prevents buffer overflow. From the buffer, the abc output can be redirected to a text window, a file or both. See the CLI Port Demo sample code for an example of the CopyBytesToBuffer routine.

long CopyBytesToBuffer(char *bytes, long numBytes);

In the code resource project, I created a function pointer to link with the CopyBytesToBuffer routine. Note that the prototypes of the CopyBytesToBuffer function and the function pointer must match.

typedef long				(*CopyBytesProcPtr)(char *, long);
CopyBytesProcPtr		CopyBytesProc = NULL;

Then FindSymbol is used to link the function pointer with the imported CopyBytesToBuffer symbol when the code resource is initialized. The complete details of symbol linking are described below.

FindSymbol(connID, "\pCopyBytesToBuffer",
	(Ptr *)&CopyBytesProc, &symClass);

Finally, a call to CopyBytesProc within the WriteCharsToConsole routine will redirect output written to the standard output to the buffer in the GUI application (Listing 6).

Listing 6. Redirecting the standard output (console.stubs.c)

typedef long (*CopyBytesProcPtr)(char *, long);
CopyBytesProcPtr	CopyBytesProc = NULL;

WriteCharsToConsole

long WriteCharsToConsole(char *buffer, long n)
{
	long	bytesWritten;
	bytesWritten = (*CopyBytesProc)(buffer, n);
	return (bytesWritten);
}

When does output written to the standard output actually show up in WriteCharsToConsole? The details are implementation specific, but the output is usually flushed at every new line or when the standard output buffer is full. Normally, the exit routine will flush the stdio and guarantee all input and output is properly handled at program termination. However, if we have overridden the exit function (see below), some bytes might be left in the stdio buffers. I handle this possibility by calling fflush in the program termination routine (Listing 12).

Linking with the Mac GUI Application

For PPC and CFM68k code resources, you want to export any symbols that the GUI needs to call, and you must import any symbols from the GUI that the code resource needs. To export the functions from your code resource that will be called by the GUI program, define routine descriptors for the routines. Here is how to define a routine descriptor named mainRD for the code resource main routine.

Listing 7. Defining a routine descriptor

for the main routine

int main(int argc, char *argv[]);

enum {
	uppMainEntryProcInfo = kCStackBased
		 | RESULT_SIZE(SIZE_CODE(sizeof(int)))
		 | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(int)))
		 | STACK_ROUTINE_PARAMETER(2, SIZE_CODE(
				sizeof(char **)))
};

RoutineDescriptor mainRD =
	BUILD_ROUTINE_DESCRIPTOR(uppMainEntryProcInfo, main);

Then export the mainRD symbol so it can be linked from the GUI program. The easiest method of exporting symbols is to use the Export linker function. Select the Use ".exp" file option from the Export drop menu in the PPC PEF Target Setting panel (Figure 9).


Figure 9. Export symbol setting

The next time your code resource is built, the compiler will generate an '.exp' file that contains ALL the symbols in your code resource. Delete all the exported symbols and add only the routine descriptor name(s) you want exported. For the example above, the only entry in the .exp file would be mainRD. Then include the .exp file in the code resource project.

Don't forget to edit the .exp file! Otherwise, your resource will suffer enormous code bloat, and you will likely encounter linker errors. A mistake I made several times was to add the name of the routine in the .exp file instead of the routine descriptor. And don't forget to add the .exp file to the code resource project, or the FindSymbol call in the GUI application will fail. To import symbols from the GUI program, you use the Code Fragment Manager (CFM) within the code resource. It's a three step process.

  • use GetProcessInformation to get the FSSpec of the GUI program.
  • call GetDiskFragment to get a connection ID to the application.
  • call FindSymbol to attach the imported symbol with the correct function pointer.

In order to minimize changes to the CLI code, this code should be in the code resource initialization routine. Write the initialization (and termination) routines in a separate file, and include the file in the code resource project. I included these routines in a file named CR.glue.c.

Listing 8. The code resource initialization routine (CR.glue.c)

typedef long				(*CopyBytesProcPtr)(char *, long);
CopyBytesProcPtr		CopyBytesProc = NULL;
MyInitCR
initialization routine for PPC and CFM68k code resource
illustrates how to link the CopyBytesProc function pointer with the
	CopyBytesToBuffer routine in the GUI code

OSErr MyInitCR(void)
{
	ProcessSerialNumber	PSN;
	FSSpec								myFSSpec;
	Str63								name;
	ProcessInfoRec				infoRec;
	CFragConnectionID 		connID;
	Str255 							errName;
	CFragSymbolClass 		symClass;
	Ptr									mainAddr;
	OSErr 								err;
	
	// use GetProcessInformation to get the FSSpec of our app
	infoRec.processInfoLength = sizeof(ProcessInfoRec);
	infoRec.processName = name;
	infoRec.processAppSpec = &myFSSpec;
	
	PSN.highLongOfPSN = 0;
	PSN.lowLongOfPSN = kCurrentProcess;
	
	err = GetProcessInformation( &PSN, &infoRec);
	if (err != noErr)
		return err;

	// GetDiskFragment will return the connID number needed for subsequent
	// calls to FindSymbol.
	// It will also return the address of main in the native App (which we ignore)
	err = GetDiskFragment(infoRec.processAppSpec, 0L, 0L,
							infoRec.processName, kLoadCFrag, &connID,
							(Ptr*)&mainAddr, errName);
	if (err != noErr)
		return err;
	
	err = FindSymbol(connID, "\pCopyBytesToBuffer",
		(Ptr *)&CopyBytesProc, &symClass);
	
	return err;
}

If MyInitCR succeeds, then the CopyBytesProc function pointer redirects output written to the standard output to the GUI application (Listing 6).

Dealing with Memory Leaks

CLI programs call malloc (or new) to allocate memory. The default malloc implementation in the MSL allocates a chunk of memory at the first memory request (generally 64k), and then suballocates the buffer as needed. In previous versions of MSL, this buffer was not freed when all of the suballocations were freed, which resulted in a memory leak for code resources and shared libraries. In CodeWarrior Pro 5, the problem is fixed: when a malloc buffer is no longer in use, it is freed and returned to the operating system.

However, your ported code may leak memory, i.e., not all allocated memory is freed before the program exits. Rather than attempting to fix the memory leak in the CLI code (remember the golden rule of porting), you can manually free the malloc buffer after the CLI code is finished. Metrowerks provides a somewhat dated file, _GetmemWithFree.c, that tracks the malloc buffer allocations and allows you to manually free the buffers with the FreeAllMallocMemory function.

CodeWarrior Examples: MacOS Examples: Code Resource
	Examples: -CW Code Resource Info: Memory Problems
	in CRs: FreeAllMallocMemory:_GetmemWithFree.c

However, there are two changes you have to make in this file for this facility to work the current version of MSL. The function _Getmem needs to be renamed __sys_alloc, and the pool_alloc.h header must be included (Listing 9).

Listing 9. Changes to _GetmemWithFree.c

#include <pool_alloc.h>	// add this header

void *__sys_alloc(size_t size) // rename _Getmem to __sys_alloc
{
	void *p;
	size_t isize = size;
	
	if (isize <= 0 || !(p = NewPtr(isize)))
		return 0L;
	PutInMallocList((Ptr)p);
	return p;
}

After changing your local copy of _GetmemWithFree.c, add the file to your code resource project. In the code resource termination routine call FreeAllMallocMemory to free all the malloc buffers. I used this technique to handle a memory leak in midi2abc.

Handling exit()

ANSI C programs commonly use the exit function to immediately abort a program if an unrecoverable error occurs. Unfortunately, exit aborts both the code resource and our Macintosh GUI application!

You can handle this situation by overriding the exit routine and using the setjmp and longjmp C facility. setjmp and longjmp provide a "non-local goto" that permits returning to a defined point set by setjmp from anywhere else in the application.

To use setjmp and longjmp, first define a global jmp_buf variable in the file that contains the main function. Then call setjmp early in the main routine (Listing 10).

Listing 10. Using setjmp and longjmp

#include <setjmp.h>

jmp_buf	myjumpbuf;

int main(int argc, char *argv[])
{
	// local variables declared here
	volatile int	val;
	
	if (val = setjmp(myjumpbuf)) {
		return (val - 1);
	}
	
	// typical error handling
	p = malloc(nbytes);
	if (p == NULL) {
		fprintf(stderr, "malloc failed!\n");
		exit(1); // abnormal termination
	}
	...

	exit(0); // normal termination
}

The first time setjmp is called, it initializes the jmp_buf variable and returns 0. Subsequent calls to longjmp returns program control to setjmp, which now returns a non-zero value. Since automatic variables are not guaranteed to be valid when program control returns to setjmp, I use the volatile qualifier with the val variable.

Next, override (redefine) the exit function and call longjmp within exit. I wrote my new exit routine in a file named exit.glue.c, and included this file in the code resource project.

Listing 11. exit.glue.c

// see Listing 10 for initializing jmp_buf
#include <stdlib.h>

exit
be certain setjmp is called before longjmp

void exit(int status)
{
	extern jmp_buf myjumpbuf;
	
	longjmp(myjumpbuf, status + 1);
	
}

When the project is compiled, the linker will complain of 
  duplicate definitions of exit, which you can ignore.


Link Warning : ignored 'exit' (code) in MSL C.PPC.Lib
Previously defined in exit.glue.c

By convention, exit is called with a 0 for a normal termination, and a 1 for an abnormal program termination. However, longjmp cannot be called with a 0 for the status parameter. Thus, I add 1 to the status parameter and then subtract 1 from the return value of setjmp. These steps ensure that exit(0) returns 0 and exit(1) return 1 when the CLI main routine exits (Listing 10 and 11). There are many hazards to using setjmp and longjmp. Read the CodeWarrior documentation for further details.

The Termination Routine

In midi2abc, I use the code resource termination routine to manually clear the malloc buffer (since there is a memory leak in the midi2abc code), and to flush the stdio buffers (since I override the exit function).

Listing 12. The code resource termination routine (CR.glue.c)

MyTerminateCR

void MyTerminateCR(void)
{
	// clear the stdio buffers
	fflush(NULL);
	// manually purge the malloc buffer
	FreeAllMallocMemory();
}

68k Code Resources and Global Data

In the 68k runtime architecture, global data is referenced from the A5 register. Code resources must use another method of accessing their global data. CodeWarrior uses the A4 register, and requires the following changes to your project. First, the appropriate A4 libraries must be used in the code resource project. Second, the EnterCodeResource and ExitCodeResource macros must be used to properly set up the A4 register (see Listing 13, below).

Supporting 68k Macintoshes without the CFM

The Code Fragment Manager (CFM) method of accessing symbols described above will also work with 68k Macs if the CFM is present. CFM for 68k Macintoshes (CFM68k) is available on Mac OS 7.1 and higher.

If you want to support 68k Macintoshes without the CFM, you have to use another method of importing symbols from the GUI application. There are a number of options available. We could add one or more parameters to the main entry, and use them to pass the GUI symbols to the 68k code resource. We could also use a "parameter block" similar to a PhotoShop plugin.

For MacMIDI2abc, I chose to overload the argc and argv parameters (Listing 13). Since argc cannot be less than 1, I use argc values of zero or less as flags. An argc value of 0 is a signal to call the code resource termination routine (Listing 12), and a negative value is a signal that the argv parameter contains the address of a routine being passed to the resource.

Listing 13. 68k code resource without CFM

// CLI code
int main(int argc, char *argv[])
{
	// local variables here
	EnterCodeResource();
	if (argc <= 0) {
		MyCR68k(argc, argv);
		ExitCodeResource();
		return 0;
	}
	...
	
	ExitCodeResource();
	return 0;
}

// CR.glue.c
MyCR68k

how to initialize and terminate a 68k code 
  resource without CFM

void MyCR68k(int argc, void *argv)
{
	switch (argc) {
		case 0:
			// call the termination routine (Listing 12)
			MyTerminateCR();
			break;
		case -1:
			// link CopyBytesToBuffer address with 
			//  the function pointer
			CopyBytesProc = (CopyBytesProc)argv;
			break;
	}
}

Step 3: Build the GUI Program

The GUI "front end" program is responsible for handling window, menu and file operations, and for converting user interface elements into argc and argv parameters that are passed to the code resource. This is the creative part of the project that gives you an opportunity to demonstrate your user interface and program design skills.

Converting command line arguments to a GUI

Typically, you will design a dialog that has button, menus, text fields and other user interface elements that reflect the original command line parameters. As an example, here is the "usage" output from the midi2abc program (reformatted slightly).

Listing 14. midi2abc usage output

midi2abc version 2.0.8
	usage :
midi2abc <options>
	-a		<beats in anacrusis>
	-xa	extract anacrusis from file (find first strong note)
	-ga	guess anacrusis (minimize ties across bars)
	-m		<time signature>
	-xm	extract time signature from file
	-xl	extract absolute note lengths from file
	-b		<bars wanted in output>
	-Q		<tempo in quarter-notes per minute>
	-k		<key signature> -6 to 6 sharps
	-c		<channel>
	-f		<input file>
Only the -f option is compulsory. Use only one of -xl,
-b and -Q. If none of these is present, the program
attempts to guess a suitable note length.

From this list and my knowledge of the midi2abc program, I created 5 user interface groups and ranked them in order of importance.

  • Time Signature
  • Key Signature
  • Channel
  • Tempo
  • Anacrusis

From these groups, I designed a single floating dialog that allowed the user to select the conversion parameters (Figure 10). The dialog also allowed the conversion parameters to be saved to disk in the Preferences Folder.


Figure 10. MacMIDI2abc Settings dialog

Handling argc and argv

For MacMIDI2abc, the most complicated portion of the GUI code is the conversion of the Settings dialog state into argc and argv parameters to be passed to the code resource. Here is an overview of the data structures and routines that I developed for this task.

  • a command data structure (CMD_t) that contains the argc and argv parameters, and related support functions
  • a custom settings resource (setg) and matching data structure (SETTINGS_DLG_t) that reflects the state of the Settings dialog
  • a routine for converting the settings data structure (SETTINGS_DLG_t) into the proper argc and argv parameters within the CMD_t structure

The first data structure to consider is the CMD_t structure, which contains the argc and argv parameters.

Listing 15. The CMD_t structure

enum {
	kNUM_ARGS		= 20,		// max # of args
	kARG_STRLEN	= 20,		// argument string length
	kFNAME_LEN		= 258		// file name string length
};

// argument vector (array of * char)
typedef char	*ARGV__t[kNUM_ARGS];
// array of C strings for args (except file name)
typedef char	ARGLIST__t[kNUM_ARGS][kARG_STRLEN];
// file name is special case (allow for MacOS  X)
typedef char	FNAME__t[kFNAME_LEN];

// command line (argc, argv)
typedef struct cmd_s {
	short				argc;			// number of arguments
	ARGV__t			argv;			// vector to argument list
	ARGLIST__t		arglist;		// array of C strings for args
	FNAME__t			fname;			// file name
} CMD__t, *CMD__p_t, *CMD__h;

In MacMIDI2abc I use data hiding for the significant data structures. The double underscore notation (CMD__t) denotes the hidden type, while the single underscore notation (CMD_t) denotes the public, incomplete definition. For simplicity, I will use the public definitions of the structures in the text of this article.

At program startup, I allocate and initialize the CMD_t structure, and set argv[0] to the program name (Listing 16).

Listing 16. Creating a new argument list (CMD_t) data structure

NewArgList
allocate memory and initialize new CMD_t
application responsible for storing result
accepts: address of CMD_t pointer (must be NULL)
	name of program
returns: error code, address of new CMD_t

OSErr NewArgList(CMD__p_t *cmd, char *prog_name)
{
	long		i, len;
	
	if (*cmd != NULL)
		return -1;
	
	*cmd = (CMD__p_t)NewPtr(sizeof(CMD__t));
	if (*cmd == NULL)
		return iMemFullErr;

	(*cmd)->argc = 1;
	
	/* set and clear the argv vector */
	for (i = 0; i < kNUM_ARGS; i++) {
		(*cmd)->arglist[i][0] = '\0';	/* empty string */
		(*cmd)->argv[i] = &((*cmd)->arglist[i][0]);
	}
	
	/* clear the file name */
	(*cmd)->fname[0] = '\0';
	
	/* copy program name to argv[0] */
	if (prog_name == NULL)
		return errInvalNewArgParam;

	len = (long)strlen(prog_name);
	if (len == 0 || len >= kARG_STRLEN)
		return -1;

	strcpy((*cmd)->argv[0], prog_name);
	
	return noErr;
}

The following support functions set and get the argc and argv parameters and reset the CMD_t structure.

Listing 17. CMD_t support functions (Args.c)

SetNthArg
copy string str to argv[n] (argv[n] <== str)
n is _zero based_ count
not allowed to set vector 0
returns: true - vector set, false - vector not set

short SetNthArg(CMD__p_t cmd, char *str, short n)
{
	unsigned long	len = 0;
	
	if (cmd == NULL || str == NULL || n < 1)
		return false;
	
	len = strlen(str);
	if (len == 0 || len >= kARG_STRLEN)
		return false;
	
	strcpy(cmd->argv[n], str);
	
	return true;

}

SetFinalArg
the last argv must be a null pointer (argv[argc] <== NULL)
note: this trashes the argv pointer, which is restored in ClearArgList

void SetFinalArg(CMD__p_t cmd, short n)
{
	if (n <= 0)
		return;
	
	cmd->argv[n] = NULL;
}

GetArgVector
return a pointer to the argv vector (char *argv[])

ARGV_t GetArgVector(CMD__p_t cmd)
{
	return cmd->argv;
}

SetArgNum
set argc to n (number of arguments + 1)

void SetArgNum(CMD__p_t cmd, short n)
{	
	cmd->argc = n;
}

GetArgNum
short GetArgNum(CMD__p_t cmd)
{
		return cmd->argc;
}

SetArgFileName
file name is special case since it might be much longer 
than other args copy string filename to 
argv[n] (argv[n] <== filename)
returns: true - filename copied; false - not copied

short SetArgFileName(CMD__p_t cmd, char *filename, short n)
{
	unsigned long	len = 0;
	
	if (cmd == NULL || filename == NULL ||
			n < 1 || n >= kNUM_ARGS)
		return false;

	/* if string is empty or too long, signal an error */
	len = strlen(filename);
	if (len == 0 || len >= kFNAME_LEN)
		return false;
	/* copy the string */
	strcpy(cmd->fname, filename);
	/* set the vector */
	cmd->argv[n] = cmd->fname;
	
	return true;
}

ClearArgList
reset CMD_t to initial state
must call before setting new argc, argv[n]

void ResetArgList(CMD__p_t cmd)
{
	long		i;
		
	cmd->argc = 1;
	
	/* reset and clear all the vectors > argv[0] */
	for (i = 1; i < kNUM_ARGS; i++) {
		cmd->arglist[i][0] = '\0';
		cmd->argv[i] = &(cmd->arglist[i][0]);
	}
	
	/* clear file name */
	cmd->fname[0] = '\0';
	
}

However, in MacMIDI2abc I don't set CMD_t directly from the dialog. Instead, I maintain a separate "settings" structure (SETTINGS_DLG_t) that mirrors the state of the user interface, and set CMD_t from this structure. Note the correlation of the settings structure with the Settings dialog (Figure 10).

Listing 18. The settings structure (SETTINGS_DLG_t)

typedef short					BOOL_t;
typedef FourCharCode		MAGIC_t;
enum { SETG_MAGIC = 'setg' };

#pragma options align=mac68k

typedef struct setting_dlg_s {
	MAGIC_t	magic1;	// 'setg'
	short		version;
	// Time Signature
	BOOL_t		time;
	short		time_val;
	BOOL_t		extract_time;
	BOOL_t		default_time;
	// Key Signature
	short		key_val;
	BOOL_t		default_key;
	// Channel
	short		channel_val;
	// Tempo
	BOOL_t		qnotes_min;
	short		qnotes_min_val;
	BOOL_t		extract_note_len;
	BOOL_t		bars_output;
	long			bars_val;
	BOOL_t		default_tempo;
	// Anacrusis
	BOOL_t		beats_anacru;
	short		anacru_val;
	BOOL_t		extract_anacru;
	BOOL_t		guess_anacru;
	BOOL_t		default_anacru;
	MAGIC_t	magic2; // 'setg'
} SETTINGS_DLG_t, *SETTINGS_DLG_p_t, **SETTINGS_DLG_h;

#pragma options align=reset

The "magic" values at the start and end of the structure are for debugging and resource data integrity checking. The routine that sets the SETTINGS_DLG_t structure based on the Settings dialog is not listed.

The SETTINGS_DLG_t structure is not dynamically allocated. Instead, I use a custom resource that matches SETTINGS_DLG_t. The resource is loaded from a preference file and typecast to a settings handle (SETTINGS_DLG_h). I used Resorcerer to create a TMPL resource (Figure 11) that matches SETTINGS_DLG_t, and then used the TMPL to create a setg resource with appropriate default settings (Figure 12). See the MacTech article on preference files (Sydow 1999) for more details on this technique.


Figure 11. Resorcerer TMPL for setg resource


Figure 12. Default setg resource (Resorcerer)

Note that the BOOL resource data type is a short integer, and that FALSE is equal to 0 and TRUE is equal to 0x0100 (256) (Figure 13).


Figure 13. Setting the BOOL resource data type in Resorcerer.

To complete the cycle, here is how I set the argc and argv parameters (in the CMD_t structure) from SETTINGS_DLG_t (Listing 19). Note how the layout of the function follows the layout of the Settings dialog (Figure 11) and SETTINGS_DLG_t (Listing 18).

Listing 19. Creating an argument list from the settings structure

#define BOOL_TRUE		0x0100
#define BOOL_FALSE		0
#define BOOL_ON			BOOL_TRUE
#define BOOL_OFF			BOOL_FALSE

ArgsFromSettingsDlg
set the CMD_t structure from the SETTINGS_DLG_t structure
returns: true - valid settings; false - invalid settings

BOOL_t ArgsFromSettingsDlg(CMD_t cmd)
{
	SETTINGS_DLG_h	set_h = NULL;
	Str15		numStr = {0},
					menuStr = {0};
	char			arg[20];
	long			num = -1;
	short		index = 0,
					argc = 1;
	
	// GetSettingsDataH returns a handle to the global settings data
	set_h = (SETTINGS_DLG_h)GetSettingsDataH();
	
	if (cmd == NULL || set_h == NULL)
		return false;

	// make the settings dialog the active port (ToolsPlus API)
	CurrentWindow(SettingsWindow);
	
	// for MacMIDI2abc, first two arguments are "-f" and file name
	SetNthArg(cmd, "-f", argc++);
	// use a file name placeholder for now
	SetArgFileName(cmd, "<no file name>", argc++);

	// Time Signature
	if ((**set_h).time == BOOL_ON) {
		// GetPopUpString is from the ToolsPlus API
		GetPopUpString(POP_Time, (**set_h).time_val, menuStr);
		CopyPascalStringToC(menuStr, arg);
		(void)SetNthArg(cmd, "-m", argc++);
		(void)SetNthArg(cmd, arg, argc++);
		
	}
	else if ( (**set_h).extract_time == BOOL_ON ) {
		(void)SetNthArg(cmd, "-xm", argc++);
	}
		
	// Key Signature
	if ( (**set_h).default_key == BOOL_OFF )
		(void)SetNthArg(cmd, "-k", argc++);
		// convert popup menu index to integer from -6...6
		NumToString( (**set_h).key_val - 7, numStr );
		CopyPascalStringToC(numStr, arg);
		(void)SetNthArg(cmd, arg, argc++);
		
	}
		
	// Channel
	if ( (**set_h).channel_val > 1) {
		(void)SetNthArg(cmd, "-c", argc++);
		// convert popup menu index to channel #
		NumToString( (**set_h).channel_val - 1, numStr );
		CopyPascalStringToC(numStr, arg);
		(void)SetNthArg(cmd, arg, argc++);
		
	}
	
	// Tempo
	if ( (**set_h).qnotes_min == BOOL_ON ) {
		(void)SetNthArg(cmd, "-Q", argc++);
		NumToString( (**set_h).qnotes_min_val, numStr );
		CopyPascalStringToC(numStr, arg);
		(void)SetNthArg(cmd, arg, argc++);
		
	} else if ( (**set_h).extract_note_len == BOOL_ON ) {
		(void)SetNthArg(cmd, "-xl", argc++);
	} else if ( (**set_h).bars_output == BOOL_ON ) {
		(void)SetNthArg(cmd, "-b", argc++);
		NumToString( (**set_h).bars_val, numStr );
		CopyPascalStringToC(numStr, arg);
		(void)SetNthArg(cmd, arg, argc++);
		
	}
	
	// Anacrusis
	if ( (**set_h).beats_anacru == BOOL_ON ) {
		(void)SetNthArg(cmd, "-a", argc++);
		// convert popup index to anacrusis value
		NumToString( (**set_h).anacru_val, numStr );
		CopyPascalStringToC(numStr, arg);
		(void)SetNthArg(cmd, arg, argc++);
		
	} else if ( (**set_h).extract_anacru == BOOL_ON ) {
		(void)SetNthArg(cmd, "-xa", argc++);
	} else if ( (**set_h).guess_anacru == BOOL_ON ) {
		(void)SetNthArg(cmd, "-ga", argc++);
	}
	
	// important: set argv[argc] to NULL
	SetFinalArg(cmd, argc);
	
	// how many parameters were set
	(void)SetArgNum(cmd, (short)(argc));
	
	CurrentWindowReset();	// ToolsPlus API
	
	return true;
}

Exporting Symbols

We have to export the GUI program symbols that the code resource will import from the resource initialization routine. Just as we did for the code resource project, set the Export option (PPC PEF Target Setting panel) to Use ".exp" file (Figure 9). The next time your GUI program is built, the IDE will create an '.exp' file with all the symbols in your program. Then, remove all the symbols except the ones you want to export and add the '.exp' file to the GUI project.

Calling the Code Resource (CFM)

To get the CLI code to perform its task, we must load the code resource then call main with the argc and argv parameters. First, we have to create UniversalProcPtrs for the CLI routines. In this example, we wish to link with main and the initialization routine (MyInitCR, Listing 8) in the code resource.

Listing 20. Creating UniversalProcPtrs for imported symbols

enum {
	uppMainEntryProcInfo = kCStackBased
		| RESULT_SIZE(SIZE_CODE(sizeof(int)))
		| STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(int)))
		| STACK_ROUTINE_PARAMETER(2, SIZE_CODE(
			sizeof(char **)))
};

#if GENERATINGCFM
typedef UniversalProcPtr MainEntryProcUPP;

#define CallMainEntryProc(userRoutine, argc, argv)\
	CallUniversalProc((UniversalProcPtr)(userRoutine),\
	uppMainEntryProcInfo, (argc), (argv))

#else

typedef MainEntryProcPtr	MainEntryProcUPP;

#define CallMainEntryProc(userRoutine, argc, argv)\
		(*(userRoutine))((argc), (argv))
#endif

enum {
	uppInitProcInfo = kCStackBased
		 | RESULT_SIZE(SIZE_CODE(sizeof(OSErr)))
};

#if GENERATINGCFM
typedef UniversalProcPtr InitProcUPP;

#define CallInitProc(userRoutine, param)\
		CallUniversalProc((UniversalProcPtr)(userRoutine), 
		  uppInitProcInfo, param )
#else

typedef InitProcPtr	InitProcUPP;

#define CallInitProc(userRoutine, param)\
		(*(userRoutine))((param))
#endif

To call the code resource with the Code Fragment Manager, get a handle to the resource and use GetMemFragment to get a connection to the resource. Then call FindSymbol to link the desired symbols with the appropriate function pointers. Finally, call the actual routine with the UniversalProcPtr listed above (Listing 20). Here is the basic outline.

  • handle = Get1Resource();
  • GetMemFragment(*handle, ..., &connID, ...);
  • FindSymbol(connID, "\pmyCRMain", ...);
  • call the initialization routine or have it called automatically
  • MainEntryProc(myCrMain, argc, argv);
  • call the termination routine (if any) or have it called automatically

And here is the full code.

Listing 21. Calling the Code Resource (CFM)

// see Listing 20 for UniversalProcPtrs
// function pointers for main and initialization routine
typedef int		(*MainEntryProcPtr)(int, char *[]);
typedef long		(*InitProcPtr)(void);

MainEntryProcUPP	myCrMain = NULL;
InitProcUPP			myInitCR = NULL;

CallCodeResPPC
call PPC code resource from PPC app
accepts: argc, argv
returns: error code, result (0 or 1) from code resource main

int CallCodeResPPC(	int argc, char *argv[],
										OSErr	*err)
{
	Str255							errName;
	Str63							CRName;
	int								result = 1;
	CfragSymbolClass		theSymClass = 0;
	CfragConnectionID		connID = 0;
	MainEntryProcPtr		mainAddr = NULL; // not used
	Handle							handle = NULL;

	*err = -1;
	
	// load the resource
	handle = Get1Resource(kCodeResType, kCodeResID);
	if (handle == NULL) {
		*err = -1;
		return result;
	}
	
	HLock(handle);
	// establish a connection
	*err = GetMemFragment(	*handle, 0L, CRName,
									kPrivateCFragCopy,&connID,
									(Ptr*)&mainAddr, errName );
					 	
	if (*err == noErr) {
		// link with the main routine descriptor
		*err = FindSymbol(connID, "\pmainRD",
										(Ptr *)&myCrMain,
										&theSymClass);	
		if (*err != noErr || myCrMain == NULL)
			goto Fin;
		// link with the MyInitCR routine descriptor
		*err = FindSymbol(connID, "\pInitCRRD",
				(Ptr *)&myInitCR, &theSymClass);	
		if (*err != noErr || myInitCR == NULL)
			goto Fin;
		// manually initialize the resource, abort if err
		*err = (short)CallInitProc(myInitCR);
		if (*err != noErr) {
			goto Fin;
		}
		// call the resource with argc, argv
		result = CallMainEntryProc(myCrMain, argc, argv);
		// close the connection to the resource
		*err = CloseConnection(&connID);
	}
	
	Fin:
	HUnlock(handle);
	ReleaseResource(handle);

	return result;
}

Note that we are linking with the routine descriptor symbols defined in our code resource, not the actual routine names (Listing 7). Also note that argc and argv have already been extracted from the CMD_t structure.

Calling the Code Resource (68k without CFM)

In MacMIDI2abc, I decided to call the 68k resource from the 68k code without the CFM, which allowed me to easily support older Macintoshes (Listing 22). Also review Listing 12, which demonstrated how the 68k code resource receives the routine address through argc and argv.

Listing 22. Calling the code resource (68k without CFM)

CallCodeRes68k
int CallCodeRes68k(int argc, char *argv[], OSErr *err)
{
	Handle	handle = NULL;
	int		result = 1;

	*err = -1;
	
	// load the resource
	handle = Get1Resource(kCodeResType, kCodeResID68k);
	if (handle == NULL) {
		*err = -1;
		return result;
	}
		
	HLock(handle);
	
	myCrMain = (MainEntryProcUPP)(*handle);

	// initialize the resource - pass the CopyBytesToBuffer address
	(void)myCrMain(-1, (void *)CopyBytesToBuffer);
	
	// now call the resource with argc, argv
	result = myCrMain(argc, argv);
	
	// call termination routine by setting argc to 0
	(void)myCrMain(0, NULL);
	HUnlock(handle);
	ReleaseResource(handle);
	
	// no error returned from 68k resource
	*err = noErr;
	
	return result;
}

Standard C File Operations

The Standard C file operations (fopen, fwrite, fread) are well supported by the MSL C libraries. However, the fopen operation takes the name of a file, not a FSSpec, and opens files in the current working directory unless a full pathname is specified for the file name. The MoreFiles library provides two useful functions for setting and restoring the current working volume and directory: SetDefault and RestoreDefault. These calls should bracket the Standard C file operations.

In MacMIDI2abc, SetDefault and RestoreDefault bracket the CallCodeResPPC and CallCodeRes68k functions (Listing 23).

Listing 23. Using SetDefault and RestoreDefault

// fspec is the file FSSpec
// get argc and argv from CMD_t
// C library file routines are used inside the code resource

err = SetDefault(spec->vRefNum, spec->parID, &oldVRefNum,
					&oldDirID);
if ( err == noErr )
{
#if powerc
	result = CallCodeResPPC(argc, argv, &err);
#else
	result = CallCodeRes68k(argc, argv, &err);
#endif

	RestoreDefault(oldVRefNum, oldDirID);
}

Read the MoreFilesExtras.h file for more details about the SetDefault and RestoreDefault routines.

MoreFiles
<http://developer.apple.com/samplecode/Sample_Code/Files/MoreFiles.htm>

Load Resource Once or Repeatedly?

In Listings 21 and 22 the code resource is loaded and unloaded each time the CLI code is executed. Why not just load the resource, move the handle into high memory and lock it when the GUI program is initialized? Loading the code resource only once can create problems if the CLI code is not multiply reentrant. Static and global variables will retain their value between invocations, and may create unpredictable results. In MacMIDI2abc, I found several errors in CLI code execution that were eliminated when I reloaded the code resource each time. Keeping in mind the golden rule of porting, I decided reloading the resource was a better solution than rewriting the CLI code.

Step 4: Putting It All Together

To build your final application, add the code resource(s) to the GUI project. Instead of dealing with fat resources (which contain both PPC and 68k code), I used different IDs for the PPC and 68k code resources, and made sure the PPC code called the PPC resource, and that the 68k code called the 68k resource. Then be sure to register a unique creator type for your application, and give it a proper application icon (Figure 2).

Debugging Tips

During program development, I found several debug aids were invaluable. For the GUI program, make a "show arguments" utility that displays the argc and argv parameters in a dialog. It is helpful to ensure the user interface is setting the command line parameters correctly before connecting the GUI program with the code resource.


Figure 14. Argument list dialog

A second debugging utility I found invaluable was the cl_echo program mentioned at the beginning of the article (Listing 3). Compiling this simple utility as a code resource and properly linking it with your GUI front end will pave the way for your more complex CLI port.

One area that frequently trips up less experienced developers is debugging a code resource. Here are the steps to do it correctly.

  • create the code resource with the Enable Debugger menu item selected
  • build the application with the code resource
  • double click the .xSYM file for the code resource (not the application) to open the debugger
  • set the breakpoints in the resource
  • launch the application, and the debugger will halt at the code resource breakpoints

Summary

We've just reviewed a lot of steps and scattered code snippets to demonstrate how to get a CLI program ported to the Macintosh using code resources. To guide you on your own porting project, Tables 2 and 3 list the routines and files that I used for MacMIDI2abc which are described in this article. Listing 24 is a listing of all the changes to main in MacMIDI2abc. Finally, review the CLI Port Demo program available on the MacTech web site.

Table 1. MacMIDI2abc GUI program files and routines

(standard Mac GUI and event handling code)
Args.c (structure and routines for handling argc and argv parameters)
	command structure (CMD_t) (Listing 15)
	NewArgList (Listing 16)
	SetNthArg, SetFinalArg, etc. (Listing 17)
Settings.c (routines and typedefs for maintaining a "settings" structure)
	Settings structure (SETTINGS_DLG_t) (Listing 18)
	setg resource (Figure 11)

	ArgsFromSettingsDlg (Listing 19)
CodeResource.c (routines that interface with code resource)

	CallCodeResPPC (Listing 21)
	CallCodeRes68k (Listing 22)

CopyBytesToBuffer (stdio output in code resource ends up here)
(see the CLI Port Demo project for an example)
SetDefault, RestoreDefault (Listing 23)
MacMIDI2abc.mcp.exp (exported symbols)

Table 2. MacMDID2abc code resource files and routines

midi2abc.c, midifile.c (original midi2abc CLI code)
	main (Listing 24)
console.stubs.c (local copy)
	WriteCharsToConsole (Listing 6)
CR.glue.c (initialization and termination routines)

	MyInitCR (Listing 8)
	MyCR68k (Listing 13)
	MyTerminateCR (Listing 12)

exit.glue.c (override MSL exit routine)
	exit (Listing 11)
_GetmemWithFree.c (local copy)
	__sys_alloc (Listing 9)
	FreeAllMallocMemory
MIDI2abc CR.mcp.exp (exported symbols)

Listing 24. Changes to main routine in midi2abc.c

#include <setjmp.h>
#include <A4Stuff.h>

jmp_buf	myjumpbuf;

main

int main (int argc, char *argv[])
{
	// local variables here
	volatile int val;
	
	EnterCodeResource();
	if (argc <= 0) {
		InitCR68k(argc, (void *)argv);
		ExitCodeResource();
		return(0);
	}

	if (val = setjmp(myjumpbuf)) {
		ExitCodeResource();
		return (val - 1);
	}
	
	// routines to handle argc, argv and 
	//  process midi files here

	ExitCodeResource();
	exit(0); 
}

References

  • Gillespy III, Thurman. "Tools Plus Pro Libraries + Framework". MacTech Magazine 15:10 (October 1999), pp. 26-42.
  • Sydow, Dan Parks. "Preference Files". MacTech Magazine 15:7 (July 1999), pp. 6-18.
  • Thompson, Tom. Porting GNU C Programs to Metrowerks' CodeWarrior C Compiler. Metrowerks, Inc, 1999.
  • Zobqiw, Joe. A Fragment of Your Imagination. Addison-Wesley, 1995.

Thurman Gillespy III is a radiologist at the Veterans Administration Puget Sound Health Care System in Seattle, Washington. He can be reached at tg3@u.washington.edu

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Call of Duty Warzone is a Waiting Simula...
It's always fun when a splashy multiplayer game comes to mobile because they are few and far between, so I was excited to see the notification about Call of Duty: Warzone Mobile (finally) launching last week and wanted to try it out. As someone who... | Read more »
Albion Online introduces some massive ne...
Sandbox Interactive has announced an upcoming update to its flagship MMORPG Albion Online, containing massive updates to its existing guild Vs guild systems. Someone clearly rewatched the Helms Deep battle in Lord of the Rings and spent the next... | Read more »
Chucklefish announces launch date of the...
Chucklefish, the indie London-based team we probably all know from developing Terraria or their stint publishing Stardew Valley, has revealed the mobile release date for roguelike deck-builder Wildfrost. Developed by Gaziter and Deadpan Games, the... | Read more »
Netmarble opens pre-registration for act...
It has been close to three years since Netmarble announced they would be adapting the smash series Solo Leveling into a video game, and at last, they have announced the opening of pre-orders for Solo Leveling: Arise. [Read more] | Read more »
PUBG Mobile celebrates sixth anniversary...
For the past six years, PUBG Mobile has been one of the most popular shooters you can play in the palm of your hand, and Krafton is celebrating this milestone and many years of ups by teaming up with hit music man JVKE to create a special song for... | Read more »
ASTRA: Knights of Veda refuse to pump th...
In perhaps the most recent example of being incredibly eager, ASTRA: Knights of Veda has dropped its second collaboration with South Korean boyband Seventeen, named so as it consists of exactly thirteen members and a video collaboration with Lee... | Read more »
Collect all your cats and caterpillars a...
If you are growing tired of trying to build a town with your phone by using it as a tiny, ineffectual shover then fear no longer, as Independent Arts Software has announced the upcoming release of Construction Simulator 4, from the critically... | Read more »
Backbone complete its lineup of 2nd Gene...
With all the ports of big AAA games that have been coming to mobile, it is becoming more convenient than ever to own a good controller, and to help with this Backbone has announced the completion of their 2nd generation product lineup with their... | Read more »
Zenless Zone Zero opens entries for its...
miHoYo, aka HoYoverse, has become such a big name in mobile gaming that it's hard to believe that arguably their flagship title, Genshin Impact, is only three and a half years old. Now, they continue the road to the next title in their world, with... | Read more »
Live, Playdate, Live! – The TouchArcade...
In this week’s episode of The TouchArcade Show we kick things off by talking about all the games I splurged on during the recent Playdate Catalog one-year anniversary sale, including the new Lucas Pope jam Mars After Midnight. We haven’t played any... | Read more »

Price Scanner via MacPrices.net

Deal Alert! B&H Photo has Apple’s 14-inch...
B&H Photo has new Gray and Black 14″ M3, M3 Pro, and M3 Max MacBook Pros on sale for $200-$300 off MSRP, starting at only $1399. B&H offers free 1-2 day delivery to most US addresses: – 14″ 8... Read more
Department Of Justice Sets Sights On Apple In...
NEWS – The ball has finally dropped on the big Apple. The ball (metaphorically speaking) — an antitrust lawsuit filed in the U.S. on March 21 by the Department of Justice (DOJ) — came down following... Read more
New 13-inch M3 MacBook Air on sale for $999,...
Amazon has Apple’s new 13″ M3 MacBook Air on sale for $100 off MSRP for the first time, now just $999 shipped. Shipping is free: – 13″ MacBook Air (8GB RAM/256GB SSD/Space Gray): $999 $100 off MSRP... Read more
Amazon has Apple’s 9th-generation WiFi iPads...
Amazon has Apple’s 9th generation 10.2″ WiFi iPads on sale for $80-$100 off MSRP, starting only $249. Their prices are the lowest available for new iPads anywhere: – 10″ 64GB WiFi iPad (Space Gray or... Read more
Discounted 14-inch M3 MacBook Pros with 16GB...
Apple retailer Expercom has 14″ MacBook Pros with M3 CPUs and 16GB of standard memory discounted by up to $120 off Apple’s MSRP: – 14″ M3 MacBook Pro (16GB RAM/256GB SSD): $1691.06 $108 off MSRP – 14... Read more
Clearance 15-inch M2 MacBook Airs on sale for...
B&H Photo has Apple’s 15″ MacBook Airs with M2 CPUs (8GB RAM/256GB SSD) in stock today and on clearance sale for $999 in all four colors. Free 1-2 delivery is available to most US addresses.... Read more
Clearance 13-inch M1 MacBook Airs drop to onl...
B&H has Apple’s base 13″ M1 MacBook Air (Space Gray, Silver, & Gold) in stock and on clearance sale today for $300 off MSRP, only $699. Free 1-2 day shipping is available to most addresses in... Read more
New promo at Visible: Buy a new iPhone, get $...
Switch to Visible, and buy a new iPhone, and Visible will take $10 off their monthly Visible+ service for 24 months. Visible+ is normally $45 per month. With this promotion, the cost of Visible+ is... Read more
B&H has Apple’s 13-inch M2 MacBook Airs o...
B&H Photo has 13″ MacBook Airs with M2 CPUs and 256GB of storage in stock and on sale for $100 off Apple’s new MSRP, only $899. Free 1-2 day delivery is available to most US addresses. Their... Read more
Take advantage of Apple’s steep discounts on...
Apple has a full line of 16″ M3 Pro and M3 Max MacBook Pros available, Certified Refurbished, starting at $2119 and ranging up to $600 off MSRP. Each model features a new outer case, shipping is free... Read more

Jobs Board

Medical Assistant - Surgical Oncology- *Apple...
Medical Assistant - Surgical Oncology- Apple Hill Location: WellSpan Medical Group, York, PA Schedule: Full Time Sign-On Bonus Eligible Remote/Hybrid Regular Apply 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
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
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
Business Analyst | *Apple* Pay - Banco Popu...
Business Analyst | Apple PayApply now " Apply now + Apply Now + Start applying with LinkedIn Start + Please wait Date:Mar 19, 2024 Location: San Juan-Cupey, PR Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.