Jump to page: 1 2
Thread overview
I Did It! Calling D Library from Objective C in XCode on OSX
Dec 14, 2015
Mike McKee
Dec 14, 2015
Ali Çehreli
Dec 14, 2015
John Colvin
Dec 14, 2015
Jacob Carlborg
Dec 14, 2015
Mike McKee
Dec 14, 2015
Mike McKee
Dec 15, 2015
Jacob Carlborg
Dec 15, 2015
Mike McKee
Dec 15, 2015
Jacob Carlborg
Dec 15, 2015
John Colvin
Dec 16, 2015
Jacob Carlborg
Dec 16, 2015
John Colvin
Dec 16, 2015
Jacob Carlborg
Dec 14, 2015
bachmeier
Dec 14, 2015
Mike McKee
Dec 14, 2015
bachmeier
Dec 15, 2015
Jacob Carlborg
Dec 15, 2015
bachmeier
December 14, 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.
December 14, 2015
On 12/14/2015 02:09 AM, Mike McKee wrote:
> I finally managed to get it working

Congratulations! But this is not the right medium for this blog post. ;) Please polish and publish it somewhere before someone puts it on Reddit now. :)

Ali

December 14, 2015
On Monday, 14 December 2015 at 11:12:03 UTC, Ali Çehreli wrote:
> On 12/14/2015 02:09 AM, Mike McKee wrote:
>> I finally managed to get it working
>
> Congratulations! But this is not the right medium for this blog post. ;) Please polish and publish it somewhere before someone puts it on Reddit now. :)
>
> Ali

+1 It's great you've achieved this, it would make a good blog post.

P.S. Of course posting here is better than not telling anyone at all, but blog posts about D are somewhat in short supply.
December 14, 2015
On 2015-12-14 11:09, Mike McKee wrote:

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

They're not available in C++. They're available in Objective-C++, which is a different language.

Yes, they can be accessed from D [1].

[1] http://dlang.org/spec/objc_interface.html

-- 
/Jacob Carlborg
December 14, 2015
On Monday, 14 December 2015 at 17:28:20 UTC, Jacob Carlborg wrote:
> [1] http://dlang.org/spec/objc_interface.html

Unfortunately, my version of DMD on this OSX doesn't support the (Objective-C) extern when I run the example given at the bottom of that objc_interface.html page.

$ dmd -m64 -L-framework -LFoundation test.d
test.d(5): Error: valid linkage identifiers are D, C, C++, Pascal, Windows, System
test.d(5): Error: found '-' when expecting ')'
test.d(5): Error: no identifier for declarator C
test.d(5): Error: declaration expected, not ')'
test.d(9): Error: unrecognized declaration

$ dmd --version
DMD64 D Compiler v2.068
Copyright (c) 1999-2015 by Digital Mars written by Walter Bright

I think I installed dmd through homebrew. I don't know how to update it -- I'm still green when it comes to homebrew and only know apt-get from Ubuntu Linux.

December 14, 2015
Is it okay if I copy your post to the wiki at this link?

http://wiki.dlang.org/Cookbook


December 14, 2015
On Monday, 14 December 2015 at 19:13:20 UTC, bachmeier wrote:
> Is it okay if I copy your post to the wiki at this link?
>
> http://wiki.dlang.org/Cookbook

Sure! :)

Feel free to fix grammar or anything out of sorts (or could be better said), if you want. My goal is to enable more people to be able to do this.

December 14, 2015
On Monday, 14 December 2015 at 18:13:02 UTC, Mike McKee wrote:
> I think I installed dmd through homebrew. I don't know how to update it -- I'm still green when it comes to homebrew and only know apt-get from Ubuntu Linux.

Oh, I found I could do:

$ sudo brew update
$ sudo brew upgrade dmd

Now it generates this error:

$ dmd -m64 -L-framework -LFoundation test.d
test.d(6): Error: undefined identifier 'selector'
test.d(12): Error: undefined identifier 'selector'
test.d(13): Error: undefined identifier 'selector'

Here's the source I'm trying to compile, and evidently it doesn't like @selector for some reason:

// test.d
module main;

extern (Objective-C)
interface Class
{
    NSString alloc() @selector("alloc");
}

extern (Objective-C)
interface NSString
{
    NSString initWithUTF8String(in char* str) @selector("initWithUTF8String:");
    void release() @selector("release");
}

extern (C) void NSLog(NSString, ...);
extern (C) Class objc_lookUpClass(in char* name);

void main()
{
    auto cls = objc_lookUpClass("NSString");
    auto str = cls.alloc().initWithUTF8String("Hello World!");
    NSLog(str);
    str.release();
}

December 14, 2015
On Monday, 14 December 2015 at 19:15:22 UTC, Mike McKee wrote:
> On Monday, 14 December 2015 at 19:13:20 UTC, bachmeier wrote:
>> Is it okay if I copy your post to the wiki at this link?
>>
>> http://wiki.dlang.org/Cookbook
>
> Sure! :)
>
> Feel free to fix grammar or anything out of sorts (or could be better said), if you want. My goal is to enable more people to be able to do this.

http://wiki.dlang.org/Calling_a_D_library_from_Objective_C_in_XCode

Anyone should feel free to edit it or move it to a better location. I don't know anything about this topic. I'm also off to give a final exam right now, so the only formatting I did was to highlight the code.
December 15, 2015
On 2015-12-14 20:20, Mike McKee wrote:

> Oh, I found I could do:
>
> $ sudo brew update
> $ sudo brew upgrade dmd

Alternatively you can install DMD using DVM [1].

> Now it generates this error:
>
> $ dmd -m64 -L-framework -LFoundation test.d
> test.d(6): Error: undefined identifier 'selector'
> test.d(12): Error: undefined identifier 'selector'
> test.d(13): Error: undefined identifier 'selector'
>
> Here's the source I'm trying to compile, and evidently it doesn't like
> @selector for some reason:
>
> // test.d
> module main;
>
> extern (Objective-C)
> interface Class
> {
>      NSString alloc() @selector("alloc");
> }
>
> extern (Objective-C)
> interface NSString
> {
>      NSString initWithUTF8String(in char* str)
> @selector("initWithUTF8String:");
>      void release() @selector("release");
> }
>
> extern (C) void NSLog(NSString, ...);
> extern (C) Class objc_lookUpClass(in char* name);
>
> void main()
> {
>      auto cls = objc_lookUpClass("NSString");
>      auto str = cls.alloc().initWithUTF8String("Hello World!");
>      NSLog(str);
>      str.release();
> }

Hmm, that should work. @selector is defined in core.attributes which should be imported automatically through the object module. Perhaps the compiler doesn't pick up the correct version of druntime.

Could you please add "-v" do the command line when compiling. Look in the beginning of the output for "import object". If you open the "object" module, it should contain a line like this, in the beginning of the file (line 52 for me) :

version (D_ObjectiveC) public import core.attribute : selector;

Then in the "attribute" module (which should come right after the "object" module in the output) should contain this, in the bottom of the file:

version (D_ObjectiveC) struct selector
{
    string selector;
}

My output with the "-v" flag looks like this:

$ dmd -v main.d
binary    dmd
version   v2.069.1
config    /Users/jacob/.dvm/compilers/dmd-2.069.1/osx/bin/dmd.conf
parse     main
importall main
import    object (/Users/jacob/.dvm/compilers/dmd-2.069.1/osx/bin/../../src/druntime/import/object.d)
import    core.attribute (/Users/jacob/.dvm/compilers/dmd-2.069.1/osx/bin/../../src/druntime/import/core/attribute.d)
import    std.stdio (/Users/jacob/.dvm/compilers/dmd-2.069.1/osx/bin/../../src/phobos/std/stdio.d)

You can also see the config file used in the beginning of the output above. That tells the compiler where do find the druntime.

Please try all these commands directly on the command line without using Xcode in anyway.

[1] https://github.com/jacob-carlborg/dvm

-- 
/Jacob Carlborg
« First   ‹ Prev
1 2