TweetFollow Us on Twitter

Debugging Terminal
Volume Number:12
Issue Number:10
Column Tag:Communications Toolbox Connections

DebugTerm

A Poor Man’s Debugging Terminal

By Mark Chally, Covina, California

Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.

The purpose of this article is to implement a useful C-language debugging tool and to discuss the basics of Apple’s Macintosh Communications Toolbox, including methods for using the Connection Manager. In the process, we will also create a base for a more powerful application that may be presented in a later issue or one that you may develop on your own.

We will begin by discussing the Communications Toolbox (CTB), what it’s good for, why you need to know about it, and why you want to read about it here. Next, we will summarize the use and structure of the CTB.

With a general understanding of the CTB under our belts, we will be ready to discuss the purpose and use of DebugTerm. Once we know what DebugTerm is expected to do, we can walk through the code and see how it takes advantage of the CTB. In the penultimate section, we’ll take DebugTerm for a test spin and see the results of function calls we make.

Finally, we will discuss problems that may occur when using DebugTerm, and possible enhancements to it. We will summarize what we have accomplished and ponder future directions.

Introduction

The Advent of the Communications Toolbox

Before Apple created the CTB, there were already many ways to connect the Macintosh to the outside world. Being so easily connected was a fabulous feature. Even the first Macs had built-in networking, and counting connections to LaserWriters, the Mac was the world’s most networked personal computer.

However, as the variety of ways to connect the Macintosh increased, so did the difficulty encountered when implementing programs that connect in various different ways. Even before the Mac // arrived, it would have been useful to have a single library which would allow a program to communicate through whatever connection the user desired, using the same high-level code for each type of connection. The application programmer would not have needed to be concerned with the intricacies of any specific connection type. Ideally, it would have been better to avoid having to deal with the complexities of different brands of modems, too. Making matters worse, the Mac // came out, with an open architecture through which a user could add internal modems and multiple serial connections. Apple had a real mess on their hands!

The Macintosh Communications Toolbox Arrives

As Apple mapped the way to System 7, they apparently became aware of the morass that had grown-and found a gem which was already under development. As the forward to Inside the Macintosh® Communications Toolbox (Inside CTB) indicates, “Initially conceived as a better way to engineer MacTerminal 2.0-it enabled MacTerminal to support new protocols without having to be revised-the Communications Toolbox has evolved into an integral component of our system software.”

With the CTB, programmers can now write programs which connect to other computers using tools for connection, terminal emulation and file transfer protocols whose intricacies the programmer is not required to know. In theory, with a well-designed program and tools, a networking protocol that the programmer was unaware of could be supported, in addition to a slew of modems and file transfer protocols.

Given such a powerful API (Application Program Interface), communications features can be uniformly and reliably added to just about any type of application, with minimal effort. When I learned this, I was eager to begin taking advantage of the CTB.

Obstacles for the Communications Toolbox, or Why read this article?

My enthusiasm for the CTB was tempered by some harsh realities. I found an early SDK (Software Developer’s Kit) woefully inadequate. It came with only a smattering of example code and unfinished documentation. Perhaps the current SDK is more useful. Until now, MacTech had never printed an article on the subject, and I saw none elsewhere. Nor did I find available on-line source code examples satisfactory. The code that I used to enhance my understanding was inspired by a simple Pascal program called Surfer, from Apple’s ETO (Essentials•Tools•Objects) mailing. Once Inside CTB was completed, I used it as documentation, which I highly recommend.

Another topic of confusion was whether or not Apple and vendors would continue to support the CTB. Since programmers did not receive it warmly, and other parts of System 7 never really “caught on,” some questioned whether it would become like PowerTalk and other orphaned technologies. It was pleasing to see that, though there have been a few problems implementing Open Transport, it seems to support the CTB seamlessly, which still works fine with the serial port, as well as over networks too. Modem manufacturers’ responses have been mixed. Some companies such as Global Village have done a good job, but some others have not. One company’s support technician seemed peeved to “have to write Apple’s code for them.” Despite a rough start, the future looks good for the CTB.

If you think you can recognize every program using the CTB, you may be surprised. There is a very recognizable user interface, but there is also an API which lets programmers implement the user interface themselves. The CTB is not difficult to learn to program. Although I am far from a CTB expert, I was able to put together the code I needed fairly quickly.

What is the Communications Toolbox?

Included as an extension to System 6 and built into System 7, the CTB consists of four managers and a set of utilities. Together, they provide a means to control three separate aspects of communications: connections, file transfers, and terminal emulation. Inside CTB has some pretty pictures-most of which are probably not necessary here. In the interest of clarity, however, here is a representative one:

Figure 1. The Realm of Macintosh
Communications Toobox Influence

Normally, the CTB and the client application do not interact much with the user or handle the details the managers oversee. That responsibility is handled by communications tools. Standard tools come with the CTB; users can obtain various others from third parties, and drop them into their extensions folder to use them with programs that employ the CTB. There are public domain, shareware, direct mail, and various other commercially available tools. Others are distributed with their client applications. The managers provide the API and the utilities handle housekeeping chores.

The Connection Manager provides an API through which the client application (your code) can use a connection tool. Each, such as the Serial Tool, Apple Modem Tool and AppleTalk ADSP Tool, allows the client application to connect through the specific medium in which the tool specializes. The Serial Tool, for example, is responsible for transferring data only through the serial ports. The Apple Modem Tool, on the other hand, has to know about the complexities of modems-which it does with the help of plug-in modules that manufacturers provide to meet Apple’s specifications. The AppleTalk ADSP Tool, provides an AppleTalk network connection using the ADSP protocol. Other connection tools exist, such as an ISDN tool, according to Inside CTB.

