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

Summon your guild and prepare for war in...
Netmarble is making some pretty big moves with their latest update for Seven Knights Idle Adventure, with a bunch of interesting additions. Two new heroes enter the battle, there are events and bosses abound, and perhaps most interesting, a huge... | Read more »
Make the passage of time your plaything...
While some of us are still waiting for a chance to get our hands on Ash Prime - yes, don’t remind me I could currently buy him this month I’m barely hanging on - Digital Extremes has announced its next anticipated Prime Form for Warframe. Starting... | Read more »
If you can find it and fit through the d...
The holy trinity of amazing company names have come together, to release their equally amazing and adorable mobile game, Hamster Inn. Published by HyperBeard Games, and co-developed by Mum Not Proud and Little Sasquatch Studios, it's time to... | Read more »
Amikin Survival opens for pre-orders on...
Join me on the wonderful trip down the inspiration rabbit hole; much as Palworld seemingly “borrowed” many aspects from the hit Pokemon franchise, it is time for the heavily armed animal survival to also spawn some illegitimate children as Helio... | Read more »
PUBG Mobile teams up with global phenome...
Since launching in 2019, SpyxFamily has exploded to damn near catastrophic popularity, so it was only a matter of time before a mobile game snapped up a collaboration. Enter PUBG Mobile. Until May 12th, players will be able to collect a host of... | Read more »
Embark into the frozen tundra of certain...
Chucklefish, developers of hit action-adventure sandbox game Starbound and owner of one of the cutest logos in gaming, has released their roguelike deck-builder Wildfrost. Created alongside developers Gaziter and Deadpan Games, Wildfrost will... | Read more »
MoreFun Studios has announced Season 4,...
Tension has escalated in the ever-volatile world of Arena Breakout, as your old pal Randall Fisher and bosses Fred and Perrero continue to lob insults and explosives at each other, bringing us to a new phase of warfare. Season 4, Into The Fog of... | Read more »
Top Mobile Game Discounts
Every day, we pick out a curated list of the best mobile discounts on the App Store and post them here. This list won't be comprehensive, but it every game on it is recommended. Feel free to check out the coverage we did on them in the links below... | Read more »
Marvel Future Fight celebrates nine year...
Announced alongside an advertising image I can only assume was aimed squarely at myself with the prominent Deadpool and Odin featured on it, Netmarble has revealed their celebrations for the 9th anniversary of Marvel Future Fight. The Countdown... | Read more »
HoYoFair 2024 prepares to showcase over...
To say Genshin Impact took the world by storm when it was released would be an understatement. However, I think the most surprising part of the launch was just how much further it went than gaming. There have been concerts, art shows, massive... | Read more »

Price Scanner via MacPrices.net

Apple Watch Ultra 2 now available at Apple fo...
Apple has, for the first time, begun offering Certified Refurbished Apple Watch Ultra 2 models in their online store for $679, or $120 off MSRP. Each Watch includes Apple’s standard one-year warranty... Read more
AT&T has the iPhone 14 on sale for only $...
AT&T has the 128GB Apple iPhone 14 available for only $5.99 per month for new and existing customers when you activate unlimited service and use AT&T’s 36 month installment plan. The fine... Read more
Amazon is offering a $100 discount on every M...
Amazon is offering a $100 instant discount on each configuration of Apple’s new 13″ M3 MacBook Air, in Midnight, this weekend. These are the lowest prices currently available for new 13″ M3 MacBook... Read more
You can save $300-$480 on a 14-inch M3 Pro/Ma...
Apple has 14″ M3 Pro and M3 Max MacBook Pros in stock today and available, Certified Refurbished, starting at $1699 and ranging up to $480 off MSRP. Each model features a new outer case, shipping is... Read more
24-inch M1 iMacs available at Apple starting...
Apple has clearance M1 iMacs available in their Certified Refurbished store starting at $1049 and ranging up to $300 off original MSRP. Each iMac is in like-new condition and comes with Apple’s... Read more
Walmart continues to offer $699 13-inch M1 Ma...
Walmart continues to offer new Apple 13″ M1 MacBook Airs (8GB RAM, 256GB SSD) online for $699, $300 off original MSRP, in Space Gray, Silver, and Gold colors. These are new MacBook for sale by... Read more
B&H has 13-inch M2 MacBook Airs with 16GB...
B&H Photo has 13″ MacBook Airs with M2 CPUs, 16GB of memory, and 256GB of storage in stock and on sale for $1099, $100 off Apple’s MSRP for this configuration. Free 1-2 day delivery is available... Read more
14-inch M3 MacBook Pro with 16GB of RAM avail...
Apple has the 14″ M3 MacBook Pro with 16GB of RAM and 1TB of storage, Certified Refurbished, available for $300 off MSRP. Each MacBook Pro features a new outer case, shipping is free, and an Apple 1-... Read more
Apple M2 Mac minis on sale for up to $150 off...
Amazon has Apple’s M2-powered Mac minis in stock and on sale for $100-$150 off MSRP, each including free delivery: – Mac mini M2/256GB SSD: $499, save $100 – Mac mini M2/512GB SSD: $699, save $100 –... Read more
Amazon is offering a $200 discount on 14-inch...
Amazon has 14-inch M3 MacBook Pros in stock and on sale for $200 off MSRP. Shipping is free. Note that Amazon’s stock tends to come and go: – 14″ M3 MacBook Pro (8GB RAM/512GB SSD): $1399.99, $200... Read more

Jobs Board

*Apple* Systems Administrator - JAMF - Syste...
Title: Apple Systems Administrator - JAMF ALTA is supporting a direct hire opportunity. This position is 100% Onsite for initial 3-6 months and then remote 1-2 Read more
Relationship Banker - *Apple* Valley Financ...
Relationship Banker - Apple Valley Financial Center APPLE VALLEY, Minnesota **Job Description:** At Bank of America, we are guided by a common purpose to help Read more
IN6728 Optometrist- *Apple* Valley, CA- Tar...
Date: Apr 9, 2024 Brand: Target Optical Location: Apple Valley, CA, US, 92308 **Requisition ID:** 824398 At Target Optical, we help people see and look great - and Read more
Medical Assistant - Orthopedics *Apple* Hil...
Medical Assistant - Orthopedics Apple Hill York Location: WellSpan Medical Group, York, PA Schedule: Full Time Sign-On Bonus Eligible Remote/Hybrid Regular Apply Now Read more
*Apple* Systems Administrator - JAMF - Activ...
…**Public Trust/Other Required:** None **Job Family:** Systems Administration **Skills:** Apple Platforms,Computer Servers,Jamf Pro **Experience:** 3 + years of Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.