Learn Cocoa
Cocoa & C

Cocoa & C

This tutorial shows you how to create your first C Cocoa app. It assumes no previous experience with Objective C.

The app you will create contains no Objective C code, yet still has some impressive Cocoa functionality. It's a basic starting point only, but enough to get you going.
 

Create a New Project

First, Launch Xcode. If you have not installed Xcode, you can find it on your Mac OS X installation CD or the disk that came with your Mac. The installer is called Xcode Tools.

Once Xcode is running, open edit the nib and make somthing like:
 
Xcode All-in-One View
Next you gotta make a Converter thing give it some outlets and actions then make a new instance:
Xcode All-in-One View
And of course hook it up correctly like so:
Xcode All-in-One View

Write the Obj-C reference code

We should now write the code for Converter.h (these will be used as reference later on)
 
#import <Cocoa/Cocoa.h> // Not for long!

@interface Converter : NSObject // ugly
{
    IBOutlet id dollars; // wtf
    IBOutlet id pounds;
}

- (IBAction)performConvertion:(id)sender; // fail

@end // I think you mean '}'
and of course the .m file as well
 
#import "Converter.h"

@implementation Converter

- (IBAction)performConvertion:(id)sender
{
    [pounds setFloatValue: // WTF is up with all these square brackets
        [dollars floatValue] * 2.0 // WHY NOT USE SSCANF?
            ]; // this is how Cocoa makes me feel.
}

@end
But we can also rename main.m into main.c and make some minor changes for later:
 
//#import <Cocoa/Cocoa.h> // Obsolete

// Provide our own prototype for NSApplicationMain because
// otherwise we have to include Cocoa!
int NSApplicationMain(int argc, const char *argv[]);

int main(int argc, char *argv[])
{
    return NSApplicationMain(argc,  (const char **) argv);
}
Yeah, so that all works ok just now if you build & run. hopefully...

A peek into /usr/include/objc

So I found this http://journal.mycom.co.jp/column/objc/027/index.html

Can't read japanese so I don't really know what it does, but it probably works. paste directly into main.c, and now we do whats called Specializing. change:
  BOOL addNewClass(
          const char* name, 
          const char* superName)
into
  BOOL makeConverterClass()
now I can search and replace "name" for "Converter" and "superName" for "NSObject", thus turing some silly generic Does-Nothing function into somthing specifically tailored for my needs!
 
BOOL makeConverterClass()
{
    struct objc_class * meta_class;
    struct objc_class * super_class;
    struct objc_class * new_class;
    struct objc_class * root_class;
//    va_list             args;
    //
    // スーパークラスが存在しており、同じ名前のクラスを誰かが
    // すでに実装していないことを保証する
    //
    super_class = (struct objc_class *)objc_lookUpClass ("NSObject");
    if (super_class == nil)
    {
        return NO;
    }
        
    if (objc_lookUpClass ("Converter") != nil) 
    {
        return NO;
    }
    // ルートクラスを探す
    root_class = super_class;
    while( root_class->super_class != nil )
    {
        root_class = root_class->super_class;
    }
    // クラスとそのメタクラス用に領域を割り当てる
    new_class = calloc( 1, sizeof(struct objc_class) );
    meta_class = calloc( 1, sizeof(struct objc_class) );


    // クラスのセットアップ
    new_class->isa      = meta_class;
    new_class->info     = CLS_CLASS;
    meta_class->info    = CLS_META;
    //
    // クラス名のコピーを作成する。
    // 効率のため、メタクラスとクラス自体で、この名前のコピーを 
    // 共有するが、これはランタイムの必須条件ではない
    // 
    //
    new_class->name = malloc (strlen ("Converter") + 1);
    strcpy ((char*)new_class->name, "Converter");
    meta_class->name = new_class->name;
    //
    // 空のメソッドリストを割り当てる。
    // メソッドの追加は後でも可能。
    //
    new_class->methodLists = calloc( 1, sizeof(struct objc_method_list *) );
    meta_class->methodLists = calloc( 1, sizeof(struct objc_method_list *) );
    //
    // クラス定義を正しい階層に接続する:
    // クラスをスーパークラスに接続。
    // メタクラスをスーパークラスのメタクラスに接続。
    // メタクラスのメタクラスを
    // ルートクラスに接続。
    new_class->super_class  = super_class;
    meta_class->super_class = super_class->isa;
    meta_class->isa         = (void *)root_class->isa;
    // 最後に、クラスをランタイムに登録する
    
    objc_addClass( new_class );
    return YES;
}
Nextup, I gotta know if its working somehow... they used objc_lookUpClass and test if its nil, so I guess ill do that to or somthing.
 