The Terminal Manager provides an API through which the client application can use terminal emulation tools. The TTY Tool behaves as a dumb terminal, as do most terminal programs in their simplest modes. An additional standard terminal emulation is the VT102 Tool, which is somewhat more complex. Tools to emulate other standard terminals, such as IBM 3270, are also available.

The File Transfer Manager provides an API through which the client application can use file transfer tools. Standard tools include the Text Tool, which sends and receives text straight through the connection, as archaic text file captures did, and the XMODEM Tool. There are tools that support other file transfer protocols, such as YMODEM, ZMODEM, and Kermit.

Users are typically unaware of the structure through which they are communicating, as Apple apparently intended. In most cases, they only know that they are using a familiar user interface for connections, file transfers, and terminal emulation. Programmers should seriously consider allowing the user to have access to the standard interface, as opposed to the scripted interface described in Inside CTB as Custom Configuration. Using the scripted interface allows the application to tell the CTB that it knows what the user needs to see. Aside from being beyond the scope of this article, it deprives the user of the look and feel he expects and may limit the availability and features of other tools if the application is implemented too narrowly. We will not cover the scripted interface.

The Communications Resource Manager provides a specialized interface to Macintosh resources for CTB purposes. It has some useful calls related to structures that are unique to the CTB. Rounding out the CTB package, the Communications Toolbox Utilities provide standardized routines that a client application may need for using the CTB.

A Poor Man’s Debugging Terminal

Purpose

The purpose of DebugTerm is to use the CTB to pipe information from an application under development to some other destination, such as a computer, terminal, or other device. Because the CTB is so handy at connecting to things, it makes a good method for piping information from your running program to...whatever! In fact, the CTB is so flexible, DebugTerm can be used to pipe information to a terminal program running on the same machine! But why do these things?

As I began learning Macintosh programming, I recall hearing of devices called hardware debuggers or debugging terminals. Not having seen one, I imagined them to use software stubs to send the status of a computer environment to the outside world, where information could be inspected using a terminal or device. Perhaps Macsbug grew out of one of those stubs. Using such an item, one might evaluate the status of an environment with little or no effect on it. At first, the Mac’s simplest debugging tool was a function, DebugStr(Str255 theString), which allowed the program to break into the debugger to display theString. The program then patiently waited for the operator to work with the debugger.

Of course, that was years ago, and development systems are more advanced today. I use source code debuggers, but sometimes I distrust them. They may provide dubious information, or change the program environment, or perhaps my program crashes before I can examine the suspect variables. Of course, I still use source code debuggers. I just do not always want to depend on them. I am not interested in using low level debuggers to examine disassembled code either.

The first inspiration for DebugTerm may have come from a MacTutor article written several years ago by Bob Gordon. Bob came up with a nifty idea for outputting debugging information to a Mac window, other than the STDIO window, so that the ANSI environment did not need to linked in. Much of debugf(), a major function within DebugTerm, was taken from The C Programming Language, 2nd Ed., (K&R) page 156. Kernighan and Ritchie used an example function to show how logic similar to printf() can be simulated using the ANSI C header stdarg.h.

DebugTerm, the C Source File

DebugTerm is a C source code file that can be compiled into an application under development. Before output can be sent, a connection must be opened using OpenDebugTerm(). While the connection is open, the simple DebugTerm() call can be used to send a Pascal string through the connection. The more flexible debugf() can be used to send formatted output through the connection in a way that mimics the C STDIO library’s printf() function. Although modest in comparison to printf(), debugf() is quite useful and easily extensible for various programming needs. The connection should be closed using CloseDebugTerm() afterward. If any of the functions encounters an error, it returns the error it tripped on-either by passing through a CTB or OS error, or one listed in DebugTerm.h.

OpenDebugTerm() is used to initialize DebugTerm and prepare it to send information to the chosen recipient. It requires three Boolean parameters: async, initCTB, and prompt, and returns an OSErr.

async indicates whether the connection will take place using asynchronous calls. If the object is to connect to a terminal program on the same machine, DebugTerm must use asynchronous calls. Otherwise, synchronous calls are probably best. Asynchronous calls allow the CTB to carry out requests in the background instead of while you wait, but the programmer assumes additional responsibilities (more on this later).

initCTB indicates whether to initialize the CTB for use. The value true is usually necessary, since the CTB managers must be initialized before being used. It is possible, however, that you may wish to use DebugTerm to debug a program which already uses and initializes the CTB. In this case, initCTB would be false. If a true value is provided for this parameter, and for some reason DebugTerm has already initialized the CTB, it will remember that it has, and refuse to reinitialize it.

prompt indicates whether to pose the Connection Settings dialog on opening, or use the parameters saved from the last session. If DebugTerm cannot find a preferences file in the folder enclosing the application being developed, or the tool changes, it will pose Connection Settings-even if prompt is false.

When called, OpenDebugTerm() begins by initializing the CTB, if the initCTB parameter is true, and it has not yet done so during the current execution of the program. It defaults to the information in the preferences file it finds in the application folder. If it finds no preferences or they are invalid, it chooses default information for the first tool it finds in the Extensions folder. If the prompt parameter is true, or the preferences are invalid or not found, the Connection Settings dialog (Figure 2) is posed, showing the default settings.

