News:

Window Maker Live 12.12 now available!

Main Menu

How to get Tiny.m by Simson Garfinkel to run on OS4.2

Started by ptek, Sep 25, 2025, 02:19 AM

Previous topic - Next topic

ptek

 
Simson L. Garfinkel wrote an excellent book for programming NS3 which he provided at https://simson.net/ref/1993/NeXTSTEP3.0.pdf. He then updated the book for MacOSX (Building Cocoa Applications: A Step by Step Guide) which is still available for sale and demonstrated on how to code a basic application without using Interface Builder. Does any one know how to get this code to work on OS4.2? I've tried replacing Cocoa.h with AppKit/AppKit.h and also replacing -framework Cocoa with -framework Appkit but have had no luck. (I do not have a Rhapsody DR2 set up to try compiling in there as well.

Non modified source code provided in Building Cocoa Applications: A Step by Step Guide
/* Tiny.m
 * A tiny Cocoa application that creates a window and then displays graphics in it.
 * IB is not used to create this application.
 *
 * Compile with:
 * cc -Wall -o Tiny Tiny.m -framework Cocoa
 *
 */
 
#import <Cocoa/Cocoa.h>         // include the Cocoa Frameworks
/************************************************************
 ** A DemoView instance object of this class draws the image.
@interface DemoView : NSView    // interface of DemoView class
{                               // (subclass of NSView class)
}
- (void)drawRect:(NSRect)rect;  // instance method interface
@end
@implementation DemoView        // implementation of DemoView class
#define X(t) (sin(t)+1) * width * 0.5     // macro for X(t)
#define Y(t) (cos(t)+1) * height * 0.5    // macro for Y(t)
- (void)drawRect:(NSRect)rect   // instance method implementation
{
    double f,g;
    double const pi = 2 * acos(0.0);
    int n = 12;                 // number of sides of the polygon
    // get the size of the application's window and view objects
    float width  = [self bounds].size.width;
    float height = [self bounds].size.height;
    [[NSColor whiteColor] set];   // set the drawing color to white
    NSRectFill([self bounds]);    // fill the view with white
    // the following statements trace two polygons with n sides
    // and connect all of the vertices with lines
    [[NSColor blackColor] set];   // set the drawing color to black
    for (f=0; f<2*pi; f+=2*pi/n) {        // draw the fancy pattern
        for (g=0; g<2*pi; g+=2*pi/n) {
            NSPoint p1 = NSMakePoint(X(f),Y(f));
            NSPoint p2 = NSMakePoint(X(g),Y(g));
            [NSBezierPath strokeLineFromPoint:p1 toPoint:p2];
        }
    }
} // end of drawRect: override method
/* windowWillClose: is a delegate method that gets invoked when
 * the on-screen window is about to close (user clicked close box).
 * In this case, we force the entire application to terminate.
 */
-(void)windowWillClose:(NSNotification *)notification
{
    [NSApp terminate:self];
}
@end  // end of DemoView implementation
/*
 * setup(  ) performs the functions that would normally be performed by
 * loading a nib file.
 */
void setup(  ) 
{
    NSWindow *myWindow;      // typed pointer to NSWindow object
    NSView   *myView;        // typed pointer to NSView object
    NSRect    graphicsRect;  // contains an origin, width, height
    // initialize the rectangle variable
    graphicsRect = NSMakeRect(100.0, 350.0, 400.0, 400.0);
    myWindow = [ [NSWindow alloc]              // create the window
               initWithContentRect: graphicsRect
                         styleMask:NSTitledWindowMask
                                  |NSClosableWindowMask
                                  |NSMiniaturizableWindowMask
                           backing:NSBackingStoreBuffered
                             defer:NO ];
    [myWindow setTitle:@"Tiny Application Window"];
    // create amd initialize the DemoView instance
    myView = [[[DemoView alloc] initWithFrame:graphicsRect] autorelease];
    [myWindow setContentView:myView ];    // set window's view
    [myWindow setDelegate:myView ];       // set window's delegate
    [myWindow makeKeyAndOrderFront: nil]; // display window
}
int main()
{
    // create the autorelease pool
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    // create the application object
    NSApp = [NSApplication sharedApplication];
    // set up the window and drawing mechanism
    setup(  );
    // run the main event loop
    [NSApp run];
    // we get here when the window is closed
    [NSApp release];      // release the app
    [pool release];       // release the pool
    return(EXIT_SUCCESS);
}

EDIT: I don't know why int main() was missing from the code, added it back in.

