OpenStep Programming Intro
Volume Number: 13
Issue Number: 5
Column Tag: OPENSTEP
What's Inside OPENSTEP... Really?
by Dave Klingler
An Introduction
The next several pages will be an attempt to give you a complete overview of
OPENSTEP's development system as it might pertain to Rhapsody. Of course, we can't
quite cover everything, but hopefully when you're done reading you'll know a little bit
about OPENSTEP's basic parts and why developing under OPENSTEP has brought a
happy grin to the faces of so many lucky programmers. OPENSTEP isn't perfect, but
it's a well-designed springboard for a Macintosh programming renaissance during the
next few years.
The major elements of OPENSTEP are the Foundation Kit, Interface Builder, Display
Postscript, and Objective-C. There are some important minor players as well:
ProjectBuilder, Header Viewer, Librarian and pswrap. All of these elements are
probably unfamiliar to you unless you've used NEXTSTEP or OPENSTEP before, so let's
start by taking a quick run through the development process under OPENSTEP to see
where the pieces fit. We'll then go back to take a closer look at the major players.
The Development Process
The development cycle under OPENSTEP is fast enough that it tends to leave you
wondering what you skipped when you're finished. I'll outline my own development
cycle here; you'll probably evolve your own techniques once you start programming
under OPENSTEP.
I usually begin by designing classes. This process is similar in any modern language; I
figure out what data my program will use and derive a rough list of the major classes
involved. Optimizing for speed versus efficiency, or server storage versus local
storage, maintainability or even communications bandwidth will influence the design
of my class system.
Over a period of about my first two years working with Objective-C I noticed that my
techniques for designing new objects changed radically and often. I attribute that to the
seemingly subtle, but huge differences between designing for Objective-C and for
conventional languages like C or Pascal or even quasi-oop languages like C++. I take
heavy advantage of dynamic typing and dynamic binding, which are not available in
most languages other than Smalltalk. Java is currently mostly statically typed as well,
but JDK 1.1 has reflection, a clear sign that the language is evolving in the right
direction.
Bizarrely enough, I've gotten to the point where I write almost all my overview
documentation before I write most of the code. I'll write test code to make sure a
concept's going to work if it's something radical, but for the most part the code's
completely described before it's written. I'm finished when any competent Objective-C
programmer could write my app from my docs.
The documentation process is made easier by the fact that every Objective-C class is
comprised of an interface file and a class implementation file. The interface file is a
fancy header file that contains all the class variables that might be needed by a
programmer using the class, plus prototypes for class methods and functions. When
I'm designing a new class, of necessity I have to decide how that class will interact with
the outside world. Whatever's externally available goes in the interface file and, with a
comparatively tiny amount of work, an explanation goes with it into the documentation.
Now I've got my interface files and all the documentation someone would need to use any
of the classes in my new app.
At this point I'll either hand it off to a group of programmers for them to complete or
paste the method and function prototypes from the interface into a new file called the
class implementation file and begin filling out the class. This is the file that will
actually contain the code for the class, and if you want to sell your classes you can
compile this file and sell the result along with the interface file and the documentation.
No one gets your source code, but they do get an explanation of all the classes'
externally available variables and methods. Their classes can interface with your
classes without knowing what's inside them, and that, after all, is what
object-oriented programming is all about.
Lots of people in the OPENSTEP world sell classes instead of applications. One of the
first classes I bought was BenaTong's Serial class, so that I could save some work
writing a telecommunications package.
Because I've got a list of method prototypes to implement, I can just work my way
through the file coding each one. When I reach the last one the class will be finished
and quite often it "just works" with little or no debugging.
After designing the classes, it's time to pull out ProjectBuilder. ProjectBuilder
manages the process of building a new application, module or palette under OPENSTEP.
I tell ProjectBuilder to create a new project, create a few icons for the program and
data files and drop them into ProjectBuilder's various boxes, drop in the new classes
and double-click the "nib" file that ProjectBuilder has created for me. "Nib" stands
for NeXT InterfaceBuilder, and that file contains the elements of the interface for the
program, arguably most of it. Using Interface Builder might be compared to using a
graphical version of ResEdit, but InterfaceBuilder does much more.
Next I design the rest of the program. Keep in mind that most of the elements in the
average program other than the ones you've just designed (and they're usually very
few) are already running in the OPENSTEP OS. This means that you can use "live
versions of those objects when you design your program's user interface by just
grabbing what you want off a palette. You can also put your new classes into their own
palette and use them too.
InterfaceBuilder allows you to design a program and "run" it without actually ever
compiling it, because the code for any objects you're using other than your own has
already been compiled. That code is running or at least available in the operating
system as a shared library. When you've finished "drawing" your program the way you
want it to work, including the classes you can't see but must be integrated anyway, you
select "test interface" from InterfaceBuilder's menu and InterfaceBuilder runs the
program for you without classes that it doesn't have yet. It gives you a big
Frankenstein-style switch that you can use to shut the program down if your
experiment doesn't like to quit. If you don't like the user interface, you can play with
it until it's easy to use or does exactly what you wanted. It's far more powerful than an
ordinary interface design program because you effectively have a graphical window
into the guts of your app.
To release my application in other countries I can design nibs in other languages.
OPENSTEP has support for English, French, Spanish, Swedish, German and Japanese.
Most of the time it's just a matter of changing the words on the menus. There are
localization firms that specialize in taking an English nib file and using it to create
other language-specific versions of the program. They'll send back the nib and you can
drop it into ProjectBuilder. They'll also translate other messages that reside in your
code if it's needed. In most cases your app will "just work" in any language your
customer chooses to use.
When I finish with InterfaceBuilder (and I might reverse the process and play with
InterfaceBuilder first because it's sometimes more productive) I tell ProjectBuilder
to compile the app. I'll select "debug" first if the app is large or I'm trying something
fancy. If I'm debugging, ProjectBuilder will build the app with all its nibs and then
compile the new classes with debugging extensions. It'll then drop me into gdb (a
debugger) with the app loaded, where it's up to me to debug. There are various other
debugging tools available under OPENSTEP that allow me to examine the app's
messaging, memory usage, optimization, etc. Quite often if I'm really bamboozled I'll
tell gdb to show me all the messaging taking place between objects. It's something like
watching your children begin to talk.
I haven't yet discussed Postscript wraps or the cool process of trying out your
Postscript code in Yap.app (there's a server running in the os, so why not?) before it
goes into your project. ProjectBuilder knows about wraps and various other resources
too, so it's an extremely useful tool for managing the process of creating a large
application. Let's take a look at wraps and Display Postscript in general.
Display Postscript
Display Postscript (DPS) did not exist when NeXT first got together with Adobe to
design a common language for writing to the screen and the printer. The general idea
was to achieve true WYSIWYG by using the same code to describe both, and so DPS was
born. Steve Jobs waxed poetic about XWindows while describing why NeXT had chosen
DPS over X11; he called X "brain damaged". It is true that like many groundbreaking
efforts, X became a little outmoded, and DPS is an elegant system. DPS is, however,
imperfect in the context of the new age of multimedia, so we'll probably see many
additions to DPS over the next few years.
Depending on your program's performance needs, there are various methods available
for adding Display Postscript to your programs. Note that most programs use very
little DPS, and unless you're designing a drawing application you probably won't have
to learn much. That said, playing with Postscript interactively can be somewhat
addictive, like fooling with turtle graphics and Logo if you're old enough to remember.
You may choose to spend more time with it than is absolutely necessary, and if you do,
the principles are the same.
You may or may not know that Postscript is a stack-based client-server page
description system. Your program is the client, and the server is effectively either the
screen or the printer. In theory it would be nice to write code that worked on both
screen and printer; in practice you can't rely on all printers being Level II
Postscript-compatible, so you may have to rewrite small sections of your display code
for safe printing if you want to use Level II features.
There are three basic methods for using Display Postscript: operator functions, wraps
and user paths, in descending order of their execution times. Operator functions are
simplest, and you'll use them when you want to make a quick call to the server for a
single function. Here's an example of a common combination of commands in
Postscript, a "moveto" command followed by a "lineto" command. They create what is
known in Postscript as a "path, and they're followed by an instruction to "stroke" the
path, or fill it with ink.
10 10 moveto
50 50 lineto
stroke
As mentioned, one of the really fun parts of OPENSTEP is getting into Yap (Yet Another
Postscript processor) and playing with Postscript interactively. Some time ago I
ported BattleZone to NEXTSTEP, and I threw the entire opening screen into Yap to test
it. I then moved elements of it around until I was happy with the way the screen looked.
It was so much fun it almost made up for the rest of the porting process.
You can include these instructions in your code by using PS operator functions.
OPENSTEP has two PS library functions for every PS operator, one for drawing in a
default context and one that allows you to specify the context. Here's the equivalent of
what's above, using the PS operator functions in psops.h, which draw in a default
context:
PSmoveto(10, 10);
PSlineto(50,50);
PSstroke;
Now you've seen the original Postscript and the way you can implement it using single
operator functions. As you can see, it is easy to convert any Postscript calls into
operator function calls and insert them into your program. The problem with doing
that is that each one of these calls is a separate message to the server. If you're calling
one function occasionally, you're sending one message to the server occasionally,
which is as good as you're going to get. For more than one line of Postscript, however,
you'd do better to package your Postscript into a "wrap".
A wrap packages your calls into a C function library that ProjectBuilder can be told to
include at compile time. You'd write a text file with the following in it:
definepsPSWDefs()
/ML {% X1 Y1 X Y
moveto
lineto
} bind def
endps
defineps multiline(float data[x]; int x; int length)
data
1 1 length ML for
endps
Drop this into ProjectBuilder, which will call a program called "pswrap", to turn the
file into a friendly, eminently readable and efficient package that looks like this:
/* ./sym/c_gpr.c generated from c_gpr.psw
by unix pswrap V1.009 Wed Apr 19 17:50:24 PDT 1989 */
#include
#include
#line 1 "c_gpr.psw
#line 10 "./sym/c_gpr.c
void PSWDefs( void )
{
typedef struct {
unsigned char tokenType;
unsigned char topLevelCount;
unsigned short nBytes;
DPSBinObjGeneric obj0;
DPSBinObjGeneric obj1;
DPSBinObjGeneric obj2;
DPSBinObjGeneric obj3;
DPSBinObjGeneric obj4;
DPSBinObjGeneric obj5;
char obj6[2];
} _dpsQ;
static const _dpsQ _dpsF = {
DPS_DEF_TOKENTYPE, 4, 54,
{DPS_LITERAL|DPS_NAME, 0, 2, 48},/* ML */
{DPS_EXEC|DPS_ARRAY, 0, 2, 32},
{DPS_EXEC|DPS_NAME, 0, DPSSYSNAME, 14}, /* bind */
{DPS_EXEC|DPS_NAME, 0, DPSSYSNAME, 51}, /* def */
{DPS_EXEC|DPS_NAME, 0, DPSSYSNAME,107}, /* moveto */
{DPS_EXEC|DPS_NAME, 0, DPSSYSNAME, 99}, /* lineto */
{‘M','L'},
}; /* _dpsQ */
register DPSContext _dpsCurCtxt = DPSPrivCurrentContext();
char pad[3];
DPSBinObjSeqWrite(_dpsCurCtxt,(char *) &_dpsF,54);
if (0) *pad = 0;/* quiets compiler warnings */
}
#line 7 "c_gpr.psw
#line 43 "./sym/c_gpr.c
void multiline(const float data[], int x, int length)
{
typedef struct {
unsigned char tokenType;
unsigned char topLevelCount;
unsigned short nBytes;
DPSBinObjGeneric obj0;
DPSBinObjGeneric obj1;
DPSBinObjGeneric obj2;
DPSBinObjGeneric obj3;
DPSBinObjGeneric obj4;
DPSBinObjGeneric obj5;
} _dpsQ;
typedef struct {
char obj6[2];
} _dpsQ1;
static const _dpsQ _dpsStat = {
DPS_DEF_TOKENTYPE, 6, 54,
{DPS_LITERAL|DPS_ARRAY, 0, 0, 48}, /* param[var]: data */
{DPS_LITERAL|DPS_INT, 0, 0, 1},
{DPS_LITERAL|DPS_INT, 0, 0, 1},
{DPS_LITERAL|DPS_INT, 0, 0, 0}, /* param: length */
{DPS_EXEC|DPS_NAME, 0, 2, 48}, /* ML */
{DPS_EXEC|DPS_NAME, 0, DPSSYSNAME, 72}, /* for */
}; /* _dpsQ */
static const _dpsQ1 _dpsF1 = {