I Did It! Calling D Library from Objective C in XCode on OSX
Mike McKee via Digitalmars-d-learn
digitalmars-d-learn at puremagic.com
Mon Dec 14 02:09:29 PST 2015
I finally managed to get it working, using some help from this
forum and stackoverflow.com, and a little bit of random luck with
tests.
// 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.
More information about the Digitalmars-d-learn
mailing list