jeffburg

This is interesting. When I removed the NIB's from my App, OpenStep 4.2 was not happy about it. Particularly the Menus in OS4.2 are not retained by NSApplication, instead they are retained by their NIB which seems crazy to me but that's how it is. So the app would always crash after launching because the menus would be released from memory and then NSApplication would try to access them so it could render them.

Where are you running into problems? Does it not even compile? Or does it compile but then crash at launch?
Grab my app, MathEdit for OpenStep - https://github.com/jeffreybergier/MathEdit
Follow me on Mastodon for Retro Mac Adventures - https://jeffburg.social/@jeff

jeffburg

If it's not even compiling, you may need to specify the exact path of AppKit in OpenStep because it's buried deep inside the developer folder. I can't remember the exact location. But that would be my next troubleshooting step.

<---------- Main function declaration should go here no?
{
    // create the autorelease pool
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    // create the application object
    NSApp = [NSApplication sharedApplication];
    // set up the window and drawing mechanism
    setup(  );
    // run the main event loop
    [NSApp run];
    // we get here when the window is closed
    [NSApp release];      // release the app
    [pool release];       // release the pool
    return(EXIT_SUCCESS);
}
Grab my app, MathEdit for OpenStep - https://github.com/jeffreybergier/MathEdit
Follow me on Mastodon for Retro Mac Adventures - https://jeffburg.social/@jeff

jeffburg

I can't open the Cocoa book because it needs to be bought, but the Nextstep programming guide has a similar Tiny.m file and it has the main function declared properly. So I think there is something wrong with the example in the cocoa book.
Grab my app, MathEdit for OpenStep - https://github.com/jeffreybergier/MathEdit
Follow me on Mastodon for Retro Mac Adventures - https://jeffburg.social/@jeff

jeffburg

But yeah, after looking at this code in the Nextstep 3.3 example code, I would highly recommend not learning that lol. It's close enough that you can learn all the concepts but just different enough to be entirely frustrating when migrating forward to OpenStep and then Cocoa development.
Grab my app, MathEdit for OpenStep - https://github.com/jeffreybergier/MathEdit
Follow me on Mastodon for Retro Mac Adventures - https://jeffburg.social/@jeff

ptek

Quote from: jeffburg on Sep 25, 2025, 02:42 AMThis is interesting. When I removed the NIB's from my App, OpenStep 4.2 was not happy about it. Particularly the Menus in OS4.2 are not retained by NSApplication, instead they are retained by their NIB which seems crazy to me but that's how it is. So the app would always crash after launching because the menus would be released from memory and then NSApplication would try to access them so it could render them.

Where are you running into problems? Does it not even compile? Or does it compile but then crash at launch?

  It doesn't compile I'm using the following to compile:
cc -Wall -o Tiny Tiny.m -framework AppKit

  It brings up a error about a missing end during an implementation while compiling. Here is a stackoverflow post (https://stackoverflow.com/questions/15756624/end-missing-in-implementation-context)
@end is missing in implementation context

  I thought I would use the Cocoa version instead of the NS3.0 version as the includes and parameters had changed especially around the NSWindow() allocations. The ones in the Cocoa version seem to be correct as I had tried to use tops or the convertor to convert from NS3.0 to OS4.2 but I was having problems with it. Thanks for having a look.

EDIT: You were correct about the int main() in post 3 I don't know why it didn't get copied over.

jeffburg

Well your @ends are definitely balanced correctly. I think the issue might be that the C compiler in OpenStep is not C99 compatible. I am not sure when C99 support was added to Project Builder but it was some time around 10.2 Jaguar. So I think the C function declaration setup() and main() need to use older C89 syntax

int main(void)
void setup(void)

also, I am not sure if it matters, the but the default main implementation in Objective C is actually

int main(int argc, char *argv[]) { /* ... */ }

https://stackoverflow.com/a/41805712
Grab my app, MathEdit for OpenStep - https://github.com/jeffreybergier/MathEdit
Follow me on Mastodon for Retro Mac Adventures - https://jeffburg.social/@jeff

jeffburg

@ptek I saw your Discord message that its still not compiling. So here is what I would do. Reduce the file to the absolute bare minimum needed and keep adding things until it breaks again.

Step 1, just the main function

#import <AppKit/AppKit.h>

int main(int argc, char *argv[])
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSApp = [NSApplication sharedApplication];
    // setup(); // comment this out because we have no setup function yet
    [NSApp run];
    [NSApp release];
    [pool release];
    return(EXIT_SUCCESS);
}