int main(int argc, char *argv[])
{
    Class converter;
    
    converter = objc_lookUpClass("Converter");
    if(converter) printf("Converter exists\n");
    else          printf("Converter doesnt exists\n");
    
    return NSApplicationMain(argc,  (const char **) argv);
}
With the objc stuff there, yep:
  [Session started at 2007-05-16 13:48:56 +0100.]
  Converter exists
  
  «PROJECTNAME» has exited with status 0.
Now if I delete the Converter.m (and should have a long time ago!):
 
Xcode All-in-One View
then run it again.. yeah, its not there.
  [Session started at 2007-05-16 13:50:10 +0100.]
  Converter doesnt exists
  
  «PROJECTNAME» has exited with status 0.
now if I shove makeConverterClass() in there, the thing gets made but none of the buttons do anything because its got no functions.

Adding methods

So I was lurking on recent pastes on rafb a few months back trying to find some good code to steal and this drifted by:
 
#import <Foundation/Foundation.h>
#include <objc/objc-class.h>

@interface Dummy : NSObject
- (void)voidInVoidOut;
- (void)idInVoidOut:(id)value;
- (int)getInt;
- (float)getFloat;
- (double)getDouble;
- (void)funcWithInt:(int)value;
- (void)funcWithFloat:(float)value;
- (void)funcWithInt:(int)value andFloat:(float)value2;
- (void)funcWithFloat:(float)value andInt:(float)value2;
@end

@implementation Dummy : NSObject
- (void)voidInVoidOut; {}
- (void)idInVoidOut:(id)value; {}
- (int)getInt; {return 0;}
- (float)getFloat; {return 0;}
- (double)getDouble; {return 0;}
- (void)funcWithInt:(int)value; {}
- (void)funcWithFloat:(float)value; {}
- (void)funcWithInt:(int)value andFloat:(float)value2; {}
- (void)funcWithFloat:(float)value andInt:(float)value2; {}
@end

#define ATmethod_types(selectar) \
    class_getInstanceMethod([Dummy class], @selector(selectar))->method_types

int main (int argc, const char * argv[]) {
    printf("%s -> \"%s\"\n",
        "- (void)voidInVoidOut;",
        ATmethod_types(voidInVoidOut));
    
    printf("%s -> \"%s\"\n",
        "- (void)idInVoidOut:(id)value;",
        ATmethod_types(idInVoidOut:));
    
    printf("%s -> \"%s\"\n",
        "- (int)getInt;",
        ATmethod_types(getInt));
    
    printf("%s -> \"%s\"\n",
        "- (int)getFloat;",
        ATmethod_types(getFloat));
    
    printf("%s -> \"%s\"\n",
        "- (int)getDouble;",
        ATmethod_types(getDouble));
    
    printf("%s -> \"%s\"\n",
        "- (void)funcWithInt:(int)value;",
        ATmethod_types(funcWithInt:));
    
    printf("%s -> \"%s\"\n",
        "- (void)funcWithFloat:(float)value;",
        ATmethod_types(funcWithFloat:));
    
    printf("%s -> \"%s\"\n",
        "- (void)funcWithInt:(int)value andFloat:(float)value2;",
        ATmethod_types(funcWithInt:andFloat:));
    
    printf("%s -> \"%s\"\n",
        "- (void)funcWithFloat:(float)value andInt:(float)value2;",
        ATmethod_types(funcWithFloat: andInt:));

    return 0;
}
I ran it and got this:
 
  - (void)voidInVoidOut; -> "v8@0:4"
  - (void)idInVoidOut:(id)value; -> "v12@0:4@8"
  - (int)getInt; -> "i8@0:4"
  - (int)getFloat; -> "f8@0:4"
  - (int)getDouble; -> "d8@0:4"
  - (void)funcWithInt:(int)value; -> "v12@0:4i8"
  - (void)funcWithFloat:(float)value; -> "v12@0:4f8"
  - (void)funcWithInt:(int)value andFloat:(float)value2; -> "v16@0:4i8f12"
  - (void)funcWithFloat:(float)value andInt:(float)value2; -> "v16@0:4f8f12"
