29

I'm creating an application in pure C on Mac OSX. What I want is to create a window in which my app will be displayed.

Preferably I want it to be pure C solution, but if I have to use Objective-C class to initialize the window and then send a context to my C code then it will be fine.

I'm not using Xcode, only a simple text editor in with I tried to import Cocoa but it just generated a lot of errors.

How to write code in simple pure C code that will display a window in OSX?

user16217248
  • 3,119
  • 19
  • 19
  • 37
Mago
  • 485
  • 1
  • 4
  • 8
  • Xcode isn't a requirement for writing an Cocoa app for OS X, but it reduces the difficulty *considerably*. Unless you're planning on writing an X-window application and relying on Quartz or some other window manager for your rendering, pure-C is pretty much not gonna happen. [Minimalist Cocoa with Objective-C](http://www.cocoawithlove.com/2010/09/minimalist-cocoa-programming.html) , then burying your core logic in C is doable, but you may ultimately find much of it easier just to use flat-out Objective-C in the end. – WhozCraig May 15 '15 at 22:04
  • What do you want to show in the window? Static text, editable text, UI controls, 3D graphics...? – JWWalker May 15 '15 at 23:31
  • @JWWalker canvas on which I will draw – Mago May 15 '15 at 23:36
  • If you're willing to do your drawing with OpenGL, then you could use the GLUT framework. See `glutInit`, `glutCreateWindow` etc. – JWWalker May 16 '15 at 00:21
  • @Mago This question is a duplicate of that other question because it asks the same thing -- how to write a Cocoa app in C. The underlying technique for both iOS and OS X is the same - use the runtime API. There's even an OS X example in that question. So this question is a duplicate. – SevenBits May 16 '15 at 12:33
  • See also: [Complete solution for writing Mac OS X application in C++](https://stackoverflow.com/questions/634404) – hippietrail Mar 23 '21 at 04:29

7 Answers7

26

I did a translation of the accepted answer to Pure C:

// based on https://stackoverflow.com/a/30269562
// Minimal Pure C code to create a window in Cocoa

// $ clang minimal.c -framework Cocoa -o minimal.app

#include <objc/runtime.h>
#include <objc/message.h>

#include <Carbon/Carbon.h>

#define cls objc_getClass
#define sel sel_getUid
#define msg ((id (*)(id, SEL, ...))objc_msgSend)
#define cls_msg ((id (*)(Class, SEL, ...))objc_msgSend)

// poor man's bindings!
typedef enum NSApplicationActivationPolicy {
    NSApplicationActivationPolicyRegular   = 0,
    NSApplicationActivationPolicyAccessory = 1,
    NSApplicationActivationPolicyERROR     = 2,
} NSApplicationActivationPolicy;

typedef enum NSWindowStyleMask {
    NSWindowStyleMaskBorderless     = 0,
    NSWindowStyleMaskTitled         = 1 << 0,
    NSWindowStyleMaskClosable       = 1 << 1,
    NSWindowStyleMaskMiniaturizable = 1 << 2,
    NSWindowStyleMaskResizable      = 1 << 3,
} NSWindowStyleMask;

typedef enum NSBackingStoreType {
    NSBackingStoreBuffered = 2,
} NSBackingStoreType;

int main(int argc, char *argv[])
{
    // id app = [NSApplication sharedApplication];
    id app = cls_msg(cls("NSApplication"), sel("sharedApplication"));

    // [app setActivationPolicy:NSApplicationActivationPolicyRegular];
    msg(app, sel("setActivationPolicy:"), NSApplicationActivationPolicyRegular);

    struct CGRect frameRect = {0, 0, 600, 500};

    // id window = [[NSWindow alloc] initWithContentRect:frameRect styleMask:NSWindowStyleMaskTitled|NSWindowStyleMaskClosable|NSWindowStyleMaskResizable backing:NSBackingStoreBuffered defer:NO];
    id window = msg(cls_msg(cls("NSWindow"), sel("alloc")),
                    sel("initWithContentRect:styleMask:backing:defer:"),
                    frameRect,
                    NSWindowStyleMaskTitled|NSWindowStyleMaskClosable|NSWindowStyleMaskResizable,
                    NSBackingStoreBuffered,
                    false);
    msg(window, sel("setTitle:"), cls_msg(cls("NSString"), sel("stringWithUTF8String:"), "Pure C App"));

    // [window makeKeyAndOrderFront:nil];
    msg(window, sel("makeKeyAndOrderFront:"), nil);

    // [app activateIgnoringOtherApps:YES];
    msg(app, sel("activateIgnoringOtherApps:"), true);

    msg(app, sel("run"));
}
S.S. Anne
  • 15,171
  • 8
  • 38
  • 76
hasen
  • 161,647
  • 65
  • 194
  • 231
17

You can use Objective-C runtime API example (iOS) Creating an iOS app in pure C

Alternative the same code in obj-c :

echo '#import <Cocoa/Cocoa.h>
int main ()
    {
        @autoreleasepool{
            [NSApplication sharedApplication];
            [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
            id applicationName = [[NSProcessInfo processInfo] processName];
            id window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 120, 120)
                styleMask:NSTitledWindowMask backing:NSBackingStoreBuffered defer:NO];
            [window cascadeTopLeftFromPoint:NSMakePoint(20,20)];
            [window setTitle: applicationName];
            [window makeKeyAndOrderFront:nil];
            [NSApp activateIgnoringOtherApps:YES];
            [NSApp run];
        }
        return 0;
}' | gcc -fobjc-arc -framework Cocoa -x objective-c -o MicroApp - ; ./MicroApp

This will run Cocoa app with 1 window. Like on screenshot below

enter image description here

You can actually add menu using NSMenu

    id applicationMenuBar = [NSMenu new];
    id appMenuItem        = [NSMenuItem new];
    [applicationMenuBar addItem:appMenuItem];
    [NSApp setMainMenu: applicationMenuBar];
Adham Zahran
  • 1,973
  • 2
  • 18
  • 35
CryingHippo
  • 5,026
  • 1
  • 28
  • 32
  • 8
    This answer is wrong. You don't have to use Objective-C for the UI. You can call most Objective-C classes from C using the Objective-C runtime API and programmatically create the UI elements. It's not practical, but it can be done. – SevenBits May 15 '15 at 22:22
  • Could you give an example pls ? – CryingHippo May 15 '15 at 22:24
  • 1
    That's for iOS but it applies equally to OS X as well. – SevenBits May 15 '15 at 22:25
  • Oh yes :) Runtime API... I will update my answer, thanks for the tip ! – CryingHippo May 15 '15 at 22:29
  • 2
    That example code isn't "mixing C/C++ and Objective-C", it's *pure* Objective-C. – nobody May 15 '15 at 22:49
  • 6
    This answer shouldn't have been accepted. It's not the correct answer to the question - this program is pure Objective-C, and the OP stated that he wanted to write the program in pure C (i.e, without Objective-C). – SevenBits May 16 '15 at 12:30