Figure 2. Connection Settings Dialog,
with the Modem Tool’s options

If the Cancel button is pressed to dismiss the Connection Settings dialog, no preferences are written and no connection is established. If the OK button is pressed, the results of any change in connection tool choice are written to the preferences file, which is created if it does not yet exist. Finally, a connection is established according to the Connection Settings dialog, or according to the preferences, if the dialog was not posed.

CloseDebugTerm() closes the connection that OpenDebugTerm() established, and deallocates resources used. It is smart enough to avoid trying to close itself a second time if it has already been closed, but it has no way to close itself if the program quits without calling CloseDebugTerm(). Therefore, it is harmless to put a call to CloseDebugTerm() in the Exit() function of the main program and it is probably a good idea, since abandoning a connection confuses the CTB. CloseDebugTerm() requires no parameters, and returns an OSErr.

DebugTerm() is a simple procedure that requires a single Str255 parameter, and returns an OSErr. Actually, it can receive a Str255 or any “Pascal string,” meaning it understands a call like:

DebugTerm(“\pHello, World!\r\n”)

DebugTerm() responds by sending the supplied string through the current connection.

debugf() is a more complex procedure which could be described, behaviorally speaking, as the progeny of DebugTerm() and printf(). Some powerful and really stupid things can be done with debugf(), just as with printf(). Most C users will recognize printf() as the STDIO function used liberally by most non-Mac programmers and rarely by Mac programmers. Nevertheless, it is one of the “way cool” features of C. It is C’s steroid-laced equivalent to Pascal’s Write statement. Of course, the previous sentence was a heresy for which I will pay stiff penance to the C/UNIX gods.

debugf() takes a variable-length parameter list. The only required parameter, a format string, is a C string. Each of the parameters that follow the format string corresponds to a ‘%’ embedded in the format string. For each ‘%’, which is followed by a specifier, there must be an additional parameter of the expected type. If not, you too must pay stiff penance to the C/UNIX gods, or the Bomb Box altar, or Macsbug gods, or the gods of whatever debugger you use. If the first string contains no ‘%’, no additional parameters are expected or used (and of course you could just as well be using DebugTerm()). For each ‘%’ embedded in the format string, the character following it is a specifier, which indicates the type of data contained in the corresponding parameter. This specifier is not case sensitive. Horrible things may happen if you specify the wrong type, so be deliberate. Following is a list of valid types for the parameters:

P Pascal string, whose first character is a length byte n, and is followed by n additional characters.

C C string, containing an arbitrary number of characters, terminated by the null character.

I int-whatever that means to your development system, short or long-it better be an int (signed) to your compiler.

S short signed integer data type.

L long signed integer data type.

If the specifier is not one of those listed above, the specifier itself is sent, having the effect of ignoring the ‘%’ and leaving the parameter to be processed with the next ‘%’. The output from debugf() is such that the string in the first parameter is sent through the connection, with the text version of each following parameter replacing its corresponding ‘%’ in the first parameter. Therefore, if:

pstr = “\pRocko”
cstr = “had an accident. He fell on his stiletto”
aShort = 47

the following call:

debugf(“%% %p %c %s times.”, pstr, cstr, aShort)

would produce the result:

% Rocko had an accident. He fell on his stiletto 47 times.

Synchronous vs. Asynchronous Calls

Some CTB calls can be executed either synchronously or asynchronously. Synchronous calls are performed while you wait. Asynchronous calls are performed in the background by the CTB tool. In a nutshell, if you choose to perform an operation synchronously, you must wait for the operation to complete or fail before your process can continue. If you do this in a cooperatively multitasking environment such as System 7, almost the entire system will wait for an answer from the tool, which will probably wait for an answer from a device-or another program on the same machine.

Synchronous calls are probably acceptable for DebugTerm in most situations. You can usually wait for DebugTerm to finish sending information before your program resumes. However, if DebugTerm is attempting to synchronously connect to or send information to a terminal program on the same Mac, you will find that there is no apparent response. The reason for this is that no cooperatively multitasking process is allotted any time by the CTB tool unless you call the tool asynchronously. If no other process is allotted any time, a terminal program running on the same machine has no chance to “notice” it is being addressed until the process you have started “gives up.” Therefore, unless you are a masochist, you will want to use DebugTerm asynchronously if you intend to send information to a terminal program on the same machine.

Asynchronous calls have a drawback. When you call them, they tell you everything is fine-but they do not know yet, because they answer before attempting to do what you asked! For this reason, routines that operate asynchronously expect to call a completion routine, which can find out how everything went, once the tool has completed or failed the process. The completion routine supplied with DebugTerm is an empty function. Much like elected government officials, it is only there to fulfill the requirement of being there. Therefore, you will want to use DebugTerm synchronously if you do not intend to send information to a terminal program on the same machine. Completion routines should always have a mechanism for trapping and/or reporting errors-that is what they are for. Of course you can use this one as is, because you get to suffer if you experience an error and you do not know why.

Source Code Walkthrough

DebugTerm and TestDebugTerm were implemented using Metrowerks C with CodeWarrior Integrated Development Environment (IDE) 1.4, so a CodeWarrior IDE project file has been provided. It has only the standard MacOS.lib linked in. If you are using another development system, your default project preferences should work. The code was intended to be compatible with Macintosh development systems, but your mileage may vary. You may wish to have a printed copy of the source code, or to view it on your monitor alongside the article as we walk through it.

