2013年5月12日

js-ctypes で Objective-C を使う (5) - double を返すメソッド

自動判別をしようと頑張った結果、やっぱりめんどくさかったので手動にする。
セレクタになり得ないものを最後に渡して、それで返り値の型を指定する。

"use strict"

let Cu = Components.utils;

Cu.import ("resource://gre/modules/ctypes.jsm");

/* なんかイイカンジにアレしてくれるアレ */
let objc = {
  /* はじめる */
  init : function () {
    this.lib = ctypes.open (ctypes.libraryName ("objc"));

    this.objc_getClass = this.lib.declare ("objc_getClass",
                                           ctypes.default_abi,
                                           objc.types.id,
                                           ctypes.char.ptr);
    this.sel_registerName = this.lib.declare ("sel_registerName",
                                              ctypes.default_abi,
                                              objc.types.SEL,
                                              ctypes.char.ptr);
    this.objc_msgSend = this.lib.declare ("objc_msgSend",
                                          ctypes.default_abi,
                                          objc.types.id,
                                          objc.types.id,
                                          objc.types.SEL,
                                          "...");
    this.objc_msgSend_fpret = this.lib.declare ("objc_msgSend_fpret",
                                                ctypes.default_abi,
                                                ctypes.double,
                                                objc.types.id,
                                                objc.types.SEL,
                                                "...");
    
    this.objc_allocateClassPair = this.lib.declare ("objc_allocateClassPair",
                                                    ctypes.default_abi,
                                                    objc.types.id,
                                                    objc.types.id,
                                                    ctypes.char.ptr,
                                                    ctypes.size_t);
    
    this.objc_registerClassPair = this.lib.declare ("objc_registerClassPair",
                                                    ctypes.default_abi,
                                                    ctypes.void_t,
                                                    objc.types.id);
    
    this.class_addMethod = this.lib.declare ("class_addMethod",
                                             ctypes.default_abi,
                                             objc.types.BOOL,
                                             objc.types.id,
                                             objc.types.SEL,
                                             objc.types.IMP,
                                             ctypes.char.ptr);
  },
  
  /* おわる */
  release : function () {
    this.lib.close ();
  },
  
  /* メッセージを送る */
  send : function (listener, sel, args) {
    return this.objc_msgSend.apply (undefined,
                                    [listener, this.sel_registerName (sel)]
                                    .concat (args));
  },
  
  /* 返り値が double なメッセージを送る */
  sendf : function (listener, sel, args) {
    return this.objc_msgSend_fpret.apply (undefined,
                                          [listener, this.sel_registerName (sel)]
                                          .concat (args));
  },

  /* NSString を作る */
  str : function (str) {
    return this.classes.NSString.alloc ()
    .initWithUTF8String (ctypes.char.array ()(str))
    .autorelease ()();
  },
  
  /* メソッド呼び出しみたいにするプロキシ */
  proxy : function (obj, sels) {
    if (!sels) {
      sels = [];
    }
    
    return Proxy.createFunction ({
        get : function (a, sel) {
          /* 使い方を間違えると valueOf や toString のメッセージが飛んで
           * クラッシュするので適当に回避 */
          if (sels.length == 0
              && (sel == "valueOf"
                  || sel == "toString"
                  || sel == "toSource")) {
            throw new Error (sel + " is called.");
          }
          
          /* セレクタを記録する */
          return objc.proxy (obj, sels.concat (sel));
        }
      },
      function () {
        if (sels.length == 0) {
          /* セレクタが無い場合は中身の取得 */
          if (arguments.length == 0) {
            /* 引数が無い場合はそのまま */
            return obj;
          }
          else {
            /* 引数がある場合はキャスト */
            return ctypes.cast (obj, arguments [0]).value;
          }
        }
        
        /* メッセージを送る */
        let args = Array.prototype.slice.call (arguments, 0);
        /* 返り値の型を指定されている場合をチェック */
        if (sels.length && sels [sels.length - 1] == "@f") {
          /* double */
          sels.pop ();
          let sel = sels.join (":");
          if (args.length > 0) {
            sel += ":";
          }
          return objc.sendf (obj, sel, args);
        }
        else if (sels.length && sels [sels.length - 1] == "@i") {
          /* int32_t */
          sels.pop ();
          let sel = sels.join (":");
          if (args.length > 0) {
            sel += ":";
          }
          return ctypes.cast (objc.send (obj, sel, args), ctypes.int32_t).value;
        }
        else {
          /* 指定がないのでオブジェクトという事にする */
          let sel = sels.join (":");
          if (args.length > 0) {
            sel += ":";
          }
          let ret = objc.send (obj, sel, args);
          return objc.proxy (ret);
        }
      });
  },
  
  /* 名前に対応するクラスを返すオプジェクト的なアレ */
  classes : new Proxy ({}, {
      get : function (obj, name) {
        return objc.proxy (objc.objc_getClass (name));
      }
    }),
  
  /* Objective-C の型 */
  types : function () {
    let types = {};
    
    types.objc_object = new ctypes.StructType ("objc_object");
    types.id = types.objc_object.ptr;
    
    types.objc_class = new ctypes.StructType ("objc_class");
    types.Class = types.objc_class.ptr;
    
    types.objc_selector = new ctypes.StructType ("objc_selector");
    types.SEL = types.objc_selector.ptr;
    
    types.IMP = ctypes.voidptr_t;
    
    types.objc_method_description
    = new ctypes.StructType ("objc_method_description",
                             [ { "method_name"  : types.SEL }, {
                                 "method_types" : ctypes.char.ptr }, {
                                 "method_imp"   : types.IMP } ]);
    types.Method = types.objc_method_description.ptr;
    
    types.BOOL = ctypes.signed_char;
    
    return types;
  }(),
  
  /* 型をエンコードする */
  encode : function (origType) {
    let type = origType;
    let str = "";
    
    /* ポインタを解決 */
    while ("targetType" in type
           && type.targetType) {
      /* 特殊な型だけ先にチェックする */
      if (type == objc.types.id) {
        str += "@";
        return str;
      }
      if (type == objc.types.Class) {
        str += "#";
        return str;
      }
      if (type == objc.types.SEL) {
        str += ":";
        return str;
      }
      
      str += "^";
      type = type.targetType;
    }
    
    /* 関数 */
    if ("abi" in type) {
      str += "?";
      return str;
    }
    
    /* 構造体 */
    if ("fields" in type) {
      str += "{" + type.name + "="
       + type.fields.map (function (field) {
          return Object.keys (field).map (function (name) {
              return objc.encode (field [name]);
            }).join ("");
         }).join ("") + "}";
      return str;
    }
    
    /* 配列 */
    if ("elementType" in type
        && "length" in type) {
      if (type.length) {
        str += "[" + type.length + objc.encode (type.elementType) + "]";
      }
      else {
        str += "^" + objc.encode (type.elementType);
      }
      return str;
    }
    
    /* プリミティブ */
    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;
  },
  
  /* メソッドの引数と返り値の型をエンコードする */
  encodeMethod : function (ret, args) {
    return [ret].concat (args).map (function (t) objc.encode (t)).join ("");
  }
};