13

Can you do this? Yes and no (you can do anything if you're persistent enough). Yes you can, but no you shouldn't. Regardless, this can be done for the incredibly persistent among you. Since coding up an example will take awhile, I found a generous soul on the net who already did it. Look at this repository on GitHub for the full code and explanations. Here are some snippets:

cmacs_simple_msgSend((id)objc_getClass("NSApplication"), sel_getUid("sharedApplication"));

if (NSApp == NULL) {
    fprintf(stderr,"Failed to initialized NSApplication...  terminating...\n");
    return;
}

id appDelObj = cmacs_simple_msgSend((id)objc_getClass("AppDelegate"), sel_getUid("alloc"));
appDelObj = cmacs_simple_msgSend(appDelObj, sel_getUid("init"));

cmacs_void_msgSend1(NSApp, sel_getUid("setDelegate:"), appDelObj);
cmacs_void_msgSend(NSApp, sel_getUid("run"));

As you'll notice, this code uses the Objective-C runtime API to create a faux AppDelegate. And creating the window is an involved process:

self->window = cmacs_simple_msgSend((id)objc_getClass("NSWindow"), sel_getUid("alloc"));

/// Create an instance of the window.
self->window = cmacs_window_init_msgSend(self->window, sel_getUid("initWithContentRect:styleMask:backing:defer:"), (CMRect){0,0,1024,460}, (NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask), 0, false);

/// Create an instance of our view class.
///
/// Relies on the view having declared a constructor that allocates a class pair for it.
id view = cmacs_rect_msgSend1(cmacs_simple_msgSend((id)objc_getClass("View"), sel_getUid("alloc")), sel_getUid("initWithFrame:"), (CMRect){ 0, 0, 320, 480 });

// here we simply add the view to the window.
cmacs_void_msgSend1(self->window, sel_getUid("setContentView:"), view);
cmacs_simple_msgSend(self->window, sel_getUid("becomeFirstResponder"));

// Shows our window in the bottom-left hand corner of the screen.
cmacs_void_msgSend1(self->window, sel_getUid("makeKeyAndOrderFront:"), self);
return YES;

So, yes. You can write a Cocoa app in pure C. But I wouldn't recommend it. 90% of that code can be replaced by an xib file, and doing it this way really restricts your app because more advanced features of the Apple development stack really on Objective-C features. While it's technically possible to do everything this way, you're making it much harder than it ought to be.

SevenBits
  • 2,836
  • 1
  • 20
  • 33
10

I remember seeing this question about a year ago, back when I so desperately wished I could open up a d*** window, googling for days and only finding the type of answers you see above this post.

I was reading up on the operating system the Mac is built on - Berkley Software Distribution. http://codex.cs.yale.edu/avi/os-book/OS9/appendices-dir/a.pdf Where on page 17 the phrase "...X Windowing System developed at MIT" hit me and I remembered how I couldn't open up a window and how pissed I was about that, and I thought maybe this was finally the solution!

I googled "BSD X Window Programming" and stumbled my way into finally getting a window open in pure C.

I just discovered it so I'm not a master yet but look at this link https://en.wikibooks.org/wiki/X_Window_Programming/Xlib and go to the example, make sure to follow the comments at the top for how to compile with the X11 library (you can ignore the -Wall and -O commands as long as you have the -lX11).

If you can't compile, if it can't find the header files, you'll need to help it find the header files.

There might be a couple different places that the X11 includes could be on your system. More than likely you'll find it in /opt/X11/include which will have all the definitions of the headers you'll need.

You could include the full path in your C programs such as:

#include "/opt/X11/include/X11/Xlib.h"

But we want it to look like this #include <X11/Xlib.h> So you could add this switch to GCC when you compile -I /opt/X11/include

Or go to your .profile or .bashrc or .bash_profile in your home directory and add:

export C_INCLUDE_PATH="$C_INCLUDE_PATH:/opt/X11/include"
/*

Simple Xlib application drawing a box in a window.

*/

From the wiki:

#include<X11/Xlib.h>
#include<stdio.h>
#include<stdlib.h> // prevents error for exit on line   18 when compiling with gcc
int main() {
  Display *d;
  int s;
  Window w;
  XEvent e;

                    /* open connection with the server */
  d=XOpenDisplay(NULL);
  if(d==NULL) {
    printf("Cannot open display\n");
    exit(1);
  }
  s=DefaultScreen(d);

                    /* create window */
  w=XCreateSimpleWindow(d, RootWindow(d, s), 10, 10, 100, 100, 1,
                     BlackPixel(d, s), WhitePixel(d, s));

  // Process Window Close Event through event handler so      XNextEvent does Not fail
  Atom delWindow = XInternAtom( d, "WM_DELETE_WINDOW", 0 );
  XSetWMProtocols(d , w, &delWindow, 1);

                    /* select kind of events we are interested in */
  XSelectInput(d, w, ExposureMask | KeyPressMask);

                    /* map (show) the window */
  XMapWindow(d, w);

                    /* event loop */
  while(1) {
    XNextEvent(d, &e);
                    /* draw or redraw the window */
    if(e.type==Expose) {
      XFillRectangle(d, w, DefaultGC(d, s), 20, 20, 10, 10);
    }
                    /* exit on key press */
    if(e.type==KeyPress)
      break;

    // Handle Windows Close Event
    if(e.type==ClientMessage)
       break;
  }

                    /* destroy our window */
  XDestroyWindow(d, w);

                    /* close connection to server */
  XCloseDisplay(d);

  return 0;
}

Compile:

 gcc -O2 -Wall -o test test.c -L /usr/X11R6/lib -lX11 -lm
  • 1
    Does this require XQuartz? I am using XCB for my pure-C app but it did not work on my M1 Mac until I installed XQuartz 2.8.1 from Homebrew. – Ignis Incendio Oct 13 '21 at 12:27
3

Unfortunately the top rated answer doesn't work on new Apple Silicon powered machines due to an ABI mismatch. Basically on ARM64 you can't use the objc_msgSend declaration with variable arguments, you must specify the correct argument types for each call. Here is the version that runs on Apple Silicon:

// based on https://stackoverflow.com/a/59596600/834108
// Minimal Pure C code to create a window in Cocoa
// Adapted to work on ARM64

// $ clang minimal.c -framework Cocoa -o minimal.app

#include <objc/runtime.h>
#include <objc/message.h>

#include <Carbon/Carbon.h>

#define cls objc_getClass
#define sel sel_getUid
#define msg ((id (*)(id, SEL))objc_msgSend)
#define msg_int ((id (*)(id, SEL, int))objc_msgSend)
#define msg_id  ((id (*)(id, SEL, id))objc_msgSend)
#define msg_ptr ((id (*)(id, SEL, void*))objc_msgSend)
#define msg_cls ((id (*)(Class, SEL))objc_msgSend)
#define msg_cls_chr ((id (*)(Class, SEL, char*))objc_msgSend)

// poor man's bindings!
typedef enum NSApplicationActivationPolicy {
    NSApplicationActivationPolicyRegular   = 0,
    NSApplicationActivationPolicyAccessory = 1,
    NSApplicationActivationPolicyERROR     = 2,
} NSApplicationActivationPolicy;

typedef enum NSWindowStyleMask {
    NSWindowStyleMaskBorderless     = 0,
    NSWindowStyleMaskTitled         = 1 << 0,
    NSWindowStyleMaskClosable       = 1 << 1,
    NSWindowStyleMaskMiniaturizable = 1 << 2,
    NSWindowStyleMaskResizable      = 1 << 3,
} NSWindowStyleMask;

typedef enum NSBackingStoreType {
    NSBackingStoreBuffered = 2,
} NSBackingStoreType;

int main(int argc, char *argv[])
{
    // id app = [NSApplication sharedApplication];
    id app = msg_cls(cls("NSApplication"), sel("sharedApplication"));

    // [app setActivationPolicy:NSApplicationActivationPolicyRegular];
    msg_int(app, sel("setActivationPolicy:"), NSApplicationActivationPolicyRegular);

    struct CGRect frameRect = {0, 0, 600, 500};

    // id window = [[NSWindow alloc] initWithContentRect:frameRect styleMask:NSWindowStyleMaskTitled|NSWindowStyleMaskClosable|NSWindowStyleMaskResizable backing:NSBackingStoreBuffered defer:NO];
    id window = ((id (*)(id, SEL, struct CGRect, int, int, int))objc_msgSend)(
        msg_cls(cls("NSWindow"), sel("alloc")),
        sel("initWithContentRect:styleMask:backing:defer:"),
        frameRect,
        NSWindowStyleMaskTitled|NSWindowStyleMaskClosable|NSWindowStyleMaskResizable,
        NSBackingStoreBuffered,
        false
    );
    msg_id(window, sel("setTitle:"), msg_cls_chr(cls("NSString"), sel("stringWithUTF8String:"), "Pure C App"));

    // [window makeKeyAndOrderFront:nil];
    msg_ptr(window, sel("makeKeyAndOrderFront:"), nil);

    // [app activateIgnoringOtherApps:YES];
    msg_int(app, sel("activateIgnoringOtherApps:"), true);

    msg(app, sel("run"));
}
meownoid
  • 159
  • 1
  • 9
0

Pure C cross-platform example: (Windows/macOS/Linux) https://nappgui.com/en/demo/products.html

About macOS portability in pure C (updated to BigSur and M1 support): https://nappgui.com/en/start/win_mac_linux.html#h2

frang
  • 69
  • 5
-1

I'm creating an application in pure C on Mac OSX. What I want is to create window in which my app will be stored.

Are you looking for a TTY window?

If so does your application need to create the window?

If not then you can simply write your pure C program and execute it from within Terminal - a TTY environment for "pure C".

If you want a double-clickable app you can write an AppleScript which will open Terminal and run your C. Something like:

tell application "Terminal"
   do script "ex /tmp/test; exit"
end tell

This opens a Terminal window showing "ex" and when that quits will terminate the shell process (so no further commands can be typed), but it will not close Terminal itself - for that you will have to work harder.

If you do want you application to create the window itself you either need to write your own simple TTY window, you might find some classes you can use, or you might be able to borrow code from an open source terminal app such as iterm.

HTH

CRD
  • 52,522
  • 5
  • 70
  • 86
  • He made it clear that he wants C code to generate a OS X (i.e Cocoa) window. So I don't think using a terminal-based app would be appropriate for his needs. – SevenBits May 16 '15 at 12:36
  • @SevenBits - Since I wrote the answer the OP has indeed written he is looking for a "canvas on which I will draw"; so unless they mean VT100 graphics, which I doubt, then this answer is not suitable as you correctly observe. However future readers may be looking to run "TTY code" in a window on OS X, so I've left the answer. – CRD May 16 '15 at 17:05