Object-oriented cdev development
Bryan Waters is a software engineer for Maynard Electronics and can be reached at 460 E Semoran Blvd., Casselberry, FL 32707.
With the addition of object-oriented extensions to Think C in Version 4.0, Think Technologies adopted the notion of "Object C," which combines the functionality of Object Pascal with the syntax of C++. While Object C supports the declaration of classes, sub-classes, inheritance, and methods, it does not support function and operator overloading, constructors and destructors, data hiding through the use of public and private keywords, or in-line functions. In this article, I'll show how Object C can be used to simplify the development of Macintosh resources, and I'll use a control panel device as an example.
A class in Object C is declared (as shown in Example 1) where class_name is the name of the class, and storage_class can be either direct or indirect. This determines whether the object is allocated as a pointer or a handle.
Example 1: Declaring an Object C class
struct class_name : storage_type { variable and/or method declarations . . . } ;
To declare a method, simply include the routine's prototype in the class declaration. Subclasses are defined using the syntax shown in Example 2.
Example 2: Declaring an Object C subclass
struct subclass_name:class_name { variable and/or method declaration ; /* NOTE: in order to override a method in the class, simply redeclare the method in the ~ subclass */ } ;
Methods are defined by using the class_name combined with the method name, separated by the scope operator': :'. The object itself is passed implicitly to the method, and can be referenced either explicitly by using the keyword this, or implicitly by referencing the field or method directly. (All of the new keywords are interpreted in context, and do conflict with variable naming; for example, you can have a non-object variable named this, although I wouldn't recommend it.)
Objects are allocated and destroyed using the functions new()and delete(). Memory that was not allocated using these functions can be treated like an object, by using the functions bless() and blessD(), respectively, for pointers and handles. The final function is a member(), which is used to determine whether an object is a member of a specific class.
Control Panel Devices
With the release of the Macintosh SE, the Control Panel desk accessory became extendible through the use of cdev, a control panel device. This allowed programmers to add their own utilities to the control panel, without using a precious desk accessory slot (currently a maximum of 15 desk accessories is allowed). The cdev takes the form of a resource file that is placed in the Macintosh's system folder. When the Control Panel DA is opened, it searches the system folder for all files of type cdev, and adds them to its list. A cdev must have a list of mandatory resources before being adopted by the control panel. This list includes the 'cdev' code resource (an icon used to represent itself in the control panel's list), a dialog item list and 'nrct' resource (which determine the cdev's interface), and a 'mach' resource (a bitmask used to determine which Mac the 'cdev' should appear on; for example, a 'cdev' to change menu bar colors, would not be appropriate for a Macintosh without color QuickDraw). The 'cdev' resource contains the code to implement the cdev and is called from the control panel in the format shown in Example 3.
Example 3: Format for calling a cdev
pascal long cdev ( message, Item, numItems, CPanelID, ev, cdevValue CPDialog ) int message, Item, numItems, CPanelID; EventRecord *ev ; long cdevValue ; DialogPtr CPDialog ;
The control panel communicates with the 'cdev' by passing messages pertaining to a specific event or requesting a specific action. For example, when the cdev is first selected, the control panel calls the 'cdev' code resource with the InitDev message. The messages are listed in Table 1.
Table 1: Messages passed between the control panel and cdev
Message Meaning ------------------------------------------------------- initDev initialization hitDev mouse down inside of a cdev dialog item closeDev cleanup nulDev idle time message updateDev update event message activDev activate event message deActivDev de-activate event message keyEvtDev key down event message macDev used for checking machine characteristics (this is only used if the 'mach' resource does not define the environment that the cdev should be used in). undoDev the user selected undo from the menu. cutDev the user selected cut from the menu. copyDev the user selected copy from the menu. pasteDev the user selected paste from the menu. clearDev the user selected clear from the menu.
OOP and the Control Panel
Included with Think C 4.0 is an object-oriented cdev development shell that I will use to develop a typical cdev. The cdev development shell is based around a cdev class, which implements the basics of a control panel device. To use this to write a cdev, you must override the methods needed by your cdev. The example I chose (see Listing One, page 86) uses the SysEnvirons() routine to obtain the current system information for display purposes; basically a system info cdev.
To implement this, you need to define a subclass that overrides the Init() method, which corresponds to the initDev message passed to the cdev-resource by the control panel. Also, you need to provide a routine named Runnable(), which examines the environment and determines whether the cdev should be shown or not. This corresponds to the macDev message, which cannot be implemented as a method because it will be called before the object is allocated. Finally, the New routine must be provided to allocate the subclass and return the object reference.
Conclusion
The cdev shell included with Think C 4.0 makes cdev development relatively easy. Essentially all you have to do is write the application-specific portions of the control panel device, a technique that can be used for the development of any Macintosh code resources like control and window definition procedures, and even device drivers. (For an example of a driver shell in Object C, see my article entitled "Writing Macintosh Device Drivers" in this issue on page 37.)
_OBJECT C AND THE MACINTOSH CONTROL PANEL_ by Bryan Waters
[LISTING ONE]
Copyright © 1989, Dr. Dobb's Journal<a name="0298_000c">
#include <DialogMgr.h> /* for dialog routines */
#include <cdev.h> /* for cdev class */
/* define subclass of class cdev */
struct sys_info:cdev {
void Init( ) ; /* override the initDev message */
};
/* dialog item numbers */
#define MACH_TYPE 1
#define SYS_VER 2
#define PROC_TYPE 3
#define HAS_FPU 4
#define HAS_CQD 5
#define KBD_TYPE 6
#define AT_VER 7
/* current system environment version */
#define curSysEnvVers 1
/* computer model */
#define envMachUnknown 0
#define env512KE 1
#define envMacPlus 2
#define envSE 3
#define envMacII 4
#define envMacIIx 5
#define envMacSE30 7
/* cpu types */
#define envCPUUnknown 0
#define env68000 1
#define env68010 2
#define env68020 3
#define env68030 4
/* keyboard types */
#define envUnknownKbd 0
#define envMacKbd 1
#define envMacAndPad 2
#define envMacPlusKbd 3
#define envAExtendKbd 4
#define envStandADBKbd 5
/* Will the cdev be usable in this environment? */
/* This routine should be used to determine whether the cdev
should appear in the control panel for this environment. An
example of this would be a cdev for setting the parameters for
the virtual memory manager for System 7.0. This cdev
would only be necessary for 68020 with the Paged Memory
Management Unit, or a 68030. In this case, the Runnable
routine would determine the type of machine it was on, and
return either TRUE or FALSE, depending on whether the cdev
serves a valid purpose on this machine */
Boolean Runnable( )
{
return TRUE ;
}
/* This routine creates the cdev. In our cdev, it doesn't really
serve any purpose, since we don't have any storage
requirements, but we still need the structure allocated for
purposes of calling the appropriate methods */
cdev *New()
{
return new( sys_info ) ;
}
/* This routine overrides the initDev message, which is called
upon selection of any item in the control panel list. For a
more complex cdev, this routine would do all initialization
required for the cdev, and the more complex interaction
required by the cdev would be implemented using remainder of
the messages, and their handlers. The remainder of the
messages, and the related method, are listed below:
hitDev - for mouse downs. Method: ItemHit( )
closeDev - for de-initialization. Method: Close( )
nulDev - idle routine, for whatever needs to be done on a
regular basis, but is not really event related.
Method: Idle( )
updateDev - for update events. Method: Update( )
activDev - for activate events. Method: Activate( )
deActivDev - for de-activate events. Method: Deactivate( )
keyEvtDev - for key downs. Method: Key( )
undoDev - for handling undos. Method: Undo( )
cutDev - for handling cuts. Method: Cut( )
copyDev - for handling copys. Method: Copy( )
pasteDev - for handling pastes. Method: Paste( )
clearDev - for handling clears. Method: Clear( )
To implement any of these messages, simply override the
method, in your subclass.
*/
void sys_info::Init( )
{
int type ;
Handle hdl ;
Rect box ;
SysEnvRec sys_env ;
long tmp ;
int count ;
char *curr1 ;
char *curr2 ;
char buffer1[10] ;
char buffer2[10] ;
/* first call inherited Init routine */
/* NOTE: this routine must be called to set up the cdev class */
inherited::Init();
/* get system environment */
SysEnvirons( curSysEnvVers, &sys_env ) ;
/* set machine type */
GetDItem( this->dp, MACH_TYPE + this->lastItem, &type, &hdl, &box ) ;
switch( sys_env.machineType ) {
default:
case envMachUnknown:
SetIText( hdl, "\punknown" ) ;
break ;
case envMacPlus:
SetIText( hdl, "\pMacintosh Plus" ) ;
break ;
case envSE:
SetIText( hdl, "\pMacintosh SE" ) ;
break ;
case envMacII:
SetIText( hdl, "\pMacintosh II" ) ;
break ;
case envMacIIx:
SetIText( hdl, "\pMacintosh IIx" ) ;
break ;
case envMacSE30:
SetIText( hdl, "\pMacintosh SE/30" ) ;
break ;
}
/* set system version */
GetDItem( this->dp, SYS_VER + this->lastItem, &type, &hdl, &box ) ;
if( sys_env.systemVersion == 0 ) {
SetIText( hdl, "\punknown" ) ;
}else{
count = 0 ;
curr1 = &buffer1[1] ;
tmp = ( sys_env.systemVersion & 0xFF00 ) >> 8 ;
NumToString( tmp, buffer2 ) ;
curr2 = &buffer2[1] ;
while( buffer2[0]-- ) {
count++ ;
*curr1 = *curr2 ;
curr1++ ; curr2 ++ ;
}
count++ ;
*curr1 = '.' ;
curr1++ ;
tmp = ( sys_env.systemVersion & 0x00FF ) ;
NumToString( tmp, buffer2 ) ;
curr2 = &buffer2[1] ;
while( buffer2[0]-- ) {
count++ ;
*curr1 = *curr2 ;
curr1++ ; curr2 ++ ;
}
buffer1[0] = count ;
SetIText( hdl, buffer1 ) ;
}
/* set processor type */
GetDItem( this->dp, PROC_TYPE + this->lastItem, &type, &hdl, &box ) ;
switch( sys_env.processor ) {
default:
case envCPUUnknown:
SetIText( hdl, "\punknown" ) ;
break ;
case env68000:
SetIText( hdl, "\pMotorola 68000" ) ;
break ;
case env68010:
SetIText( hdl, "\pMotorola 68010" ) ;
break ;
case env68020:
SetIText( hdl, "\pMotorola 68020" ) ;
break ;
case env68030:
SetIText( hdl, "\pMotorola 68030" ) ;
break ;
}
/* set has FPU */
GetDItem( this->dp, HAS_FPU + this->lastItem, &type, &hdl, &box ) ;
SetIText( hdl, sys_env.hasFPU ? "\pyes":"\pno" ) ;
/* set has colorQD */
GetDItem( this->dp, HAS_CQD + this->lastItem, &type, &hdl, &box ) ;
SetIText( hdl, sys_env.hasColorQD ? "\pyes":"\pno" ) ;
/* set keybord type */
GetDItem( this->dp, KBD_TYPE + this->lastItem, &type, &hdl, &box ) ;
switch( sys_env.keyBoardType ) {
default:
case envUnknownKbd:
SetIText( hdl, "\punknown" ) ;
break ;
case envMacKbd:
SetIText( hdl, "\pstandard" ) ;
break ;
case envMacAndPad:
SetIText( hdl, "\pstandard with key pad" ) ;
break ;
case envMacPlusKbd:
SetIText( hdl, "\pMacintosh Plus Keyboard" ) ;
break ;
case envAExtendKbd:
SetIText( hdl, "\pApple extended" ) ;
break ;
case envStandADBKbd:
SetIText( hdl, "\pstandard ADB" ) ;
break ;
}
/* set appletalk version */
GetDItem( this->dp, AT_VER + this->lastItem, &type, &hdl, &box ) ;
if( sys_env.atDrvrVersNum == 0 ) {
SetIText( hdl, "\pnot loaded" ) ;
}else{
tmp = ( sys_env.systemVersion & 0xFF00 ) >> 8 ;
NumToString( tmp, buffer1 ) ;
SetIText( hdl, buffer1 ) ;
}
/* finished */
return ;
}
Example 1: Declaring an Object C class
struct class_name : storage_type {
variable and/or method declarations ;
.
.
.
} ;
Example 2: Declaring an Object C subclass
struct subclass_name:class_name {
variable and/or method declaration ;
/* NOTE: in order to override a method in the class,
simply redeclare the method in the subclass */
} ;
Example 3: Format for calling a cdev
pascal long cdev( message, Item, numItems, CPanelID, ev,
cdevValue CPDialog )
int message, Item, numItems, CPanelID ;
EventRecord *ev ;
long cdevValue ;
DialogPtr CPDialog ;