www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - I Did It! Calling D Library from Objective C in XCode on OSX

reply Mike McKee <volomike gmail.com> writes:
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.
Dec 14 2015
next sibling parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
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
Dec 14 2015
parent John Colvin <john.loughran.colvin gmail.com> writes:
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.
Dec 14 2015
prev sibling next sibling parent reply Jacob Carlborg <doob me.com> writes:
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
Dec 14 2015
parent reply Mike McKee <volomike gmail.com> writes:
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.
Dec 14 2015
parent reply Mike McKee <volomike gmail.com> writes:
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(); }
Dec 14 2015
parent reply Jacob Carlborg <doob me.com> writes:
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
Dec 14 2015
parent reply Mike McKee <volomike gmail.com> writes:
On Tuesday, 15 December 2015 at 07:52:50 UTC, Jacob Carlborg 
wrote:
 Could you please add "-v" do the command line when compiling.
Mine was completely different: $ dmd -v test.d binary dmd version v2.069 config /usr/local/bin/dmd.conf parse test importall main import object (/Library/D/dmd/src/druntime/import/object.d) semantic main entry main test.d semantic2 main test.d(7): Error: undefined identifier 'selector' test.d(13): Error: undefined identifier 'selector' test.d(14): Error: undefined identifier 'selector' Also, look what I have in my import/core folder. It doesn't match yours: atomic.d checkedint.d demangle.d internal/ memory.d simd.d sync/ thread.d vararg.d bitop.d cpuid.d exception.d math.d runtime.d stdc/ sys/ time.d So, then I uninstalled dmd via brew, but found it left a /Library/D folder behind. Therein lies the problem. So, I did sudo brew uninstall --force dmd, sudo su, removed the /Library/D folder, and then sudo brew install dmd. Now it works! volomike:cpptod4 mike$ dmd -m64 -L-framework -LFoundation test.d volomike:cpptod4 mike$ ls test test.d test.o volomike:cpptod4 mike$ ./test 2015-12-15 03:07:52.669 test[7308:116958] Hello World! volomike:cpptod4 mike$
Dec 15 2015
parent reply Jacob Carlborg <doob me.com> writes:
On 2015-12-15 09:08, Mike McKee wrote:
 On Tuesday, 15 December 2015 at 07:52:50 UTC, Jacob Carlborg wrote:
 Could you please add "-v" do the command line when compiling.
Mine was completely different: $ dmd -v test.d binary dmd version v2.069 config /usr/local/bin/dmd.conf parse test importall main import object (/Library/D/dmd/src/druntime/import/object.d) semantic main entry main test.d semantic2 main test.d(7): Error: undefined identifier 'selector' test.d(13): Error: undefined identifier 'selector' test.d(14): Error: undefined identifier 'selector' Also, look what I have in my import/core folder. It doesn't match yours: atomic.d checkedint.d demangle.d internal/ memory.d simd.d sync/ thread.d vararg.d bitop.d cpuid.d exception.d math.d runtime.d stdc/ sys/ time.d So, then I uninstalled dmd via brew, but found it left a /Library/D folder behind. Therein lies the problem. So, I did sudo brew uninstall --force dmd, sudo su, removed the /Library/D folder, and then sudo brew install dmd. Now it works!
So a broken installer. I recommend using DVM [1]. It can install multiple versions of DMD and they live side by side completely independent.
 volomike:cpptod4 mike$ dmd -m64 -L-framework -LFoundation test.d
 volomike:cpptod4 mike$ ls
 test    test.d    test.o
 volomike:cpptod4 mike$ ./test
 2015-12-15 03:07:52.669 test[7308:116958] Hello World!
 volomike:cpptod4 mike$
Cool :) [1] https://github.com/jacob-carlborg/dvm -- /Jacob Carlborg
Dec 15 2015
parent reply John Colvin <john.loughran.colvin gmail.com> writes:
On Tuesday, 15 December 2015 at 09:04:45 UTC, Jacob Carlborg 
wrote:
 On 2015-12-15 09:08, Mike McKee wrote:
 On Tuesday, 15 December 2015 at 07:52:50 UTC, Jacob Carlborg 
 wrote:
 Could you please add "-v" do the command line when compiling.
