C-Macs – a pure C macOS application

2 years ago (github.com)

> A little bit of this also has to do to stick it to all those Luddites on the internet who post "that's impossible" or "you're doing it wrong" to Stack Overflow questions... Requesting permissions in the JNI "oh you have to do that in Java" or other dumb stuff like that. I am completely uninterested in your opinions of what is or is not possible. This is computer science. There aren't restrictions. I can do anything I want. It's just bits. You don't own me.

From the wonderful CNLohr's rawdraw justification[0]. I always enjoy these kinds of efforts because they embody the true hacker spirit. This is Hacker News after all!

0: https://github.com/cnlohr/rawdrawandroid?tab=readme-ov-file#...

  • I like this guys spirit. Once I had to pick up old code from a guy with this spirit and it was f-ing horrible.

    • I love this project (from 2014?) and perhaps my favorite part other than the f-u of the whole thing is the commit message "complete refactoring".

This app uses objc_msgSend, which feels a lot like cheating, but if you simply want to avoid using nibs (while still using Objective-C or Swift), check out my NiblessMenu project and my "Working without a nib" blog series.

https://github.com/lapcat/NiblessMenu

https://lapcatsoftware.com/articles/working-without-a-nib-pa...

  • You can probably do a "true" pure C application (as in no Cocoa at all and no linking to objc-runtime) by dropping down to the CoreGraphics layer, but a lot of it is undocumented.

    There's also Carbon (which I always assumed directly hit the same underlying quartz apis), but that's long deprecated and iirc never supported the UI portions in 64-bit

I found this repo while looking for an equivalent to Win32 hello world[0] as a learning exercise during a long flight (with my work MacBook instead of my personal Windows machines).

That's something I really like about Windows APIs — I can pick a new programming language I want to play with, as long as there is a way to interface with C I can try to port the Win32 hello world then play around.

0: https://learn.microsoft.com/en-us/windows/win32/learnwin32/y...

  • The WIN32 API is somewhat ugly. I hate how all types are capitalized and their short abbreviations in arguments are sometimes weird. I hate how they have their own version of lots of standard C types that you have to be careful about (they weren't standard at the time, to be fair).

    But it's quite straightforward and easy to use. Any language that has FFI can call into it and build GUI apps, I've made a windowed Hello World in PHP with it.

    And it has been that way for 30+ years at this point. It's quite remarkable, really!

    • I remember ages ago when I was about 12, I got a copy of Turbo C++ for Windows 3.1 for my birthday. (I had only written in Basic before, and wanted to learn a "real" programming language. My dad was a Fortran programming physicist, and had heard that C++ was what everyone was using then-a-days.) I mostly wrote simple console programs, and it came with the Borland ObjectWindows library with a fun GUI builder, but there was a section in the manual talking about how to write directly using the Windows API.

      I didn't really understand any of what I was reading, but I typed in the whole basic sample complete with HWNDs and all that nonsense. I can't remember if I could even get it to compile, but the idea of an event-loop was beyond my comprehension as a 12yo. I'm sure if I went back it would make a lot more sense to me, but back in 1994 it seemed like dark magic, especially compared to the much simpler and more understandable OWL components.

Very nice. In similar fashion, a few years back I set out to create Wuhoo.

Wuhoo loosely stands for W indows U sing H eaders O nly. It is an attempt to create a single-header library (in the spirit of STB) for graphics related window management, compatible with both C and C++.

It works on Windows, Linux and Mac.

https://github.com/ViNeek/wuhoo

I did a straight C++ app for MacOS but 1) I used SDL2 and 2) it was a full-screen game so no Cocoa UI needed. It was kind of fun though in a retro-computing way.

