Mercurial > projects > dstep
view dstep/objc/bridge/Bridge.d @ 27:57371c29ef73 default tip
ObjcWrap is now automatically mixed in. Added support for building as a dylib with DMD.
author | Jacob Carlborg <doob@me.com> |
---|---|
date | Fri, 09 Apr 2010 23:00:22 +0200 |
parents | b9de51448c6b |
children |
line wrap: on
line source
/** * Copyright: Copyright (c) 2009 Jacob Carlborg. * Authors: Jacob Carlborg * Version: Initial created: Feb 4, 2009 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0) */ module dstep.objc.bridge.Bridge; version (Tango) { import tango.core.Memory; import tango.core.Traits : ParameterTupleOf, ReturnTypeOf; } else { import GC = std.gc : addRoot; import std.traits : ParameterTypeTuple, ReturnType; alias ReturnType ReturnTypeOf; alias ParameterTypeTuple ParameterTupleOf; } import dstep.internal.String; import dstep.internal.Version; import dstep.objc.bridge.Capsule; import dstep.objc.bridge.ClassInitializer; import dstep.objc.bridge.Type; import dstep.objc.bridge.TypeEncoding; import dstep.objc.bridge.Wrapper; import dstep.objc.message; import dstep.objc.objc; import dstep.objc.runtime; /** * Builds a string representing a selector out of the given method * * It will build the string using the parameter names as a part of the selector, * like this: * * Examples: * --- * foo (int x, int y); * bar (); * fooBar (int x); * * static assert(selectorAsString!(foo) == "foo:y:"); * static assert(selectorAsString!(bar) == "bar"); * static assert(selectorAsString!(fooBar) == "fooBar:"); * --- * * Params: * method = the method alias to build the selector of * * Returns: a string representing the selector */ template selectorAsString (alias method) { const selectorAsString = buildSelector!(method); } /** * Registers a method with the Objective-C runtime system, * maps the method name to a selector, and returns the selector value. * * You must register a method name with the Objective-C runtime system to obtain * the method’s selector before you can add the method to a class definition. * If the method name has already been registered, this function simply returns * the selector. * * Examples: * --- * SEL sel = selector!("foo:"); * --- * * Params: * str = the string to register * * Returns: a pointer of type SEL specifying the selector for the named method. */ SEL selector (string str) () { return sel.registerName!(str); } /** * Registers a method with the Objective-C runtime system, * maps the method name to a selector, and returns the selector value. * * Using selectorAsString to get the string representation of the selector. * * You must register a method name with the Objective-C runtime system to obtain * the method’s selector before you can add the method to a class definition. * If the method name has already been registered, this function simply returns * the selector. * * Examples: * --- * foo (int x); * SEL sel = selector!(foo); * --- * * Params: * method = the method to register * * Returns: a pointer of type SEL specifying the selector for the named method. */ SEL selector (alias method) () { return sel.registerName!(selectorAsString!(method)); } /** * All Objective-C wrappers should mix in this template. * * Mixes in: $(D_PSYMBOL dstep.objc.bridge.ClassInitializer.ObjcSubclassInitializer) * * * Examples: * --- * class AppController : NSObject * { * mixin ObjcWrap; * } * --- */ template ObjcWrap () { /// This variable represents the Objective-C class. static private dstep.objc.objc.Class __objcClass; /// This variable represents the Objective-C super class. static private dstep.objc.objc.Class __objcSuperClass; /** * Allocates a new instance of the receiver. * * Returns: a new instance of the receiver */ static typeof(this) alloc () { return invokeObjcSelfClass!(typeof(this), "alloc"); } mixin dstep.objc.bridge.ClassInitializer.ObjcSubclassInitializer!(this.stringof, super.stringof); } /** * All Objective-C wrappers should mix in this string. * * Mixes in: $(D_PSYMBOL dstep.objc.bridge.ClassInitializer.ObjcSubclassInitializer) * * Examples: * --- * class NSString : NSObject * { * mixin ObjcClusterWrap; * } * --- */ template ObjcClusterWrap () { /// This variable represents the Objective-C class. static private dstep.objc.objc.Class __objcClass; /// This variable represents the Objective-C super class. static private dstep.objc.objc.Class __objcSuperClass; /** * Allocates a new instance of the receiver. * * Returns: a new instance of the receiver */ static typeof(this) alloc () { return invokeObjcSuperClass!(typeof(this), "alloc"); } mixin dstep.objc.bridge.ClassInitializer.ObjcSubclassInitializer!(this.stringof, super.stringof); } /** * Makes the given field available as an IBOutlet. * * Mixes in a method that is called by the Objective-C side to set the value of the * given field. * * Mixes in: $(D_PSYMBOL ObjcBindMethod) * * Examples: * --- * class AppController : NSObject * { * NSButton button; * mixin IBOutlet!(button); * } * --- * * Params: * field = the field make available as an IBOutlet */ template IBOutlet (alias field) { static assert (is(typeof(field) : Object), dstep.objc.bridge.Bridge.buildIBOutletErrorMessage!(field)); /// Sets the field void __setMethod (typeof(field) value) { field = value; } mixin dstep.objc.bridge.Bridge.ObjcBindMethod!(__setMethod, "set" ~ dstep.objc.bridge.Bridge.toUpper(field.stringof[0]) ~ field.stringof[1 .. $] ~ ":"); } char toUpper (char c) { if (c >= 'a' && c <= 'z') return c - 32; return c; } template buildIBOutletErrorMessage (alias field) { const buildIBOutletErrorMessage = `The type "` ~ typeof(field).stringof ~ `" of the given field "` ~ field.stringof ~ `" in the class "` ~ typeof(this).stringof ~ `" is not a valid IBOutlet type. IBOutlets can only be of the type Object (or any of its subclasses)`; } /** * Binds a selector to an instance method. * * This will create a receiver function which will forward the call to $(D_PARAM method), * decapsulating arguments and encapsulating the return value as appropriate. * This $(D_KEYWORD template) will use the buildSelector $(D_KEYWORD template) to build * the selector. It will automatically infer the return type and the argument types * of the method. An action method can only have one parameter and of the type Object * (or any of its subclasses). * * Mixes in: ObjcBindMethod * * Examples: * --- * class AppController : NSObject * { * void foo (Object sender) {} * mixin IBAction!(foo); * } * --- * * Params: * method = the method to bind */ template IBAction (alias method) { static assert (dstep.objc.bridge.Bridge.ParameterTupleOf!(method).length == 1, "An action method is only allowed to have one parameter"); static assert (is(dstep.objc.bridge.Bridge.ParameterTupleOf!(method)[0] : Object), "An action method can only have a parameter of the type Object (or any of its subclasses)"); mixin ObjcBindMethod!(method, dstep.objc.bridge.TypeEncoding.buildSelector!(method)); } /** * Binds a selector to an instance method. * * This will create a receiver function which will forward the call to $(D_PARAM method), * decapsulating arguments and encapsulating the return value as appropriate. * This $(D_KEYWORD template) will use the buildSelector $(D_KEYWORD template) to build * the selector. It will automatically infer the return type and the argument types * of the method. * * Mixes in: ObjcBindMethod * * Examples: * --- * class AppController : NSObject * { * void foo () {} * mixin ObjcBindMethod!(foo); * } * --- * * Params: * method = the method to bind */ template ObjcBindMethod (alias method) { mixin ObjcBindMethod!(method, dstep.objc.bridge.TypeEncoding.buildSelector!(method)); } /** * Binds a selector to an instance method. * * This will create a receiver function which will forward the call to $(D_PARAM method), * decapsulating arguments and encapsulating the return value as appropriate. * It will automatically infer the return type and the argument types * of the method. * * Mixes in: ObjcBindMethod * * Examples: * --- * class AppController : NSObject * { * void foo () {} * mixin ObjcBindMethod!(foo, "foo"); * } * --- * * Params: * method = the method to bind * selector = the selector to bind the method to */ template ObjcBindMethod (alias method, string selector) { mixin ObjcBindMethod!(method, dstep.objc.bridge.Bridge.ReturnTypeOf!(method), selector, dstep.objc.bridge.Bridge.ParameterTupleOf!(method)); } /** * Binds a selector to an instance method. * * This will create a receiver method which will forward the call to $(D_PARAM method), * decapsulating arguments and encapsulating the return value as appropriate. * * Mixes in: ObjcWrap * * Examples: * --- * class AppController : NSObject * { * int foo (int x) * { * return x; * } * * mixin ObjcBindMethod!(foo, int, "foo:", int); * } * --- * * Params: * method = the method to bind * R = the return type of the method * selector = the selector to bind the method to * ARGS = the argument types of the method */ template ObjcBindMethod (alias method, R, string selector, ARGS...) { private { /** * Resolves the virtual call * * Returns: a $(D_KEYWORD delegate) to the binded method */ R delegate (ARGS) __resolveVirtualCall () { return &method; } /// A type tuple with all the encapsulated types alias dstep.objc.bridge.Type.ObjcTypes!(ARGS) __ObjcArgs; /** * The receiver method, this will be the method called from the Objective-C side * * Params: * self = the Objective-C instance to call the method on * cmd = the Objective-C selector representing the method to call * objcArgs = the encapsulated arguments to the binded method * * Returns: whatever the binded method returns, encapsulated */ extern (C) static dstep.objc.bridge.Type.ObjcType!(R) __forwardVirtualCall (dstep.objc.objc.id self, dstep.objc.objc.SEL cmd, __ObjcArgs objcArgs) in { assert(dstep.objc.bridge.Capsule.isCapsule(self)); } body { R delegate (ARGS) delegate () dg; dg.ptr = cast(void*) dstep.objc.bridge.Capsule.decapsule!(typeof(this))(self); dg.funcptr = &__resolveVirtualCall; ARGS args; foreach (i, a ; objcArgs) { alias typeof(args[i]) ArgType; args[i] = dstep.objc.bridge.Capsule.decapsule!(ArgType)(a); } static if (is(R == void)) dg()(args); else return dstep.objc.bridge.Capsule.encapsule!(R)(dg()(args)); } /// The Objective-C method declaration for the binded method ObjcMethodDeclaration!(__forwardVirtualCall, R, selector, ARGS) __objcMethodDeclaration; static if (!is(typeof(this.__objcClass))) mixin ObjcWrap; } } /** * Binds a selector to a class (static) method. * * This will create a receiver function which will forward the call to $(D_PARAM method), * decapsulating arguments and encapsulating the return value as appropriate. * This $(D_KEYWORD template) will use the buildSelector $(D_KEYWORD template) * to build the selector. It will automatically infer the return type and the * argument types of the method. * * Mixes in: $(D_PSYMBOL ObjcBindClassMethod) * * Examples: * --- * class AppController : NSObject * { * static void foo () {} * mixin ObjcBindClassMethod!(foo); * } * --- * * Params: * method = the method to bind */ template ObjcBindClassMethod (alias method) { mixin ObjcBindClassMethod!(method, dstep.objc.bridge.TypeEncoding.buildSelector!(method)); } /** * Binds a selector to a class (static) method. * * This will create a receiver function which will forward the call to $(D_PARAM method), * decapsulating arguments and encapsulating the return value as appropriate. * It will automatically infer the return type and the argument types * of the method. * * Mixes in: $(D_PSYMBOL ObjcBindClassMethod) * * Examples: * --- * class AppController : NSObject * { * static void foo () {} * mixin ObjcBindClassMethod!(foo, "foo"); * } * --- * * Params: * method = the method to bind * selector = the selector to bind the method to */ template ObjcBindClassMethod (alias method, string selector) { mixin ObjcBindClassMethod!(method, dstep.objc.bridge.Bridge.ReturnTypeOf!(method), selector, dstep.objc.bridge.Bridge.ParameterTupleOf!(method)); } /** * Binds a selector to a class (static) method. * * This will create a receiver method which will forward the call to $(D_PARAM method), * decapsulating arguments and encapsulating the return value as appropriate. * * Examples: * --- * class AppController : NSObject * { * static int foo (int x) * { * return x; * } * * mixin ObjcBindClassMethod!(foo, int, "foo:", int); * } * --- * * Params: * method = the method to bind * R = the return type of the method * selector = the selector to bind the method to * ARGS = the argument types of the method */ template ObjcBindClassMethod (alias method, R, string selector, ARGS...) { private { /// A type tuple with all the encapsulated types alias dstep.objc.bridge.Type.ObjcTypes!(ARGS) __ObjcArgs; /** * The receiver method, this will be the method called from the Objective-C side * * Params: * objcArgs = the encapsulated arguments to the binded method * * Returns: whatever the binded method returns, encapsulated */ extern (C) static dstep.objc.bridge.Type.ObjcType!(R) __forwardStaticCall (dstep.objc.objc.Class self, dstep.objc.objc.SEL cmd, __ObjcArgs objcArgs) in { assert(dstep.objc.bridge.Capsule.isCapsule(self)); } body { R function (ARGS) funcPtr = &method; ARGS args; foreach (i, a ; objcArgs) { alias typeof(args[i]) ArgType; args[i] = dstep.objc.bridge.Capsule.decapsule!(ArgType)(a); } static if (is(R == void)) funcPtr()(args); else return dstep.objc.bridge.Capsule.encapsule!(R)(funcPtr()(args)); } /// The Objective-C method declaration for the binded method ObjcMethodDeclaration!(__forwardStaticCall, R, selector, ARGS) __objcClassMethodDeclaration; static if (is(typeof(this.__objcClass))) mixin ObjcWrap; } } /** * This $(D_KEYWORD struct) represents an Objective-C method declaration. * * Examples: * --- * class C : NSObject * { * void foo (int x) {} * ObjcMethodDeclaration!(foo, void, "foo", int) objcMethodDecl; * } * --- * * Params: * imp = the D method * R = the return type of the method * name = the name of the method * ARGS = the argument types of the method */ struct ObjcMethodDeclaration (alias imp, R, string name, ARGS...) { dstep.objc.objc.IMP methodImp = cast(dstep.objc.objc.IMP) &imp; alias R returnType; const string methodName = name; alias ARGS argsType; } /** * Binds a D free function to an Objective-C free function. * * This will create a receiver function which will forward the call to the * binded function, decapsulating arguments and encapsulating the return value * as appropriate. * * Mixes in: $(D_PSYMBOL ObjcBindFunction) * * Examples: * --- * void foo (); * mixin ObjcBindFunction!(foo); * --- */ template ObjcBindFunction (alias func) { mixin ObjcBindFunction!(func, dstep.objc.bridge.Bridge.ReturnTypeOf!(func), dstep.objc.bridge.Bridge.ParameterTupleOf!(func)); } /** * Binds a D free function to an Objective-C free function. * * This will create a receiver function which will forward the call to the * binded function, decapsulating arguments and encapsulating the return value * as appropriate. * * Examples: * --- * char foo (int); * mixin ObjcBindFunction!(foo, char, int); * --- * * Params: * func = the function to bind * R = the return type of the function * ARGS = the argument types of the function */ template ObjcBindFunction (alias func, R, ARGS...) { private { /// A type tuple with all the encapsulated types alias dstep.objc.bridge.Type.ObjcTypes!(ARGS) __ObjcArgs; /** * The receiver function, this will be the function called from the Objective-C side * * Params: * objcArgs = the encapsulated arguments to the binded function * * Returns: whatever the binded function returns, encapsulated */ extern (C) dstep.internal.Types.ObjcType!(R) __forwardFunctionCall (__ObjcArgs objcArgs) { R function (ARGS) funcPtr = &func; ARGS args; foreach (i, a ; objcArgs) { alias typeof(args[i]) ArgType; args[i] = dstep.objc.bridge.Capsule.decapsule!(ArgType)(a); } static if (is(R == void)) funcPtr()(args); else return dstep.objc.bridge.Capsule.encapsule!(R)(funcPtr()(args)); } } } /// This $(D_KEYWORD class) acts like a name space for various methods and functions class Bridge { private static Bridge bridgeInstance; /// The name of the method declaration variable mixed in in a $(D_KEYWORD class) const objcMethodDeclarationVar = "__objcMethodDeclaration"; /// The name of the class method declaration variable mixed in in a $(D_KEYWORD class) const objcClassMethodDeclarationVar = "__objcClassMethodDeclaration"; /// The name of the variable used on the Objective-C side to store the D object const dObjectVar = "dObject"; /// This alias is used as an internal representation of a D object //alias Object DObjectType; alias void* DObjectType; /** * Gets the only instance of this class * * Returns: the instance */ static Bridge instance () { if (bridgeInstance) return bridgeInstance; return bridgeInstance = new Bridge; } static: /** * Gets the value of an Objective-C instance variable * * Examples: * --- * id self; * int x = getObjcIvar!(int, "x")(self); * --- * * Params: * T = the type of the instance variable * name = the name of the instance variable * self = the Objective-C instance * * Returns: the value of the Objective-C variable */ T getObjcIvar (T, string name) (id self) { T value; self.getInstanceVariable!(T, name)(value); return value; } /** * Sets the value of an Objective-C instance variable * * Examples: * --- * id self; * Bridge.setObjcIvar!(int, "x")(self, 3); * --- * * Params: * T = the type of the instance variable * name = the name of the instance * objcObject = the Objective-C instance * value = the value to set */ void setObjcIvar (T, string name) (id self, T value) { GC.addRoot(value); self.setInstanceVariable!(T, dObjectVar)(value); } /** * Gets the D object stored in the given Objective-C instance * * Examples: * --- * NSObject object = new NSObject; * id self = object.objcObject; * assert(object == Bridge.getDObject(self)); * --- * * Params: * self = the Objective-C instance * * Returns: the D object or null */ package DObjectType getDObject (id self) { return getObjcIvar!(DObjectType, dObjectVar)(self); } /** * Stores the given D object in the given Objective-C instance * * Examples: * --- * NSObject object = new NSObject; * id self = object.objcObject; * Bridge.setDObject(object, self); * --- * * Params: * dObject = the D object to store * objcObject = the Objective-C instance to store the D object in * * Returns: the Objective-C instance */ package id setDObject (Object dObject, id objcObject) { auto o = cast(DObjectType) dObject; setObjcIvar!(DObjectType, dObjectVar)(objcObject, o); return objcObject; } /** * Deregisters the given Objective-C instance from the bridge * * Params: * objcInstance = the Objective-C instance to deregister */ package void deregisterObjcInstance (id objcInstance) { GC.removeRoot(getObjcIvar!(DObjectType, dObjectVar)(objcInstance)); } /** * This method wraps the family of $(D_PSYMBOL objc_msgSend) methods which is used to send a message * to an instance of a class. * * This method chooses the appropriate $(D_PSYMBOL objc_msgSend) method depending on the return value, * decapsulating arguments and encapsulating the return value as appropriate. * * Params: * R = the return type * selector = the selector to call * ARGS = the argument types * self = the reciver of the call * args = the arguments to the method * * Returns: whatever the method returns, encapsulaed */ R invokeObjcMethod (R, string selector, ARGS...) (id self, ARGS args) { static assert (checkSelector!(selector, ARGS), "The selector \"" ~ selector ~ "\" and the arguments " ~ ARGS.stringof ~ " do not match"); SEL sel = sel.registerName!(selector); ObjcTypes!(ARGS) objcArgs; foreach (i, a ; args) { alias typeof(a) ArgType; objcArgs[i] = encapsule!(ArgType)(a); } static if (is(R == struct)) { R result; self.msgSend_stret(result, sel, objcArgs); return result; } else static if (is(R == float) || is(R == double) || is(R == real)) { version (X86) return self.msgSend_fpret!(R, ObjcTypes!(ARGS))(sel, objcArgs); else version (X86_64) { static if (is(R == real)) return self.msgSend_fpret!(R)(sel, objcArgs); else return self.msgSend!(R)(sel, objcArgs); } else return self.msgSend!(R)(sel, objcArgs); } else static if (needsEncapsulation!(R)) return decapsule!(R)(self.msgSend(sel, objcArgs)); else return self.msgSend!(R, ObjcTypes!(ARGS))(sel, objcArgs); } /** * This method wraps the family of $(D_PSYMBOL objc_msgSend) methods which is used to send a message * to a class. * * This method chooses the appropriate $(D_PSYMBOL objc_msgSend) method depending on the return value, * decapsulating arguments and encapsulating the return value as appropriate. * * Params: * R = the return type * selector = the selector to call * ARGS = the argument types * cls = the reciver of the call * args = the arguments to the method * * Returns: whatever the method returns, encapsulaed */ R invokeObjcClassMethod (R, string selector, ARGS...) (Class cls, ARGS args) { return invokeObjcMethod!(R, selector, ARGS)(cast(id) cls, args); } /** * This method wraps the family of $(D_PSYMBOL objc_msgSendSuper) methods which is used to send a message * to an instance of a superclass. * * This method chooses the appropriate $(D_PSYMBOL objc_msgSendSuper) method depending on the return value, * decapsulating arguments and encapsulating the return value as appropriate. * * Params: * R = the return type * selector = the selector to call * ARGS = the argument types * self = the reciver of the call * args = the arguments to the method * * Returns: whatever the method returns, encapsulaed */ R invokeObjcSuperMethod (R, string selector, ARGS...) (objc_super* self, ARGS args) { static assert (checkSelector!(selector, ARGS), "The selector \"" ~ selector ~ "\" and the arguments " ~ ARGS.stringof ~ " do not match"); SEL sel = sel.registerName!(selector); ObjcTypes!(ARGS) objcArgs; foreach (i, a ; args) { alias typeof(a) ArgType; objcArgs[i] = encapsule!(ArgType)(a); } static if (is(R == struct)) { R result; self.msgSendSuper_stret(result, sel, objcArgs); return result; } else static if (needsEncapsulation!(R)) return decapsule!(R)(self.msgSendSuper(sel, objcArgs)); else return self.msgSendSuper!(R, ObjcTypes!(ARGS))(sel, objcArgs); } /** * This method wraps a call to a regular C free function, decapsulating arguments and * encapsulating the return value as appropriate. * * Params: * R = the return type * func = the function to call * ARGS = the argument types * args = the arguments to the function * * Returns: whatever the method returns, encapsulaed */ R invokeObjcFunction (R, alias func, ARGS...) (ARGS args) { auto funcPtr = &func; // use a function pointer instead of the alias because the function may not be public. ObjcTypes!(ARGS) objcArgs; foreach (i, a ; args) { alias typeof(a) ArgType; objcArgs[i] = encapsule!(ArgType)(a); } static if (is(R == void)) funcPtr(objcArgs); else return decapsule!(R)(funcPtr(objcArgs)); } }