If that compiles go to step 2 where we add the setup function

#import <AppKit/AppKit.h>

void setup(void)
{
    NSWindow *myWindow;
    NSView   *myView;
    NSRect    graphicsRect;

    graphicsRect = NSMakeRect(100.0, 350.0, 400.0, 400.0);
    myWindow = [ [NSWindow alloc]
               initWithContentRect: graphicsRect
                         styleMask:NSTitledWindowMask
                                  |NSClosableWindowMask
                                  |NSMiniaturizableWindowMask
                           backing:NSBackingStoreBuffered
                             defer:NO ];
    [myWindow setTitle:@"Tiny Application Window"];

    // Change this to NSView because we have no implemented DemoView yet
    myView = [[[NSView alloc] initWithFrame:graphicsRect] autorelease];
    [myWindow setContentView:myView ];
    [myWindow setDelegate:myView ];
    [myWindow makeKeyAndOrderFront: nil];
}

int main(int argc, char *argv[])
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSApp = [NSApplication sharedApplication];
    setup(); // uncomment this because we now have a setup function
    [NSApp run];
    [NSApp release];
    [pool release];
    return(EXIT_SUCCESS);
}


If this compiles and runs you should see a blank window. And now you can go to step 3 where we will add our empty NSView subclass


#import <AppKit/AppKit.h>

@interface DemoView: NSView
{
}
- (void)drawRect:(NSRect)rect;
@end

@implementation DemoView
- (void)drawRect:(NSRect)rect;
{
}
-(void)windowWillClose:(NSNotification*)notification;
{
}
@end

void setup(void)
{
    NSWindow *myWindow;
    NSView   *myView;
    NSRect    graphicsRect;

    graphicsRect = NSMakeRect(100.0, 350.0, 400.0, 400.0);
    myWindow = [ [NSWindow alloc]
               initWithContentRect: graphicsRect
                         styleMask:NSTitledWindowMask
                                  |NSClosableWindowMask
                                  |NSMiniaturizableWindowMask
                           backing:NSBackingStoreBuffered
                             defer:NO ];
    [myWindow setTitle:@"Tiny Application Window"];

    // Change this back to DemoView
    myView = [[[DemoView alloc] initWithFrame:graphicsRect] autorelease];
    [myWindow setContentView:myView ];
    [myWindow setDelegate:myView ];
    [myWindow makeKeyAndOrderFront: nil];
}

int main(int argc, char *argv[])
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSApp = [NSApplication sharedApplication];
    setup(); // uncomment this because we now have a setup function
    [NSApp run];
    [NSApp release];
    [pool release];
    return(EXIT_SUCCESS);
}


if that compiles and runs then we can implement super minimum code in the two methods of DemoView



#import <AppKit/AppKit.h>

@interface DemoView: NSView
{
}
- (void)drawRect:(NSRect)rect;
@end

@implementation DemoView
- (void)drawRect:(NSRect)rect;
{
    [[NSColor redColor] set];   // set the drawing color to red
    NSRectFill([self bounds]);  // fill the view with red
}
-(void)windowWillClose:(NSNotification*)notification;
{
  [NSApp terminate:self];
}
@end

void setup(void)
{
    NSWindow *myWindow;
    NSView   *myView;
    NSRect    graphicsRect;

    graphicsRect = NSMakeRect(100.0, 350.0, 400.0, 400.0);
    myWindow = [ [NSWindow alloc]
               initWithContentRect: graphicsRect
                         styleMask:NSTitledWindowMask
                                  |NSClosableWindowMask
                                  |NSMiniaturizableWindowMask
                           backing:NSBackingStoreBuffered
                             defer:NO ];
    [myWindow setTitle:@"Tiny Application Window"];

    // Change this back to DemoView
    myView = [[[DemoView alloc] initWithFrame:graphicsRect] autorelease];
    [myWindow setContentView:myView ];
    [myWindow setDelegate:myView ];
    [myWindow makeKeyAndOrderFront: nil];
}

int main(int argc, char *argv[])
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSApp = [NSApplication sharedApplication];
    setup(); // uncomment this because we now have a setup function
    [NSApp run];
    [NSApp release];
    [pool release];
    return(EXIT_SUCCESS);
}


So if that compiles and runs, you should get a red window and when you close the window the application should quit. If that is working, then there is something with the gobbly-gook drawing code in drawRect: that OpenStep doesn't like. So maybe you could debug that on your own.

