TweetFollow Us on Twitter

Stream for All Seasons

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

A Stream for All Seasons

by Neil Mayhew, Calgary, AB

A lightweight ostream-compatible class using Pascal strings

Getting Your Message Across

They say a picture is worth a thousand words, but a picture without any words is often hard to figure out. The Macintosh user interface does a wonderful job of being graphical, but it also uses text very effectively in appropriate places.

Can you imagine using a large application without a single word of text on its menus and dialogs? Consumer electronics devices that use cute little icons for everything can be extremely puzzling until you have spent a little time studying the manual-in text form, of course.

The ability to manipulate text is therefore crucial to even the most graphical Macintosh program. Text in the user-interface-rather than text as data-uses Pascal string conventions. This doesn't always fit very well with C, which has it's own string conventions. The problem is worse with C++, which has a much richer standard library of text manipulation functions-none of which understand the Pascal string format.

However, the power and flexibility of C++ can be used to overcome many of these limitations in a way that is both transparent and economical. Yet again, templates, inline functions, and stack-based objects can come to our rescue-if we know how to take advantage of them.

This month's article describes a lightweight class that builds Pascal strings using the << notation used by the standard C++ ostream. Next month, I'll introduce a program that uses STL-compatible iterators to monitor your System Folder for unexpected changes to extensions and control panels by wayward installer programs.

Printf Considered Harmful

Much of the time, text that is being displayed in the user-interface is a fixed string taken from a resource. Sometimes, though, text is built up from a number of pieces, some fixed and some not. We have come to appreciate having the text of the Undo menu item change to reflect the action that is being undone. In fact, there are all kinds of situations in which user-interface text needs to be built up programmatically: prompts, error messages, status messages, progress messages, and so on. In all of these cases, the generated text needs to be inserted into the user-interface as a Pascal string.

There are a number of simple approaches to doing this. For alerts and dialogs, the ParamText mechanism is useful. Or, a sequence of string-concatenation operations can be performed. Even better, one of the printf family of functions can be used to format a string with complex substitutions via % characters.

Although the printf approach is powerful, it has some severe limitations in any setting:

  • substitutions are not type-checked or counted, and are therefore error-prone
  • sprintf's output string is not protected against overflow

In a Mac OS setting, there are some additional limitations:

  • Pascal strings are not understood as arguments
  • the output is a C string rather than a Pascal string
  • printf cannot be used at interrupt time or in a code resource
  • the C i/o library increases code size considerably

The C++ iostream library takes care of the first two problems very effectively, using ostringstream, but the Mac OS-specific problems remain. How could we retain the elegant << syntax and yet be able to handle Pascal strings with very low overheads?

Simple Is Beautiful

Early on in my programming career, I learnt that writing large, complicated programs is relatively easy. What is hard is keeping them simple! A more experienced colleague had a wonderful knack for developing small, sophisticated programs that contained just enough functionality for the task in hand-and no more. The C++ iostreams library quite rightly has a huge range of functionality, much of which we do not need for our limited task of formatting strings. If we select just the functionality that is useful to us-concatenating strings, integers and floating-point numbers-it should not be hard to implement a lightweight class that supports << syntax for those items.

The output needs to be a Pascal string, and various manipulation capabilities for Pascal strings will be needed. It makes sense therefore first to develop a C++ encapsulation of Pascal strings. This will simplify our stream implementation, and will be a useful facility in itself.

As I mentioned in last month's article, the constant template-argument feature of C++ is specifically aimed at the implementation of fixed-length arrays such as Pascal strings. This enables us to store various sizes of strings on the stack, instead of having to allocate variable-length blocks from the heap. Without this, we would not be able to meet our design criterion of usability at interrupt time or in a code resource.

Making It To First Base

Last month's template used only inline functions, and these were sufficiently simple that they more or less disappeared in the generated code. The functions to process strings are not so simple, however. If we make them inline, we face two possible outcomes: either a substantial chunk of code will be inserted into every routine that calls one of our template's methods, or the compiler will opt to make them non-inline anyway.