Mine was completely different: $ dmd -v test.d binary dmd version v2.069 config /usr/local/bin/dmd.conf parse test importall main import object (/Library/D/dmd/src/druntime/import/object.d) semantic main entry main test.d semantic2 main test.d(7): Error: undefined identifier 'selector' test.d(13): Error: undefined identifier 'selector' test.d(14): Error: undefined identifier 'selector' Also, look what I have in my import/core folder. It doesn't match yours: atomic.d checkedint.d demangle.d internal/ memory.d simd.d sync/ thread.d vararg.d bitop.d cpuid.d exception.d math.d runtime.d stdc/ sys/ time.d So, then I uninstalled dmd via brew, but found it left a /Library/D folder behind. Therein lies the problem. So, I did sudo brew uninstall --force dmd, sudo su, removed the /Library/D folder, and then sudo brew install dmd. Now it works!
Using sudo with homebrew is strongly recommended against (https://github.com/Homebrew/homebrew/blob/master/share/doc/homebrew/FAQ.md#why-does-homeb ew-say-sudo-is-bad- ). I have no idea how you got something in /Library/D, but it doubt it was from homebrew. In particular, if you weren't using root then I don't think homebrew would even be able to write there.
 So a broken installer.
Possible, but more likely a leftover from installing dmd some other way. Seems to be the case in 90% of "I installed dmd using X and it didn't work".
 I recommend using DVM [1]. It can install multiple versions of 
 DMD and they live side by side completely independent.
The important thing is not to mix and match installers unless you know how they work.
Dec 15 2015
parent reply Jacob Carlborg <doob me.com> writes:
On 2015-12-15 15:43, John Colvin wrote:

 I have no idea how you got something in /Library/D, but it doubt it
 was from homebrew.
The native installer installs into /Library/D.
 Possible, but more likely a leftover from installing dmd some other way.
 Seems to be the case in 90% of "I installed dmd using X and it didn't
 work".
Shouldn't an installer make sure the files it installed is the files being used?
 The important thing is not to mix and match installers unless you know
 how they work.
DVM will take precedence :) -- /Jacob Carlborg
Dec 15 2015
parent reply John Colvin <john.loughran.colvin gmail.com> writes:
On Wednesday, 16 December 2015 at 07:46:53 UTC, Jacob Carlborg 
wrote:
 On 2015-12-15 15:43, John Colvin wrote:

 I have no idea how you got something in /Library/D, but it 
 doubt it
 was from homebrew.
The native installer installs into /Library/D.
Well that probably explains the problem then.
 Possible, but more likely a leftover from installing dmd some 
 other way.
 Seems to be the case in 90% of "I installed dmd using X and it 
 didn't
 work".
Shouldn't an installer make sure the files it installed is the files being used?
How exactly would it do that? I guess it could parse some output from dmd to check that it's looking in the right directories, that would be a nice enhancement. I wouldn't want it to try and do much to fix the situation if it didn't work, that would be too intrusive.
 The important thing is not to mix and match installers unless 
 you know
 how they work.
DVM will take precedence :)
How does it do that? DVM seems useful if you really need multiple versions of DMD installed, but in general I don't like having a special installer tool for each piece of software, each with its own way of doing things.
Dec 16 2015
parent Jacob Carlborg <doob me.com> writes:
On 2015-12-16 10:19, John Colvin wrote:

 How exactly would it do that? I guess it could parse some output from
 dmd to check that it's looking in the right directories, that would be a
 nice enhancement.
Not sure, but for one thing, don't put any files any global directories. That is, don't put dmd.conf in /etc, don't put phobos in /usr/lib and so on.
 How does it do that?
It adds DMD to the beginning of the PATH environment variable and doesn't rely on any global files.
 DVM seems useful if you really need multiple versions of DMD installed,
 but in general I don't like having a special installer tool for each
 piece of software, each with its own way of doing things.
I can see the point of that. But with the current update rate of D and the fact it breaks my code in each single release, I think being able to have multiple versions of DMD installed is crucial. Another advantage is that DVM is cross-platform. If you're using multiple platforms you can use the same way to install DMD on all the platforms. -- /Jacob Carlborg
Dec 16 2015
prev sibling parent reply bachmeier <no spam.com> writes:
Is it okay if I copy your post to the wiki at this link?

http://wiki.dlang.org/Cookbook
Dec 14 2015
next sibling parent reply Mike McKee <volomike gmail.com> writes:
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.
Dec 14 2015
parent bachmeier <no spam.com> writes:
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.
Dec 14 2015
prev sibling parent reply Jacob Carlborg <doob me.com> writes:
On 2015-12-14 20:13, bachmeier wrote:
 Is it okay if I copy your post to the wiki at this link?

 http://wiki.dlang.org/Cookbook
Please don't. There's a lot of errors and weird things in the post. It doesn't really do what the title says. At least it's not interesting at all since it uses the C++ interface to call D, not the Objective-C interface. Which there is no reason to do, it could just use the C interface. No point in mixing C++ in the picture. -- /Jacob Carlborg
Dec 14 2015
parent bachmeier <no spam.com> writes:
On Tuesday, 15 December 2015 at 07:57:56 UTC, Jacob Carlborg 
wrote:
 On 2015-12-14 20:13, bachmeier wrote:
 Is it okay if I copy your post to the wiki at this link?

 http://wiki.dlang.org/Cookbook
Please don't. There's a lot of errors and weird things in the post. It doesn't really do what the title says. At least it's not interesting at all since it uses the C++ interface to call D, not the Objective-C interface. Which there is no reason to do, it could just use the C interface. No point in mixing C++ in the picture.
I removed the link to it, but I can't delete a wiki page.
Dec 15 2015