If it doesn't work, let me know where it breaks.
Grab my app, MathEdit for OpenStep - https://github.com/jeffreybergier/MathEdit
Follow me on Mastodon for Retro Mac Adventures - https://jeffburg.social/@jeff

jeffburg

And looking at the drawing code, I think this is the problem. This I think is not allowed in C89, variable declarations in the middle of the function

    for (f=0; f<2*pi; f+=2*pi/n) {        // draw the fancy pattern
        for (g=0; g<2*pi; g+=2*pi/n) {
            NSPoint p1 = NSMakePoint(X(f),Y(f)); <---- These are new variables declared in the middle of the function
            NSPoint p2 = NSMakePoint(X(g),Y(g));
            [NSBezierPath strokeLineFromPoint:p1 toPoint:p2];
        }
    }

So you might be able to fix it by changing the drawrect code to this

- (void)drawRect:(NSRect)rect   // instance method implementation
{
    double f,g;
    double const pi = 2 * acos(0.0);
    int n = 12;

    NSPoint p1,p2; <---- declare the variables at the beginning

    float width  = [self bounds].size.width;
    float height = [self bounds].size.height;
    [[NSColor whiteColor] set];
    NSRectFill([self bounds]);
    [[NSColor blackColor] set];
    for (f=0; f<2*pi; f+=2*pi/n) {
        for (g=0; g<2*pi; g+=2*pi/n) {
            p1 = NSMakePoint(X(f),Y(f));
            p2 = NSMakePoint(X(g),Y(g)); <------ remove the NSPoint to reuse the existing variables instead of declaring new ones
            [NSBezierPath strokeLineFromPoint:p1 toPoint:p2];
        }
    }
}

But like I said, you should do the step by step in my previous post trying to modify this part.
Grab my app, MathEdit for OpenStep - https://github.com/jeffreybergier/MathEdit
Follow me on Mastodon for Retro Mac Adventures - https://jeffburg.social/@jeff

ptek

Quote from: jeffburg on Sep 25, 2025, 08:20 AM@ptek I saw your Discord message that its still not compiling. So here is what I would do. Reduce the file to the absolute bare minimum needed and keep adding things until it breaks again.


#import <AppKit/AppKit.h>

@interface DemoView: NSView
{
}
- (void)drawRect:(NSRect)rect;
@end

@implementation DemoView
- (void)drawRect:(NSRect)rect;
{
    [[NSColor redColor] set];  // set the drawing color to red
    NSRectFill([self bounds]);  // fill the view with red
}
-(void)windowWillClose:(NSNotification*)notification;
{
  [NSApp terminate:self];
}
@end

void setup(void)
{
    NSWindow *myWindow;
    NSView  *myView;
    NSRect    graphicsRect;

    graphicsRect = NSMakeRect(100.0, 350.0, 400.0, 400.0);
    myWindow = [ [NSWindow alloc]
              initWithContentRect: graphicsRect
                        styleMask:NSTitledWindowMask
                                  |NSClosableWindowMask
                                  |NSMiniaturizableWindowMask
                          backing:NSBackingStoreBuffered
                            defer:NO ];
    [myWindow setTitle:@"Tiny Application Window"];

    // Change this back to DemoView
    myView = [[[DemoView alloc] initWithFrame:graphicsRect] autorelease];
    [myWindow setContentView:myView ];
    [myWindow setDelegate:myView ];
    [myWindow makeKeyAndOrderFront: nil];
}

int main(int argc, char *argv[])
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSApp = [NSApplication sharedApplication];
    setup(); // uncomment this because we now have a setup function
    [NSApp run];
    [NSApp release];
    [pool release];
    return(EXIT_SUCCESS);
}


Thanks so the code was able to compile with post #7 code block 4 which is listed above.

Adding the following code block below does not work as there is no NSBezierPath() on OS4.2 so I will try to shoehorn the Post Script draw code from the NS3.0.

- (void)drawRect:(NSRect)rect   // instance method implementation
{
    double f,g;
    double const pi = 2 * acos(0.0);
    int n = 12;

    NSPoint p1,p2; <---- declare the variables at the beginning

    float width  = [self bounds].size.width;
    float height = [self bounds].size.height;
    [[NSColor whiteColor] set];
    NSRectFill([self bounds]);
    [[NSColor blackColor] set];
    for (f=0; f<2*pi; f+=2*pi/n) {
        for (g=0; g<2*pi; g+=2*pi/n) {
            p1 = NSMakePoint(X(f),Y(f));
            p2 = NSMakePoint(X(g),Y(g)); <------ remove the NSPoint to reuse the existing variables instead of declaring new ones
            [NSBezierPath strokeLineFromPoint:p1 toPoint:p2];
        }
    }
}