What happens if we have non-inline methods in a template class? Ordinarily, we would put the implementation of non-inline methods into a .cp file that is compiled and linked. However, most development environments (CodeWarrior included) are not able to handle template methods this way. A separate copy of the method needs to be generated for each set of template arguments that are used with its class, and the compiler can't know which copies to generate until it has compiled the whole project. This restriction is normally overcome by putting the implementation of the method into the header file, but not declaring it inline. The compiler generates 'real' code for each combination of template arguments, and the linker takes care of eliminating redundant copies.

In a large project, this can slow build times by an order of magnitude. This is especially unfortunate when the majority of the copies generate almost identical machine code. For example, in the case of our Pascal string class, this would mean a separate copy of the code for each different string size (31, 63, 255, etc.) even though all the copies are performing virtually the same operation. This effect is known as code bloat.

If the foregoing paragraphs haven't made much sense to you, Don't Panic! Just remember that code bloat is something you definitely want to avoid. However, this doesn't mean that you have to stay clear of templates altogether. There is a very simple solution.

If you find that the majority of your template's methods are going to generate virtually identical code, then they are not really part of the template at all. Why not put them into a non-template class and have your template call them using very simple inline methods? The easiest way to do this is by having your template inherit from a non-template class that contains most of the actual code. This technique is known as a template class with a non-template base, and is sufficient to cure most kinds of code bloat. There are other solutions for the more difficult cases, but we won't go into those here. If you are interested, try looking up explicit instantiation and partial specialization in any good C++ book.

A Solid Foundation

The algorithms to copy, append and compare Pascal strings are the same regardless of the buffer length. The first step in building our C++ encapsulation is therefore to define a non-template base class that implements this functionality. We could of course simply use global functions, but by including them in the class, we can give them shorter, simpler names. We also don't have to worry whether they provide behavior that would be needed when used from outside the class, such as returning an error on overflow.

The class declaration looks like this:

class PascalStringBase {
protected:
	typedef unsigned char uchar;
	static void assign(int max, uchar* dst, const uchar*);
	static void assign(int max, uchar* dst, const char*);
	static void append(int max, uchar* dst, const uchar*);
	static void append(int max, uchar* dst, const char*);
	static void append(int max, uchar* dst, uchar);
	static void append(int max, uchar* dst, char);
	static int  compare(const uchar*, const uchar*);
	static int  compare(const uchar*, const char*);
	static bool equal(const uchar*, const uchar*);
	static bool equal(const uchar*, const char*);
};

Note that it is possible to pass in both C and Pascal strings for assignment, concatenation and comparison. This is done by overloading the function names to handle both signed and unsigned string pointers. We also haven't arranged to return any error codes, as we are not interested in checking for error codes whilst building strings. Therefore the assign and append functions silently truncate the destination string if overflow occurs.

There is a good reason why all these functions are declared static. The string buffer, referred to in these functions by the dst pointer, has to be a part of the template, since the length will be a template argument. The buffer address must therefore be passed as one of the arguments, along with its length. Since these methods do not refer to any data members (instance variables), they are independent of any instance of the class and they should properly be made static. In the same way, no constructors or destructor are necessary since there is nothing to initialize or release.

The implementation of PascalStringBase is not very interesting, so we won't include it here. It defines a local version of strlen to help with the C strings, and it uses BlockMoveData for copying data around. I opted to use RelString and CompareString for the Pascal comparisons, but this would need to be changed if the code was ever going to be used from an interrupt routine. The other methods could also have used standard Toolbox or glue functions, but overflow management would still have been necessary.

Dressing It Up

Armed with this low-level functionality, we can define a clean, efficient C++ encapsulation of Pascal strings:

template<unsigned char max>
class PascalString : public PascalStringBase {

protected:
	uchar data[max + 1];

public:
	PascalString()
			{ clear(); }
	PascalString(const PascalString& p)
			{ *this = p; } // Calls operator =
	PascalString(const uchar* p)
			{ *this = p; } // Calls operator =
	PascalString(const char* p)
			{ *this = p; } // Calls operator =

