Sunday, July 12, 2009

Delphi Prism - First Experiment

After writing a simple Winforms application I figured I should try out Delphi Prism and Monobjc to write a program for Mac OS X.

My first attempt was a disaster and I ran into three bugs in the IDE (now reported to Embarcadero), and two problems that I caused myself while trying to figure out how to deal with everything else that went wrong.

But at the end somebody in the Embarcadero newsgroups managed to explain everything so clearly that even I could understand it.

First, I spent a Saturday afternoon at Powerscourt Gardens in Wicklow, which I now realise is an excellent way to start a project.

It rained, which meant that most visitors were spending their valuable free time in the main building. We pretty much had the entire park for ourselves. It wasn't raining hard.

Powerscourt Gardens has an English garden, a Japanese garden, and a Robin-Hood style valley with cannon tower.


But be that as it may, Saturday evening and Sunday was spent learning Delphi Prism and playing StarCraft.

Delphi Prism supports Cocoa via Cocoa# and via Monobjc. Monojbc is now the preferred method. Most Cocoa classes, including GUI and other classes are exposed to .NET via wrappers provided by Monobjc. It is possible to create an NSMutableString in Prism and I assume it can be cast into a System.String but I haven't tried.

But what is perhaps most important is that Monobjc allows Prism (and any other .NET language) to use Cocoa NIB files. NIB files contain, as I understand it, archived (or "serialised") Cocoa objects like windows, buttons, text fields etc.. which can be unarchived and connected to delegates at runtime. Via Monobjc a .NET class can act as the delegate for these Cocoa objects.

Delphi Prism provides tools to read NIB files into Visual Studio and Prism and allows for somewhat easy interaction between the Windows and Mac worlds. Prism also creates a bundle out of the compiled .NET assemblies and adds all required DLLs the usual way.

After building the application in Visual Studio, the result is a bundle with a Macintosh icon (in this case the default icon for Monobjc projects in Delphi Prism):





The application uses native Cocoa windows fresh out of a NIB file. Here the text in the text box is not part of the window definition from the NIB but was added by a delegate method awakeFromNib: which is implemented in Pascal.



The text field ("textMessage") is mapped to the NIB in designable.pas, a Pascal file in the NIB folder.

The code for the actual method looks like this:

method ApplicationController.AwakeFromNib;

begin


textInfo.StringValue := 'Hello Prism Cocoa 2 Judgement Day';


end;


AwakeFromNib() is mapped to the Objective-C method awakeFromNib: automatically. I think Prism's NIB reader tool does that. Not all delegate methods are automatic like that. More about that later.


Here we have an alert box (NSRunAlertPanel) displayed when the OK button in the main window is clicked.

The method for the button event has to be added as an action in the NIB file and will then show up in designable.pas in the NIB folder. Somehow that maps the Pascal method to the action in the NIB file as Cocoa expects it.

Embarcadero say that this is a well-designed feature but I think it is magic.


Code:

[ObjectiveCMessage('PushOK:')]

method PushOK(aSender: NSObject); partial; empty;


(This is the generated code in designable.pas that maps the method to the action in the NIB file.)


method ApplicationController.PushOK(aSender: NSObject);

var


msgbox: Integer := AppKitFramework.NSRunAlertPanel('Hello Prism Cocoa 2 Judgement Day', 'Somebody pressed the OK button!', 'Really?', nil, nil, nil);


begin


if msgbox = NSPanel.NSAlertDefaultReturn then textInfo.StringValue := 'Somebody pressed the OK button just then.';


end;

(This is the code I wrote to make the alert box appear and change the text in the text field.)

The text field's contents has been changed by the method called by the button pressed.

Everything in the text field is selected. I didn't care because I was only experimenting. But I guess I have to play with a few attributes a bit more.


