2015年1月14日

js-ctypes で Objective-C を使う (6) - ES6 Proxy と ES6 Symbol を使う

前回の記事の時点では Proxy が Firefox 固有の古い方だったので、その辺を書き直したり、objc_msgSend_stret を使うかどうかを自動で判断するようにしたり、返り値の取得方法を Symbol 使ってもうちょっとマシにしたり、あと何か適当に色んなヘッダから型情報とか関数とか引っぱり出したり。

2015/01/15 修正: class_addMethodtypes に C の第一、第二引数を追加。いくつかの関数のエラーを報告、バックエンドのクラスをコアとラッパーに分割。

// objc.jsm

"use strict";

(function(module) {

let { utils: Cu } = Components;
let { ctypes } = Cu.import("resource://gre/modules/ctypes.jsm", {});

/**
 * Types related to Objective-C.
 * @type {Object}
 */
let objcTypes = (function() {
  let types = {};

  let LP64 = (ctypes.voidptr_t.size == 8);

  /* Types in objc.h. */
  types.objc_object = ctypes.StructType("objc_object", [
    { "isa": ctypes.voidptr_t } // Class isa;
  ]);
  types.id = types.objc_object.ptr;

  types.objc_selector = ctypes.StructType("objc_selector");
  types.SEL = types.objc_selector.ptr;

  types.IMP = ctypes.FunctionType(ctypes.default_abi,
                                  types.id,
                                  [types.id,
                                   types.SEL,
                                   "..."]).ptr;

  types.BOOL = ctypes.signed_char;
  types.arith_t = LP64 ? ctypes.long : ctypes.int;
  types.uarith_t = LP64 ? ctypes.unsigned_long : ctypes.unsigned_int;
  types.STR = ctypes.char.ptr;

  /* Types in runtime.h. */
  types.objc_method = ctypes.StructType("objc_method", []);
  types.Method = types.objc_method.ptr;
  types.objc_ivar = ctypes.StructType("objc_ivar", []);
  types.Ivar = types.objc_ivar.ptr;
  types.objc_category = ctypes.StructType("objc_category", []);
  types.Category = types.objc_category.ptr;
  types.objc_property = ctypes.StructType("objc_property", []);
  types.objc_property_t = types.objc_property.ptr;

  types.objc_class = types.objc_object;
  types.Class = types.objc_class.ptr;

  types.Protocol = types.objc_object;

  types.objc_method_description
    = ctypes.StructType("objc_method_description", [
      { "name":  types.SEL },
      { "types": ctypes.char.ptr },
    ]);
  types.objc_property_attribute_t
    = ctypes.StructType("objc_property_attribute_t", [
      { "name":  ctypes.char.ptr },
      { "value": ctypes.char.ptr },
    ]);

  types.objc_AssociationPolicy = ctypes.uintptr_t;

  /* Types in message.h. */
  types.objc_super
    = ctypes.StructType("objc_super", [
      { "receiver":     types.id },
      { "super_class":  types.Class },
    ]);

  /* Some Foundation data types. */
  if (LP64) {
    types.CGFloat = ctypes.float64_t;
    types.NSInteger = ctypes.long;
    types.NSUInteger = ctypes.unsigned_long;
  } else {
    types.CGFloat = ctypes.float32_t;
    types.NSInteger = ctypes.int;
    types.NSUInteger = ctypes.unsigned_int;
  }
  types.NSTimeInterval = ctypes.double;

  types.NSPoint = ctypes.StructType("NSPoint",
                                    [ { "x": types.CGFloat },
                                      { "y": types.CGFloat } ]);
  types.NSPointPointer = types.NSPoint.ptr;
  types.CGPoint = types.NSPoint;

  types.NSSize = ctypes.StructType("NSSize",
                                   [ { "width":  types.CGFloat },
                                     { "height": types.CGFloat } ]);
  types.NSSizePointer = types.NSSize.ptr;
  types.CGSize = types.NSSize;

  types.NSRect = ctypes.StructType("NSRect",
                                   [ { "origin": types.NSPoint },
                                     { "size":  types.NSSize } ]);
  types.NSRectPointer = types.NSRect.ptr;
  types.CGRect = types.NSRect;

  types.CGVector = ctypes.StructType("CGVector",
                                   [ { "dx": types.NSPoint },
                                     { "dy":  types.NSSize } ]);

  types.NSRange = ctypes.StructType("NSRange",
                                    [ { "location" : types.NSUInteger },
                                      { "length"   : types.NSUInteger } ]);
  types.NSRangePointer = types.NSRange.ptr;

  return Object.freeze(types);
})();

/**
 * Backend implementation of Objective-C ctypes.
 */
let objcImpl = Object.seal({
  initialized: false,

  lib: null,
  funcs: null,

  /**
   * Open Objective-C library and load functions.
   */
  init: function() {
    if (this.initialized) {
      return;
    }

    this.lib = ctypes.open(ctypes.libraryName("objc"));
    this.funcs = this._generateFuncs(this.lib);

    this.initialized = true;
  },

  /**
   * Release library.
   */
  release: function() {
    if (this.initialized) {
      this.lib.close();
      this.initialized = false;
    }
  },

  /* ---- classes/types ---- */

  /**
   * Register an Objective-C class with the specified super class.
   *
   * @param   {objcTypes.id} superClassInstance
   *          An instance of the super class.
   * @param   {string} className
   *          A name of the sub class to register.
   * @returns {objcTypes.id}
   *          A pointer to the registered class.
   */
  registerClass: function(superClassInstance, className) {
    let Class = this.funcs.objc_allocateClassPair(superClassInstance,
                                                  className, 0);
    if (!Class) {
      throw "objc_allocateClassPair retuns null";
    }
    this.funcs.objc_registerClassPair(Class);

    return Class;
  },

  /* ---- message ---- */

  /**
   * Send a message to an instance of a class.
   *
   * @param   {function} msgSend
   *          objc_msgSend* function
   * @param   {objcTypes.id} self
   *          A pointer that points to the instance of the class that is
   *          to receive the message.
   * @param   {string} sel
   *          The selector of the method that handles the message.
   * @returns {*}
   *          The return value of the method.
   */
  send: function(msgSend, self, sel, args) {
    return msgSend(self, this.funcs.sel_registerName(sel),
                   ...args);
  },

  /**
   * Serialize selector parts.
   *
   * @param   {Array.<string>} sels
   *          An array contains selector parts.
   * @param   {Array.<*>} args
   *          An array contains arguments or argument types.
   * @returns {string}
   *          A selector name.
   */
  serializeSelector: function(sels, args) {
    let s = sels.join(":");
    if (args.length > 0) {
      s += ":";
    }
    return s;
  },

  /**
   * Encode CType to Objective-C type.
   *
   * @param   {CType} origType
   *          A type.
   * @returns {string}
   *          A string represents Objective-C type.
   */
  encodeType: function(origType) {
    let type = origType;
    let str = "";

    /* Pointer. */
    while ("targetType" in type &&
           type.targetType) {
      /* Special types. */
      if (type == objcTypes.id) {
        str += "@";
        return str;
      }
      if (type == objcTypes.Class) {
        str += "#";
        return str;
      }
      if (type == objcTypes.SEL) {
        str += ":";
        return str;
      }

      str += "^";
      type = type.targetType;
    }

    /* Function. */
    if ("abi" in type) {
      str += "?";
      return str;
    }

    /* Struct. */
    if ("fields" in type) {
      str += "{" + type.name + "=" + type.fields.map(field => {
        let name = Object.keys(field)[0];
        return this.encodeType(field[name]);
      }).join("") + "}";
      return str;
    }

    /* Array. */
    if ("elementType" in type &&
        "length" in type) {
      if (type.length) {
        str += "[" + type.length + this.encodeType(type.elementType) + "]";
      } else {
        str += "^" + this.encodeType(type.elementType);
      }
      return str;
    }

    /* Primitive types. */
    switch (type) {
      case ctypes.int8_t:              str += "c"; break;
      case ctypes.uint8_t:             str += "C"; break;
      case ctypes.int16_t:             str += "s"; break;
      case ctypes.uint16_t:            str += "S"; break;
      case ctypes.int32_t:             str += "i"; break;
      case ctypes.uint32_t:            str += "I"; break;
      case ctypes.int64_t:             str += "l"; break;
      case ctypes.uint64_t:            str += "L"; break;

      case ctypes.float32_t:           str += "f"; break;
      case ctypes.float64_t:           str += "d"; break;

      case ctypes.bool:                str += "B"; break;
      case ctypes.short:               str += "s"; break;
      case ctypes.unsigned_short:      str += "S"; break;
      case ctypes.int:                 str += "i"; break;
      case ctypes.unsigned_int:        str += "I"; break;
      case ctypes.long:                str += "l"; break;
      case ctypes.unsigned_long:       str += "L"; break;
      case ctypes.long_long:           str += "q"; break;
      case ctypes.unsigned_long_long:  str += "Q"; break;

      case ctypes.float:               str += "f"; break;
      case ctypes.double:              str += "d"; break;

      case ctypes.char:                str += "c"; break;
      case ctypes.signed_char:         str += "c"; break;
      case ctypes.unsigned_char:       str += "C"; break;

      case ctypes.size_t:              str += "L"; break;
      case ctypes.ssize_t:             str += "l"; break;
      case ctypes.intptr_t:            str += "l"; break;
      case ctypes.uintptr_t:           str += "L"; break;

      case ctypes.jschar:              str += "s"; break;

      case ctypes.void_t:              str += "v"; break;

      case ctypes.Int64:               str += "q"; break;
      case ctypes.UInt64:              str += "Q"; break;

      default: throw new Error("Unsupported type: " + origType);
    }

    return str;
  },

  /**
   * Encode CType to Objective-C type.
   *
   * @param   {CType} ret
   *          A return value type.
   * @param   {Array.<CType>} args
   *          An array of arguments types.
   * @returns {string}
   *          An string represents Objective-C type.
   */
  encodeMethod: function(ret, args) {
    return [ret, ...args].map(t => this.encodeType(t)).join("");
  },

  /* ---- function ---- */

  /**
   * Generate lazy functions object.
   *
   * @param   {Library} lib
   *          An objc library.
   * @returns {Object}
   *          A lazy functions object.
   */
  _generateFuncs: function(lib) {
    let types = objcTypes;

    let funcs = {};
    let funcProps = [
      /* Functions in objc.h. */
      ["sel_getName", ctypes.default_abi,
       ctypes.char.ptr, types.SEL],
      ["sel_registerName", ctypes.default_abi,
       types.SEL, ctypes.char.ptr],
      ["object_getClassName", ctypes.default_abi,
       ctypes.char.ptr, types.id],
      ["object_getIndexedIvars", ctypes.default_abi,
       ctypes.voidptr_t, types.id],
      ["sel_isMapped", ctypes.default_abi,
       types.BOOL, types.SEL],
      ["sel_getUid", ctypes.default_abi,
       types.SEL, ctypes.char.ptr],

      /* Functions in runtime.h. */
      ["object_copy", ctypes.default_abi,
       types.id, types.id, ctypes.size_t],
      ["object_dispose", ctypes.default_abi,
       types.id, types.id],
      ["object_getClass", ctypes.default_abi,
       types.Class, types.id],
      ["object_setClass", ctypes.default_abi,
       types.Class, types.id, types.Class],
      ["object_getClassName", ctypes.default_abi,
       ctypes.char.ptr, types.id],
      ["object_getIndexedIvars", ctypes.default_abi,
       ctypes.voidptr_t, types.id],
      ["object_getIvar", ctypes.default_abi,
       types.id, types.id, types.Ivar],
      ["object_setIvar", ctypes.default_abi,
       ctypes.void_t, types.id, types.Ivar, types.id],
      ["object_setInstanceVariable", ctypes.default_abi,
       types.Ivar, types.id, ctypes.char.ptr, ctypes.voidptr_t],
      ["object_getInstanceVariable", ctypes.default_abi,
       types.Ivar, types.id, ctypes.char.ptr, ctypes.voidptr_t.ptr],
      ["objc_getClass", ctypes.default_abi,
       types.Class, ctypes.char.ptr],
      ["objc_getMetaClass", ctypes.default_abi,
       types.Class, ctypes.char.ptr],
      ["objc_lookUpClass", ctypes.default_abi,
       types.Class, ctypes.char.ptr],
      ["objc_getRequiredClass", ctypes.default_abi,
       types.Class, ctypes.char.ptr],
      ["objc_getClassList", ctypes.default_abi,
       ctypes.int, types.Class.ptr, ctypes.int],
      ["objc_copyClassList", ctypes.default_abi,
       types.Class.ptr, ctypes.unsigned_int.ptr],
      ["class_getName", ctypes.default_abi,
       ctypes.char.ptr, types.Class],
      ["class_isMetaClass", ctypes.default_abi,
       types.BOOL, types.Class],
      ["class_getSuperclass", ctypes.default_abi,
       types.Class, types.Class],
      ["class_setSuperclass", ctypes.default_abi,
       types.Class, types.Class, types.Class],
      ["class_getVersion", ctypes.default_abi,
       ctypes.int, types.Class],
      ["class_setVersion", ctypes.default_abi,
       ctypes.void_t, types.Class, ctypes.int],
      ["class_getInstanceSize", ctypes.default_abi,
       ctypes.size_t, types.Class],
      ["class_getInstanceVariable", ctypes.default_abi,
       types.Ivar, types.Class, ctypes.char.ptr],
      ["class_getClassVariable", ctypes.default_abi,
       types.Ivar, types.Class, ctypes.char.ptr],
      ["class_copyIvarList", ctypes.default_abi,
       types.Ivar.ptr, types.Class, ctypes.unsigned_int.ptr],
      ["class_getInstanceMethod", ctypes.default_abi,
       types.Method, types.Class, types.SEL],
      ["class_getClassMethod", ctypes.default_abi,
       types.Method, types.Class, types.SEL],
      ["class_getMethodImplementation", ctypes.default_abi,
       types.IMP, types.Class, types.SEL],
      ["class_getMethodImplementation_stret", ctypes.default_abi,
       types.IMP, types.Class, types.SEL],
      ["class_respondsToSelector", ctypes.default_abi,
       types.BOOL, types.Class, types.SEL],
      ["class_copyMethodList", ctypes.default_abi,
       types.Method.ptr, types.Class, ctypes.unsigned_int.ptr],
      ["class_conformsToProtocol", ctypes.default_abi,
       types.BOOL, types.Class, types.Protocol.ptr],
      ["class_copyProtocolList", ctypes.default_abi,
       types.Protocol.ptr.ptr, types.Class, ctypes.unsigned_int.ptr],
      ["class_getProperty", ctypes.default_abi,
       types.objc_property_t, types.Class, ctypes.char.ptr],
      ["class_copyPropertyList", ctypes.default_abi,
       types.objc_property_t.ptr, types.Class, ctypes.unsigned_int.ptr],
      ["class_getIvarLayout", ctypes.default_abi,
       ctypes.uint8_t.ptr, types.Class],
      ["class_getWeakIvarLayout", ctypes.default_abi,
       ctypes.uint8_t.ptr, types.Class],
      ["class_addMethod", ctypes.default_abi,
       types.BOOL, types.Class, types.SEL, types.IMP, ctypes.char.ptr],
      ["class_replaceMethod", ctypes.default_abi,
       types.IMP, types.Class, types.SEL, types.IMP, ctypes.char.ptr],
      ["class_addIvar", ctypes.default_abi,
       types.BOOL, types.Class, ctypes.char.ptr, ctypes.size_t, ctypes.uint8_t, ctypes.char.ptr],
      ["class_addProtocol", ctypes.default_abi,
       types.BOOL, types.Class, types.Protocol.ptr],
      ["class_addProperty", ctypes.default_abi,
       types.BOOL, types.Class, ctypes.char.ptr, types.objc_property_attribute_t.ptr, ctypes.unsigned_int],
      ["class_replaceProperty", ctypes.default_abi,
       ctypes.void_t, types.Class, ctypes.char.ptr, types.objc_property_attribute_t.ptr, ctypes.unsigned_int],
      ["class_setIvarLayout", ctypes.default_abi,
       ctypes.void_t, types.Class, ctypes.uint8_t.ptr],
      ["class_setWeakIvarLayout", ctypes.default_abi,
       ctypes.void_t, types.Class, ctypes.uint8_t.ptr],
      ["objc_getFutureClass", ctypes.default_abi,
       types.Class, ctypes.char.ptr],
      ["objc_setFutureClass", ctypes.default_abi,
       ctypes.void_t, types.Class, ctypes.char.ptr],
      ["class_createInstance", ctypes.default_abi,
       types.id, types.Class, ctypes.size_t],
      ["objc_constructInstance", ctypes.default_abi,
       types.id, types.Class, ctypes.voidptr_t],
      ["objc_destructInstance", ctypes.default_abi,
       ctypes.voidptr_t, types.id],
      ["objc_allocateClassPair", ctypes.default_abi,
       types.Class, types.Class, ctypes.char.ptr, ctypes.size_t],
      ["objc_registerClassPair", ctypes.default_abi,
       ctypes.void_t, types.Class],
      ["objc_duplicateClass", ctypes.default_abi,
       types.Class, types.Class, ctypes.char.ptr, ctypes.size_t],
      ["objc_disposeClassPair", ctypes.default_abi,
       ctypes.void_t, types.Class],
      ["method_getName", ctypes.default_abi,
       types.SEL, types.Method],
      ["method_getImplementation", ctypes.default_abi,
       types.IMP, types.Method],
      ["method_getTypeEncoding", ctypes.default_abi,
       ctypes.char.ptr, types.Method],
      ["method_getNumberOfArguments", ctypes.default_abi,
       ctypes.unsigned_int, types.Method],
      ["method_copyReturnType", ctypes.default_abi,
       ctypes.char.ptr, types.Method],
      ["method_copyArgumentType", ctypes.default_abi,
       ctypes.char.ptr, types.Method, ctypes.unsigned_int],
      ["method_getReturnType", ctypes.default_abi,
       ctypes.void_t, types.Method, ctypes.char.ptr, ctypes.size_t],
      ["method_getArgumentType", ctypes.default_abi,
       ctypes.void_t, types.Method, ctypes.unsigned_int, ctypes.char.ptr, ctypes.size_t],
      ["method_getDescription", ctypes.default_abi,
       types.objc_method_description.ptr, types.Method],
      ["method_setImplementation", ctypes.default_abi,
       types.IMP, types.Method, types.IMP],
      ["method_exchangeImplementations", ctypes.default_abi,
       ctypes.void_t, types.Method, types.Method],
      ["ivar_getName", ctypes.default_abi,
       ctypes.char.ptr, types.Ivar],
      ["ivar_getTypeEncoding", ctypes.default_abi,
       ctypes.char.ptr, types.Ivar],
      //  ["ivar_getOffset", ctypes.default_abi,
      //   types.ptrdiff_t,  types.Ivar],
      ["property_getName", ctypes.default_abi,
       ctypes.char.ptr, types.objc_property_t],
      ["property_getAttributes", ctypes.default_abi,
       ctypes.char.ptr, types.objc_property_t],
      ["property_copyAttributeList", ctypes.default_abi,
       types.objc_property_attribute_t.ptr, types.objc_property_t, ctypes.unsigned_int.ptr],
      ["property_copyAttributeValue", ctypes.default_abi,
       ctypes.char.ptr, types.objc_property_t, ctypes.char.ptr],
      ["objc_getProtocol", ctypes.default_abi,
       types.Protocol.ptr, ctypes.char.ptr],
      ["objc_copyProtocolList", ctypes.default_abi,
       types.Protocol.ptr.ptr, ctypes.unsigned_int.ptr],
      ["protocol_conformsToProtocol", ctypes.default_abi,
       types.BOOL, types.Protocol.ptr, types.Protocol.ptr],
      ["protocol_isEqual", ctypes.default_abi,
       types.BOOL, types.Protocol.ptr, types.Protocol.ptr],
      ["protocol_getName", ctypes.default_abi,
       ctypes.char.ptr, types.Protocol.ptr],
      ["protocol_getMethodDescription", ctypes.default_abi,
       types.objc_method_description, types.Protocol.ptr, types.SEL, types.BOOL, types.BOOL],
      ["protocol_copyMethodDescriptionList", ctypes.default_abi,
       types.objc_method_description.ptr, types.Protocol.ptr, types.BOOL, types.BOOL, ctypes.unsigned_int.ptr],
      ["protocol_getProperty", ctypes.default_abi,
       types.objc_property_t, types.Protocol.ptr, ctypes.char.ptr, types.BOOL, types.BOOL],
      ["protocol_copyPropertyList", ctypes.default_abi,
       types.objc_property_t.ptr, types.Protocol.ptr, ctypes.unsigned_int.ptr],
      ["protocol_copyProtocolList", ctypes.default_abi,
       types.Protocol.ptr.ptr, types.Protocol.ptr, ctypes.unsigned_int.ptr],
      ["objc_allocateProtocol", ctypes.default_abi,
       types.Protocol.ptr, ctypes.char.ptr],
      ["objc_registerProtocol", ctypes.default_abi,
       ctypes.void_t, types.Protocol.ptr],
      ["protocol_addMethodDescription", ctypes.default_abi,
       ctypes.void_t, types.Protocol.ptr, types.SEL, ctypes.char.ptr, types.BOOL, types.BOOL],
      ["protocol_addProtocol", ctypes.default_abi,
       ctypes.void_t, types.Protocol.ptr, types.Protocol.ptr],
      ["protocol_addProperty", ctypes.default_abi,
       ctypes.void_t, types.Protocol.ptr, ctypes.char.ptr, types.objc_property_attribute_t.ptr, ctypes.unsigned_int, types.BOOL, types.BOOL],
      ["objc_copyImageNames", ctypes.default_abi,
       ctypes.char.ptr.ptr, ctypes.unsigned_int.ptr],
      ["class_getImageName", ctypes.default_abi,
       ctypes.char.ptr, types.Class],
      ["objc_copyClassNamesForImage", ctypes.default_abi,
       ctypes.char.ptr.ptr, ctypes.char.ptr, ctypes.unsigned_int.ptr],
      ["sel_getName", ctypes.default_abi,
       ctypes.char.ptr, types.SEL],
      ["sel_getUid", ctypes.default_abi,
       types.SEL, ctypes.char.ptr],
      ["sel_registerName", ctypes.default_abi,
       types.SEL, ctypes.char.ptr],
      ["sel_isEqual", ctypes.default_abi,
       types.BOOL, types.SEL, types.SEL],
      ["objc_enumerationMutation", ctypes.default_abi,
       ctypes.void_t, types.id],
      ["objc_setEnumerationMutationHandler", ctypes.default_abi,
       ctypes.void_t, ctypes.FunctionType(ctypes.default_abi, ctypes.void_t, [types.id]).ptr],
      ["objc_setForwardHandler", ctypes.default_abi,
       ctypes.void_t, ctypes.voidptr_t, ctypes.voidptr_t],
      ["imp_implementationWithBlock", ctypes.default_abi,
       types.IMP, types.id],
      ["imp_getBlock", ctypes.default_abi,
       types.id, types.IMP],
      ["imp_removeBlock", ctypes.default_abi,
       types.BOOL, types.IMP],
      ["objc_loadWeak", ctypes.default_abi,
       types.id, types.id.ptr],
      ["objc_storeWeak", ctypes.default_abi,
       types.id, types.id.ptr, types.id],
      ["objc_setAssociatedObject", ctypes.default_abi,
       ctypes.void_t, types.id, ctypes.voidptr_t, types.id, types.objc_AssociationPolicy],
      ["objc_getAssociatedObject", ctypes.default_abi,
       types.id, types.id, ctypes.voidptr_t],
      ["objc_removeAssociatedObjects", ctypes.default_abi,
       ctypes.void_t, types.id],
      ["class_lookupMethod", ctypes.default_abi,
       types.IMP, types.Class, types.SEL],
      ["class_respondsToMethod", ctypes.default_abi,
       types.BOOL, types.Class, types.SEL],
      ["_objc_flush_caches", ctypes.default_abi,
       ctypes.void_t, types.Class],
      ["object_copyFromZone", ctypes.default_abi,
       types.id, types.id, ctypes.size_t, ctypes.voidptr_t],

      ["class_createInstanceFromZone", ctypes.default_abi,
       types.id, types.Class, ctypes.size_t, ctypes.voidptr_t],

      /* Functions in message.h. */
      ["objc_msgSend", ctypes.default_abi,
       types.id, types.id, types.SEL, "..."],
      ["objc_msgSendSuper", ctypes.default_abi,
       types.id, types.objc_super.ptr, types.SEL, "..."],
      ["objc_msgSend_stret", ctypes.default_abi,
       ctypes.void_t, ctypes.voidptr_t, types.id, types.SEL, "..."],
      ["objc_msgSendSuper_stret", ctypes.default_abi,
       ctypes.void_t, ctypes.voidptr_t, types.objc_super.ptr, types.SEL, "..."],
      ["objc_msgSend_fpret", ctypes.default_abi,
       ctypes.double, types.id, types.SEL, "..."],

      ["method_invoke", ctypes.default_abi,
       types.id, types.id, types.Method, "..."],
      ["method_invoke_stret", ctypes.default_abi,
       ctypes.void_t, ctypes.voidptr_t, types.id, types.Method, "..."],

      ["_objc_msgForward", ctypes.default_abi,
       types.id, types.id, types.Method, "..."],
      ["_objc_msgForward_stret", ctypes.default_abi,
       ctypes.void_t, ctypes.voidptr_t, types.id, types.Method, "..."],
    ];
    for (let tmp of funcProps) {
      let name = tmp[0];
      let args = tmp;
      Object.defineProperty(funcs, name, {
        get: function () {
          delete funcs[name];
          return funcs[name] = lib.declare(...args);
        },
        configurable: true,
        enumerable: true
      });
    }

    return funcs;
  }
});

/**
 * Backend implementation of wrapping Objective-C data.
 */
let objcWrapper = Object.seal({
  initialized: false,

  get_unwrapped: null,
  as_unwrapped: null,
  as_float: null,
  ret_functions: null,

  /**
   * Initialize wrapper functions.
   */
  init: function() {
    if (objcWrapper.initialized) {
      return;
    }

    this.get_unwrapped = Symbol.for("objc_get_unwrapped");
    this.as_unwrapped = Symbol.for("objc_as_unwrapped");
    this.as_float = Symbol.for("objc_as_float");
    this.ret_functions = new Map();
    this.ret_functions.set(this.as_unwrapped, objcImpl.funcs.objc_msgSend);
    this.ret_functions.set(this.as_float, objcImpl.funcs.objc_msgSend_fpret);
  },


  /**
   * Release wrapper functions.
   */
  release: function() {
    if (this.initialized) {
      this.ret_functions.clear();
      this.initialized = false;
    }
  },

  /**
   * Register a msgSend* function for the specified return value type.
   *
   * @param   {CType} type
   *          A return value type.
   * @returns {Symbol}
   *          An unique symbol for the return value type.
   */
  registerFunctionForType: function(type) {
    if (type == ctypes.float || type == ctypes.double ||
        type == ctypes.float32_t || type == ctypes.float64_t) {
      return this.as_float;
    }

    let sym = Symbol.for("objc_as_" + type.toString);

    if (type.size > ctypes.voidptr_t.size * 2) {
      this.ret_functions.set(sym, function(...args) {
        let ret = type();
        objcImpl.funcs.objc_msgSend_stret(ret.address(), ...args);
        return ret;
      });
    } else {
      let func = objcImpl.lib.declare("objc_msgSend",
                                      ctypes.default_abi,
                                      type,
                                      objcTypes.id,
                                      objcTypes.SEL,
                                      "...");
      this.ret_functions.set(sym, func);
    }

    return sym;
  },

  /* ---- wrap ---- */

  /**
   * Wrap the specified raw Objective-C object into Proxy object.
   *
   * @param   {objcTypes.id} obj
   *          A pointer that points Objective-C class/instance.
   * @param   {Array} sels
   *          An array contains selector names.
   * @returns {Proxy.<function>}
   *          A wrapped Objective-C Proxy.
   */
  wrap: function(obj, sels=[]) {
    return new Proxy(
      /**
       * Send a message to an instance of a class.
       *
       * @param   {...} args
       *          A variable argument list containing the arguments to the
       *          method.
       * @returns {Proxy}
       *          A wrapped Objective-C Proxy of the return value of the method.
       */
      function(...args) {
        if (sels.length) {
          let name = sels[sels.length - 1];
          if (typeof name == "symbol") {
            sels.pop();

            if (!this.ret_functions.has(name)) {
              throw new Error("Unknown symbol: " + name);
            }

            let func = this.ret_functions.get(name);
            return objcImpl.send(func,
                                 obj, objcImpl.serializeSelector(sels, args),
                                 args);
          }
        }

        let ret = objcImpl.send(objcImpl.funcs.objc_msgSend,
                                obj, objcImpl.serializeSelector(sels, args),
                                args);
        return this.wrap(ret);
      }.bind(this), {
        /**
         * Define a method of a custom Class.
         *
         * @param   {*} _
         *          Not used.
         * @param   {string} sel
         *          A part of selector.
         * @param   {Object}
         *          Return value type, arguments types, and the function of the
         *          method.
         */
        set: function(_, sel, methodProps) {
          /* Due to the restriction of function Proxy, some property name cannot
           * be used (e.g. length), so use it with leading "____", which  will
           * be removed here. */
          if (typeof sel == "string") {
            sel = sel.replace(/____/, "");
          }
          let {ret, args, impl} = methodProps;
          let selAll = objcImpl.serializeSelector(sels.concat(sel), args);
          let argsTypeAll = [objcTypes.id, objcTypes.SEL, ...args];
          let funcType = ctypes.FunctionType(ctypes.default_abi,
                                             ret, argsTypeAll);
          if (!objcImpl.funcs.class_addMethod(obj,
                                              objcImpl.funcs.sel_registerName(selAll),
                                              ctypes.cast(funcType.ptr(impl), objcTypes.IMP),
                                              objcImpl.encodeMethod(ret, argsTypeAll))) {
            throw "class_addMethod returns NO";
          }
        }.bind(this),

        /**
         * Return a wrapped object for the specified method.
         *
         * @param   {*} _
         *          Not used.
         * @param   {string} sel
         *          A part of selector.
         * @returns {Proxy.<function>}
         *          A wrapped Objective-C Proxy for the method.
         */
        get: function(_, sel) {
          /* JavaScript runtime will sometimes call valueOf/toString,
           * and it will result in sending those unexistent message,
           * and cause crash. */
          if (sels.length == 0
              && (sel == "valueOf" ||
                  sel == "toString" ||
                  sel == "toSource")) {
            throw new Error(sel + " is called.");
          }

          if (typeof sel == "string") {
            sel = sel.replace(/____/, "");
          }

          if (sel == this.get_unwrapped) {
            return obj;
          }

          return this.wrap(obj, sels.concat(sel));
        }.bind(this)
      });
  }
});

/**
 * Frontend implementation of Objective-C ctypes.
 */
let objc = Object.freeze({
  /**
   * Open Objective-C library and load functions.
   */
  init: function() {
    objcImpl.init();
    objcWrapper.init();
  },

  /**
   * Release library.
   */
  release: function() {
    objcImpl.release();
    objcWrapper.release();
  },

  /* ---- classes/types ---- */

  /**
   * Type specifier.
   * @type {Proxy.<function>}
   *
   * @param   {CType} type
   *          A return value type.
   * @returns {Symbol}
   *          An unique symbol for the return value type.
   */
  as: new Proxy(function(type) {
    return objcWrapper.registerFunctionForType(type);
  }, {
    /**
     * Return an unique symbol for the specified simple type.
     *
     * @param   {*} _
     *          Not used.
     * @param   {string} name
     *          A property name of objcTypes or ctypes for the return value
     *          type.
     * @returns {Symbol}
     *          A unique symbol for the return value type.
     */
    get: function(_, name) {
      if (name == "unwrapped") {
        return objcWrapper.as_unwrapped;
      }

      let type = null;
      if (name in objcTypes) {
        type = objcTypes[name];
      } else if (name in ctypes) {
        type = ctypes[name];
      } else {
        throw new Error("unknown type: " + name);
      }

      return objcWrapper.registerFunctionForType(type);
    },
  }),

  /**
   * Proxy object to get an Objective-C Class.
   * @type {Proxy.<object>}
   */
  classes: new Proxy({}, {
    /**
     * Return an Objective-C class for the specified name.
     *
     * @param   {*} _
     *          Not used.
     * @param   {string} className
     *          A name of the Objective-C class.
     * @returns {Proxy.<function>}
     *          A wrapped Objective-C Proxy for the class.
     */
    get: function(_, className) {
      return objcWrapper.wrap(objcImpl.funcs.objc_getClass(className));
    },

    /**
     * Determine the existence of the Objective-C class with the specified name.
     *
     * @param   {*} _
     *          Not used.
     * @param   {string} className
     *          A name of the Objective-C class.
     * @returns {boolean}
     *          The existence of the class.
     */
    has: function(_, className) {
      return !objcImpl.funcs.objc_getClass(className).isNull();
    }
  }),

  /**
   * Proxy object to register a custom Objective-C Class.
   * @type {Proxy.<object>}
   */
  newClass: new Proxy({}, {
    /**
     * Return an Objective-C class for the specified name.
     *
     * @param   {*} _
     *          Not used.
     * @param   {string} className
     *          A name of the Objective-C class.
     * @returns {function}
     *          A function which takes a super class instance and
     *          returns a wrapped Objective-C Proxy for the class.
     */
    get: function(_, className) {
      /**
       * Return an Objective-C class for the specified name and superClass.
       *
       * @param   {objc.types.id} superClassInstance
       *          An instance of the super class.
       * @returns {function}
       *          A wrapped Objective-C Proxy for the class.
       */
      return function(superClassInstance) {
        return objcWrapper.wrap(objcImpl.registerClass(superClassInstance,
                                                       className));
      };
    }
  }),

  /**
   * Types related to Objective-C.
   * @type {Object}
   */
  types: objcTypes,

  /**
   * Register a selector.
   *
   * @param   {String} name
   *          A name of selector
   * @returns {objc.types.SEL}
   *          A selector
   */
  sel: function(name) {
    return objcImpl.funcs.sel_registerName(name);
  },

  /* ---- wrap ---- */

  /**
   * Wrap raw Objective-C object into Proxy object.
   *
   * @param   {objc.types.id} obj
   *          A pointer that points Objective-C class/instance.
   * @param   {Array} sels
   *          An array contains selector names.
   * @returns {Proxy}
   *          A wrapped Objective-C Proxy.
   */
  wrap: function(obj, sels=[]) {
    return objcWrapper.wrap(obj, sels);
  },

  /**
   * Unwrap Objective-C Proxy object to raw ctypes pointer.
   *
   * @param   {Proxy} obj
   *          Objective-C Proxy object.
   * @returns {objc.types.id}
   *          A pointer that points Objective-C class/instance.
   */
  unwrap: function(obj) {
    return obj[objcWrapper.get_unwrapped];
  },

  /* ---- misc ---- */

  /**
   * Call specified function in the temporary pool.
   *
   * @param   {function} f
   *          function to call in the pool
   * @returns {*}
   *          returned value from f
   */
  autoreleasepool: function(f) {
    let pool = this.classes.NSAutoreleasePool.
        alloc().
        init();
    try {
      return f();
    } finally {
      pool.release();
    }
  },

  /**
   * Make NSString instance
   *
   * @param   {string} str
   *          UTF8 encoded string
   * @returns {objc.types.id}
   *          NSString instance
   */
  NSString: function(str) {
    return this.classes.NSString.
      alloc().
      initWithUTF8String(ctypes.char.array()(str)).
      autorelease[this.as.unwrapped]();
  }
});

module.EXPORTED_SYMBOLS = ["objc"];
module.objc = objc;

})(this);

