December 14, 2017
On 2017-12-14 14:56, mrphobby wrote:

> Also, it feels a bit awkward to implement the callback handling methods as static methods, with the "self" and SEL arguments. Would have been nice if it was possible to use instance methods.

That's currently not possible. The "self" and SEL arguments are required because that's how Objective-C methods are implemented under the hood.

> After thinking about it for a while I guess it could work with one static dispatch method that maps selectors to specific instance methods. So when callbacks are made from Objective-C they are made to this one static method, which would then call the right method on the class.

It's not possible to use instance methods because:

* Interfaces can only have final instance methods
* The function needs to have C linkage, which a D instance method cannot have
* It's not possible to use classes because then the compiler would complain about missing methods when Objective-C methods are specified without any body

> I'm still struggling with D syntax, so here's some pseudo code:
> 
> class AppDelegate {
>     static var methodMap = {}    // Maps selector name to methods
> 
>     static void handleCallback(AppDelegate self, SEL sel, ...) {
>        var method = methodMap[sel];
>        self.call(method, va_list);      // Call the method with args (not sure if possible in D)
>     }
> 
>     void applicationDidFinishLaunching(NSNotification notification) {
>        // Normal instance method here
>     }
> }
> 
> Now, you would also need a registration step somewhere that sets up the selectors to use, perhaps in a static constructor that is run when AppDelegate class is used for the first time.
> 
> I hope this makes sense. Just throwing out some ideas :)

No, that would not work. Several years ago I created an Objective-C bridge [1], which allowed a syntax similar to above:

class AppController : NSObject
{
    void foo() {}
    mixin ObjcBindMethod!foo;
}

The bridge basically wrapped an Objective-C object inside a D object, resulted in two objects for each object instead of one. That bridge turned out to be a failure, vary complicated, huge amount of template instantiations and bloat from a lot of virtual methods that could not be removed. A simple test application with a window and a button resulted in a 60 MB executable.

DIP43 is the next step in the evolution after the bridge and it's a much better approach. When DIP43 is done, or at least has made some more progress it will be much simpler.

Please keep in mind that all this is work in progress, that's why it is as complicated now as it is.

[1] http://dsource.org/projects/dstep/browser/dstep/objc