/* ---- ここから本体 ---- */

/* はじめる */
objc.init ();

/* プールを作る */
let pool = objc.classes.NSAutoreleasePool.alloc ().init ();

/* クラスを作る */
let testJSClass = objc.objc_allocateClassPair (objc.classes.NSObject (),
                                               "TestJSClass", 0);

/* メソッドを作る */
objc.class_addMethod (testJSClass,
                      objc.sel_registerName ("pi"),
                      ctypes.FunctionType (ctypes.default_abi,
                                           ctypes.double,
                                           [objc.types.id,
                                            objc.types.SEL])
                      .ptr (function (self, sel) {
                          return Math.PI;
                        }),
                      objc.encodeMethod (ctypes.double,
                                         [objc.types.id,
                                          objc.types.SEL]));
objc.class_addMethod (testJSClass,
                      objc.sel_registerName ("twice:"),
                      ctypes.FunctionType (ctypes.default_abi,
                                           ctypes.int32_t,
                                           [objc.types.id,
                                            objc.types.SEL,
                                            ctypes.int32_t])
                      .ptr (function (self, sel, a) {
                          return a * 2;
                        }),
                      objc.encodeMethod (ctypes.int32_t,
                                         [objc.types.id,
                                          objc.types.SEL,
                                          ctypes.int32_t]));
objc.class_addMethod (testJSClass,
                      objc.sel_registerName ("divide:by:"),
                      ctypes.FunctionType (ctypes.default_abi,
                                           ctypes.int32_t,
                                           [objc.types.id,
                                            objc.types.SEL,
                                            ctypes.int32_t,
                                            ctypes.int32_t])
                      .ptr (function (self, sel, a, b) {
                          return Math.floor (a / b);
                        }),
                      objc.encodeMethod (ctypes.int32_t,
                                         [objc.types.id,
                                          objc.types.SEL,
                                          ctypes.int32_t,
                                          ctypes.int32_t]));
/* クラスを登録する */
objc.objc_registerClassPair (testJSClass);

/* オブジェクトを作る */
let test = objc.classes.TestJSClass.alloc ().init ().autorelease ();

/* メソッドを呼ぶ */
print (test.pi ["@f"] ());
print (test.twice ["@i"] (ctypes.int32_t (123)));
print (test.divide.by ["@i"] (ctypes.int32_t (100), ctypes.int32_t (20)));

/* プールを開放 */
pool.release ();

/* おわる */
objc.release ();

ちなみに自動判断に使おうとした関数は以下のとおり
  • object_getClass
  • class_getClassMethod
  • class_getInstanceMethod
objc_msgSend の前に object_getClass でクラス取得して、class_getClassMethod でメソッド取得して、型をチェック、という手順でいけそうかなーと思ったら、自作のクラスではメソッドが取得できなかった、という辺りで断念。

0 件のコメント:

コメントを投稿