こちらがテストコード。

let { utils: Cu } = Components;
let { ctypes } = Cu.import("resource://gre/modules/ctypes.jsm", {});
let { objc } = Cu.import("path_to_objc.jsm", {});

objc.init();

// Call speech API.
objc.autoreleasepool(function() {
  let voice = "com.apple.speech.synthesis.voice.kyoko.premium";
  let synth = objc.classes.NSSpeechSynthesizer.
      alloc().
      initWithVoice(objc.NSString(voice)).
      autorelease();

  let NSUTF8StringEncoding = ctypes.int32_t(4);
  let text = objc.classes.NSString.
      alloc().
      initWithCString.encoding(ctypes.char.array()("a"),
                               NSUTF8StringEncoding).
      autorelease();

  synth.startSpeakingString(objc.unwrap(text));

  while (synth.isSpeaking[objc.as.int32_t]()) {}
});

// Create custom class and add methods, call them.
objc.autoreleasepool(function() {
  let TestJSClass = objc.newClass.TestJSClass(objc.unwrap(objc.classes.NSObject));
  TestJSClass.pi = {
    ret: ctypes.double,
    args: [],
    impl: function(self, sel) {
      return Math.PI;
    }
  };
  TestJSClass.twice = {
    ret: ctypes.int32_t,
    args: [ctypes.int32_t],
    impl: function(self, sel, a) {
      return a * 2;
    }
  };
  TestJSClass.divide.by = {
    ret: ctypes.int32_t,
    args: [ctypes.int32_t, ctypes.int32_t],
    impl: function(self, sel, a, b) {
      return Math.floor(a / b);
    }
  };

  TestJSClass.rect = {
    ret: objc.types.NSRect,
    args: [objc.types.CGFloat, objc.types.CGFloat,
           objc.types.CGFloat, objc.types.CGFloat],
    impl: function(self, sel, x, y, width, height) {
      return objc.types.NSRect(objc.types.NSPoint(x, y),
                               objc.types.NSSize(width, height));
    }
  };

  let test = objc.classes.TestJSClass.
      alloc().
      init().
      autorelease();

  print(test.pi[objc.as.double]());
  print(test.twice[objc.as.int32_t](ctypes.int32_t(123)));
  print(test.divide.by[objc.as.int32_t](ctypes.int32_t(100),
                                        ctypes.int32_t(20)));
  print(test.rect[objc.as.NSRect](objc.types.CGFloat(12),
                                  objc.types.CGFloat(23),
                                  objc.types.CGFloat(34),
                                  objc.types.CGFloat(45)));
});

// Get mouse location.
objc.autoreleasepool(function() {
  let loc = objc.classes.NSEvent.mouseLocation[objc.as.NSPoint]();
  print(loc);
});

objc.release();

3 件のコメント:

  1. This is absolutely awesome! Tons of data types described here. I was wondering what NSUInteger was and now I see it here!

    Also explains the abstraction layer so I can understand the other blogs better. Thank you!

    返信削除
  2. UnMHT still saves the elements blocked by Adblock+. Please fix this issue.

    返信削除
    返信
    1. If you have any problem with UnMHT, please post it to the forum:
      http://www.unmht.org/forum/en/topics.html

      削除