	PascalString& clear()
			{ data[0] = 0; return *this; }
	
	// Assignment
	PascalString& operator = (const PascalString& p)
			{ assign(max, data, p); return *this; }
	PascalString& operator = (const uchar* p)	
			{ assign(max, data, p); return *this; }
	PascalString& operator = (const char* p)
			{ assign(max, data, p); return *this; }

	// Concatenation
	PascalString& operator += (const uchar* p)
			{ append(max, data, p); return *this; }
	PascalString& operator += (const char* p)
			{ append(max, data, p); return *this; }

	PascalString& operator += (uchar c)
			{ append(max, data, c); return *this; }
	PascalString& operator += (char c)
			{ append(max, data, c); return *this; }
	
	// Comparison
	bool operator <  (const uchar* p) const
			{ return compare(data, p) < 0; }
	bool operator <  (const char* p) const
			{ return compare(data, p) < 0; }
	bool operator == (const uchar* p) const
			{ return equal(data, p); }
	bool operator == (const char* p) const
			{ return equal(data, p); }

	// Conversion operator
	operator const uchar * () const { return data; }
	
	// Information
	uchar length()  const { return data[0]; }
	uchar maximum() const { return max; }
};

All of the methods are trivial wrappers for the 'worker' functions from the base class. Yet, the constant template argument max takes care of the ugly details of buffer allocation and length checking. objects of any size can be defined as stack variables, and take up no more space than the data itself. In fact, because the memory layout of the object is the same as the equivalent raw Pascal string, an unsigned char* can be cast to a PascalString<nnn>* that is used without restriction. A static inline method that performs such a cast would be a useful addition.

Note that because we have declared max as an unsigned char, the compiler will ensure that its value is within the correct range.

PascalString explicitly defines three of the fundamental four methods that always need to be considered when designing a class.

  • The default constructor (no arguments) is necessary to ensure that the default value of a PascalString is a zero-length string.
  • If the copy constructor (const PascalString&) is not explicitly defined, the compiler will supply an automatic version which copies every byte of the entire object. For larger values of max, and short strings, this is undesirable. Like the others, this constructor delegates the work to the equivalent assignment operator. No prior initialization is needed, since any previous state is ignored by PascalString's assignment operators.
  • The compiler will also supply a default assignment operator if one is not explicitly defined. Again, this would copy the entire object in every case. We already have an assign function that takes a const unsigned char*, so we make implicit use of the conversion operator and call this function to perform the copy.
  • The destructor is the only one of the four that is not defined, since there is no cleanup needed when a PascalString is destroyed.

The conversion operator allows a PascalString to be used in any context that requires a raw Pascal string. It implicitly converts a PascalString to a const unsigned char*. However, since this gives up any control of overflow checking, the pointer has to be const to prevent modification of the internal data. If max is 255, it is always safe to pass a writable pointer to the internal data, but there is no simple way to arrange for this to happen automatically for just this value of max. A compromise would be to define a method that takes a size as an argument, returns a writable pointer to the data if the size is less than or equal to max, and returns nil otherwise. This would allow a PascalString to be passed as a receive-buffer to Toolbox routines without the need for casting.

A more radical solution would be to fool around with MacTypes.h to redefine Str31 as PascalString<31>* and so on, so that the Mac OS APIs appear to be expecting instances of our template class instead of raw pointers. Since the pointer value that is passed is the same, the APIs will work as expected, but the API arguments will automatically be checked for correctness of length. It would be more correct, but more work, to write inline wrapper functions for all the APIs that take writable Pascal strings as arguments. These would simply perform a suitable cast on their PascalString& argument and pass the result on to the OS. The API name could be retained if the wrappers were placed in a different namespace.

Heading For The Goal

Our main goal is the creation of a stream class. This is not hard now that we have the needed PascalString class.

