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:
Next you gotta make a Converter thing give it some outlets and actions then make a new instance:
And of course hook it up correctly like so:
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>
@interface Converter : NSObject {
IBOutlet id dollars; IBOutlet id pounds;
}
- (IBAction)performConvertion:(id)sender;
@end
and of course the .m file as well
#import "Converter.h"
@implementation Converter
- (IBAction)performConvertion:(id)sender
{
[pounds setFloatValue: [dollars floatValue] * 2.0 ]; }
@end
But we can also rename main.m into main.c and make some minor changes for later:
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;
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!):
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.
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);
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**)£s);
#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.