Cocoa-Java Porting Step 1: Triage

February 8th, 2007

When I chose to purchase an existing Java-based Cocoa application, I knew I was taking on some risk. Mac OS X still has a technology within it called the Cocoa-Java bridge, which makes it relatively easy for Java classes to participate in the Cocoa runtime, automatically translating objects like arrays and strings to their language-specific counterparts, and allowing for messaging between objects of the two languages. In short, it makes it possible for Java developers to write Cocoa apps just like an Objective-C developer would, only using Java for all of the custom classes in their code.

Unfortunately, Apple has all but abandoned the technology. The implications for developers are that Cocoa-Java applications cannot easily adopt new APIs, are mysteriously buggy, and are not easily maintained with the latest versions of Xcode. In short, they are a mine-field of opportunities for disappointment and despair.

Nonetheless, I decided to buy a Cocoa-Java application, after looking through the existing sources and making best and worst-case scenario predictions. The previous owner had already disclosed some nuanced issues with running on Intel Macs, so I knew there would be some work before I could ship it in good faith. The amount of work would range from “a few hacks to get it working on Intel as Cocoa-Java” to “a complete rewrite in Objective C.” I planned to port to Objective C eventually, but I hoped to be able to put the product on the market soon with just a few tweaks to make it robust on Intel. Then I could port to Objective C at my leisure while counting all the money.

Now that I’m 95% done with the process of getting the application into shape, I’m ready to start writing about it. I hope that by explaining the process of evaluating and overcoming obstacles in the process of doing this port, it will establish a reference for others who are stuck doing the same kind of work. I expect there will be many Cocoa-Java parts in the months to come, as support for the legacy technology continues to diminish. Hopefully some of the wisdom I have earned will come in handy for others.

Evaluate The Source Base

The first thing you’ll want to do in approaching any port is to look closely at the existing source files. You don’t need to completely understand what everything does, you just need to see it and take away an impression. At this stage the style and organization of the content should pose as much interest to you as the raw technical functionality. For example, is the object hierarchy conducive to a typical Cocoa design? Is the code well-commented? Does the code exploit language specific features being that will be difficult to replicate in the target language?

In my case the code is well-written, well-commented, and since it’s Cocoa-Java, it already conforms more or less to a conventional Cocoa way of doing things. Lucky me! Nice purchase, Daniel. The only part of the project that doesn’t directly translate to Objective-C is the notion in Java of a class that “extends Thread.” There is no Objective-C class representation of a “thread,” so this would obviously take some modest reorganization. But the use of threads was not extensive, so I did not get scard off.

It doesn’t hurt that Java and C are such close siblings, any amount of porting that does need to be done will retain much of its syntactic appearance. In particular, when porting Cocoa-Java to Cocoa-ObjC, probably 80% of the work will fall into two broad categories of concern:

  1. Convert syntax().like().this() to [[syntax like] this].
  2. Pay attention to memory management.

The syntax point is a simplification, but really a lot of it could be ported by trained monkeys, or better yet, regular expressions. Anyway, I’ll talk more about this in a later segment, but suffice to say that the work of straight porting from Java classes to Objective-C classes can be tedious, but is eminently doable.

Build And Run

Realistically, this step was probably the first thing you did in evaluating the sources. You’ve got the Xcode project staring right back at you, how can you resist at least trying? The great news is that my product built and run flawlessly, from the minute I unpacked the source archives and installed them on my Mac. The bad news goes back to those aforementioned “subtle Intel problems.” Specifically? Crash on print. Crash on window zoom. Ouch.

The crashes in either case point to the Cocoa-Java bridge. An infuriatingly cryptic backtrace essentially lets me know that Cocoa-Java was trying to print some log message (perhaps explaining why it was about to crash?) when it bit the dust:


#0  0x90a53387 in objc_msgSend ()
#1  0xbfffe250 in ?? ()
#2  0x908103a2 in _CFStringAppendFormatAndArgumentsAux ()
#3  0x9080ec8c in _CFStringCreateWithFormatAndArgumentsAux ()
#4  0x925e2a5d in -[NSPlaceholderString initWithFormat:locale:arguments:] ()
#5  0x92604670 in -[NSString initWithFormat:arguments:] ()
#6  0x9672d308 in _BRIDGELog ()
#7  0x9673305d in _BRIDGEMethodImpStructReturn ()
#8  0x93519c6e in -[NSWindow _standardFrame] ()
#9  0x93732d7c in -[NSWindow zoom:] ()
#10 0x9335cd88 in -[NSApplication sendAction:to:from:] ()
#11 0x9335cce1 in -[NSControl sendAction:to:] ()
#12 0x9335ee91 in -[NSCell _sendActionFrom:] ()
#13 0x9335e94c in -[NSButtonCell performClick:] ()
#14 0x97723a6b in Java_com_apple_cocoa_application_NSWindow_performZoom ()