It doesn't make much sense to build a stream on anything less than a PascalString<255>, so our stream class doesn't need to be a template since we don't need to specify a size. C++ does allow us to specify default values for template arguments, so we could make it a template with a default size of 255. However, this would start another round in the fight against code bloat, and there is no need for this additional complication. Remember, simple is beautiful!

We now have the interesting phenomenon of a class inheriting from a template inheriting from a class:

class PascalStringStream : public PascalString<255>
{
public:
	// Concatenation
	PascalStringStream& operator << (const unsigned char* s)
			{ return *this += s; }
	PascalStringStream& operator << (const char* s)
			{ return *this += s; }
	PascalStringStream& operator << (unsigned char c)
			{ return *this += c; }
	PascalStringStream& operator << (char c)
			{ return *this += c; }

	// Numbers
	PascalStringStream& operator << (long);
	PascalStringStream& operator << (double);

	// Convenience
	const unsigned char* contents() const { return *this; }
	PascalStringStream&  reset() { clear(); return *this; }
};

It makes sense to use inheritance rather than aggregation (embedding PascalString as a data member), since we want our stream to be more or less interchangeable with a PascalString. It also saves us the effort of redeclaring several methods. However, using aggregation also works quite well.

We have chosen not to implement advanced features such as field-widths and justification. This is very rarely appropriate in a GUI context with proportional-width fonts, and we want to keep the size and complexity to a minimum.

Why create an extra class?

The concatenation methods are just re-presentations of the ones from PascalString, using a different operator. Why bother with this? Because the associativity of << is different from +=, so that it becomes possible to 'cascade' a series of concatenations in typical ostream style:

s << "\pOverwrite " << oldf << "\p with " << newf << "\p?"

In which case, why not just put all this functionality into PascalString? Because accidental use of some of PascalStringStream's operators may bring surprises. For example, if we accidentally pass an int instead of a char to a concatenation operation on a string, do we want a decimal representation appended to the end of a string? A warning from the compiler would be preferable. By requiring us to include stream functionality explicitly, through our choice of class, these ambiguities are avoided.

There is another advantage to using the << operator. Every expression that uses a PascalStringStream is upwardly compatible with the standard C++ ostream. If, for example, you write some utility code that dumps out parameters from OpenTransport, that code will work equally well with either type of stream. If you later decide to send the output to a file rather than to a dialog, you only need to change the stream declaration and everything will work as expected. If the formatting capabilities of PascalStringStream turn out to be too simple, you can move up to using an ostringstream without a problem.

It is even possible to place your text-generating code inside a template function (not a template class), so that it will work with any type of stream you choose to pass to it. Just prefix the function or method definition with template<class Stream> and use Stream wherever you need to declare a stream as a variable or an argument. The compiler can infer the template argument from the function arguments, so you can still call the function as if it were a non-template one.

Internal details

There are only two methods that are not trivial and inline. These are implemented in a .cp file. The details are not very interesting, but for completeness, we'll include them here. They each create a text representation of the number in a buffer on the stack, and append it to the main string.

#include <fp.h>		// Mac OS APIs for manipulating floating-point numbers

// Decimal integer output

PascalStringStream&
		PascalStringStream::operator << (long n)
{
	Str31 num;
	NumToString(n, num);
	return *this << num;
}

// Quick-and-easy floating-point output: X.XXXXXe±Y

const int precision	=  6;

PascalStringStream&
		PascalStringStream::operator << (double d)
{
	decform form;
	decimal result;
	char output[DECSTROUTLEN];

	form.style = FLOATDECIMAL;
	form.digits = precision;
	num2dec(&form, d, &result);
	
	// Use fixed-point notation if it fits within a small enough space
	if (3 >= result.exp && result.exp >= -precision - 3)
	{
		form.style = FIXEDDECIMAL;
		form.digits = -result.exp;
		num2dec(&form, d, &result);
	}

	dec2str(&form, &result, output);

	return *this << output;
}

Again, this code should not be called at interrupt time, due to the use of NumToString. However, it would be very easy to replace the call to NumToString with something homegrown that doesn't move memory.