DebugTerm.h

This file contains declarations necessary for both DebugTerm and the client application. Literals have been defined to make Boolean parameters more readable. Some error codes have been defined for DebugTerm to return if an unreasonable request is made but does not cause a system error. If the error returned by DebugTerm is in this range, it is probably the situation noted here, not a system error. An effort was made to choose a range not found in the system error list. The file is completed by a list of prototypes for the functions externally available to the client application:

OSErr OpenDebugTerm(Boolean async, Boolean initCTB,
 Boolean prompt);
OSErr CloseDebugTerm(void);
OSErr DebugTerm(Str255 outStr);
OSErr debugf(char *fmt, ...);

If you are concerned about the readability of your code, consider using the literals supplied, instead of true or false, for OpenDebugTerm() parameters. The line of code:

OpenDebugTerm(kAsync, kDoInitCTB, kDoPrompt);

says much more than:

OpenDebugTerm(true, true, true);

TestDebugTerm.h

This file contains mostly resource IDs and offsets used to implement TestDebugTerm, the application we will use to demonstrate DebugTerm.

DebugTerm.c

This file contains the brains of DebugTerm. It holds the source code for the externally declared functions, as well as the ones declared and used internally. Some of the functions, many of those declared as static, are simply utilities to make the business of writing the program easier, while others relate to the use of the CTB. Here, we will discuss the latter ones, in the order you will find them in the source code.

CMCompletion() is a function whose address must be furnished when making asynchronous calls to the CTB. It is included in DebugTerm.c to make asynchronous calls possible. Asynchronous calls, as opposed to synchronous calls, cause work to be performed in the background by the CTB tool, while asynchronous calls are performed while you wait. The difference and their use, and a warning about leaving this and any other completion routine “empty” are discussed in detail earlier in this article. Please do not distribute a product with a useless completion routine just because you saw it here!

CMSendProc() gets called any time DebugTerm sends information through the connection. Originally written as a wrapper for the CTB function CMWrite(), it became more sophisticated to support asynchronous calls. The first thing it does is validate that gConn points to an actual connection record.

Used synchronously, it simply calls CMWrite(), and if all the requested characters are not written, it calls it again, until they all are. If at any time in the process it finds an error, it aborts, reporting the error code.

Used asynchronously, CMSendProc() has extra duties. Before each call to CMWrite(), it checks, by calling CMStatus(), to be certain that there are no write or open operations pending. Because asynchronous calls are in use, the tool may not have completed its last assignment, or even begun. If it finds that a write or open operation is still pending, it calls WaitNextEvent() to kill about one sixtieth of a second before resuming. Because it calls WaitNextEvent(), other processes in the environment get housekeeping time-including your terminal program, if you are using one on the same machine to listen to DebugTerm. Notice that it uses a nullEvent mask to avoid getting any real events because it does not actually deal with them.

Another extra duty involved when sending asynchronously is finding out how many bytes were actually sent. When CMWrite() is called asynchronously, it returns a bogus value for the number of bytes sent, because it does not actually know yet. Therefore, CMSendProc() continuously monitors the cmDataOut element of gConn’s asyncCount array for the correct answer.

InitCTBStuff() is called by OpenDebugTerm() before it does any other business, and calls the CTB procedures responsible for initializing the various managers used. However, before taking the caller’s word, it checks to see if it has initialized the CTB since the program was launched. If it has, it ignores the call. Otherwise, it calls InitCTBUtilities(), InitCRM() and InitCM(), which must always be called before another connection manager routine is called.

FindTool() is called by ReadPrefs() to get the ID and name of the tool last used by DebugTerm. It does this by looking in the preferences file’s resource fork for the one with the ID and type whose literals are in the header file. When it finds the resource, it copies the string, containing the name of the tool last used. If there is no resource file open (probably because it does not exist yet), or the tool is no longer there (CMGetProcID() fails) then it calls CRMGetIndToolName() specifying the connection tool class kClassCM, and an index of 1, to get the name of the first tool the Connection Manager finds. If it still cannot find a tool, it returns the error kToolNotAvailErr.

ReadPrefs() is called by OpenDebugTerm() to find the preferences written the last time DebugTerm was executed. It first calls OpenPrefsFile(), instructing it to avoid creating the file if it does not find it (WritePrefs() will create the file later if necessary). Even if the preferences file cannot be opened, FindTool() will provide the name of the first tool the Connection Manager finds. Once it obtains the name of the tool last used, it opens the resource in the preferences file that has been previously saved with the tool’s name. Because the configuration string is saved with the tool’s name, a preference can be stored and retrieved separately for each tool used.

WritePrefs() is fairly long, mostly because it includes so much error checking and resource housekeeping. It is not complicated. The first thing it does is return the error kNoConnectionErr if there is no current connection. If there is a connection, after getting the name of the currently used tool from CMGetToolName() (which it supplies the ID it got from the connection record) it gets the connection tool’s configuration string using CMGetConfig(). Because the tool in use must have a name, there is no error checking on CMGetToolName(), but it may be possible for CMGetConfig() to fail, perhaps because there was not enough memory available to allocate the configuration string. If so, it returns kGetConfigErr. Notice that disposal of the connection string is the application’s responsibility (DebugTerm’s), not the CTB’s.

