MonoMac Bindings: Blending Cocoa and .NET on OSX

by Miguel de Icaza

Today we released MonoMac, a new foundation for building Cocoa applications on OSX using Mono.

MonoMac is the result of years of experimentation in blending .NET with Objective-C and is inspired by the same design principles that we used for MonoTouch.

It is also the result of weekend hacking as our day to day work revolves around Mono's efforts on Linux servers, Linux desktops, Visual Studio integration and our mobile efforts. Luckily, it shares some components with MonoTouch.

To get MonoMac, you need to get two modules from GitHub: monomac and maccore.

Background

Many years ago Geoff Norton produced CocoaSharp, the first set of .NET bindings to the Cocoa API. CocoaSharp was a fine first binding at the time and it was a good place to start learning about the challenges of binding Objective-C APIs to be consumed by .NET clients.

Over the years three other frameworks were created to integrate the Objective-C world and the Objective-C APIs with C# and other .NET languages. Each one of these new frameworks had its pros and cons, and a year ago we made a call for all three competing frameworks to be merged, but sadly nothing came out of it.

When we created MonoTouch, we wanted a binding for the Cocoa APIs that would fit the patterns and idioms used in the C# and .NET worlds, that would comply with the .NET Framework Design Guidelines and would allow give developers all the tools required to build full Cocoa applications.

We had two main requirements: the binding should just work and the code should be MIT X11 licensed. For the binding to just work, we turned to the .NET Framework Design Guidelines book as it captures years of design decisions, programming idioms and advise that would help C# and .NET developers. By following the Design Guidelines we:

  • Avoid surprises
  • Blend with other C# and .NET libraries
  • Reduce the room for errors
  • Increase developer joy
  • Minimizes time for the developer to be productive
  • Every bit of existing .NET knowledge translates

Luckily for us, .NET was designed from the start to be an interoperability framework. A framework that supports the most advanced requirements to make multiple runtimes and frameworks to communicate seamlessly with each other. We used these features to create our bindings.

The above goals turned into the following technical requirements:

  • Developers should be able to consume Cocoa APIs as C# APIs
  • Allow developers to subclass Objective-C classes
    • Subclass should work with C# standard constructs
    • Derive from an existing class
    • Call base constructor
    • Overriding methods should be done with C#'s override system
    • Do not expose developers to Objective-C selectors
  • Provide a mechanism to call arbitrary Objective-C libraries
  • Make common Objective-C tasks easy, and hard Objective-C tasks possible
  • Expose Objective-C properties as C# properties
  • Expose a strongly typed API, for example instead of exposing the generic-container NSArray or individual NSObjects. This means that developers get a few benefits:
    • MonoDevelop can flag errors as you write the code
    • MonoDevelop can present documentation popups on types, methods, properties and parameters as you type them.
    • Minimize runtime errors by catching invalid casts at compile time.
    • Encourage in-IDE API exploration without rebuilding, and without having to look up the types in the documentation.
  • Turn int and uint parameters that should have been enums as C# enumerations and C# enumerations with [Flags] attributes
  • Expose the basic Foundation as C# native types:
    • NSString becomes string
    • NSArray becomes strongly-typed array
  • Events and notifications, give users a choice between:
    • Support the Objective-C delegate pattern:
      • Strongly typed version is the default
      • Weakly typed version for advance use cases
    • C# event system
  • Class libraries should be MIT X11 licensed, like the rest of Mono's class libraries.
  • Expose C# delegates (lambdas, anonymous methods and System.Delegate) to Objective-C APIs as "blocks".
  • Curated APIs: there is no point in binding every UNIX or CoreFoundation C API available, as those are not very useful in practice. Bind only those that are required to build applications or get access to mandatory functionality.

More information about our API can be found here: http://monotouch.net/Documentation/API_Design

Binding Cocoa

Cocoa consists of two sets of APIs, one set is an object oriented C-callable API and another one is the Objective-C based API.

C-based APIs were bound using a traditional P/Invoke approach where the C-based API is wrapped in a C# class. This includes APIs like AudioToolbx, CoreGraphics, CoreFoundation and CoreText. There is very little magic in these bindings, they are straight forward bindings, similar in spirit to what you would do if you wrapped those APIs for C++ use. I am in particular very proud of the much simpler AudioToolbox API.

The Objective-C APIs is where the UI heavy lifting takes place and where most of the high-level functionality is found, and this includes APIs like Foundation and AppKit. The Objective-C APIs are bound using a new binding engine (MonoMac.ObjCRuntime) and the btouch binding generator.

The btouch binding generator consumes an API contract in the form of a C# source file and generates a binding that matches the specified contract., for example, this is the API definition for the NSActionCell:

	[BaseType (typeof (NSCell))]
	interface NSActionCell {
		[Export ("initTextCell:")]
		IntPtr Constructor (string aString);
	
		[Export ("initImageCell:")]
		IntPtr Constructor (NSImage  image);
	
		[Export ("target")]
		NSObject Target  { get; set; }
	
		[Export ("action")]
		Selector Action  { get; set; }
	
		[Export ("tag")]
		int Tag  { get; set; }
	
	}
	

We produced a comprehensive guide to binding Objective-C APIs with MonoTouch that applies directly to MonoMac.

Since a lot of the work of binding an Objective-C API is very repetitive, we have also included a header parser that does most of the heavy lifting in producing the above API from the Objective-C header file. The parser output then needs to be then massaged a bit to produce a binding that satisfies our design requirements. For example, NSArray arguments and return types must be looked up on the documentation and the proper strong typed inserted.

Status

Unlike MonoTouch, MonoMac is not a complete binding for all of the Cocoa APIs at this point. This has been a weekend effort for Geoff and myself but it has reached the point where it can be used for building applications and it has reached the point where we can start taking contributions to the effort.

Currently MonoMac binds:

  • CoreFoundation (the parts that are needed, see the design principles)
  • CoreText (done)
  • CoreGraphics (done)
  • Foundation (the parts that are needed, and helper tools to support the rest)
  • AppKit (About 30% left to be done)

If you are interested in advancing the state of MonoMac, we are currently looking for contributors to help us bind the other Objective-C frameworks and help us complete AppKit.

Where we are going

MonoMac is merely the library that gives C# developers access to the underlying APIs on OSX, it does not include the tooling necessary to create a full application bundle.

Luckily, MonoDevelop has already most of the code needed in the form of the MonoTouch plugin. We will update this plugin to also support creating full application bundles for OSX.

A new feature that developers will be interested in is the new "Mono bundler" tool that we are hoping we can include in Mono 2.8. This bundler examines your .NET application and generates an application bundle that contains both your application code and any dependencies that it needs from Mono in a self-contained package.

This is the technology being used by Banshee on OSX today. The tool constructs a self-contained application based on your system installed Mono that you can distribute to your users, without requirement them to install Mono in the first place.

But we need your help. There are many small and medium tasks that developers can help us with that will free our already busy weekends and will allow us to have a full MonoMac experience done in a shorter period of time.

The more help we get, the sooner this will be done.

We need contributors in the following areas:

  • API binding for the rest of the Frameworks
  • We need samples to be written
  • We need tutorials to be written (like the ones we did for MonoTouch)
  • We need to port existing Cocoa samples to C#:
    • To exercise the binding
    • To serve as reference for new developers
    • To identify missing frameworks
    • To prioritize bindings
  • We need to alter MonoDevelop's plugin to produce OSX Application bundles.

Please join us on the mono-osx mailing list to discuss the future of MonoMac.

Update Currently this requires a preview Mono to work from http://mono.ximian.com/monobuild/preview/download-preview/

Posted on 19 Apr 2010