Note that it would not be hard to make PascalStringStream fully compatible with Mac OS international number formatting, which would not be possible with a standard ostream.

Putting it all to use

The reset method is provided as a convenience. It enables a single stream to be reused multiple times in the same function, and because it returns a reference to the stream, it can be used as the first operation in a cascade:

	gMessage.reset() << "\pReading data from " << filename;

An entire cascade expression can also be passed as the argument to a function:

	SetDialogItemText(item, message << "Hello, " << name);

In this case, a C string literal has been used instead of a Pascal one; it now makes very little difference which kind is used.

Rather than defining a single stream at the start and reusing it repeatedly, it is also possible to use unnamed temporaries for creating and passing stream-generated text:

	SetDialogItemText(item,
		PascalStringStream() << "Hello, " << name);

This allocates and constructs an unnamed temporary variable on the stack, performs all the operations in the cascade, passes a const unsigned char * to SetDialogItemText, and destroys the variable when it is no longer needed. Quite a lot of work in one line! And not too cryptic either.

The only drawback is that the compiler's idea of 'no longer needed' may be the end of the current block, and not just the statement containing the call to SetDialogItemText. If you use many calls like this in one function, you can end up needing a lot of stack space. This isn't usually much of a problem, but you do need to be aware of it. The best solution is to have a reusable named variable instead. Another is to limit the scope using additional sets of braces. It is a pity that one of the final additions to the official C++ standard was to increase the longevity of unnamed temporaries.

A note on localization

All of our examples have used embedded string literals and string variables. In a production program, we would need to allow for localization by storing strings in resources. It is quite possible to do this in combination with PascalStringStream, and a class that can assist with this is mentioned in a later section.

However, there is a well-known localization issue that PascalStringStream does not help to address. This concerns the order of substitutions in a generated message. Different natural languages may need to make the data insertions in different orders, so the localization process needs to be able to specify the relative order of the different parts of the message. This is handled in a primitive way by the ParamText mechanism, but a general solution is more of a challenge.

A solution is more or less impossible when using PascalStringStream, because the substitution pattern is hard-coded within the program. On the other hand, allowing substitutions to be specified apart from the code makes it impossible to check for consistency with the data, which was one of our key design criteria. Real life is full of conflicts!

PascalStringStream is therefore not a universal solution to all text-handling needs within the UI. It is however both powerful and easy to use in a wide variety of situations. It is especially valuable for debugging, and for the development of applications that will not be heavily localized.

Bombs Away!

Before I developed PascalStringStream, I thought I was very smart in defining an error-reporting function that used ParamText to concatenate its arguments, and used C++ overloading to allow this to be called with a variety of argument numbers and types. This worked quite well in some ways, although I sometimes found it frustrating not being able to handle more than four substitutions.

I definitely found it a problem, however, that my library function was dependent on resources. This meant that the appropriate resource file had to be included in the build alongside the library code, and resource IDs for the ALRT and DITL had to be reserved for the library. It also meant that my reporting functions were not completely bombproof. If the resource map got scribbled on and was no longer functional, or the resources didn't get included in the build, there was nothing I could do except 'beep in despair'.

Another frustration was that the Alert always had a fixed size, regardless of the amount of text in it. It needed to be big enough to hold the largest amount of text that the function was capable of displaying (4 x 255 bytes), and yet this looked clumsy in the common case when only a few bytes were used.

I must confess I was rather envious of our fellow-programmers 'on the other side'. That system has an API that takes a string and puts up a dialog box that is sized to fit the text. There is virtually no way that the call can fail since it is handled by the system.

That was until I thought of using the Mac OS Notification Manager. This system-provided facility does not depend on resources, is almost incapable of failing, can be used from an interrupt routine or extension, and (on Mac OS 8) sizes itself to fit the text! The only restriction is that it doesn't support the ParamText mechanism. That is what finally spurred me into designing PascalStringStream.

A better mousetrap

I replaced my once-great set of error functions with very economical equivalents layered on top of PascalStringStream and the Notification Manager. To assist with this, I wrote a simple function that wrapped the NM interface, called Notify:

static pascal void callbackProc(NMRecPtr record) {
	record->nmRefCon = 1;	// Signal completion
}

void Notify(const unsigned char* msg, bool beep) {

	// Code Fragment Manager stuff
#if TARGET_RT_MAC_CFM
	RoutineDescriptor callbackRoutine =
			BUILD_ROUTINE_DESCRIPTOR(uppNMProcInfo,
					 callbackProc);
#else
	#define callbackRoutine callbackProc
#endif

	if (msg == 0)
		msg = "\p<null message>";

	// Prepare notification request
	NMRec request;
	request.qType = nmType;
	request.nmMark = 1;
	request.nmIcon = 0;
	request.nmSound = beep ? Handle(-1) : 0; // -1 means system beep
	request.nmStr = const_cast<StringPtr>(msg);
	request.nmResp = &callbackRoutine;
	request.nmRefCon = 0;
	
	// This can only fail if qType != nmType, which is impossible here
	if (NMInstall(&request) != noErr) {
		SysBeep(60);
		return;
	}
	
	// Await completion before destroying msg
	while (request.nmRefCon == 0) {
		EventRecord event;
		// Wait 10 ticks, and don't fetch any events
		WaitNextEvent(0, &event, 10, 0);
	}

	NMRemove(&request);
}

Of course, the call to WaitNextEvent would need to be changed if this code was used in an extension or an interrupt routine. The details of the replacement would be determined by the context. The most likely strategy would be to require that msg was global or static, and to enable auto-removal of the NMRec by the Notification Manager instead of the callback routine. A more sophisticated solution would be to make Notify a mix-in class instead of a function, with a virtual method for the callback.

Wrapping it up

The error-reporting functions now look like this:

// Use a global to avoid double-evaluation in macros
static OSErr					gLastError;
// Save space by defining repeated text once
static unsigned char	gErrorIntro[] = "\p\r\rError ";

// Display a stream message, with and without a beep
#define Message(X)	Notify(PascalStringStream() << X, false)
#define Error(X)		Notify(PascalStringStream() << X, true)

// Display a stream message and an error number
#define Failure(E, X)	Error(X << gErrorIntro << (E))

// Conditionally display an OS error message
#define OSFailure(E, X) \
	((gLastError = (E)) != noErr && \
	 (Failure(gLastError, X), true))

// Conditionally display a Resource error message
#define ResFailure(H, X) \
	((H) == nil && \
	 (gLastError = ResError(), Failure(gLastError, X), true))

These functions are used like this:

Message("\pHello, " << name);

Failure(theErr, "\pCould not write " << filename);