One common problem with Cocoa (it's by design for good reason) is that closing the last window does not quit the program. This is very useful in a Web browser or word processor but annoying when the program only has one window and is useless without that one window.

So I figured this would be a good way to test surprising delegate methods (i.e. those that are not awakeFromNib:).

I display an alert box when the red button to close the window is clicked on.

This requires two things.

First, the event has to be caught, which turns out is easy because the event has its own delegate method which I can implement somewhere. If I can manage to map my Pascal method to the delegate method the NIB expects, it should work.

Then, the event has to be cancelled in case the user decides "No" and doesn't want to quit.

This definition in the public type section of the ApplicationController.pas class file maps the Pascal function to the delegate method:

[ObjectiveCMessage('windowShouldClose:')]

function WindowShouldClose(window: NSWindow): Boolean; partial;

And this code, further down, implements the function:

function ApplicationController.WindowShouldClose(window: NSWindow): Boolean;

var


msgbox: Integer := AppKitFramework.NSRunAlertPanel('Hello Prism Cocoa 2 Judgement Day', 'Really Quit?', 'Yes', 'No', nil, nil);


begin


if msgbox = NSPanel.NSAlertDefaultReturn then Environment.Exit(0) else Result := false;


end;


It works beautifully.

Of course, this program only works on Mac OS X. But I now have a Mac program that can use .NET DLLs at will. This allows for some great portability, as code written for a Windows (.NET) program can also be used for a Mac program. Only the GUI parts and those parts that use other native classes (from Cocoa or Windows COM+) have to be written per platform.

Potential issues:

I assume that when I use other Cocoa classes that are not available in the form of existing objects from a NIB file, I will have to keep track of them and release them when I am done with them to avoid memory leaks.

Plus, all programs will have to be written in a way that allows for replacing everything GUI-related without breaking the business logic.

(I am hearing that Delphi for Win32 will be cross-platform soon. This will add another choice to a field dominated still by Java and REALbasic.)


Monday, July 6, 2009

iPhone debug zombies and finally VMware

A few weeks ago I had an iPhone app running in the simulator that wouldn't quit. I also could not kill it, not using Activity Monitor nor kill or kill -9 or killall.

cyrus:~ ajbrehm$ ps auxc|grep Vocky
ajbrehm
76450 0.0 0.1 153116 5212 ?? UE 12:59pm 0:00.05 Vocky
ajbrehm
76437 0.0 0.1 153116 5212 ?? UE 12:58pm 0:00.05 Vocky
ajbrehm
76350 0.0 0.1 153116 5212 ?? UE 12:57pm 0:00.05 Vocky
ajbrehm
76336 0.0 0.1 153116 5212 ?? UE 12:56pm 0:00.05 Vocky
ajbrehm
76329 0.0 0.1 153116 5212 ?? UE 12:56pm 0:00.05 Vocky
ajbrehm
76276 0.0 0.1 153116 5212 ?? UE 12:56pm 0:00.05 Vocky
ajbrehm
76245 0.0 0.1 153116 5212 ?? UE 12:55pm 0:00.05 Vocky
ajbrehm
76176 0.0 0.1 153116 5212 ?? UE 12:55pm 0:00.05 Vocky
ajbrehm
76159 0.0 0.1 153116 5212 ?? UE 12:54pm 0:00.05 Vocky
ajbrehm
76150 0.0 0.1 155136 9264 ?? UE 12:54pm 0:00.10 Vocky

This happened whenever I ran the application in the simulator. Sometimes Xcode's debugger picked up on it and the simulator wouldn't run the application any more because, as the console sayd, it was already running.

It took me a while to figure out what was going on. But then misfortune struck and taught me the solution.

After a while VMware also started behaving oddly and left "vmware" zombies behind in Activity Monitor. One of the VMware instances then blocked the GUI. (It also kept changing the mouse pointer tracking speed for some reason. I have no idea why VMware is allowed to change system preferences and block the GUI.)

Ultimately I had to bring down the GUI (I killed loginwindow and windowserver) and solve the problem.

A few days earlier I had tried switching off Spotlight search because it had started indexing 600 GB of incoming data during a copy process (which took nearly 6 hours because of that, from a Firewire 800 disk). For some reason the only way to stop Spotlight from restarting itself during that copy process was to rename ReportCrash to something else ("ReportCrash2") so that it couldn't be found. That made sense at the time.

The problem was that I had forgotten that I had done that.

Once ReportCrash was usable again, each of the zombies reported its crash and went away, both the iPhone program and the many instances of the VMware interface. This took about 5 minutes. (In the process I learned that the 24" Apple Cinema Display is an EXCELLENT monitor for a text interface! Finally I could see each line without linebreaks.)

After that was done, I restarted windowserver and loginwindow and could log back into the GUI. My VMware VMs had also survived (since I didn't reboot) and VMware worked again and I could connect to all the Windows VMs and the zombie processes had gone.

Turned out it was a ten minute issue once it was clear that it had nothing to do with Xcode or gdb.

I hate ReportCrash.