-- 
/Jacob Carlborg
December 14, 2017
On Thursday, 14 December 2017 at 14:07:25 UTC, Adam D. Ruppe wrote:
>
> I was playing with this myself based on Jacob's code and made it look like this:
>
> extern (Objective-C) interface ViewController : NSViewController {
>         extern (C)
>         @ObjCMethodOverride("loadView")
>         static void loadView(ViewController self, SEL sel) {
>                 printf("loadView\n");
>         }
>
>         extern (C)
>         @ObjCMethodOverride("viewDidLoad")
>         static void viewDidLoad(ViewController self, SEL sel) {
>                 printf("viewDidLoad\n");
>         }
>
>         ViewController init() @selector("init");
>         mixin RegisterObjCClass;
> }
>
>
>
> so the mixin does some registering based on the method override attrs. It is still static with self cuz I actually felt hiding that made things a bit worse (inheritance wouldn't work like you expect), but most the registration stuff is now pulled from the attribute metadata.
>
>
> Of course, my goal here isn't actually to do all of obj-c... just enough to port my simpledisplay.d. So I'm not sure if I'll make this public yet or just leave it as private and/or undocumented inside my library file.

please make it public.
December 14, 2017
On Thursday, 14 December 2017 at 16:12:30 UTC, Jacob Carlborg wrote:
> No, that would not work. Several years ago I created an The bridge basically wrapped an Objective-C object inside a D object, resulted in two objects for each object instead of one. That bridge turned out to be a failure, vary complicated, huge amount of template instantiations and bloat from a lot of virtual methods that could not be removed. A simple test application with a window and a button resulted in a 60 MB executable.
>
> DIP43 is the next step in the evolution after the bridge and it's a much better approach. When DIP43 is done, or at least has made some more progress it will be much simpler.
>
> Please keep in mind that all this is work in progress, that's why it is as complicated now as it is.
>
> [1] http://dsource.org/projects/dstep/browser/dstep/objc

Ok, I see! Sounds like I'll have to wait then. How long do you think it will take until DIP43 is available for use? Is it months or years we are talking about here?
December 14, 2017
On Thursday, 14 December 2017 at 14:07:25 UTC, Adam D. Ruppe wrote:
> I was playing with this myself based on Jacob's code and made it look like this:
>
> extern (Objective-C) interface ViewController : NSViewController {
>         extern (C)
>         @ObjCMethodOverride("loadView")
>         static void loadView(ViewController self, SEL sel) {
>                 printf("loadView\n");
>         }
>
>         extern (C)
>         @ObjCMethodOverride("viewDidLoad")
>         static void viewDidLoad(ViewController self, SEL sel) {
>                 printf("viewDidLoad\n");
>         }
>
>         ViewController init() @selector("init");
>         mixin RegisterObjCClass;
> }
>
>
>
> so the mixin does some registering based on the method override attrs. It is still static with self cuz I actually felt hiding that made things a bit worse (inheritance wouldn't work like you expect), but most the registration stuff is now pulled from the attribute metadata.
>
>
> Of course, my goal here isn't actually to do all of obj-c... just enough to port my simpledisplay.d. So I'm not sure if I'll make this public yet or just leave it as private and/or undocumented inside my library file.

This looks pretty awesome and very much like something I was looking for. Would really appreciate if you could share your work. Otherwise I'll have to roll up my sleeves and try hacking it on my own :)

Thanks for sharing your ideas!

December 14, 2017
On 2017-12-14 20:06, mrphobby wrote:

> Ok, I see! Sounds like I'll have to wait then. How long do you think it will take until DIP43 is available for use? Is it months or years we are talking about here?

It will take more than months to have the complete set of features of DIP43 available, but many of them are convince features. There are two important features missing: implementing classes and static/class methods. I don't recall right now how much work it was to implement them but I hope it wouldn't take more than a couple of months.

Keep in mind that a lot of time is usually spent on reviewing the changes as well before they are merged.

Also, this is currently not anything that is prioritized for me, I have other things that I need to complete before I can continue on this.

-- 
/Jacob Carlborg
December 21, 2017
On Thursday, 14 December 2017 at 19:10:26 UTC, mrphobby wrote:
> On Thursday, 14 December 2017 at 14:07:25 UTC, Adam D. Ruppe wrote:
>> I was playing with this myself based on Jacob's code and made it look like this:
>>
>> extern (Objective-C) interface ViewController : NSViewController {
>>         extern (C)
>>         @ObjCMethodOverride("loadView")
>>         static void loadView(ViewController self, SEL sel) {
>>                 printf("loadView\n");
>>         }
>>
>>         extern (C)
>>         @ObjCMethodOverride("viewDidLoad")
>>         static void viewDidLoad(ViewController self, SEL sel) {
>>                 printf("viewDidLoad\n");
>>         }
>>
>>         ViewController init() @selector("init");
>>         mixin RegisterObjCClass;
>> }
>>
> This looks pretty awesome and very much like something I was looking for. Would really appreciate if you could share your work. Otherwise I'll have to roll up my sleeves and try hacking it on my own :)

Ok, I finally had some time to work this out today. Thanks to the ideas posted here I was able to wrap something up despite my very limited knowledge of D and its compile time features :)

Basically this is what your classes will look like:

extern (Objective-C)
@ObjCSuperClass("NSObject")
interface AppDelegate : NSApplicationDelegate {
	mixin ObjCClass;

	AppDelegate init() @selector("init");

	extern (C):

	@ObjCMethod("applicationDidFinishLaunching:")
	static void applicationDidFinishLaunching(AppDelegate self, SEL sel, NSNotification notification) {
        writefln("applicationDidFinishLaunching");
	}
}

In this case I needed to add the explicit superclass attribute since NSApplicationDelegate is a protocol and not a class. If your object inherits from an interface representing a regular Objective-C class you don't need this.

The actual registration and setup principle is based on Jacobs code and I added stuff to handle the attributes. Obviously you need declarations for Objective-C runtime calls but you can find those in Jacobs example source as well.

Anyway, hope this helps. Feel free to fork and improve!
https://gist.github.com/mrphobby/a247deb15d38aea86b3346079f32ce58


December 21, 2017
On Thursday, 14 December 2017 at 19:10:26 UTC, mrphobby wrote:
> This looks pretty awesome and very much like something I was looking for. Would really appreciate if you could share your work. Otherwise I'll have to roll up my sleeves and try hacking it on my own :)

oh sorry i forgot to post this sooner here's my code so far.

when i'm reasonably happy with it, it will be part of my simpledisplay.d. I might leave it undocumented, but if you wanted to dive into my source the final version will be in there somewhere.


CODE MODULE

---------
import helper_module;
import core.stdc.math;
import core.stdc.stdio;

void main() {
	auto delegate_ = AppDelegate.alloc.init;
	assert(delegate_, "AppDelegate null");

	NSApp.delegate_ = delegate_;

	NSApp.setActivationPolicy(NSApplicationActivationPolicy.regular);
	NSApp.run();
}

@ObjCParentOverride("NSObject")
extern (Objective-C) interface AppDelegate : NSApplicationDelegate {

	mixin IVar!(NSWindow, "window");
	mixin IVar!(ViewController, "controller");

	extern (C)
	@ObjCMethodOverride("applicationShouldTerminateAfterLastWindowClosed:")
	static bool applicationShouldTerminateAfterLastWindowClosed(AppDelegate self, SEL sel, NSNotification notification) {
		return true;
	}

	extern (C)
	@ObjCMethodOverride("applicationDidFinishLaunching:")
	static void applicationDidFinishLaunching(AppDelegate self, SEL sel, NSNotification notification) {
		NSApp.menu = mainMenu();

		immutable style = NSWindowStyleMask.resizable |
			NSWindowStyleMask.closable |
			NSWindowStyleMask.miniaturizable |
			NSWindowStyleMask.titled;

		auto window = NSWindow.alloc.initWithContentRect(
			NSMakeRect(10, 10, 300, 300),
			style,
			NSBackingStoreType.buffered,
			false
		);

		window.title = "D Rox";

		auto controller = ViewController.alloc.init;
		window.contentView = controller.view;

		window.center();

		self.window = window;
		self.controller = controller;

		window.makeKeyAndOrderFront(null);
		NSApp.activateIgnoringOtherApps(true);
	}

	// copy these two lines on any class
	typeof(this) init() @selector("init");
	mixin RegisterObjCClass;
}

extern (Objective-C) interface ViewController : NSViewController {
	extern (C)
	@ObjCMethodOverride("loadView")
	static void loadView(ViewController self, SEL sel) {
		printf("loadView\n");
	}

	extern (C)
	@ObjCMethodOverride("viewDidLoad")
	static void viewDidLoad(ViewController self, SEL sel) {
		printf("viewDidLoad\n");
	}

	ViewController init() @selector("init");
	mixin RegisterObjCClass;
}

NSMenu mainMenu() {
	auto mainMenu = NSMenu.alloc.init("MainMenu".toNSString);

	auto title = "Apple";
	auto menu = NSMenu.alloc.init(title.toNSString);

	auto item = mainMenu.addItem(title.toNSString, null, "".toNSString);
	mainMenu.setSubmenu(menu, item);

	menu.addItem(NSMenuItem.alloc.init("Quit", "stop:", "q"));

	return mainMenu;
}

---------

HELPER MODULE:

-----

version(OSX) {
	/* **************************** */
	// Obj-C / Cocoa bindings and helpers
	/* **************************** */

	// Special thanks to Jacob Carlborg
	// see: http://forum.dlang.org/thread/qzitebxwvavcfamsluji@forum.dlang.org

	/// Add an instance var to an Obj-C subclass
	mixin template IVar(T, string name) {
		extern(D) final {
			mixin("@IVarAttr T " ~ name ~ "() {
				return this.ivar!(name, T);
			}");

			mixin("void " ~ name ~ "(T v) {
				this.ivar!(name, T) = v;
			}");
		}
	}

	/// Add to your extern(Objective-C) interfaces if you need the parent class to be different
	/// from what in inherits from
	struct ObjCParentOverride { string parentClassName; }

	/// Attach to a method like `extern(C) static R name(typeof(this) self, SEL sel, Args...)`
	/// to give it a selector to override.
	struct ObjCMethodOverride { string selector; }

	/// And mix this in to your subclasses of the extern(Objective-C) interfaces
	mixin template RegisterObjCClass() {
		mixin ClassTrait;

		private static objc_method method(alias imp, string selector, const char* type = null)() {
			return objc_method(sel_registerName(selector.ptr), type, cast(IMP) &imp);
		}

		shared static this() {
			string name = typeof(this).stringof;
			string parent = "NSObject";
			static if(is(typeof(this) P == super)) {
				parent = P[0].stringof;
			}
			foreach(attr; __traits(getAttributes, typeof(this))) {
				static if(is(typeof(attr) == ObjCParentOverride))
					parent = attr.parentClassName;
			}

			objc_method[] methods;
			objc_ivar[] ivars;

			foreach(member; __traits(derivedMembers, typeof(this))) {
				foreach(attr; __traits(getAttributes, __traits(getMember, typeof(this), member))) {
					static if(is(attr == IVarAttr)) {
						static if(is(typeof(__traits(getMember, typeof(this), member)) R == return)) {
							ivars ~= objc_ivar(member, "^v", cast(int) log2(R.sizeof), R.sizeof);
						}
					} else static if(is(typeof(attr) == ObjCMethodOverride)) {
						enum sel = attr.selector; // weird hack here, passing attr.selector directly below caused compile errors :S
						methods ~= method!(__traits(getMember, typeof(this), member), sel);
					}
				}
			}
			
			registerClassInternal(name.ptr, parent.ptr, methods, ivars);
		}
	}

	/// leaks memory
	NSString toNSString(string str) {
		return NSString.alloc.initWithBytes(
		cast(immutable(ubyte)*) str.ptr,
		str.length,
		NSStringEncoding.NSUTF8StringEncoding
		);
	}


	/* Rest is private and/or bindings */

	enum IVarAttr;

	void registerClassInternal(const char* name, const char* superClassName, objc_method[] methods, objc_ivar[] ivars = []) {
		auto superClass = objc_lookUpClass(superClassName);
		assert(superClass, "Failed to lookup superclass");

		auto cls = objc_allocateClassPair(superClass, name, 0);
		assert(cls, "Failed to allocate class pair");

		foreach (method ; methods) {
			auto result = cls.class_addMethod(
				method.method_name,
				method.method_imp,
				method.method_types
			);

			assert(result, "Failed to add method");
		}

		foreach (objc_ivar ivar ; ivars) {
			auto result = cls.class_addIvar(
				ivar.ivar_name,
				size_t(ivar.space),
				cast(byte) ivar.ivar_offset,
				ivar.ivar_type
			);

			assert(result, "Failed to add instance variable");
		}

		objc_registerClassPair(cls);
	}

	struct IvarInternal(const char* name, T) {
		id self;

		alias value this;

		T value() {
			T value;
			object_getInstanceVariable(self, name, cast(void**) &value);
			return value;
		}

		void opAssign(T value) {
			object_setInstanceVariable(self, name, cast(void*) value);
		}
	}

	auto ivar(const char* name, T, Self)(Self self) {
		return IvarInternal!(name, T)(cast(id) self);
	}

	extern (Objective-C)
	enum NSApplicationActivationPolicy : ptrdiff_t {
		/* The application is an ordinary app that appears in the Dock and may have a user interface.  This is the default for bundled apps, unless overridden in the Info.plist. */
		regular,

		/* The application does not appear in the Dock and does not have a menu bar, but it may be activated programmatically or by clicking on one of its windows.  This corresponds to LSUIElement=1 in the Info.plist. */
		accessory,

		/* The application does not appear in the Dock and may not create windows or be activated.  This corresponds to LSBackgroundOnly=1 in the Info.plist.  This is also the default for unbundled executables that do not have Info.plists. */
		prohibited
	};

	extern (Objective-C)
	interface NSApplication : NSResponder {
		interface Class {
		mixin MetaclassTrait;
		NSApplication shared_() @selector("sharedApplication");
		}

		extern (D) static NSApplication shared_() {
		return Class.classof.shared_();
		}

		NSApplicationDelegate delegate_() @selector("delegate");
		void delegate_(NSApplicationDelegate) @selector("setDelegate:");

		bool setActivationPolicy(NSApplicationActivationPolicy activationPolicy) @selector("setActivationPolicy:");

		// use `int` as workaround for https://github.com/ldc-developers/ldc/issues/2387
		void activateIgnoringOtherApps(int flag) @selector("activateIgnoringOtherApps:");

		void run() @selector("run");
	}

	extern (Objective-C)
	__gshared NSApplication NSApp_;

	NSApplication NSApp() {
		if(NSApp_ is null)
			NSApp_ = NSApplication.shared_;
		return NSApp_;
	}

	extern (Objective-C)
	interface NSApplicationDelegate {
		void applicationDidFinishLaunching(NSNotification notification) @selector("applicationDidFinishLaunching:");
	}

	extern (Objective-C)
	interface NSColor {
		private alias This = typeof(this);

		interface Class {
		mixin MetaclassTrait;

		This alloc() @selector("alloc");

		NSColor redColor() @selector("redColor");
		}

		extern (D) {
		private static Class classof() {
			return Class.classof;
		}

		static This alloc() {
			return classof.alloc();
		}

		static NSColor redColor() {
			return classof.redColor;
		}
		}

		CGColorRef CGColor() @selector("CGColor");
	}

	enum NSBackingStoreType : size_t {
		retained = 0,
		nonretained = 1,
		buffered = 2
	}

	extern (Objective-C)
	interface NSMenu : NSObject {
		mixin ClassTrait;

		NSMenu init() @selector("init");
		NSMenu init(NSString title) @selector("initWithTitle:");

		void setSubmenu(NSMenu menu, NSMenuItem item) @selector("setSubmenu:forItem:");
		void addItem(NSMenuItem newItem) @selector("addItem:");

		NSMenuItem addItem(
		NSString title,
		SEL selector,
		NSString charCode
		) @selector("addItemWithTitle:action:keyEquivalent:");
	}

	extern (Objective-C)
	interface NSMenuItem : NSObject {
		mixin ClassTrait;

		NSMenuItem init() @selector("init");

		NSMenuItem init(
		NSString title,
		SEL selector,
		NSString charCode
		) @selector("initWithTitle:action:keyEquivalent:");

		extern (D) final
		{
		NSMenuItem init(string title, const(char)* selector, string charCode) {
			return init(title.toNSString, sel_registerName(selector), charCode.toNSString);
		}
		}
	}

	extern (Objective-C)
	interface NSResponder : NSObject {
		mixin ClassTrait;

		NSMenu menu() @selector("menu");
		void menu(NSMenu menu) @selector("setMenu:");
	}

	extern (Objective-C)
	interface NSView {
		mixin ClassTrait;

		NSView init() @selector("init");
		NSView initWithFrame(NSRect frameRect) @selector("initWithFrame:");

		void addSubview(NSView view) @selector("addSubview:");

		bool wantsLayer() @selector("wantsLayer");
		// use `int` as workaround for https://github.com/ldc-developers/ldc/issues/2387
		void wantsLayer(int value) @selector("setWantsLayer:");

		CALayer layer() @selector("layer");
		void uiDelegate(NSObject) @selector("setUIDelegate:");
	}

	extern (Objective-C)
	interface NSViewController : NSObject {
		NSView view() @selector("view");
		void view(NSView view) @selector("setView:");
	}

	extern (Objective-C)
	enum NSWindowStyleMask : size_t {
		borderless = 0,
		titled = 1 << 0,
		closable = 1 << 1,
		miniaturizable = 1 << 2,
		resizable	= 1 << 3,

		/* Specifies a window with textured background. Textured windows generally don't draw a top border line under the titlebar/toolbar. To get that line, use the NSUnifiedTitleAndToolbarWindowMask mask.
		 */
		texturedBackground = 1 << 8,

		/* Specifies a window whose titlebar and toolbar have a unified look - that is, a continuous background. Under the titlebar and toolbar a horizontal separator line will appear.
		 */
		unifiedTitleAndToolbar = 1 << 12,

		/* When set, the window will appear full screen. This mask is automatically toggled when toggleFullScreen: is called.
		 */
		fullScreen = 1 << 14,

		/* If set, the contentView will consume the full size of the window; it can be combined with other window style masks, but is only respected for windows with a titlebar.
		 Utilizing this mask opts-in to layer-backing. Utilize the contentLayoutRect or auto-layout contentLayoutGuide to layout views underneath the titlebar/toolbar area.
		 */
		fullSizeContentView = 1 << 15,

		/* The following are only applicable for NSPanel (or a subclass thereof)
		 */
		utilityWindow			= 1 << 4,
		docModalWindow		 = 1 << 6,
		nonactivatingPanel		= 1 << 7, // Specifies that a panel that does not activate the owning application
		hUDWindow = 1 << 13 // Specifies a heads up display panel
	}

	extern (Objective-C)
	interface NSWindow {
		mixin ClassTrait;

		NSWindow init() @selector("init");

		NSWindow initWithContentRect(
		NSRect contentRect,
		NSWindowStyleMask style,
		NSBackingStoreType bufferingType,
		bool flag
		) @selector("initWithContentRect:styleMask:backing:defer:");

		void makeKeyAndOrderFront(id sender) @selector("makeKeyAndOrderFront:");
		NSView contentView() @selector("contentView");
		void orderFrontRegardless() @selector("orderFrontRegardless");
		void center() @selector("center");
		void contentView(NSView view) @selector("setContentView:");

		NSString title() @selector("title");
		void title(NSString value) @selector("setTitle:");

		extern (D) final
		{
		void title(string value) {
			this.title = value.toNSString;
		}
		}
	}

	struct CGColor;
	alias CGColorRef = CGColor*;

	alias CGFloat = double;

	struct NSPoint {
		CGFloat x;
		CGFloat y;
	}

	struct NSSize {
		CGFloat width;
		CGFloat height;
	}

	struct NSRect {
		NSPoint origin;
		NSSize size;
	}

	pragma(inline, true) NSPoint NSMakePoint(CGFloat x, CGFloat y) {
		NSPoint p;
		p.x = x;
		p.y = y;
		return p;
	}

	pragma(inline, true) NSSize NSMakeSize(CGFloat w, CGFloat h) {
		NSSize s;
		s.width = w;
		s.height = h;
		return s;
	}

	pragma(inline, true) NSRect NSMakeRect(CGFloat x, CGFloat y, CGFloat w, CGFloat h) {
		NSRect r;
		r.origin.x = x;
		r.origin.y = y;
		r.size.width = w;
		r.size.height = h;
		return r;
	}

	extern (Objective-C)
	interface NSNotification {
	}

	extern (Objective-C)
	interface NSObject {
		mixin ClassTrait;

		NSObject init() @selector("init");
	}
	extern (Objective-C)
	interface NSString {
		mixin ClassTrait;

		NSString init() @selector("init");

		NSString initWithBytes(
			const(ubyte)* bytes,
			NSUInteger length,
			NSStringEncoding encoding
		) @selector("initWithBytes:length:encoding:");
	}

	extern (Objective-C)
	enum NSStringEncoding : NSUInteger {
		NSASCIIStringEncoding = 1,		/* 0..127 only */
		NSNEXTSTEPStringEncoding = 2,
		NSJapaneseEUCStringEncoding = 3,
		NSUTF8StringEncoding = 4,
		NSISOLatin1StringEncoding = 5,
		NSSymbolStringEncoding = 6,
		NSNonLossyASCIIStringEncoding = 7,
		NSShiftJISStringEncoding = 8,		  /* kCFStringEncodingDOSJapanese */
		NSISOLatin2StringEncoding = 9,
		NSUnicodeStringEncoding = 10,
		NSWindowsCP1251StringEncoding = 11,	/* Cyrillic; same as AdobeStandardCyrillic */
		NSWindowsCP1252StringEncoding = 12,	/* WinLatin1 */
		NSWindowsCP1253StringEncoding = 13,	/* Greek */
		NSWindowsCP1254StringEncoding = 14,	/* Turkish */
		NSWindowsCP1250StringEncoding = 15,	/* WinLatin2 */
		NSISO2022JPStringEncoding = 21,		/* ISO 2022 Japanese encoding for e-mail */
		NSMacOSRomanStringEncoding = 30,

		NSUTF16StringEncoding = NSUnicodeStringEncoding,	  /* An alias for NSUnicodeStringEncoding */

		NSUTF16BigEndianStringEncoding = 0x90000100,		  /* NSUTF16StringEncoding encoding with explicit endianness specified */
		NSUTF16LittleEndianStringEncoding = 0x94000100,	   /* NSUTF16StringEncoding encoding with explicit endianness specified */

		NSUTF32StringEncoding = 0x8c000100,
		NSUTF32BigEndianStringEncoding = 0x98000100,		  /* NSUTF32StringEncoding encoding with explicit endianness specified */
		NSUTF32LittleEndianStringEncoding = 0x9c000100		/* NSUTF32StringEncoding encoding with explicit endianness specified */
	}

	alias NSUInteger = size_t;
	alias NSInteger = ptrdiff_t;

	mixin template MetaclassTrait() {
		import std.meta : Alias;

		alias Parent = Alias!(__traits(parent, typeof(this)));

		// extern (C) pragma(mangle, "objc_lookUpClass")
		//	 static typeof(this) objc_lookUpClass(const(char)* name);

		extern (D) private static Class classof() {
			enum name = __traits(identifier, Parent);

			auto cls = cast(typeof(this)) objc_lookUpClass(name);
			assert(cls, "Failed to lookup class: " ~ name);

			return cls;
		}
	}

	mixin template ClassTrait() {
		extern(Objective-C) interface Class {
			mixin MetaclassTrait;

			Parent alloc() @selector("alloc");
		}

		extern (D) private static Class classof() {
			return Class.classof;
		}

		extern (D) static typeof(this) alloc() {
			return classof.alloc();
		}
	}

	alias objc_ivar* Ivar;
	alias objc_method* Method;
	alias objc_object Protocol;

	alias char* SEL;
	alias objc_class* Class;
	alias objc_object* id;

	alias extern (C) id function(id, SEL, ...) IMP;

	version (X86)
		const int STRUCT_SIZE_LIMIT = 8;
	else version (PPC)
		const int STRUCT_SIZE_LIMIT = 4;
	else version (X86_64)
		const int STRUCT_SIZE_LIMIT = 16;
	else version (PPC64)
		const int STRUCT_SIZE_LIMIT = 16;

	struct objc_object {
		Class isa;
	}

	struct objc_super {
		id receiver;
		Class clazz;

		// for dwt compatibility
		alias clazz cls;
		alias clazz super_class;
	}

	struct objc_class {
		Class isa;
		Class super_class;
		const char* name;
		int versionn;
		int info;
		int instance_size;
		objc_ivar_list* ivars;
		objc_method_list** methodLists;
		objc_cache* cache;
		objc_protocol_list* protocols;
	}

	struct objc_ivar {
		const(char)* ivar_name;
		const(char)* ivar_type;
		int ivar_offset;

		version (X86_64)
		int space;
	}

	struct objc_ivar_list {
		int ivar_count;

		version (X86_64)
		int space;

		/* variable length structure */
		objc_ivar[1] ivar_list;
	}

	struct objc_method {
		SEL method_name;
		const(char)* method_types;
		IMP method_imp;
	}

	struct objc_method_list {
		objc_method_list* obsolete;

		int method_count;

		version (X86_64)
		int space;

		/* variable length structure */
		objc_method[1] method_list;
	}

	struct objc_cache {
		uint mask /* total = mask + 1 */;
		uint occupied;
		Method[1] buckets;
	}

	struct objc_protocol_list {
		objc_protocol_list* next;
		long count;
		Protocol*[1] list;
	}

	extern (Objective-C)
	interface CALayer {
		CGFloat borderWidth() @selector("borderWidth");
		void borderWidth(CGFloat value) @selector("setBorderWidth:");

		CGColorRef borderColor() @selector("borderColor");
		void borderColor(CGColorRef) @selector("setBorderColor:");
	}

	extern (C) {
		bool class_addIvar (Class cls, const(char)* name, size_t size, byte alignment, const(char)* types);
		bool class_addMethod (Class cls, SEL name, IMP imp, const(char)* types);
		bool class_addProtocol(Class cls, Protocol* protocol);
		IMP class_getMethodImplementation(Class cls, SEL name);
		const(char)* class_getName(Class cls);
		Class objc_allocateClassPair (Class superclass, const(char)* name, size_t extraBytes);
		Class objc_getClass (const(char)* name);
		Protocol* objc_getProtocol(const(char)* name);
		Class objc_lookUpClass (const(char)* name);
		void objc_registerClassPair (Class cls);
		Class object_getClass (id object);
		const(char)* object_getClassName (id obj);
		Class object_setClass (id object, Class cls);
		Ivar object_getInstanceVariable (id obj, const(char)* name, void** outValue);
		Ivar object_setInstanceVariable (id obj, const(char)* name, void* value);
		SEL sel_registerName (const(char)* str);
		// id objc_msgSend (id theReceiver, SEL theSelector, ...);
		void objc_msgSend_stret(void* stretAddr, id theReceiver, SEL theSelector, ...);
		id objc_msgSendSuper (objc_super* superr, SEL op, ...);
		Method class_getClassMethod (Class aClass, SEL aSelector);
		Method class_getInstanceMethod (Class aClass, SEL aSelector);
		Class class_getSuperclass (Class cls);
		IMP method_setImplementation (Method method, IMP imp);
		id class_createInstance (Class cls, size_t extraBytes);
		id objc_getMetaClass (char* name);
		void objc_msgSendSuper_stret(void* stretAddr, objc_super* superContext, SEL theSelector, ...);

		void instrumentObjcMessageSends(bool val);

		version (X86)
			double objc_msgSend_fpret(id self, SEL op, ...);
	}
}

-----
December 21, 2017
On Thursday, 21 December 2017 at 15:45:32 UTC, Adam D. Ruppe wrote:
> oh sorry i forgot to post this sooner here's my code so far.
>
> when i'm reasonably happy with it, it will be part of my simpledisplay.d. I might leave it undocumented, but if you wanted to dive into my source the final version will be in there somewhere.

Thanks for sharing! Your solution is more complete for sure. I think I will borrow a few ideas here :)