if (OSFailure(FSpOpenDF(&spec, fsWrPerm, &refNum),
			"Could not open "<<name<<" for writing")
	return;

The reason these are defined as macros and not functions is twofold. First, a macro can supply the repetitive PascalStringStream() << part. There is no way to avoid including this somewhere in the caller. Second, with the conditional versions, the message-building code does not need to be evaluated when there is not an error. If these were real functions, the message would have to be passed as an argument and therefore would always be evaluated.

Bells And Whistles

Although we chose not to include field-width and justification features in our stream class, a few more features would be extremely useful when displaying debug messages, with DebugStr or with Notify. In particular, we do not have any way to display hexadecimal values. It is also very helpful to have a convenient way of displaying four-character codes such as file and resource types.

It would be possible to add more machinery to PascalStringStream to handle these cases, but there is a cleaner way to achieve the same result. Recall how our number-formatting routines created a text representation of the number in a buffer on the stack, and then appended that to the main string. We can use exactly the same technique for other types of data, without adding more methods to our stream. Instead, we create a subclass of PascalString for each type that we wish to format. This will have a constructor that converts the datatype into its text representation. The newly constructed object can then be passed to the stream for inclusion in the output. An unnamed temporary is ideal for this purpose.

Applying this to the additional features already mentioned, we can define classes hex and ostype that are used like this:

	message << "Mask: " << hex(mask, 8);
	message << "File type: " << ostype(type);

The first line will zero-fill the value of mask to 8 digits and append it to message. The second line appends the four characters of type, without quotes.

These classes are declared in a header file like this:

class hex : public PascalString<sizeof(unsigned long) * 2> {
	public:
		hex(unsigned long x, int width = 0);
};

class ostype : public PascalString<sizeof(OSType)> {
	public:
		ostype(OSType t);
};

The default value of the width parameter of the hex constructor suppresses zero-filling. An additional constructor taking a void * argument could be defined if it is necessary to display the values of pointers.

The implementation of these constructors can be placed in a .cp file, since these are not template classes. The hex constructor chops its argument into nibbles and appends the corresponding characters to the buffer. The ostype constructor performs a four-byte copy from its argument to its buffer and sets the length to four. In both cases, the underlying string buffer is just big enough to contain the corresponding data, and no more. As you can see, any constant expressions can be used as the size.

A similar class could be defined that initializes itself with a string stored in a resource. Such helper classes are easy to implement and easy to use, largely because of the design of the PascalString class that they are based on.

The Thoroughly Modern Macintosh

There is no longer a need to use printf in a modern C++ program. The iostream classes are superior in almost every way. In some situations standard iostreams come at too high a cost, or don't work with our data, but we still don't have to sacrifice the entire ostream paradigm. The amazing flexibility of C++ allows us to develop extensions to the language that are compatible with the standard library, and yet are well adapted to our specific context.

The solution involves templates, inheritance, inline and non-inline functions, and the use of unnamed temporaries, yet nothing is obscure or complicated. An average programmer with suitable experience should be able to produce this kind of tool on a regular basis. The effort involved in developing the PascalString and PascalStringStream classes was not high, but the results have wide-ranging potential for reusability.

So throw out those out-of-date techniques, and spend a little time each week polishing your tools-the results are definitely worth it.


Neil Mayhew works for Wycliffe Bible Translators, a non-profit organization dedicated to translating the Bible for the world's 400 million people that do not have it in their own language. Neil started programming in C in 1983, and graduated to the Mac in 1989. When he's not at his Mac or trying to beat his kids at video games you might find him flying a stunt kite if it's windy, or throwing a boomerang if it's not.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Fallout Shelter pulls in ten times its u...
When the Fallout TV series was announced I, like I assume many others, assumed it was going to be an utter pile of garbage. Well, as we now know that couldn't be further from the truth. It was a smash hit, and this success has of course given the... | Read more »
Recruit two powerful-sounding students t...
I am a fan of anime, and I hear about a lot that comes through, but one that escaped my attention until now is A Certain Scientific Railgun T, and that name is very enticing. If it's new to you too, then players of Blue Archive can get a hands-on... | Read more »
Top Hat Studios unveils a new gameplay t...
There are a lot of big games coming that you might be excited about, but one of those I am most interested in is Athenian Rhapsody because it looks delightfully silly. The developers behind this project, the rather fancy-sounding Top Hat Studios,... | Read more »
Bound through time on the hunt for sneak...
Have you ever sat down and wondered what would happen if Dr Who and Sherlock Holmes went on an adventure? Well, besides probably being the best mash-up of English fiction, you'd get the Hidden Through Time series, and now Rogueside has announced... | Read more »
The secrets of Penacony might soon come...
Version 2.2 of Honkai: Star Rail is on the horizon and brings the culmination of the Penacony adventure after quite the escalation in the latest story quests. To help you through this new expansion is the introduction of two powerful new... | Read more »
The Legend of Heroes: Trails of Cold Ste...
I adore game series that have connecting lore and stories, which of course means the Legend of Heroes is very dear to me, Trails lore has been building for two decades. Excitedly, the next stage is upon us as Userjoy has announced the upcoming... | Read more »
Go from lowly lizard to wicked Wyvern in...
Do you like questing, and do you like dragons? If not then boy is this not the announcement for you, as Loongcheer Game has unveiled Quest Dragon: Idle Mobile Game. Yes, it is amazing Square Enix hasn’t sued them for copyright infringement, but... | Read more »
Aether Gazer unveils Chapter 16 of its m...
After a bit of maintenance, Aether Gazer has released Chapter 16 of its main storyline, titled Night Parade of the Beasts. This big update brings a new character, a special outfit, some special limited-time events, and, of course, an engaging... | Read more »
Challenge those pesky wyverns to a dance...
After recently having you do battle against your foes by wildly flailing Hello Kitty and friends at them, GungHo Online has whipped out another surprising collaboration for Puzzle & Dragons. It is now time to beat your opponents by cha-cha... | Read more »
Pack a magnifying glass and practice you...
Somehow it has already been a year since Torchlight: Infinite launched, and XD Games is celebrating by blending in what sounds like a truly fantastic new update. Fans of Cthulhu rejoice, as Whispering Mist brings some horror elements, and tests... | Read more »

Price Scanner via MacPrices.net

Verizon has Apple AirPods on sale this weeken...
Verizon has Apple AirPods on sale for up to 31% off MSRP on their online store this weekend. Their prices are the lowest price available for AirPods from any Apple retailer. Verizon service is not... Read more
Apple has 15-inch M2 MacBook Airs available s...
Apple has clearance, Certified Refurbished, 15″ M2 MacBook Airs available starting at $1019 and ranging up to $300 off original MSRP. These are the cheapest 15″ MacBook Airs for sale today at Apple.... Read more
May 2024 Apple Education discounts on MacBook...
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 up to $300 off the purchase of a new MacBook... Read more
Clearance 16-inch M2 Pro MacBook Pros in stoc...
Apple has clearance 16″ M2 Pro MacBook Pros available in their Certified Refurbished store starting at $2049 and ranging up to $450 off original MSRP. Each model features a new outer case, shipping... Read more
Save $300 at Apple on 14-inch M3 MacBook Pros...
Apple has 14″ M3 MacBook Pros with 16GB of RAM, Certified Refurbished, available for $270-$300 off MSRP. Each model features a new outer case, shipping is free, and an Apple 1-year warranty is... Read more
Apple continues to offer 14-inch M3 MacBook P...
Apple has 14″ M3 MacBook Pros, Certified Refurbished, available starting at only $1359 and ranging up to $270 off MSRP. Each model features a new outer case, shipping is free, and an Apple 1-year... Read more
Apple AirPods Pro with USB-C return to all-ti...
Amazon has Apple’s AirPods Pro with USB-C in stock and on sale for $179.99 including free shipping. Their price is $70 (28%) off MSRP, and it’s currently the lowest price available for new AirPods... Read more
Apple Magic Keyboards for iPads are on sale f...
Amazon has Apple Magic Keyboards for iPads on sale today for up to $70 off MSRP, shipping included: – Magic Keyboard for 10th-generation Apple iPad: $199, save $50 – Magic Keyboard for 11″ iPad Pro/... Read more
Apple’s 13-inch M2 MacBook Airs return to rec...
Apple retailers have 13″ MacBook Airs with M2 CPUs in stock and on sale this weekend starting at only $849 in Space Gray, Silver, Starlight, and Midnight colors. These are the lowest prices currently... Read more
Best Buy is clearing out iPad Airs for up to...
In advance of next week’s probably release of new and updated iPad Airs, Best Buy has 10.9″ M1 WiFi iPad Airs on record-low sale prices for up to $200 off Apple’s MSRP, starting at $399. Sale prices... Read more

Jobs Board

Liquor Stock Clerk - S. *Apple* St. - Idaho...
Liquor Stock Clerk - S. Apple St. Boise Posting Begin Date: 2023/10/10 Posting End Date: 2024/10/14 Category: Retail Sub Category: Customer Service Work Type: Part Read more
*Apple* App Developer - Datrose (United Stat...
…year experiencein programming and have computer knowledge with SWIFT. Job Responsibilites: Apple App Developer is expected to support essential tasks for the RxASL 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
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
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
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.