Searching the web for help on this failure was pretty fruitless. I downloaded some other applications that I knew to be Cocoa-Java based, and they didn’t crash on zoom. So what’s the deal? What is my involvement in this problem. Through hard work and luck, I narrowed both crashing problems down to a pretty simple thesis:

“Cocoa-Java will crash on Intel if Cocoa asks Java to return an NSRect.”

It might be overstating the case, but those are the two crashing cases in my application. I suspect there is a byte swapping problem or something. If you return an NSRect, you will crash. Sad story, but fixable. The two method calls in question for me are NSWindowController’s “windowWillUseStandardFrame:defaultFrame:” and NSView’s “rectForPage:”.

Keep It Running

I went to the auto parts store a few months back, around the beginning of autumn. My car had been running hot, and on the verge of overheating all through the summer. I brainstormed with the clerk about what might be wrong, and we finally agreed that it is probably a problem with my thermostat, but could be a problem with the radiator itself. Fine, it’s settled. I’ll replace the thermostat first. But even this involves draining the radiator, perhaps removing it, and a lot of other messy stuff. The shop clerk looked outside at the chilling weather and asked me a few pointed questions: “How old is this car?” “Do you drive it a lot?”, etc. After hearing my responses of “pretty old,” and “not too much,” he gave his pragmatic verdict: “It’s not gonna overheat in wintah, and who knows if it will even run in the spring?”

The point being, it’s not always the right time to do the right thing. I’m confident that that my Cocoa-Java NSRect crashes will disappear if I port the application to Objective C. But can I get away with patching the rough spots today? Moreover, won’t my customers (existing customers of this application) benefit from having the Intel problem solved sooner, rather than later? I decided to attack the problem of crashing NSRect across the bridge.

My first thought was to try to gauge exactly why it crashes. If it’s truly a byte-swapping issue, I could detect the fact that I’m running on Intel, and perhaps massage the data to suit the bridge. The major downside to an approach like this is you’re now at the mercy of Apple leaving the bug in place. In the unlikely event that Apple shows the bridge some love, and fixes the crash, where does that leave me? Now I’m byte-swapping myself into the same crash some ways down the road.

The solution I came up with is simple albeit somewhat of a hack. It’s the duct tape around the muffler, if you will. If you think about the crashing problem with returning an NSRect, an obvious solution would be for Java to not return an NSRect. To work around this problem, I modified the affected Java classes to return NSString instead of NSRect:


public String javaSafeStandardFrame(theWindow, defaultFrame)
public String javaSafeRectForPage(pageNumber)

Now the program doesn’t crash. Of course, it doesn’t zoom or print correctly, either. The problem of course is that Cocoa doesn’t know to call these “javaSafe” versions of the methods, and even if it did, it wouldn’t know to expect a string in return.

This is where the hacking comes in to play. The fact that I’m working with a Cocoa-Java project doesn’t mean I can’t also have Objective-C classes in my project. In fact, at runtime the majority of classes in memory are Objective-C, coming from Apple’s frameworks. What’s to keep me from selectively implementing some classes of my own to help soothe this problem? Using a custom class and the powerful technique of class posing, I’m able to effectively patch out standard versions of the above methods, and replace them with code that calls through to Java, gets a string representation of the rect, and converts it to NSRect for the benefit of Cocoa. Since the NSRect is now returned from Objective C, there’s no more crash. In my main.m source file:


[RectSafeWindowController poseAsClass:[NSWindowController class]];

And in my custom NSWindowController implementation:


- (NSRect)windowWillUseStandardFrame:(NSWindow *)theWindow defaultFrame:(NSRect)newFrame
{
  // Your java class instead of "JavaController"
  Class myJavaClass = NSClassFromString(@"JavaController")
  if ([self isKindOfClass:myJavaClass])
  {
    NSString* goodRectString = [self javaSafeStandardFrame:theWindow :newFrame];
    return NSRectFromString(goodRectString);
  }
  else if ([super respondsToSelector:@selector(windowWillUseStandardFrame:defaultFrame:)])
  {
    return [super windowWillUseStandardFrame:theWindow defaultFrame:newFrame];
  }
  else
  {
    // Just return the default
    return newFrame;
  }
}

A similar approach was used in the NSView override of rectForPage.

Take Stock

Now that my application builds and runs without crashing, it’s time to ship it, right? Possibly, but in this case I chose not to. Although the application has a mature feature set and benefits from years of evolutionary design, it has become a little bit dated in some ways that I grew increasingly uncomfortable with.

Now that the application is running crash free, I can breathe a sigh of relief. I could ship this thing, but I choose not to. I want to be prudent about what I change for the “1.0 relaunch,” but there are some nuances of the appearance and performance that need tweaking. And to comfortably tweak those things, I’m going to have to selectively port some of the Java classes to Objective C. But after all that hard work getting the application to “shippable,” I want to be careful not to backslide too much. Tune in next time for a detailed look at migrating Java classes to Objective C without disrupting the overall functionality of the application.

Continue reading – Step 2: Life Support.

Steve Jobs On DRM