February 05, 2018
On Thursday, 21 December 2017 at 17:56:54 UTC, mrphobby wrote:
>
> Thanks for sharing! Your solution is more complete for sure. I think I will borrow a few ideas here :)

I've been playing around with this a bit and it works pretty well. One thing that bothers me is the handling of NSString. I came up with the following class to handle strings and make sure they do not leak. But maybe there is a nicer way to handle this in D?

class NSStringRef {
public:
    this(string s) {
        str_ = NSString.alloc.initWithBytes(cast(immutable(ubyte)*) s.ptr,
                                            s.length,
                                            NSStringEncoding.NSUTF8StringEncoding);
    }

    ~this() {
        str_.release();
    }

    NSString str() @property {
        return str_;
    }

private:
    NSString str_;
}

And then you have to use it like this:

    NSStringRef s = new NSStringRef("Hello");
    NSLog(s.str);

Ideally I would like to write code more like this (without leaking the NSString):

    NSLog("Hello".nsstring);

Surely this must be possible in D somehow? :)
February 05, 2018
On 2018-02-05 16:49, mrphobby wrote:

> I've been playing around with this a bit and it works pretty well. One thing that bothers me is the handling of NSString. I came up with the following class to handle strings and make sure they do not leak. But maybe there is a nicer way to handle this in D?

Note that this applies to all classes, not just NSString.

> class NSStringRef {
> public:
>      this(string s) {
>          str_ = NSString.alloc.initWithBytes(cast(immutable(ubyte)*) s.ptr,
>                                              s.length,
> NSStringEncoding.NSUTF8StringEncoding);
>      }
> 
>      ~this() {
>          str_.release();
>      }

Note that this is not deterministic. There's not even a guarantee that a destructor for a class will be run at all.

>      NSString str() @property {
>          return str_;
>      }
> 
> private:
>      NSString str_;
> }
> 
> And then you have to use it like this:
> 
>      NSStringRef s = new NSStringRef("Hello");
>      NSLog(s.str);

You can add an "alias this" [1] to avoid calling "str" explicitly.

> Ideally I would like to write code more like this (without leaking the NSString):
> 
>      NSLog("Hello".nsstring);
> 
> Surely this must be possible in D somehow? :)

Currently the only correct way would be to wrap the class in a struct. There as been some talk to extend the language to support for reference counted classes [2].

[1] https://dlang.org/spec/class.html#alias-this
[2] https://wiki.dlang.org/DIP74

-- 
/Jacob Carlborg