Calling Cocoa Commandline

June 25th 19:31
by why

The euphoria. All of Shoes’ Carbon code is gone, rewritten using entirely Cocoa native code. Not only did this shrink the native code to nearly half of its previous size, but things actually work now. The euphoria. And also: the elation.

The only painful part was figuring out how to get started with only gcc and get it all linked with the C code. To get away without an IDE. It’s been done in lots of other cross-platform libs, sure. Buried under autotools and lost in deep directories.

Here’s a summary of using Cocoa from the commandline.


A truly trivial Cocoa program reads thusly:

#import <Cocoa/Cocoa.h>

#define INIT    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
#define RELEASE [pool release];

int
main(int argc, char *argv[])
{
  NSApplication *app = [NSApplication sharedApplication];
  INIT;

  NSWindow *win = [[NSWindow alloc] initWithContentRect: NSMakeRect(0, 0, 340, 140)
    styleMask: (NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask)
    backing: NSBackingStoreBuffered defer: NO];
  NSTextField *text = [[[NSTextField alloc] initWithFrame: NSMakeRect(40, 90, 260, 18)]
    autorelease];

  [[win contentView] addSubview: text];
  [text setBezeled: NO];
  [text setStringValue: @"Drink Coke-O"];
  [text setBackgroundColor: [NSColor windowBackgroundColor]];
  [text setEditable: NO];
  [text setSelectable: NO];

  [win center];
  [win orderFront: nil];

  RELEASE;
  [app run];
  return 0;
}

Save this as coke-o.m and compile it (you’ll need the Xcode tools installed):

$ gcc -framework Cocoa -o coke-o coke-o.m

Run it: ./coke-o (although the window will appear behind your terminal for now.)

While we’re messing with gcc, let’s compile it as a Universal binary. Since Universal binaries aren’t that much bigger for little progs.

$ export MACOSX_DEPLOYMENT_TARGET=10.4
$ gcc -O -isysroot /Developer/SDKs/MacOSX10.4u.sdk -arch i386 -arch ppc \
    -framework Cocoa -o coke-o coke-o.m

Now, about the window coming up behind the Terminal. To me, this just seems to be one of those things that Apple is so pedantic about that it ends up not really making sense when you’re starting out with Cocoa.

From what I can gather, this is all tied into OS X’s demand that every app have a menu and an icon. And, in order to get a menu and an icon, you’ve got to get an Info.plist file and a .app directory structure and all of that. So, that’s one way of solving the problem.

Since our commandline app is being launched from the terminal, though, it’s just becoming another window in Terminal.app’s environment.

So, if you just want your app to work from the terminal, expand the win center call to also move the window up to the floating level.

[win center];
[win setLevel: NSFloatingWindowLevel];
[win makeKeyAndOrderFront: nil];

In the case of the Shoes installer, I also needed to sniff out the CPU type. I was okay having the little installer stub be a universal binary, but I really wanted to have it download other libraries specific to either PowerPC or Intel architectures.

The simplest way of doing this is to use the __ppc__ pre-processor def.

NSString *url;
#ifdef __ppc__
url = @"http://hacketyhack.net/pkg/osx/shoes-ppc";
#else
url = @"http://hacketyhack.net/pkg/osx/shoes";
#endif

Since the universal binary will actually compile this code twice (once for -arch i386 and once for -arch ppc,) each of the url assignment lines will end up in the proper side of the binary for its architecture.

If you’d rather sniff out the CPU type at runtime (or if you need the CPU frequency as well,) I’d look into calling sysctl with the CTL_HW and HW_CPU_FREQ flags. For some nice, complete examples, see the arch_info.c sample from the Darwin sources.


Lastly, I will mention briefly about linking to C. While you may be able to get away with just writing plain C functions in coke-o.m that can be exposed to C, you might eventually need to pass around Cocoa objects in your C structs.

Since Objective-C is a superset of C, I found it easiest to just compile everything as Objective-C. All of Cocoa’s controls can be passed as C pointers.

I kept all my C sources with the .c extension and added -x objective-c to the gcc flags when compiling vanilla C sources. And then you can safely #include <Cocoa/Cocoa.h> throughout your program.

Now begin the comments …

12 comments

aemadrid

said on June 25th 16:10

I’m totally lost. Does this all mean the next release of Shoes will support PPC back again? That would be a dream. I’m still a poor developer running an old G4 macbook.

_why

said on June 25th 17:30

Yes, definitely, the PPC builds will be out with the next set. Sans video support, however.

Dr Nic

said on June 25th 17:56

This is some sweet behind-the-scenes compiler info. I suck at this stuff. Hmm, I wonder if I can just pass ‘arm’ as the -arch flag for the rubycocoa.framework to get it into the iphone.

matthew

said on June 25th 18:21

hey sweet. curious why you made those INIT and RELEASE macros, tho.

Dr Nic

said on June 25th 19:29

FYI , in TextMate the ‘pool’ snippet generates:

NSAutoreleasePool *pool = [NSAutoreleasePool new];
$0
[pool release];

(the cursor ends at $0; its not printed)

Tim Burks

said on June 25th 19:50

Good digging! You might also enjoy having your Cocoa the Nu way:

#!/usr/local/bin/nush

(import Cocoa)

(set app (NSApplication sharedApplication))

(set window ((NSWindow alloc) initWithContentRect:'(0 0 450 140)
             styleMask:(+ NSTitledWindowMask
                          NSClosableWindowMask
                          NSMiniaturizableWindowMask)
             backing:NSBackingStoreBuffered
             defer:NO))

((window contentView) addSubview:
 (((NSTextField alloc) initWithFrame:'(40 90 400 18))
  set:(bezeled:NO
       stringValue:"Get nush, the Nu shell, at http://programming.nu."
       backgroundColor:(NSColor windowBackgroundColor)
       editable:NO
       selectable:NO)))

(window center)
(window makeKeyAndOrderFront:nil)

(app run)

Tim Burks

said on June 25th 19:57

One more thing: put this before (app run) to make your program exit when the window is closed.

(class WindowDelegate is NSObject
     (- (void) windowWillClose:(id) sender is
        ((NSApplication sharedApplication) terminate:0)))
(window setDelegate:(set windowdelegate ((WindowDelegate alloc) init)))

_why

said on June 25th 21:47

Lovely, Tim. That delegate is edible.

Say, while you’re here. Did you ever figure out Cocotron? I saw that you were trying to get Nu going on that. Anyhow, I downloaded Cocotron and I couldn’t make head nor tail of it. Does it go?

Tim Burks

said on June 25th 23:58

Not for me, but I didn’t spend much time with it. That’s mainly because its focus is Windows, which just isn’t on my radar. I’ve ported Nu to Linux using a smaller and simpler library called libFoundation. People have also expressed interest in GNUstep, but I haven’t seen a GNUstep-based Nu myself yet. I thought I would be doing that by now, but this iPhone SDK has been so much fun…

Socketwiz

said on June 26th 05:44

Your “Shoes” link in the right column is pointing to a broken web site: http://code.whytheluckystiff.net/shoes/

till

said on June 26th 12:34

“If you’d rather sniff out the CPU type at runtime …”

/usr/bin/arch + NSTask

aemadrid

said on June 26th 16:54

Can’t wait for the next release!

Comments are closed for this entry.