Calling a D library from Objective C in XCode

From D Wiki
Revision as of 12:47, 27 February 2018 by D user (talk | contribs) (Category:HowTo)
Jump to: navigation, search

This is based on a post in the D forum by Mike McKee.

// test.d
extern (C++) immutable(char)* dfunc(const char *s) {
	import std.string;
	return toStringz(fromStringz(s) ~ "-response");
}

I compiled with dmd -c test.d and it created a test.o file. I then created a Cocoa Objective C application in XCode7, but could have created a console application in XCode just as well.

Next, drag your test.o file into your XCode project at the top, underneath the first icon on the left. A dialog will pop open -- just go with the defaults.

I renamed the main.m as main.mm in the project. Remember the XCode quirk where if you use a .mm file, you may have to go to the project settings under Compile Sources and ensure your .mm file is there. If not, add it. I know that it won't do that with the main.m file as main.mm, but I think it has a problem with AppDelegate.m when renamed as AppDelegate.mm. So, keep that in mind.

By making it a .mm file, I can now mix Objective C and C++ together.

In the main.mm, I had this:

#import <Cocoa/Cocoa.h>

char *dfunc(const char *);
extern "C" int rt_init();
extern "C" int rt_term();

int main(int argc, const char * argv[]) {
	
	const char *sTest = "request";
	rt_init(); // Call D Runtime
	NSLog(@"Result=%s",dfunc(sTest));
	rt_term(); // End D Runtime
	
  return NSApplicationMain(argc, argv);
}

Next, you have to link in AppKit.Framework by going to your project settings and choosing Targets > (click your target .app) > Build Phases > Link Binary with Libraries, and choose AppKit.Framework. This is a quirk in XCode.

Next, you have to link in libphobos2.a from your /usr/local/lib folder in a special way. You have to go into your project settings. Instead of clicking on your Targets item, click on your Projects item and then click > Build Settings > Linking > Other Linker Flags. Click the + on that and simply type the file path as /usr/local/lib/libphobos2.a. Do not put a -l in front of that or even a single "l" in front of it (without the dash). See, this is not g++ or gcc, which support that parameter. Instead, this is clang++.

Now, Run your project. When you run it, you'll see your default Cocoa form, yes, but in the debugger console you'll see "Result=request-response". This is because we passed the string "request" to dfunct() and it returned it tacked on "-response" on the end.

Okay you could stop there, but let's make this even cooler. Let's make it where you can code D code in XCode, have it compile and create your .o file, before Objective C is compiled and calls that .o file. So, include the actual source of the test.d into your project by dragging it under the first little folder icon from the top and telling the popup dialog to copy the file there.

Edit that test.d so that we return -response2 instead of -response, just to let us know we're calling the latest file.

Now, XCode thinks that .d files are DTrace files. It will automatically try to compile this test.d file as a DTrace file. So, to turn that off, go to your project settings > Targets > (your target) > Compile Sources, and remove test.d out of that list.

Next, we need to remove that test.o out of the project so that we build this before linktime and include it during linking. So, remove test.o out of the project. Next, go your project settings > Targets > (your target) > Build Phases > and click the + and choose New Run Script Phase. Simply paste the following into the black code area below the shell field:

/usr/local/bin/dmd -c $SRCROOT/test.d

(Note, this may take some finagling. For instance, in my case for some strange reason I had a folder called sample with another folder inside also called sample, and the test.d was in that. So, my line had to be:)

/usr/local/bin/dmd -c $SRCROOT/sample/test.d

Next, here's another quirk in XCode. It won't let you drag and drop this Run Script Phase to before the Compile Sources phases. However, it will let you drag other items below it. So, it was kind of quirky and tricky, but I made the order like so:

1. Target Dependencies 2. Copy Bundle Resources 3. Run Script 4. Link Binary with Libraries 5. Compile Sources

That ensures that when the linker phase runs, it will see a test.o file.

Now we need to ensure the linker sees the test.o file. To do that, go to your project settings > Targets > (choose your target) > Build Settings > Basic > Linking > Other Linker Flags. Remember where we added the libphobos2.a file there? Now we need to add the test.o file there. So, doubleclick it's value and click the + sign. Type in:

$(SRCROOT)/test.o

Note that even though my test.d was in $SRCROOT/sample, for some reason it dropped the test.o in $SRCROOT. So, it may take some finagling in your case to ensure you have the right path. Note also that paths in XCode are in format $(ENV_VAR), but in Bash (/bin/sh) are in $ENV_VAR format.

Next, ensure that the libphobos2.a line is above the test.o line in the Other Linker Flags. So, drag it there if you have to do so.

At that point, we're set -- let's compile and run. What will happen is that it will compile the test.d file first with the dmd compiler and make a test.o, then the linker will link libphobos2.a, then link test.o, and then start to compile your Objective C and C++. XCode will then merge in what it needs from Phobos2 and your test.o into the executable binary and will no longer require either one. To prove my point, you can run an otool -L command on the binary executable that XCode creates and you'll see no references to phobos or test.o.

The output you'll see in your debugger console will say RESPONSE=request-response2 because it's running through your latest source code.

So, from here on out, you can use Objective C minimally, and Objective C++ minimally, and have a lot of your code in D. This code can then interact with your Cocoa application. Now, as you may know (or learn), Cocoa apps are pretty plain -- Apple's pretty fascist here and will let you make any kind of app as long as the elements are grey or white, lol. This is why I recommend using the Cocoa webkit framework -- you'll get far more color and style opportunities to make widgets of any sort. I also recommend enabling the Objective C embedding into the HTML DOM so that Javascript can call that. At that point, your Javascript can call ObjC minimally, and then your ObjC can call D, and then D can return a response right back through ObjC back to the Javascript.

As for D calling the Apple Foundation Classes, they are, evidently, available to C++, so perhaps they can be loaded in D.