Once the tool name and configuration string are available, they can be written to the preferences file. This is done by updating the resources accessed by ReadPrefs(). WritePrefs(), however, has to do the business of creating the resources if necessary, which includes a lot of busy work and error checking. Notice the care taken to name the resources it creates by adding tName as the resource name, and to find a unique resource ID above a specified value for the resource containing the tool’s configuration string by calling UniqueID(). Memory that was allocated during this process is also deallocated.

ChooseConnection() is called by OpenDebugTerm() to choose a tool and configure it. It calls CMChoose(), providing the connection handle and a Point indicating the upper-left-hand corner of the dialog. It sends a nil for the last parameter, because there is no need for an “idle” procedure. It is a good idea to place the dialog near the top-left corner of the screen, because there is no way to know how much space various tools will use. If CMChoose() returns chooseDisaster, a fatal error has occurred, and the connection record has been destroyed. If it returns chooseFailed, the user pressed Cancel. In both cases, ChooseConnection() returns its own error, kNothingChosenErr. If CMChoose() returns chooseOKMinor, the user changed the current tool and pressed OK. If it returns chooseOKMajor, the user has kept the current tool and pressed OK. In both of these situations, ChooseConnection() returns noErr. In the case of an unpredicted error, ChooseConnection() returns that error.

OpenDebugTerm() is called by the client application sometime before DebugTerm() or debugf(). It does all the things necessary for DebugTerm to live in the application’s world. The first thing it does is check to see whether the global handle gConn points to an existing connection record. If it does, DebugTerm is already open, and OpenDebugTerm() returns kAlreadyOpenErr. Otherwise, it calls InitCTB(), if the client application has requested, and returns any error encountered. After that, it calls ReadPrefs() to get the ID of the saved or default tool, and its saved or default configuration information. If ReadPrefs() returned kToolNotAvailErr because it was unable to select the tool indicated in preferences (or there were no preferences) then a flag is set so that the Connection Settings dialog can be posed later. If the tool ID is -1, then no connection tool was found, and kNoConnToolErr is returned.

Now CMNew() can be called to create a connection record. Because only a basic connection is required, the minimal information is passed along in addition to the tool ID. The sizes array gets filled with zeros, indicating that the connection tool is expected to choose a size for its own buffers and maintain them itself. The cmData constant is passed, indicating that only a data channel is necessary-some tools support multiple channels. No refCon or userData value is passed. If the call to CMNew() fails, gConn is nil, and kCMNewErr is returned. Otherwise, CMSetConfig() is called to set the configuration preferences to the values retrieved, and the handle to the connection record is moved as high in memory as the Memory Manager can get it before it is locked. In either case, the configuration record’s handle is deallocated afterward.

Originally, I wondered why Surfer.p locked the connection record. I have assumed that it was being locked so it could be accessed during interrupt time, when unlocked handles are invalid. It is probably a good idea to lock that handle, which is what DebugTerm does.

Now that a new connection record has been successfully created and configured, it is a good time to allow the user to choose a different tool or reconfigure the current one. If the prompt parameter was set to true or a new tool was defaulted to because the tool in preferences was not available, or the preferences did not exist, the Connection Settings dialog is posed. Following that, the preferences and tool choice that have been agreed on are written to the preferences file, which is created if it does not yet exist.

Only now is a connection actually opened, if no error has occurred so far. The connection handle is provided as a parameter, along with the caller’s choice of whether the attempt should be asynchronous, the address to the completion routine, and -1 to indicate that the tool is allowed to take as long as it needs to establish the connection.

If there is an error in CMOpen(), ChooseConnection() or WritePrefs(), it is finally checked at this point. If a connection record was established, it is deallocated and the handle is marked nil before the error is returned. Otherwise, the global gAsynchronousCalls is set so other functions can reference it, and another global variable, gFlags, is set to false. Other Connection Manager calls will pass gFlags through to the tool as necessary to indicate that we do not wish to send end-of-message indicators.

CloseDebugTerm() is called when the client application is done using DebugTerm. It closes the connection, if any, and deallocates the connection record. Of course, it first verifies that there is a connection record before it attempts to deallocate it, and if so it verifies that it is open or opening before attempting to close it.

DebugTerm() makes the simplest possible call to illustrate the debugging terminal concept. It looks at the length byte of the parameter, and uses it to tell CMSendProc() how long the output will be. It also passes along a pointer which points to the first character to be sent, one position past the length byte.

debugf() is the function around which the project is centered. Its purpose is to send formatted output through the current connection. The mechanism by which this works is similar to printf() and minprintf(), found in K&R (second edition), page 156.

debugf() begins life knowing only the address of the first parameter, fmt, it has received. After verifying that a connection exists, it calls va_start() to fill the list ap with pointers to arguments, starting at fmt. Having built the list, it begins iterating through the characters in fmt. Until it reaches a ‘%’, it simply responds by sending fmt’s current character.

When a ‘%’ is encountered, the next job is to act on the specifier that precedes it. The switch statement channels the logic so that Pascal strings and C strings are correctly passed to CMSendProc(). Integer values, whether short, long or int, are converted to strings and passed through. If the specifier is not in the switch statement, the character itself is passed. This has the effect of passing “%” through the connection when “%%” is found in the string, and otherwise ignoring bad specifiers. Bad specifiers are a really bad thing anyway, because a parameter fails to get handled when expected, but it is okay because if you screw up your debugging code, you get to be the victim- since you are the user. va_end() is called last, and if there is an error, you get to hear about that too.