which looks pretty leet so I kept it. Turns out it actually came in useful today!

I paste in addNewMethod off something like python cocoa bridge or somthing I can't remember but anyway change the prototype to:
void addNewMethod(Class c,
                  char * method_name,
                  char * method_type,
                  IMP funcptr)
and move the guts around a bit to use the new parameters. so heres where im at basically:
 
void addNewMethod(Class c, char * method_name, char * method_type, IMP funcptr)
{
    struct objc_method myMethod;
    myMethod.method_name = sel_registerName(method_name);
    myMethod.method_types = method_type;
    myMethod.method_imp  = funcptr;

    struct objc_method_list * myMethodList;
    myMethodList = malloc (sizeof(struct objc_method_list));
    myMethodList->method_count = 1;
    myMethodList->method_list[0] = myMethod;
    
    class_addMethods ( c, myMethodList );
}

void Converter_performConvertion(id self, SEL _cmd, id blah) {
    printf("DEBUG: Converter_performConvertion called");
}

int main(int argc, char *argv[])
{
    Class converter;
    
    makeConverterClass();
    
    converter = objc_lookUpClass("Converter");
    if(converter) printf("Converter exists\n");
    else          printf("Converter doesnt exists\n");
    
    addNewMethod(converter, "performConvertion:",
                 "v12@0:4@8", (IMP)Converter_performConvertion);
    
    return NSApplicationMain(argc,  (const char **) argv);
}

Ivars?!

ok experimentation must be done, so comment out all the stuff in main we added and put Converter.m back in the target. In converter.m import the so we can look at this classe s insides a bit.. We can inspect the ivars a bit like this:
 
    Ivar inp = class_getInstanceVariable([self class], "dollars");
    
    NSLog(@"ivar {\n"
           "   name=%s\n"
           "   type=%s\n"
           "   offset=%d }",
           inp->ivar_name,
           inp->ivar_type,
           inp->ivar_offset);
outputs:
ivar {
   name=dollars
   type=@
   offset=4 }

ivar {
   name=pounds
   type=@
   offset=8 }
so.. sizeof(id) == 4 for me, I guess they add 4 each time but where does it start? :/
A while of looking through objc-class.h and its obviously
super_class->instance_size
so thats fine. We now know how to fill in the ivar structs.
 
    //// stuff to add ivars
    
    long start_offset = super_class->instance_size;
    int ivar_count = 2;
    
    new_class->ivars = malloc(sizeof(struct objc_ivar_list)
                + ivar_count*(sizeof(struct objc_ivar)));
    
    new_class->ivars->ivar_count = ivar_count;
    
    new_class->ivars->ivar_list[0].ivar_name = "dollars";
    new_class->ivars->ivar_list[0].ivar_type = "@";
    new_class->ivars->ivar_list[0].ivar_offset = start_offset;
    
    new_class->ivars->ivar_list[1].ivar_name = "pounds";
    new_class->ivars->ivar_list[1].ivar_type = "@";
    new_class->ivars->ivar_list[1].ivar_offset = start_offset + sizeof(id);
    
    //// </stuff>
Now we can actually use the ivars and port the final bit of code to C:
 
void Converter_performConvertion(id self, SEL _cmd, id blah) {
    id dollars, pounds;
    
    object_getInstanceVariable(self, "dollars", (void**)&dollars);
    object_getInstanceVariable(self, "pounds", (void**)&pounds);
    
#ifdef __i386__
    float (*fptr1)(id, SEL) = objc_msgSend_fpret;
#else
    float (*fptr1)(id, SEL) = objc_msgSend;
#endif
    id (*fptr2)(id, SEL, float) = objc_msgSend;
    
    fptr2(pounds, sel_getUid("setFloatValue:"),
        fptr1(dollars, sel_getUid("floatValue")) * 2.0);
}
and thats it done! No more Cocoa!

You can download the Xcode project here.