Using the D/Objective-C Bridge

Perhaps you’re someone interested in the bridge between language D and Objective-C I introduced last week. Last week article was more about how to solve the various problems so that it just work. Today, I’ll explain how you can use the bridge with the Decagon demo app (part of the downloadable package for the D/Objective-C Xcode project).

So if you’ve downloaded the package, you have an Xcode project, a bunch of D code file and a few application resource. Seen from inside Xcode, it looks like this:

If you open the decagon group (which mirrors the decagon folder in the filesystem) you’ll see three code files: “adder.d”, “controller.d”, “main.d”. Each of these constitute a module in D parlance.

Let’s first take a glance at decagon’s main module. Since I do not expect all my readers to be fluent in D, I’ll start by explaining things a few more than I’d do normally.

The first line (under the license, that is) reads like this:

module decagon.main;

This simply declare that we’re in module “decagon.main”. The module declaration isn’t required, but can be helpful to detect module mismatch — when the file located at one place does not correspond to what it is supposed to be. This particular declaration means that we’re in file “main.d” inside the “decagon” folder, and this is how the compiler is expected to find this module. Now, let’s go to the more interesting stuff:

import decagon.adder;
import decagon.controller;
import cocoa.cocoa;

The first import search for the file “adder.d” inside the “decagon” folder, reads it and make all it’s symbols accessible in this module scope. The second one does the same for “controller.d”. The last one imports all the symbols from the bridged Cocoa framework.

int main(char[][] args) {

This is simply the declaration for the main function. If you’re wondering about the argument type, char[][], it’s an array of arrays of characters. Since an array of characters is generally called a string, we can call this an array of strings, and since arrays know their length in D, we don’t need an argc argument to tell us how many argument there is (args.length does the job.)

But let’s go back to our main topic an inspect what the main function does. It begins with this:

    // Initialize controller classes before loading nib file by getting
    // its objcClass property.
    AppController.objcClass;
    AdderController.objcClass;

First, we’re getting a pointer to the Objective-C class definition for AppController and AdderController. First, what are those controllers? Remember the “decagon.adder” and “decagon.controller” modules? Those are classes from these from out there.

Second, why are we getting that, or what’s the point of getting a variable and storing it nowhere? The answer is that objcClass isn’t a variable, it’s a property (a function in short). Getting this property will not only get a pointer to the class definition, but if the class hasn’t already been initialized and registered with the Objective-C runtime, it’ll be. Since this is the first code which gets executed in the app, it’s pretty certain they haven’t been initialized and the whole point of getting the property is to do that initialization, not getting the value.

    auto app = new NSApplication;

Ah, now that’s serious: we’re creating a new application. If you’re wondering, the auto keyword in D just means to deduce automatically the type from the assignment on the right. So now we have declared variable app, of type NSApplication and initialized it with a new application.

    NSApplication.loadNib("Main.nib", app);

Here we’re loading the “Main.nib” file which contains a serialization of our user interface elements. This file instantiate and connect windows, menus, and controllers as it has been defined in Interface Builder… did I just say controllers? Perhaps this reminds you of the two classes we’ve initialized at the start of the main function. That wasn’t for nothing: it allowed the nib file to actually create an instance of each of these to classes and to plug the right objects to their exposed outlets. Now our two controllers (which we haven’t visited yet) are ready to do their job.

    app.run();

Yeah, now we run the application. This will enter in a run loop and exit when the terminate method on the application is called, at which time it’ll be time to end the main function with a magnificent:

    return 0;
}

Good. Now, let’s go see this mysterious AdderController class. Follow me in the “decagon/adder.d” file. I’ll skip the comments and only show you the code from now on.

After a few lines of imports, we can see a class declaration:

class AdderController : ObjcObject {

As you can guess, this defines a new class. Note that ObjcObject is an interface, not a class, so the superclass isn’t as it would seem: ObjcObject. No, the superclass, the class this AdderController class is derived from, is the base class Object at the root of every object in D. ObjcObject is only an interface and was added because if we didn’t, a polite error message would have told us to put it in — the ObjcObject interface is required when creating a subclass which must expose methods to the Objective-C side of the application.

Going further, we see three member variables in our class:

    NSControl firstValueField;
    NSControl secondValueField;
    NSControl resultField;

These are of type NSControl. Nothing spectacular here, move on.

Oh, what do we have next? Is it dark magic?

    mixin IBOutlet!(firstValueField, "firstValueField");
    mixin IBOutlet!(secondValueField, "secondValueField");
    mixin IBOutlet!(resultField, "resultField");

No. It’s magic alright, but it’s D template magic.1 Basically, this creates an “outlet” in which Interface Builder, and the nib file it produce, can connect other objects. When loading the nib file (remember in the main function?), the three member variables above are connected to their corresponding controls, “magically”.

    mixin ObjcBindInitializer!("init");

That’s simply something to bind the init Objective-C method to our class default constructor. It’s needed because we want Interface Builder to call our constructor when instanciating the controller.2

    void calculate(Object sender) {
        resultField.floatValue =
            firstValueField.floatValue + secondValueField.floatValue;
    }

Oh, wow. Now we have a function adding the value of the two fields and putting and setting it to the third.

    mixin IBAction!(calculate);

Magic again? You bet.3 This makes the calculate function above a possible target for actions specified in the nib file. When a button is clicked, that button fire an action to it’s target and the target handles it. Our class is ready to receive calculate actions.

And that’s all for our AdderCountroller class as this closing brace tells us:

}

So what happens again when the application starts? First it initializes our controller’s Objective-C classes, then the application object is created, then the nib file is loaded, instantiating our controller and connecting its outlet, and then the application runs, waiting in a loop for user input.

If you inspect the nib file, you’ll see that the calculate method is the target of the two first text fields in the adder window: when one field value changes, it calls the calculate method on our controller which calculate the result and set it to the result text field.

Great, we now have great application to do additions! :-)

What is the other controller in “decagon/controller.d” about? Well, by looking at it we can see it has two actions: openWebsite and openWindow which more or less do what they’re supposed to do (remember that it’s an early demo app). Then there are two more intriguing methods: openUntitledFile and shouldTerminateAfterLastWindowClosed. Those are application delegate methods. Our controller is connected to the delegate outlet of the application, and the application sends to its delegate messages when certain actions occur, or when it needs to know about certain things. The delegate isn’t required to respond to everything, it may only implement a few methods. This is what we’re doing here: defining two application delegate methods.

The first method (openUntitledFile) is called when the application wants to open a new untitled file after launch or in reaction to a click on its dock icon. Because Objective-C objects can’t see methods in D objects, the method need to be bound to an Objective-C selector. To do that, we need to know the selector name (which is the name of the method in Objective-C where each argument is represented by a single colon), the return type and the argument types.

So, to make callable from Objective-C the following D method:

bool openUntitledFile(NSApplication sender);

we mixin this template4:

mixin ObjcBindMethod!(openUntitledFile, bool, 
    "applicationOpenUntitledFile:", NSApplication);

which takes as argument the method to bind, the return type, the corresponding Objective-C selector, followed by the type of each arguments.

The second method (shouldTerminateAfterLastWindowClosed) is called when the last window of the application is closed to determine if the application should terminate. The method is bound much in the same way as above.

So this completes our tour of Decagon 0.1. I hope it has been interesting and that you’ll give the D/Objective-C bridge a try. You can download the Xcode project for the bridge and Decagon from the D/Objective-C bridge project page. You also need the GDC compiler for D and the D plugin for Xcode (links are on the project page). There is also a mailing list you can subscribe to if you need help or want to ask questions.


  1. It’s not magic enough to my taste however. I don’t like how the variable name has to be repeated in a string as the second template argument. Anyone with a bright (and working) idea of how to get the name of the aliased member variable from a template is welcome. ↩︎

  2. The next version of the bridge (which I’m preparing) will make this line optional: if there is a default constructor (with no arguments) and no explicitly declared initializer (with ObjcBindInitializer), then the init method will automatically be bound to that default constructor. ↩︎

  3. Notice how for actions you don’t have to specify the name as the second argument? That’s because it’s easy to get the name of an aliased method, while it’s not possible (that I know of) to get the name of an aliased variable. ↩︎

  4. This template has the wrong return type in version 0.1 of Decagon (void). This has been corrected in this article and will be fixed with the next release of the D/Objective-C bridge. ↩︎


  • © 2003–2024 Michel Fortin.