Quote from: jeffburgThis is interesting. When I removed the NIB's from my App, OpenStep 4.2 was not happy about it. Particularly the Menus in OS4.2 are not retained by NSApplication, instead they are retained by their NIB which seems crazy to me but that's how it is. So the app would always crash after launching because the menus would be released from memory and then NSApplication would try to access them so it could render them.

  Your explanation about the menus is interesting because I was wondering why he didn't include it for the example for MacOSX.

jeffburg

I'm glad it works! And yeah, great catch! I totally missed it! NSBezierPath was introduced in OSX, so it won't work in OpenStep.

But yeah, think of the code troubleshooting like you would hardware troubleshooting. For example, if the computer won't boot and the error is related to RAM failure... what do you do first? Remove all the RAM except for 1 stick and boot again. If it boots, you put in another stick and try again. If it boots again you put in another stick of RAM over and over until it stops booting. Then you have found your bad stick of RAM.

Then when you master this technique you move on to the next technique which is split-half search. Remove half of the RAM and boot the computer. If it boots up, then you put all the RAM back and remove the other half. Then it should fail to boot, and you remove half of the remaining RAM and see if it boots. By moving back and forth in halves instead of 1 at a time, you double your troubleshooting speed ;)

https://www.oreilly.com/library/view/apple-training-series/0321335465/0321335465_ch03lev1sec3.html
Grab my app, MathEdit for OpenStep - https://github.com/jeffreybergier/MathEdit
Follow me on Mastodon for Retro Mac Adventures - https://jeffburg.social/@jeff

ptek

Quote from: jeffburg on Sep 25, 2025, 02:09 PMI'm glad it works! And yeah, great catch! I totally missed it! NSBezierPath was introduced in OSX, so it won't work in OpenStep.
Interesting, is NSBezierPath() a commonly used function on OSX? I wonder if that function is in Rhapsody DR2?

Quote from: jeffburg on Sep 25, 2025, 02:09 PMBut yeah, think of the code troubleshooting like you would hardware troubleshooting. For example, if the computer won't boot and the error is related to RAM failure... what do you do first? Remove all the RAM except for 1 stick and boot again. If it boots, you put in another stick and try again. If it boots again you put in another stick of RAM over and over until it stops booting. Then you have found your bad stick of RAM.

Then when you master this technique you move on to the next technique which is split-half search. Remove half of the RAM and boot the computer. If it boots up, then you put all the RAM back and remove the other half. Then it should fail to boot, and you remove half of the remaining RAM and see if it boots. By moving back and forth in halves instead of 1 at a time, you double your troubleshooting speed ;)

https://www.oreilly.com/library/view/apple-training-series/0321335465/0321335465_ch03lev1sec3.html

  Thank you for taking the time into having a quick look into this :) I was looking for a example that doesn't use .nib files as I like to keep both options available it's also good as when people open the .app file and don't see any .nib files it will be harder for them to disassemble. Not that I code anything advanced  ;D

  But I know that menus will need to be done via Interface Builder.

  I still need to finish of the program and get something to display but as posted earlier the Red Rectangle code works.

jeffburg

Quote from: ptek on Sep 26, 2025, 02:55 AMInteresting, is NSBezierPath() a commonly used function on OSX? I wonder if that function is in Rhapsody DR2?

Before 10.6 or so, it was the best way to draw like "non-square" shapes. But yeah, it's not used much any more. I am not sure if it was in Rhapsody or not.
Grab my app, MathEdit for OpenStep - https://github.com/jeffreybergier/MathEdit
Follow me on Mastodon for Retro Mac Adventures - https://jeffburg.social/@jeff

Rhetorica

Quote from: jeffburg on Sep 26, 2025, 05:30 AMI am not sure if it was in Rhapsody or not.

It was introduced in DR1:

Quote from: /NextLibrary/Documentation/NextDev/ReleaseNotes/AppKit.htmlNSBezierPath and NSAffineTransform
These two classes help provide a more complete abstraction in the Application Kit framework layer for graphics operations. NSBezierPath enables standard operations with lines, user-defined paths, and arrays of glyphs, such as stroking, filling, and clipping. It also provides simple bounds computation and hit detection methods. NSAffineTransform provides an abstraction for the graphics transformation matrix.
WARNING: preposterous time in Real Time Clock -- CHECK AND RESET THE DATE!