TestDebugTerm.r

Boilerplate definitions for the resources used in TestDebugTerm can be found here, including the various items for the Test menu. By the way, if you hate having to calculate the enableFlags value for rez (you know, that “0x7FFFF819” thing) then check out the HexFlags desk accessory. It may save you some grief.

Test Spin

It is time to see how DebugTerm responds to your requests. There are various ways to run TestDebugTerm, but you will probably want to think about a few things before you blaze ahead.

Means of Connection

DebugTerm can be used in many ways. If you choose the Apple Modem Tool, you can connect via the modem-probably not very practical for debugging, but if you want to, have fun. The same thing goes for any other type of connection not listed here. I am interested in hearing about your experiences.

Using the Serial Tool you can connect to another Mac, PC, Newton, or device. You can even wire a connection between the modem port and serial port on the same Macintosh. To connect by serial port requires a null modem cable. This is the cable the Macintosh uses to print to most printers and to communicate with the Newton by serial port. It is up to you to get the connection wired right, but a hint here: if it works with a terminal program, it should work with DebugTerm. I have tested the Serial Tool from Mac-to-Mac, from one Mac’s modem port to its own printer port, and from a Mac-to-Newton. None of these options has been problematic.

If you choose the AppleTalk ADSP Tool, you can connect between two Macs on a network, or from one Mac to itself. To connect a Mac to itself with the AppleTalk ADSP tool, no physical connection is necessary, but make sure AppleTalk is turned on in the Chooser, or you will get nowhere fast.

Connecting to Yourself

If you choose to connect the Macintosh to itself, either by using AppleTalk ADSP for a virtual hookup, or Serial Tool from modem to printer port, you will need to keep one thing in mind. To connect a Macintosh to itself, you must use asynchronous calls. Many readers, who do not have two machines or other devices handy, will probably take advantage of the feature.

“Come here Watson, I need you”

The following assumes you are using ClarisWorks to monitor the connection, but you can use anything you have available to you, such as FreeTerm. You can use a Newton or Macintosh with a terminal program, and whatever cable necessary. ClarisWorks will not care whether DebugTerm is on the same machine or another. If you do not have the Communications Toolbox installed or do not have the connection tool you need, install and/or get them. We do not have a license to distribute them, but they come with System 7 and various products such as ClarisWorks. They may also be on your copy of ETO or the CTB SDK.

If you are using the ClarisWorks terminal emulator for the first time, it will choose default tools and preferences for you. Select the Connection... item from the Settings menu. You should see a Connection Settings dialog similar to figure 2. Next, use the Method: popup menu to choose the AppleTalk ADSP Tool or Serial Tool, according to the method you have chosen. Next you will see one of the following dialogs:

Figure 3. Connection Settings Dialog, with the AppleTalk ADSP Tool’s options

Figure 4. Connection Settings Dialog,
with the Serial Tool’s options

If you chose the serial tool, you will probably want to use the options shown above, selecting the appropriate serial port. You will not want to change anything for the AppleTalk ADSP Tool. Having chosen the tool you expect to use and set sensible communications parameters if using the Serial Tool, dismiss the dialog by pressing OK.

If you are using the Serial Tool, choose Open Connection from the Settings menu. ClarisWorks will indicate that the connection is established. As far as it is concerned, a connection is established-though you are only connected to your serial cable.

If you are using the AppleTalk ADSP tool, select Wait for Connection from the Session menu. ClarisWorks patiently waits for a connection to be opened from remote. Now that ClarisWorks is listening, it is time to fire up TestDebugTerm.

If you were curious and you have already run TestDebugTerm, it would be a good idea to throw away the file called “DebugTerm Prefs” right now. You can find it in the same folder as TestDebugTerm. Launch TestDebugTerm. If you are connecting to a terminal program on the same machine, make sure Asynchronous Calls on the Test menu is checked. Select Open DebugTerm from the Test menu. Because DebugTerm has not been run, and you left Prompt for Tool on the Test menu checked, it will pose the Connection Settings dialog. Choose the same tool you chose from ClarisWorks. If you are using the Serial Tool, make sure you select the correct port and that the parameters match those you selected in ClarisWorks, then press OK.

If you are using the AppleTalk ADSP Tool, you should see the name of at least one remote process in the scrollable list on the right-hand side. If you did not change anything, the name will correspond to the machine you are trying to connect to. Select that list item, and press OK. ClarisWorks now notices that you have established a connection.

Now that the two programs are connected, various tests can be conducted. Select Simple DebugTerm from the Test menu. ClarisWorks responds with “Hello World!” followed by a line feed and a carriage return. The menu item initiated the following call:

DebugTerm(“\pHello, World!\n\r”);

Select Sluggish Method. The output in ClarisWorks may or may not come through sluggishly:

Elapsed Time: 5 minutes and 3 seconds.

This resulted from the call:

debugf(“Elapsed time: %s minutes and %i seconds.\n\r”, 5, 3);

Go ahead and try the Faster Method, Imaginative Use and Absurd Use items. They produce the results you should expect:

Figure 5. Sample ClarisWorks output from TestDebugTerm

If you try the Stress Flow Control item, you may find it does not pass all 500 lines through. This is probably because you did not select handshake in the serial tool. Go back and choose either XON/XOFF or DTR & CTS on each end, and try again. If you still have problems with DTR & CTS, perhaps you do not have a proper hardware handshaking cable (XON/XOFF does not require a handshaking cable). Choose Close DebugTerm from the Test menu and watch ClarisWorks notice that the connection has been terminated.

