discover:
Allan Hsu
Still verging... or something.
Posted in the monoland diaries on Sep 05, 2005 at 5:50 AM

Many of the employees here at imeem are Mac users. For some time, we'd all been talking about an OS X port of imeem, but with our limited resources, we weren't able to divert much time from Windows development towards this end.

Sometime around the beginning of this summer, Bryan Berg and I started really getting down into the details of bringing imeem to the Mac. We'd been wrapping our heads around it for a while (Bryan more than I), but we hadn't done much concrete implementation. Most of the Windows imeem client is written in C# .NET, with a good amount of abstraction between the UI and the rest of the client.

We wanted to write the Mac UI with Cocoa because we wanted the end product to feel like a genuine Mac application regardless of what was going on under the covers. We knew we would have to use Mono in some fashion; we weren't about to rewrite the imeem backend. With these things in mind, we entered:

The Forest of Fear, Uncertainty, and Infuriatingly Weird Bugs: Cocoa#
If you read my last post about Dumbarton, you may wonder why we didn't use Cocoa#. Well, we tried. If you are unfamiliar with Cocoa#, using it involves running a code generator on OS X frameworks in order to produce .cs files that you can then use to call Objective-C code from C#. There are also some facilities to call C# from Objective-C. Bryan had written a simple client using only C# and the Cocoa# generated bindings, but it didn't look like it was going to work out. Writing a Cocoa application in C# was incredibly unholy at times and it didn't seem like Cocoa# was really ready for us to use. Being in the early stages of development, Cocoa# had decent support for some of the Cocoa Appkit, but some things just don't work right yet. The auto-generated code for a lot of Core Foundation was often hilariously wrong/weird.

We spent a couple weeks of hard hackery on a hybrid approach, where we wrote the UI layer in Objective-C and compiled it into a framework. We used Cocoa# to load the framework and execute the UI, as well as pass information back and forth. This approach ultimately ended in tears; sometimes Cocoa# just didn't work right. For example, to instantiate an NSNumber with the integer value 5 in C#, I had to do something like this:

NSNumber someNumber = new NSNumber(5); //this doesn't actually set the value.
someNumber = someNumber.initWithInt(5); //this sets the value.

If you're familiar with Objective-C, you'll notice the alloc/init pattern. Graaaah. I have no clue how the above code plays with the retain/release memory management semantics of Objective-C/Cocoa. I don't really want to know. Debugging showed that the second assignment would actually assign someNumber to a different object than the one returned by the constructor. Also, you can forget about creating more than one NSNumber with the same value. That causes Cocoa# to crash.

After our experience with Cocoa#, we decided that we needed something simpler and more sane. We decided to roll our own solution, and to build:

The Bridge Over the Stinky Salt Marsh: Dumbarton
Using the Mono embedded C API, we built an OS X framework that works quite differently from Cocoa#. Its focus is on calling managed code from unmanaged code; though the opposite is entirely possible. Our approach is a lot less "magic" than Cocoa#; it requires more work by the developer, but is also quite a bit more lightweight.

To make calls into native code from managed code, we use the System.Runtime.CompilerServices namespace to mark our methods as internal calls and then use Dumbarton to register native function pointers for each method at runtime. To make calls from Objective-C into C#, we wrote classes that work to make method invocation easy and painless (at least relative to the raw embedded Mono API). So far, we've written these features (some of which I mentioned in my last post):
1. Objective-C representations of C# classes and objects that allow property getting/setting and method invocation (mostly intended for subclassing by the developer).
2. Mapping between the Cocoa/Objective-C retain/release model of memory management and the mono garbage collector.
3. Translation of C# exceptions into NSExceptions for method invocation, so that @try/@catch/@finally works for C# exceptions.
4. A series of class categories for easy conversion back and forth between common types (ie: NSString<->MonoString, NSData<->MonoArray)
5. An NSThread poser that makes it safe to call into the managed runtime from threads started using Cocoa. This helps particularly for threads created by code outside of the developer's control. More on this later.
6. Utility subclasses of the representations mentioned in part 1 for common C# interfaces (IList, ArrayList, etc).
7. Macros for boxing/unboxing value types.

Right now, the shared library for Dumbarton.framework compiles down to about 40k of code, sans debugging symbols.

Eventually, I'd like to get Dumbarton contributed back to Mono, but it's not at the top of our priorities right now. Before we ship an OS X client, we still have to figure out how we'll deal with our Mono dependency, which will be the subject of a later post...


Comments (3)

 Loading Comments...
Login to leave a comment.

RssFeed

Rate this thread:
Average Rating:
Report Post as Objectionable