How to write iOS app purely in C
Solution 1
Damn, it took me a while but I got it:
main.c:
#include <CoreFoundation/CoreFoundation.h>
#include <objc/runtime.h>
#include <objc/message.h>
// This is a hack. Because we are writing in C, we cannot out and include
// <UIKit/UIKit.h>, as that uses Objective-C constructs.
// however, neither can we give the full function declaration, like this:
// int UIApplicationMain (int argc, char *argv[], NSString *principalClassName, NSString *delegateClassName);
// So, we rely on the fact that for both the i386 & ARM architectures,
// the registers for parameters passed in remain the same whether or not
// you are using VA_ARGS. This is actually the basis of the objective-c
// runtime (objc_msgSend), so we are probably fine here, this would be
// the last thing I would expect to break.
extern int UIApplicationMain(int, ...);
// Entry point of the application. If you don't know what this is by now,
// then you probably shouldn't be reading the rest of this post.
int main(int argc, char *argv[])
{
// Create an @autoreleasepool, using the old-stye API.
// Note that while NSAutoreleasePool IS deprecated, it still exists
// in the APIs for a reason, and we leverage that here. In a perfect
// world we wouldn't have to worry about this, but, remember, this is C.
id autoreleasePool = objc_msgSend(objc_msgSend(objc_getClass("NSAutoreleasePool"), sel_registerName("alloc")), sel_registerName("init"));
// Notice the use of CFSTR here. We cannot use an objective-c string
// literal @"someStr", as that would be using objective-c, obviously.
UIApplicationMain(argc, argv, nil, CFSTR("AppDelegate"));
objc_msgSend(autoreleasePool, sel_registerName("drain"));
}
AppDelegate.c:
#import <objc/runtime.h>
#import <objc/message.h>
// This is equivalent to creating a @class with one public variable named 'window'.
struct AppDel
{
Class isa;
id window;
};
// This is a strong reference to the class of the AppDelegate
// (same as [AppDelegate class])
Class AppDelClass;
// this is the entry point of the application, same as -application:didFinishLaunchingWithOptions:
// note the fact that we use `void *` for the 'application' and 'options' fields, as we need no reference to them for this to work. A generic id would suffice here as well.
BOOL AppDel_didFinishLaunching(struct AppDel *self, SEL _cmd, void *application, void *options)
{
// we +alloc and -initWithFrame: our window here, so that we can have it show on screen (eventually).
// this entire method is the objc-runtime based version of the standard View-Based application's launch code, so nothing here really should surprise you.
// one thing important to note, though is that we use `sel_getUid()` instead of @selector().
// this is because @selector is an objc language construct, and the application would not have been created in C if I used @selector.
self->window = objc_msgSend(objc_getClass("UIWindow"), sel_getUid("alloc"));
self->window = objc_msgSend(self->window, sel_getUid("initWithFrame:"), (struct CGRect) { 0, 0, 320, 480 });
// here, we are creating our view controller, and our view. note the use of objc_getClass, because we cannot reference UIViewController directly in C.
id viewController = objc_msgSend(objc_msgSend(objc_getClass("UIViewController"), sel_getUid("alloc")), sel_getUid("init"));
// creating our custom view class, there really isn't too much
// to say here other than we are hard-coding the screen's bounds,
// because returning a struct from a `objc_msgSend()` (via
// [[UIScreen mainScreen] bounds]) requires a different function call
// and is finicky at best.
id view = objc_msgSend(objc_msgSend(objc_getClass("View"), sel_getUid("alloc")), sel_getUid("initWithFrame:"), (struct CGRect) { 0, 0, 320, 480 });
// here we simply add the view to the view controller, and add the viewController to the window.
objc_msgSend(objc_msgSend(viewController, sel_getUid("view")), sel_getUid("addSubview:"), view);
objc_msgSend(self->window, sel_getUid("setRootViewController:"), viewController);
// finally, we display the window on-screen.
objc_msgSend(self->window, sel_getUid("makeKeyAndVisible"));
return YES;
}
// note the use of the gcc attribute extension (constructor).
// Basically, this lets us run arbitrary code before program startup,
// for more information read here: http://stackoverflow.com/questions/2053029
__attribute__((constructor))
static void initAppDel()
{
// This is objc-runtime gibberish at best. We are creating a class with the
// name "AppDelegate" that is a subclass of "UIResponder". Note we do not need
// to register for the UIApplicationDelegate protocol, that really is simply for
// Xcode's autocomplete, we just need to implement the method and we are golden.
AppDelClass = objc_allocateClassPair(objc_getClass("UIResponder"), "AppDelegate", 0);
// Here, we tell the objc runtime that we have a variable named "window" of type 'id'
class_addIvar(AppDelClass, "window", sizeof(id), 0, "@");
// We tell the objc-runtime that we have an implementation for the method
// -application:didFinishLaunchingWithOptions:, and link that to our custom
// function defined above. Notice the final parameter. This tells the runtime
// the types of arguments received by the function.
class_addMethod(AppDelClass, sel_getUid("application:didFinishLaunchingWithOptions:"), (IMP) AppDel_didFinishLaunching, "i@:@@");
// Finally we tell the runtime that we have finished describing the class and
// we can let the rest of the application use it.
objc_registerClassPair(AppDelClass);
}
View.c
#include <objc/runtime.h>
// This is a strong reference to the class of our custom view,
// In case we need it in the future.
Class ViewClass;
// This is a simple -drawRect implementation for our class. We could have
// used a UILabel or something of that sort instead, but I felt that this
// stuck with the C-based mentality of the application.
void View_drawRect(id self, SEL _cmd, struct CGRect rect)
{
// We are simply getting the graphics context of the current view,
// so we can draw to it
CGContextRef context = UIGraphicsGetCurrentContext();
// Then we set it's fill color to white so that we clear the background.
// Note the cast to (CGFloat []). Otherwise, this would give a warning
// saying "invalid cast from type 'int' to 'CGFloat *', or
// 'extra elements in initializer'. Also note the assumption of RGBA.
// If this wasn't a demo application, I would strongly recommend against this,
// but for the most part you can be pretty sure that this is a safe move
// in an iOS application.
CGContextSetFillColor(context, (CGFloat []){ 1, 1, 1, 1 });
// here, we simply add and draw the rect to the screen
CGContextAddRect(context, (struct CGRect) { 0, 0, 320, 480 });
CGContextFillPath(context);
// and we now set the drawing color to red, then add another rectangle
// and draw to the screen
CGContextSetFillColor(context, (CGFloat []) { 1, 0, 0, 1 });
CGContextAddRect(context, (struct CGRect) { 10, 10, 20, 20 });
CGContextFillPath(context);
}
// Once again we use the (constructor) attribute. generally speaking,
// having many of these is a very bad idea, but in a small application
// like this, it really shouldn't be that big of an issue.
__attribute__((constructor))
static void initView()
{
// Once again, just like the app delegate, we tell the runtime to
// create a new class, this time a subclass of 'UIView' and named 'View'.
ViewClass = objc_allocateClassPair(objc_getClass("UIView"), "View", 0);
// and again, we tell the runtime to add a function called -drawRect:
// to our custom view. Note that there is an error in the type-specification
// of this method, as I do not know the @encode sequence of 'CGRect' off
// of the top of my head. As a result, there is a chance that the rect
// parameter of the method may not get passed properly.
class_addMethod(ViewClass, sel_getUid("drawRect:"), (IMP) View_drawRect, "v@:");
// And again, we tell the runtime that this class is now valid to be used.
// At this point, the application should run and display the screenshot shown below.
objc_registerClassPair(ViewClass);
}
It's ugly, but it works.
If you would like to download this, you can get it from my dropbox here
You can get it from my GitHub repository here:
Solution 2
Objective-C is a superset of the C-language, so it is theoretically possible to write a program entirely in C, however, unless you are thoroughly versed in OpenGL ES
, You'll need to do at least some objC (Even Rich's sample has a const NSString* in it), else you'll have to write the views yourself.
OK, the above is completely wrong. Let me say, I'm astounded Rich achieved this lofty goal, so I ported it over to the mac (source here). The files below have no headers, do not link to Cocoa, nor does the project have a nib:
AppDelegate.m
#include <objc/runtime.h>
#include <objc/message.h>
extern id NSApp;
struct AppDel
{
Class isa;
//Will be an NSWindow later, for now, it's id, because we cannot use pointers to ObjC classes
id window;
};
// This is a strong reference to the class of the AppDelegate
// (same as [AppDelegate class])
Class AppDelClass;
BOOL AppDel_didFinishLaunching(struct AppDel *self, SEL _cmd, id notification) {
//alloc NSWindow
self->window = objc_msgSend(objc_getClass("NSWindow"),
sel_getUid("alloc"));
//init NSWindow
//Adjust frame. Window would be about 50*50 px without this
//specify window type. We want a resizeable window that we can close.
//use retained backing because this thing is small anyhow
//return no because this is the main window, and should be shown immediately
self->window = objc_msgSend(self->window,
sel_getUid("initWithContentRect:styleMask:backing:defer:"),(NSRect){0,0,1024,460}, (NSTitledWindowMask|NSClosableWindowMask|NSResizableWindowMask|NSMiniaturizableWindowMask),NSBackingStoreRetained,NO);
//send alloc and init to our view class. Love the nested objc_msgSends!
id view = objc_msgSend(objc_msgSend(objc_getClass("View"), sel_getUid("alloc")), sel_getUid("initWithFrame:"), (struct CGRect) { 0, 0, 320, 480 });
// here we simply add the view to the window.
objc_msgSend(self->window, sel_getUid("setContentView:"), view);
objc_msgSend(self->window, sel_getUid("becomeFirstResponder"));
//makeKeyOrderFront: NSWindow to show in bottom left corner of the screen
objc_msgSend(self->window,
sel_getUid("makeKeyAndOrderFront:"),
self);
return YES;
}
static void initAppDel()
{
//Our appDelegate should be NSObject, but if you want to go the hard route, make this a class pair of NSApplication and try initing those awful delegate methods!
AppDelClass = objc_allocateClassPair((Class)
objc_getClass("NSObject"), "AppDelegate", 0);
//Change the implementation of applicationDidFinishLaunching: so we don't have to use ObjC when this is called by the system.
class_addMethod(AppDelClass,
sel_getUid("applicationDidFinishLaunching:"),
(IMP) AppDel_didFinishLaunching, "i@:@");
objc_registerClassPair(AppDelClass);
}
void init_app(void)
{
objc_msgSend(
objc_getClass("NSApplication"),
sel_getUid("sharedApplication"));
if (NSApp == NULL)
{
fprintf(stderr,"Failed to initialized NSApplication... terminating...\n");
return;
}
id appDelObj = objc_msgSend(
objc_getClass("AppDelegate"),
sel_getUid("alloc"));
appDelObj = objc_msgSend(appDelObj, sel_getUid("init"));
objc_msgSend(NSApp, sel_getUid("setDelegate:"), appDelObj);
objc_msgSend(NSApp, sel_getUid("run"));
}
//there doesn't need to be a main.m because of this little beauty here.
int main(int argc, char** argv)
{
//Initialize a valid app delegate object just like [NSApplication sharedApplication];
initAppDel();
//Initialize the run loop, just like [NSApp run]; this function NEVER returns until the app closes successfully.
init_app();
//We should close acceptably.
return EXIT_SUCCESS;
}
View.m
#include <objc/runtime.h>
#include <objc/message.h>
#include <ApplicationServices/ApplicationServices.h>
// This is a strong reference to the class of our custom view,
// In case we need it in the future.
Class ViewClass;
// This is a simple -drawRect implementation for our class. We could have
// used a UILabel or something of that sort instead, but I felt that this
// stuck with the C-based mentality of the application.
void View_drawRect(id self, SEL _cmd, CGRect rect)
{
//make a red NSColor object with its convenience method
id red = objc_msgSend(objc_getClass("NSColor"), sel_getUid("redColor"));
// fill target rect with red, because this is it!
NSRect rect1 = NSMakeRect ( 21,21,210,210 );
objc_msgSend(red, sel_getUid("set"));
NSRectFill ( rect1 );
}
// Once again we use the (constructor) attribute. generally speaking,
// having many of these is a very bad idea, but in a small application
// like this, it really shouldn't be that big of an issue.
__attribute__((constructor))
static void initView()
{
// Once again, just like the app delegate, we tell the runtime to
// create a new class, this time a subclass of 'UIView' and named 'View'.
ViewClass = objc_allocateClassPair((Class) objc_getClass("NSView"), "View", 0);
// and again, we tell the runtime to add a function called -drawRect:
// to our custom view. Note that there is an error in the type-specification
// of this method, as I do not know the @encode sequence of 'CGRect' off
// of the top of my head. As a result, there is a chance that the rect
// parameter of the method may not get passed properly.
class_addMethod(ViewClass, sel_getUid("drawRect:"), (IMP) View_drawRect, "v@:");
// And again, we tell the runtime that this class is now valid to be used.
// At this point, the application should run and display the screenshot shown below.
objc_registerClassPair(ViewClass);
}
prefix.pch
//
// Prefix header for all source files of the 'CBasedMacApp' target in the 'CBasedMacApp' project
//
#ifdef __OBJC__
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#endif
Solution 3
I read here Learn C Before Objective-C?
Usually I then replace some Obj-C code with pure C code (after all you can mix them as much as you like, the content of an Obj-C method can be entirely, pure C code)
Is this true?
Could I build an iPhone app purely in the C programming language?
The quoted passage is true, but the answer to your question is no.
To illustrate what answerer Mecki on that other question was talking about:
- (void) drawRect:(CGRect)dirtyRect { //Objective-C
CGContextRef context = UIGraphicsGetCurrentContext(); //C
CGContextSetRGBFillColor(context, 1.0, 0.0, 0.0, 1.0); //C
CGContextFillRect(context, dirtyRect); //C
} //Objective-C (balances above “- (void) drawRect:…” line)
There is nothing but pure C code within this method, but the method itself is Objective-C code, as is the class that contains this method.
So it is possible to do what Mecki said, but you can't (practically—as Richard J. Ross III showed, it's technically possible but quite a lot of typing) write a whole Cocoa Touch program in pure C.
001
Only questions with complete answers are accepted as solutions.
Updated on August 18, 2020Comments
-
001 almost 4 years
I read here Learn C Before Objective-C?
Usually I then replace some Obj-C code with pure C code (after all you can mix them as much as you like, the content of an Obj-C method can be entirely, pure C code)
Is this true?
Is it possible to build an iPhone app purely in the C programming language?
-
Richard J. Ross III about 12 yearsNot true, you can use the objc runtime to build an app in C, give me a few minutes and I'll show you
-
bbum about 12 yearsYes, and you can dig a foundation with a spoon, but that doesn't make it either a good idea nor terribly effective.
-
Richard J. Ross III about 12 years@MahmoudAl-Qudsi I didn't give up :)
-
harry about 12 years@Richard you dug a foundation with a spoon and didn't give up? I commend you! (I kid, I kid.. but that was my joke in the earlier comment :)
-
hansvb about 12 yearsGreat. So in order to avoid learning Objective-C (which I think was the gist of the question) you now have to learn the implementation details and C-level API of the Objective-C runtime.
-
Hejazzman about 12 yearsWell, the skill might also come handy when you are in the Shawshank redemption...
-
XCool about 12 yearsany idea how one would compile and run the above sample code?
-
Richard J. Ross III about 12 years@XCool I added a link in the post where you can get it. I has a few minor changes, such as renaming .m files to .c files, but other than that it is largely the same.
-
Adam Rosenfield about 12 yearsIf you do decide to convert this to assembly, as per several of the suggestions, make sure to do it in ARM (regular and thumb instruction sets!) and in x86 so that it works in the simulator. Maybe also PowerPC for good measure, if you want to port it to Mac OS X v10.4.
-
CodaFi almost 12 yearsYeah. The thing that gets me, is if it weren't for the modern runtime code, this would work on every Mac with an X in its software name.
-
Dave almost 12 yearsFor what it's worth, the
(CGFloat [])
is not a cast: it is part of a compound literal (a C99 feature). -
Richard J. Ross III almost 12 years@Dave but it's still C, so it stays.
-
Dave almost 12 yearsNo problem with them -- my correction was that they aren't, as your comments claim, casts. The C99 bit was just an extra detail.
-
Richard J. Ross III almost 12 years@Dave feel free to edit the comments then. (with a proper link to the specification for that)
-
jamesmortensen over 11 yearsHey CodaFi, your link appears to be broken: github.com/CodaFi/CBasedMacApp. Any chance of getting that fixed? Also, I noticed that you're still using .m files? Do they compile as .c files?
-
CodaFi over 11 years@jmort253: my apologies. I renamed the repo to C-Macs (ha!), and didn't think to redo the link. Fixing it now. Also, the files must use the .m extension to avoid weird header import issues. It all turns into .o's anyways.
-
CodaFi over 11 yearsI would be careful with that. The application is highly dependent on the user having libObjc to link against (which means Mac and the occasional Linux user).
-
esh about 11 yearsCorrect me if I am wrong, but isn't
id
an objective-c keyword (used in the main method)? Shouldn't that bevoid *
? -
Richard J. Ross III about 11 years@blackFlam3 nope,
id
is a typedef forstruct objc_object *
, and is defined inobjc/objc.h
. -
Richard J. Ross III about 11 years@landonf not entirely true. That is te case if you are compiling as objective-c, but since we are only compiling as C, the compiler has no knowledge that this method is special, this actually invoking it with varargs.
-
Richard J. Ross III about 11 years@iandonf well, its a good thing that ios is developed for the ARM & x86 architectures, then, as the registers used for the parameters don't change.
-
landonf about 11 years@RichardJ.RossIII If you're comfortable with people taking this answer and writing code that's actually architecture-specific, I guess. Of course, I also didn't even touch on _stret/_fpret handling, which is another issue. It's just not really possible to write portable ObjC in C; the generated code is inescapably ABI-specific.
-
Richard J. Ross III about 11 years@iandof you could argue that all of C is "ABI-specific", and non portable (how often do you have to put a
#if MSVC
in your "portable" code), because Every platform and compiler has its quirks an bugs, but that doesn't mean that you shouldn't write code in C! -
Richard J. Ross III about 11 years@landonf I am completely aware of that. I am simply saying that playing the standards, or in this case, the architecture game is rarely useful, as these ABIs are unlikely to change in the near future when this answer is relevant. At any rate, we're clogging up the comments here, so +1 and /thread.
-
Richard J. Ross III about 11 years@Quuxplusone I suppose I could, if I had thought about it. It's still technically undefined behavior to pass one type to a function declared to take another, even if there exists an implicit conversion. I like that comment, though, so it stays.
-
Richard J. Ross III about 11 years@Quuxplusone and I don't think I want to change that line. I don't care that much for
+new
, as I feel its less explicit as to what's going on behind the scenes. The distinction between allocation & initialization is important IMO. -
Richard J. Ross III about 11 years@Quuxplusone I don't think there's any way to get around the fact that this code indeed is arcane.
-
Richard J. Ross III almost 11 years@Maxthon of course that is correct - and I may make those changes eventually to the git repo, but I'm not going to bump the question again for a minor change like that.
-
Maxthon Chan almost 11 years@RichardJ.RossIII The
__class
thing is actually from my Objective-C HTTP server project - with that I exposed several constant strings andCGIAppllicationMain(int, const char **, const char *, const char *)
into C (and thus I can useCGIApplicationMain
inmain.c
) -
Sport over 10 yearsaccording to requirement we are using c code in iPhone and iOS development.
-
Gabriele Petronella over 10 years
objc_msgSend()
is pure C. The fact that i callsinitWithFrame:
doesn't matter, since method implementations are C functions as well. -
make.it.floss over 10 yearsobjc_msgSend() is a C function, yes, but it is part of the Objective-C runtime, right?
-
Engineer almost 9 years@RichardJ.RossIII, I'd like to port a C/OpenGL app to iOS and need to interface with the existing Obj-C input (touch, IME) mechanisms. What Apple documentation would I seek out to do this? Or is this sort of thing - e.g. what you have done here - entirely undocumented? -- sorry to ask, totally new to iOS.
-
PineForestRanch about 8 yearsThis is not pure C. NSApp is a dynamically shared variable. It requires special sections in Mach-O file, which are produced by ObjC compiler. If you compile it with C compiler, it won't work, probably won't even compile.
-
CodaFi about 8 yearsIf you want to get technical, I have a repository you can contribute a fix to. I'm sure there's a way to mock NSApplication with the runtime to achieve a "pure" implementation to your satisfaction, wouldn't you say?
-
CodaFi about 8 yearsOh, and if
clang -x c -arch x86_64 -std=gnu99 -I ./CMacs -c $(SRCFILES)
ever fails to work - which seems the case on your end - please file an issue. I can't help you in comments! -
Benj over 7 yearscan you please explain what the Class keyword in the stateents
Class isa;
andClass AppDelClass;
does? is this a C keyword? I tried to google it, but gl on finding anything when googling 'Class' -
Richard J. Ross III over 7 years@Benj Class is a typedef defined in
objc/runtime.h
, for theobjc_class
struct, which is an opaque pointer. -
techcraver over 7 yearsI couldn't see any Obj-C construct in the code posted there. But still this works even invoking obj-c libraries in "C" way!