Reopen the connection if you like, and play with the parameters to see how it handles. Build the program from your development system with some of your favorite printf()-style statements.

Some Notes

“Waiter, there’s a fly in my soup.”

Bugs? What bugs? I do not know of any existing bugs, but....

There are many opportunities to cause spectacular anomalies by passing bad arguments to debugf() or calling functions out of order. One problem could be the “empty” completion routine. Filling in that void is left as an exercise for the reader. There could also be a messy result if the number of parameters to debugf() is not the same as the number of ‘%’ signs. Passing an invalid type specifier to debugf() may generate an opportunity for a coffee break while you restart your Mac.

Possible Enhancements

Perhaps you may wish to add better error handling, but that goes without saying for such source code examples as this one. Obviously, it would be nice to add derived types that debugf() would understand. One interesting diversion might be to support the toolbox Rect structure, for example, by recursively calling debugf() with the various elements. The fragment, placed in the debugf() switch statement, might look like this:

case ‘R’:
case ‘r’:
 r = va_arg(ap, Rect)
 theErr = debugf(“{%s, %s, %s, %s}”,
 r.top, r.left, r.bottom, r.right);
 break;

Certainly this brings other possibilities to mind, but it also makes obvious the limited utility of single-character specifiers; perhaps another project would provide a better parsing algorithm. A keyword, terminated by some token, may be a useful alternative. Another possibility would be to provide a mechanism to query DebugTerm from the terminal program. This would clearly be a serious project unto itself.

Other CTB Topics

Of course, this article has merely scratched the surface. This example does not handle any incoming information, or terminal emulation, or file transfer tools. Later articles may cover some of those topics.

Conclusion

In this article, we have discussed the basics of Apple’s Macintosh Communications Toolbox, including methods for using the Connection Manager. We used a variety of Connection Manager function calls to configure and operate connection tools, in addition to using some general CTB routines.

We implemented DebugTerm, a useful debugging tool, which was a good initial project for the CTB because it provided a simple example of how to program a CTB connection. Because DebugTerm is so simple, it may be desirable in the future to discuss more complex examples that would build on this code base to create a more complete example.

Do not hesitate to send your comments, questions, and criticism regarding this and future articles on the subject.

References

Following is a list of the most salient publications used to produce DebugTerm.

• Gordon - Gordon, Bob. “A Print Window for Debugging”. MacTutor Magazine 2:10 (October, 1986)

• Inside CTB - Apple Computer, Inc. Inside Macintosh® Communications Toolbox. Addison Wesley, 1991.

• Inside Mac Files - Apple Computer, Inc. Inside Macintosh® Files. Addison Wesley, 1992.

• K&R - Kernighan, Brian W. and Dennis M. Ritchie. The C Programming Language, 2nd Ed. Prentice Hall, 1988

• Surfer - Alex Kazim. “Surfer” (Sample application program with Pascal source code) Essentials•Tools•Objects Vol 13. Apple Computer, 1989.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Dropbox 193.4.5594 - Cloud backup and sy...
Dropbox is a file hosting service that provides cloud storage, file synchronization, personal cloud, and client software. It is a modern workspace that allows you to get to all of your files, manage... Read more
Google Chrome 122.0.6261.57 - Modern and...
Google Chrome is a Web browser by Google, created to be a modern platform for Web pages and applications. It utilizes very fast loading of Web pages and has a V8 engine, which is a custom built... Read more
Skype 8.113.0.210 - Voice-over-internet...
Skype is a telecommunications app that provides HD video calls, instant messaging, calling to any phone number or landline, and Skype for Business for productive cooperation on the projects. This... Read more
Tor Browser 13.0.10 - Anonymize Web brow...
Using Tor Browser you can protect yourself against tracking, surveillance, and censorship. Tor was originally designed, implemented, and deployed as a third-generation onion-routing project of the U.... Read more
Deeper 3.0.4 - Enable hidden features in...
Deeper is a personalization utility for macOS which allows you to enable and disable the hidden functions of the Finder, Dock, QuickTime, Safari, iTunes, login window, Spotlight, and many of Apple's... Read more
OnyX 4.5.5 - Maintenance and optimizatio...
OnyX is a multifunction utility that you can use to verify the startup disk and the structure of its system files, to run miscellaneous maintenance and cleaning tasks, to configure parameters in the... Read more
Hopper Disassembler 5.14.1 - Binary disa...
Hopper Disassembler is a binary disassembler, decompiler, and debugger for 32- and 64-bit executables. It will let you disassemble any binary you want, and provide you all the information about its... Read more

Latest Forum Discussions