(I'm a big fan of SDL now.)

Good example of digging with a spoon as they stated was the original goal. And it still compiles on an M1 macbook despite being a decade old code base.

As someone with no experience in native application development, could someone explain to me why this is significant? I have a rough idea, but I would like to understand it properly.

  • It's an exercise in recreating how an Objective-C app works in C from first-principles. For example, the creation of the AppDelegate (see CreateAppDelegate() in AppDelegate.c) is interesting because it shows how to create a class from NSObject and attach the applicationDidFinishLaunching: method along with it's implementation in C. I've used objc_msgSend() before from C (to access the pasteboard from a CLI app), but never implemented any ObjC classes using C!

    It's similar how you might attempt to build a C++ class from only C components by creating a vtable.

  • MacOS apps are typically written in either Objective-C or Swift as these are the officially supported languages for the MacOS APIs.

    The code in this template is interfacing with the Objective-C runtime but with pure C.

    • Does this mean that, theoretically, this could lead to the ability to build MacOS apps in higher languages that interoperate well with C such as Python? I know you can build MacOS apps with Python now, but does this potentially improve the experience?

      6 replies →

    • That has always been possible. Also, under the ObjC layer, is good ol' ANSI C (FreeBSD Unix).

      There's a number of apps that run on modern Macs, that were written in C, but it is unusual to see ones that leverage the GUI.

      That said, it's possible to walk from Boston to Portland (OR), but I'd rather take a plane.

      14 replies →

    • > officially supported languages for the MacOS APIs

      How often do you need to get support assistance from Apple? Just do what you want. If it means using Obj-C or Swift for ease, do that. If it means integrating another language, do that.

  • > why this is significant?

    Ultimately, because new people keep being born and missed the years where this was pretty common and haven't yet bumped into the corners where it still is.

    The repo and the SO discussion it was inspired by are themselves 11 years old and seem to be rooted in a new generation of iOS app developers starting to get more deeply curious about the system they're running on and how else it might be approached, which this person then ran with on MacOS.

    Apple invites people to get started in making software for their platforms using (what's meant to be) more accessible tooling like Objective-C, Interface Builder, Swift, SwiftUI, etc but there's of course a whole BSD-rooted operating system sitting there once those those developers start digging. It's no secret, but it's a discovery that some people need to make on their own.

    • This isn’t an example of that. Have you looked at the code? This uses ObjC and Cocoa implementations in an unintuitive method. Nice exercise to learn about the ObjC runtime, but has zero practicality or use.

    • The funny thing is, Apple themselves seems to have forgotten about the whole BSD-rooted operating system. Anybody who ventures off the beaten path of developing software for a modern Mac will inevitably encounter a lot of cobwebs. One of my favorites: When Apple implemented app bundles, they never updated dyld's search paths to be aware of the app bundle directory structure, meaning you have to manually patch your rpaths. Not a huge deal, and one that's hidden from you if you only ever know xcode, but it's one of many very sloppy things I noticed coming from a strong Unix background to Mac. There's a lot of really weird incongruence where the deeper you go into the system, the more everything feels covered in dust and neglected. I was always sold on Macs having really great top-to-bottom integration and have all the polish and attention to detail you can expect out of a corporate Unix-like, but that's not what I found. It's more like a really shiny superstructure bolted on top of an ancient, rust-eaten BSD. Don't get me started on how a lot of the "newer" stuff at that low level tends to be some of the absolute worst takes on a concept. The "App Sandbox" might be the most disgusting, slap-dash design for process isolation I've ever seen bolted onto a kernel.

      I get Apple's target market is quite literally the opposite kind of user that I am. That being said, I always find it curious that people still tout Mac as this kind of "Super Polished Desktop/Workstation Unix" and often cite the Unix certification. It feels like the more you try to use a Mac like you would any other Unix machine, the more you have to fight it. Often it doesn't feel any different to trying to wrangle Windows with WSL. I had less hiccups and trip-ups learning Plan 9 than I did coming to terms with macOS.

      10 replies →

  • I guess it's normally impossible to write a GUI application for mac os without objective-c libraries doing the talking to the OS, but what do I know.

    • It is not. This was just a new wave of people finally looking under the hood.

      (Where they would have found many many dusty but detailed man pages and docs waiting for them)

    • Beneath Cocoa are Core Foundation and things like Core Graphics for example, which both are C.

Uses under 1.5 MB of memory at any one time (most of it is used for drawing the window).

Unfortunately there is no screenshot and I have no access to a Mac at the moment, but if View.c is where things actually happen, that is a huge amount of memory just for a (resizeable?) window with a little filled rectangle in it. The memory usage of a trivial app like this should be measured in kilobytes.

Comparing applications written in the same language across platforms is IMHO a good gauge of their relative efficiency. From what I've seen, Win32 and Linux (Xlib) are pretty close but Mac is clearly very "think different". I'm sure the liberal use of string constants here doesn't help either.

Here's a related interesting comparison: https://zserge.com/posts/fenster/

  • It also depends on how the operating system and frameworks actually work. For instance, the backing store for the window may be “charged to” the application on macOS but may be provided by the OS on another platform, or there may not even be one.

Here's something similar by Garrett Bass:

https://github.com/garettbass/oc

...I also experimented a bit with parsing macOS system headers via clang-ast-dump and then code-generating C and Zig bindings but that didn't get far:

https://github.com/floooh/objc-ast-experiments

...with a bit of effort and maybe using libclang instead of clang-ast-dump that's definitely feasible though.

I guess a similar approach is used by the official C++ bindings for Metal:

https://developer.apple.com/metal/cpp/

...also shameless plug: the sokol headers allow to write simple macOS applications (just a Metal canvas in a window) in various non-Apple languages (currently C, C++, Zig, Rust, Odin, Nim):

https://github.com/floooh/sokol

...I'm cheating though and use ObjC under the hood to talk to Cocoa and Metal ;)

Love this, if I never have to write a wrapper around objc again I'd be in heaven.

  • This uses the ObjC runtime to obtain the underlying ObjC method implementation functions, to call directly, instead of relying on the runtime to call them. If you find this more elegant than a wrapper with some ObjC, good for you.

Reminds me of an app I built ~20 years ago now.

We wanted a cross-platform C++ layer and native Cocoa front end. Objective C++ wasn’t a thing then, and having built a plain C shim previously I didn’t want to repeat the experience.

We built our own bridge by registering our C++ classes with the Obj-C runtime, generating selectors for all the methods so you could send messages to (carefully constructed) C++ objects using Obj-C syntax, or even subclass from C++ to Obj-C.

It was a pretty neat trick, but would’ve been difficult to port to the Obj-C 2 runtime.

We used the same technique to port our C applications to Apple platforms, but to do so we wrote an automated tool that created a C wrapper for any cocoa API we need. It works on all current Apple platforms we have tried it on iOS,iPadOS, and Apple Silicon Macs.

Its open source:

https://felixk15.github.io/posts/c_ocoa/

Seeing some confusion. This is using what are effectively FFI interfaces to the Objective-C runtime. There’s really no good reason to do this in production code that isn’t a language bridge. It’s not as efficient at runtime as writing the functionally equivalent Objective-C, because the ObjC compiler statically allocates class/method data structures, and it’s not as safe, because you’re bypassing ARC.

This is basically what Go PenPoint programming was like. When you read the Go documentation it’s very clear they intended to use Objective-C or an equivalent preprocessor; I suspect they were stymied by NeXT’s acquisition or Stepstone and their own lack of support for GNU C.

This would've been a lot easier to wrap my head around when I had to work with Objective C for the first time

It would be nice to have something like this except for Metal. I'm unsure why Apple made Metal an Objective-C API when C + Core Foundation would suffice. One big advantage of a C API is it's easy to interop with other programming languages.

  • Apple is opinionated about which languages should be used on their platforms, and they enforce it with these kinds of decisions.

    For example, there was a time where Steve Jobs threatened to forbid iPhone apps not written in Obj-C, which would have destroyed the cross-platform ecosystem, at Apple's expense. Luckily, he was talked down from being that extreme.

    • Is there any way to enforce that in a way that can't be worked around with a shim? Demanding source code? Looking for signs of other languages in the binary and not allowing them in the appstore if detected?

      2 replies →

This would seem more complete if it had a simple Makefile rather than an xcodeproj.

You don't need a xcodeproj at all for a pure C macOS application (or do you need it 10 years ago? I see the last commit is 10 years)

No license typically means copyright with all rights reserved in the U.S.

Perhaps you want to release this into the public domain (see SQLite)?