February 6th, 2007

In an unusual move, Steve Jobs has taken the text podium on the Apple web site, writing about his Thoughts On Music, though it might be more aptly titled “Thoughts on DRM” (Digital Rights Management).

“Imagine a world where every online store sells DRM-free music encoded in open licensable formats. In such a world, any player can play music purchased from any store, and any store can sell music which is playable on all players. This is clearly the best alternative for consumers, and Apple would embrace it in a heartbeat.” — Steve Jobs

There’s been a lot of speculation over the years that Apple either would or wouldn’t support DRM if the music industry didn’t force them to. I find it interesting to at least hear lip service from Jobs to the idea that DRM is bad for consumers.

But this kind of public “for the record” stance seems highly unusual. In the wake of the Apple vs. Beatles settlement, I can’t help but wonder if Apple is setting the stage for some kind of DRM-defying move. If they could sell the Beatles exclusively and DRM-free, what kind of precedent would that set for the rest of the industry?

As the article wraps up, it becomes clear that the probable purpose of the highly visible stance is to defend Apple against claims by some European countries that iTunes is against their consumer protection laws. I think Jobs (or his researchers) makes an excellent point in drawing attention to the significant European ownership of the media companies who are apparently responsible for demanding DRM protection. It will be interesting to see if this sparks a public debate between Apple and the music companies.

Xcode Pasteboard Accumulator

February 1st, 2007

Way back in the MPW days, I used to rely heavily on an extension for the IDE that put powerful pasteboard manipulation tools in the window header. Essentially these tools let you treat your pasteboard like a stack, so you could easily accumulate multiple copies and then paste them all out at once.

For some dumb reason, I’ve been living without such conveniences for several years now. I know there are many 3rd party products that add an epic amount of functionality to the standard copy and paste of the system, but I’m not really interested in running a separate application for this purpose.

It occurred to me that this would be a perfect use of Xcode’s Menu Scripts. Xcode not only supports the addition of custom scripts to the menu bar, but supports a unique format by which the scripts can advertise keyboard shortcuts, and integrate seamlessly with Xcode’s text editor. For instance, you can use a script to process the selected text in Xcode and spit back out something else in its place.

Append To Pasteboard is a simple python script that takes advantage of the “pbcopy” and “pbpaste” commands to tack whatever you’ve selected in Xcode on to the end of the existing pasteboard. You stick this within your (horribly located) user scripts folder, where you must name the folders and files with numeric prefixes to inform Xcode where they belong. For example, the script lives in my home directory:

/Users/daniel/Library/Application Support/
	Apple/Developer Tools/Scripts/
	10-User Scripts/1-DCJ/

Unfortunately Xcode doesn’t handle the situation where some scripts come from your home directory, and some come from the /Library installation directory. So I put everything in my home directory. The “10-User Scripts” is how I tell it to name the menu bar item “User Scripts.” The “1-DCJ” folder is the folder with all my custom scripts in it, and since it is “1,” it comes before all of Apple’s standard scripts that I copied in (10-Open, 20-Search, etc). The process of providing scripts to Xcode is ugly but when you finally get it all put together, this is what you end up with:

Now it’s a snap to cruise through a .m and accumulate method declarations, for pasting into a corresponding .h. Note that the script always appends a newline to the existing pasteboard, which could be annoying depending on what you want to use it for. Since I envision myself almost always double-clicking a function or method declaration and appending it to a list, I find this behavior to be most convenient.

Thanks to Jon Wight for helping with Python.

Update: Any productivity gained by using this macro was certainly lost as I tried desperately to debug Xcode’s latest propensity to “beachball” on launch. Finally, I attached with gdb and found a telling backtrace. Hmm? Yep, when I “zipped” the script for upload to the blog, I left a zipped copy in the scripts folder. Next time I launched Xcode, it choked big time on trying to treat that zip file as a script.

Making Omni Cry

February 1st, 2007

There has been some speculation among my programmer buddies that Omni has lost a developer. Given the recent job listing for a Senior Cocoa Coder, many figured they must be restocking the talent. I figured they were just growing (and they may well be), but it turns out they did lose a developer, and quite a veteran at that.

Len Case is Omni CEO Ken Case’s brother, and apparently left the company in November, after 13 years of service, to become the lead developer at STX. He tells an interesting personal story about the decision, explaining that he thought about leaving in past years, but hesitated because of how his brother might take the news. The entry also includes some great context surrounding Omni’s formation way back when.

The Omni bio page still lists Len as a developer for the company, but perhaps this is a sort of “emeritus” honor (or they’re just slow in updating). What did Len do at Omni? The bio is purposely vague, but it looks like OmniDiskSweeper may suffer for his departure. By peering into the past, I gathered clues that indicate maybe his contributions were mostly in the consulting services that Omni provides to other companies. By the way, peering into the past is fun. And embarrassing. Don’t look me up. Please.

These details were all discovered after following Rentzsch’s incredibly vague del.icio.us entry pointing to Len’s blog.