See All

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 »
TouchArcade Game of the Week: ‘Vroomies’
So here’s a thing: Vroomies from developer Alex Taber aka Unordered Games is the Game of the Week! Except… Vroomies came out an entire month ago. It wasn’t on my radar until this week, which is why I included it in our weekly new games round-up, but... | Read more »
SwitchArcade Round-Up: ‘MLB The Show 24’...
Hello gentle readers, and welcome to the SwitchArcade Round-Up for March 15th, 2024. We’re closing out the week with a bunch of new games, with Sony’s baseball franchise MLB The Show up to bat yet again. There are several other interesting games to... | Read more »
Steam Deck Weekly: WWE 2K24 and Summerho...
Welcome to this week’s edition of the Steam Deck Weekly. The busy season has begun with games we’ve been looking forward to playing including Dragon’s Dogma 2, Horizon Forbidden West Complete Edition, and also console exclusives like Rise of the... | Read more »
Steam Spring Sale 2024 – The 10 Best Ste...
The Steam Spring Sale 2024 began last night, and while it isn’t as big of a deal as say the Steam Winter Sale, you may as well take advantage of it to save money on some games you were planning to buy. I obviously recommend checking out your own... | Read more »
New ‘SaGa Emerald Beyond’ Gameplay Showc...
Last month, Square Enix posted a Let’s Play video featuring SaGa Localization Director Neil Broadley who showcased the worlds, companions, and more from the upcoming and highly-anticipated RPG SaGa Emerald Beyond. | Read more »
Choose Your Side in the Latest ‘Marvel S...
Last month, Marvel Snap (Free) held its very first “imbalance" event in honor of Valentine’s Day. For a limited time, certain well-known couples were given special boosts when conditions were right. It must have gone over well, because we’ve got a... | Read more »
Warframe welcomes the arrival of a new s...
As a Warframe player one of the best things about it launching on iOS, despite it being arguably the best way to play the game if you have a controller, is that I can now be paid to talk about it. To whit, we are gearing up to receive the first... | Read more »
Apple Arcade Weekly Round-Up: Updates an...
Following the new releases earlier in the month and April 2024’s games being revealed by Apple, this week has seen some notable game updates and events go live for Apple Arcade. What The Golf? has an April Fool’s Day celebration event going live “... | Read more »

Price Scanner via MacPrices.net

Apple Education is offering $100 discounts on...
If you’re a student, teacher, or staff member at any educational institution, you can use your .edu email address when ordering at Apple Education to take $100 off the price of a new M3 MacBook Air.... Read more
Apple Watch Ultra 2 with Blood Oxygen feature...
Best Buy is offering Apple Watch Ultra 2 models for $50 off MSRP on their online store this week. Sale prices available for online orders only, in-store prices may vary. Order online, and choose... Read more
New promo at Sams Club: Apple HomePods for $2...
Sams Club has Apple HomePods on sale for $259 through March 31, 2024. Their price is $40 off Apple’s MSRP, and both Space Gray and White colors are available. Sale price is for online orders only, in... Read more
Get Apple’s 2nd generation Apple Pencil for $...
Apple’s Pencil (2nd generation) works with the 12″ iPad Pro (3rd, 4th, 5th, and 6th generation), 11″ iPad Pro (1st, 2nd, 3rd, and 4th generation), iPad Air (4th and 5th generation), and iPad mini (... Read more
10th generation Apple iPads on sale for $100...
Best Buy has Apple’s 10th-generation WiFi iPads back on sale for $100 off MSRP on their online store, starting at only $349. With the discount, Best Buy’s prices are the lowest currently available... Read more
iPad Airs on sale again starting at $449 on B...
Best Buy has 10.9″ M1 WiFi iPad Airs on record-low sale prices again for $150 off Apple’s MSRP, starting at $449. Sale prices for online orders only, in-store price may vary. Order online, and choose... Read more
Best Buy is blowing out clearance 13-inch M1...
Best Buy is blowing out clearance Apple 13″ M1 MacBook Airs this weekend for only $649.99, or $350 off Apple’s original MSRP. Sale prices for online orders only, in-store prices may vary. Order... Read more
Low price alert! You can now get a 13-inch M1...
Walmart has, for the first time, begun offering new Apple MacBooks for sale on their online store, albeit clearance previous-generation models. They now have the 13″ M1 MacBook Air (8GB RAM, 256GB... Read more
Best Apple MacBook deal this weekend: Get the...
Apple has 13″ M2 MacBook Airs available for only $849 today in their Certified Refurbished store. These are the cheapest M2-powered MacBooks for sale at Apple. Apple’s one-year warranty is included,... Read more
New 15-inch M3 MacBook Air (Midnight) on sale...
Amazon has the new 15″ M3 MacBook Air (8GB RAM/256GB SSD/Midnight) in stock and on sale today for $1249.99 including free shipping. Their price is $50 off MSRP, and it’s the lowest price currently... Read more

Jobs Board

Early Preschool Teacher - Glenda Drive/ *Appl...
Early Preschool Teacher - Glenda Drive/ Apple ValleyTeacher Share by Email Share on LinkedIn Share on Twitter Read more
Senior Software Engineer - *Apple* Fundamen...
…center of Microsoft's efforts to empower our users to do more. The Apple Fundamentals team focused on defining and improving the end-to-end developer experience in Read more
Relationship Banker *Apple* Valley Main - W...
…Alcohol Policy to learn more. **Company:** WELLS FARGO BANK **Req Number:** R-350696 **Updated:** Mon Mar 11 00:00:00 UTC 2024 **Location:** APPLE VALLEY,California Read more
Medical Assistant - Surgical Oncology- *Apple...
Medical Assistant - Surgical Oncology- Apple Hill WellSpan Medical Group, York, PA | Nursing | Nursing Support | FTE: 1 | Regular | Tracking Code: 200555 Apply Now Read more
Early Preschool Teacher - Glenda Drive/ *Appl...
Early Preschool Teacher - Glenda Drive/ Apple ValleyTeacher Share by Email Share on LinkedIn Share on Twitter Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.