nodobjc-x
Version:
The Node.js ⇆ Objective-C bridge
410 lines (372 loc) • 15.7 kB
JavaScript
/**
* This 'core' module is the `libffi` wrapper. All required native
* functionality is instantiated and then exported in this module.
*
* ### References:
*
* * [Objective-C Runtime Reference](http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ObjCRuntimeRef/Reference/reference.html)
*/
/*!
* Module dependencies.
*/
var ref = require('ref')
, ffi = require('ffi')
, types = require('./types')
, Struct = require('ref-struct')
, uintptr_t = ref.sizeof.pointer == 8 ? 'uint64' : 'uint32' // 'uintptr_t' isn't natively supported by node-ffi
, libc = new ffi.Library('libc', {
malloc: [ 'void*', [ 'size_t' ] ]
, free: [ 'void', [ 'void*' ] ]
})
, free = libc.free
, objc = new ffi.Library('libobjc', {
class_addIvar: [ 'uint8', [ 'pointer', 'string', 'size_t', 'uint8', 'string' ] ]
, class_addMethod: [ 'uint8', [ 'pointer', 'pointer', 'pointer', 'string' ] ]
, class_addProtocol: [ 'uint8', [ 'pointer', 'pointer' ] ]
, class_copyIvarList: [ 'pointer', [ 'pointer', 'pointer' ] ]
, class_copyMethodList: [ 'pointer', [ 'pointer', 'pointer' ] ]
, class_copyPropertyList: [ 'pointer', [ 'pointer', 'pointer' ] ]
, class_copyProtocolList: [ 'pointer', [ 'pointer', 'pointer' ] ]
, class_getClassMethod: [ 'pointer', [ 'pointer', 'pointer' ] ]
, class_getClassVariable: [ 'pointer', [ 'pointer', 'string' ] ]
, class_getInstanceMethod: [ 'pointer', [ 'pointer', 'pointer' ] ]
, class_getInstanceSize: [ 'size_t', [ 'pointer' ] ]
, class_getInstanceVariable: [ 'pointer', [ 'pointer', 'string' ] ]
, class_getIvarLayout: [ 'string', [ 'pointer' ] ]
, class_getName: [ 'string', [ 'pointer' ] ]
, class_getProperty: [ 'pointer', [ 'pointer', 'string' ] ]
, class_getSuperclass: [ 'pointer', [ 'pointer' ] ]
, class_getVersion: [ 'int32', [ 'pointer' ] ]
, class_getWeakIvarLayout: [ 'string', [ 'pointer' ] ]
, class_isMetaClass: [ 'uint8', [ 'pointer' ] ]
, class_setIvarLayout: [ 'void', [ 'pointer', 'string' ] ]
, class_setSuperclass: [ 'pointer', [ 'pointer', 'pointer' ] ]
, class_setVersion: [ 'void', [ 'pointer', 'int32' ] ]
, class_setWeakIvarLayout: [ 'void', [ 'pointer', 'string' ] ]
, ivar_getName: [ 'string', [ 'pointer' ] ]
, ivar_getOffset: [ 'int32', [ 'pointer' ] ]
, ivar_getTypeEncoding: [ 'string', [ 'pointer' ] ]
, method_copyArgumentType: [ 'pointer', [ 'pointer', 'uint32' ] ]
, method_copyReturnType: [ 'pointer', [ 'pointer' ] ]
, method_exchangeImplementations: [ 'void', [ 'pointer', 'pointer' ] ]
, method_getImplementation: [ 'pointer', [ 'pointer' ] ]
, method_getName: [ 'pointer', [ 'pointer' ] ]
, method_getNumberOfArguments: [ 'uint32', [ 'pointer' ] ]
, method_getTypeEncoding: [ 'string', [ 'pointer' ] ]
, method_setImplementation: [ 'pointer', [ 'pointer', 'pointer' ] ]
, objc_allocateClassPair: [ 'pointer', [ 'pointer', 'string', 'size_t' ] ]
, objc_copyProtocolList: [ 'pointer', [ 'pointer' ] ]
, objc_getAssociatedObject: [ 'pointer', [ 'pointer', 'pointer' ] ]
, objc_getClass: [ 'pointer', [ 'string' ] ]
, objc_getClassList: [ 'int32', [ 'pointer', 'int32' ] ]
, objc_getProtocol: [ 'pointer', [ 'string' ] ]
, objc_registerClassPair: [ 'void', [ 'pointer' ] ]
, objc_removeAssociatedObjects: [ 'void', [ 'pointer' ] ]
, objc_setAssociatedObject: [ 'void', [ 'pointer', 'pointer', 'pointer', uintptr_t ] ]
, object_getClass: [ 'pointer', [ 'pointer' ] ]
, object_getClassName: [ 'string', [ 'pointer' ] ]
, object_getInstanceVariable: [ 'pointer', [ 'pointer', 'string', 'pointer' ] ]
, object_getIvar: [ 'pointer', [ 'pointer', 'pointer' ] ]
, object_setClass: [ 'pointer', [ 'pointer', 'pointer' ] ]
, object_setInstanceVariable: [ 'pointer', [ 'pointer', 'string', 'pointer' ] ]
, object_setIvar: [ 'void', [ 'pointer', 'pointer', 'pointer' ] ]
, property_getAttributes: [ 'string', [ 'pointer' ] ]
, property_getName: [ 'string', [ 'pointer' ] ]
, protocol_conformsToProtocol: [ 'uint8', [ 'pointer', 'pointer' ] ]
, protocol_copyMethodDescriptionList: [ 'pointer', [ 'pointer', 'uint8', 'uint8', 'pointer' ] ]
, protocol_copyPropertyList: [ 'pointer', [ 'pointer', 'pointer' ] ]
, protocol_copyProtocolList: [ 'pointer', [ 'pointer', 'pointer' ] ]
, protocol_getMethodDescription: [ 'pointer', [ 'pointer', 'pointer', 'uint8', 'uint8' ] ]
, protocol_getName: [ 'string', [ 'pointer' ] ]
, protocol_getProperty: [ 'pointer', [ 'pointer', 'string', 'uint8', 'uint8' ] ]
, sel_getName: [ 'string', [ 'pointer' ] ]
, sel_registerName: [ 'pointer', [ 'string' ] ]
})
, selCache = {};
objc.objc_msgSend = ffi.DynamicLibrary().get('objc_msgSend')
, objc.objc_msgSend_stret = ffi.DynamicLibrary().get('objc_msgSend_stret')
, objc.objc_msgSendSuper = ffi.DynamicLibrary().get('objc_msgSendSuper')
, objc.objc_msgSendSuper_stret = ffi.DynamicLibrary().get('objc_msgSendSuper_stret');
/**
* Convenience wrapper around loading dynamic libraries.
*/
function dlopen (path) {
return new ffi.DynamicLibrary(path)
}
/**
* Convienience function to return an Array of Strings of the names of every
* class currently in the runtime. This gets used at the during the import
* process get a name of the new classes that have been loaded.
* TODO: Could be replaced with a native binding someday for speed. Not overly
* important as this function is only called during import()
*/
function getClassList () {
var rtn = [];
// first get just the count
var num = objc.objc_getClassList(null, 0);
if (num > 0) {
var c = null;
var classes = new Buffer(ref.sizeof.pointer * num);
objc.objc_getClassList(classes, num);
for (var i = 0; i < num; i++) {
c = classes.readPointer(i * ref.sizeof.pointer);
rtn.push(objc.class_getName(c));
}
// `classes` Buffer will be freed by V8
}
return rtn;
}
/**
* Copies and returns an Array of the instance variables defined by a given
* Class pointer. To get class variables, call this function on a metaclass.
*/
function copyIvarList (classPtr) {
var rtn = []
, numIvars = ref.alloc('uint')
, ivars = objc.class_copyIvarList(classPtr, numIvars)
, count = numIvars.deref();
for (var i=0; i<count; i++)
rtn.push(objc.ivar_getName(ivars.readPointer(i * ref.sizeof.pointer)));
free(ivars);
return rtn;
}
/**
* Copies and returns an Array of the instance methods the given Class pointer
* implements. To get class methods, call this function with a metaclass.
*/
function copyMethodList (classPtr) {
var numMethods = ref.alloc('uint')
, rtn = []
, methods = objc.class_copyMethodList(classPtr, numMethods)
, count = numMethods.deref();
for (var i=0; i<count; i++)
rtn.push(wrapValue(objc.method_getName(methods.readPointer(i * ref.sizeof.pointer)),':'));
free(methods);
return rtn;
}
/**
* Convienience function to get the String return type of a Method pointer.
* Takes care of free()ing the returned pointer, as is required.
*/
function getMethodReturnType (method) {
return getStringAndFree(objc.method_copyReturnType(method));
}
/**
* Gets a cstring from the pointer and free's the memory
*/
function getStringAndFree (ptr) {
var str = ptr.readCString();
free(ptr);
return str;
}
/**
* Wraps up a node-ffi pointer if needed (not needed for Numbers, etc.)
*/
function wrapValue (val, type) {
var basetype = type.type ? type.type : type;
if(basetype == '@' || basetype == '#') return createObject(val, basetype);
else if (basetype[0] == '@') return createObject(val, '@');
else if (basetype == '@?') return createObject(createBlock(val, '@'));
else if (basetype == '^?') return createUnwrapperFunction(val, type);
else if (basetype == ':') return objc.sel_getName(val);
else if (basetype == 'B') return val ? true : false;
else if (basetype == 'c' && val === 1) return true;
else if (basetype == 'c' && val === 0) return false;
else return val;
}
/**
* Accepts an Array of raw objc pointers and other values, and an array of ObjC
* types, and returns an array of wrapped values where appropriate.
*/
function wrapValues (values, objtypes) {
var result = [];
for(var i=0; i < objtypes.length; i++) result.push(wrapValue(values[i], objtypes[i]));
return result;
}
/**
* Unwraps a previously wrapped NodObjC object.
*/
function unwrapValue (val, type) {
var basetype = type.type ? type.type : type;
if (basetype == '@?') return createBlock(val, basetype);
else if (basetype == '^?') return createWrapperPointer(val, type);
else if (basetype == '@' || basetype == '#') {
if(Buffer.isBuffer(val)) return val;
return val ? val.pointer : null;
}
else if (basetype == ':') return selCache[val] || (selCache[val] = objc.sel_registerName(val));
else if (val === true) return 1;
else if (val === false) return 0;
else return val;
}
/**
* Accepts an Array of wrapped NodObjC objects and other values, and an array
* of their cooresponding ObjC types, and returns an array of unwrapped values.
*/
function unwrapValues (values, objtypes) {
var result = [];
for(var i=0; i < objtypes.length; i++) result.push(unwrapValue(values[i], objtypes[i]));
return result;
}
/**
* Represents a wrapped `IMP` (a.k.a. method implementation). `IMP`s are function pointers for methods. The first two arguments are always:
*
* 1. `self` - The object instance the method is being called on.
* 2. `_cmd` - The `SEL` selector of the method being invoked.
*
* Any additional arguments that get passed are the actual arguments that get
* passed along to the method.
*/
/**
* Creates an ffi Function Pointer to the passed in 'func' Function. The
* function gets wrapped in an "wrapper" function, which wraps the passed in
* arguments, and unwraps the return value.
*
* @param {Function} A JS function to be converted to an ffi C function.
* @param {Object|Array} A "type" object or Array containing the 'retval' and
* 'args' for the Function.
* @api private
*/
function createWrapperPointer (func, type) {
var argTypes = type.args || type[1] || [];
var rtnType = type.retval || type[0] || 'v';
if (func.pointer) return func.pointer;
return new ffi.Callback(types.map(rtnType), types.mapArray(argTypes), function() {
return unwrapValue(func.apply(null, wrapValues(arguments, argTypes)), rtnType);
});
}
/**
* Creates a JS Function from the passed in function pointer. When the returned
* function is invoked, the passed in arguments are unwrapped before being
* passed to the native function, and the return value is wrapped up before
* being returned for real.
*
* @param {Pointer} The function pointer to create an unwrapper function around
* @param {Object|Array} A "type" object or Array containing the 'retval' and
* 'args' for the Function.
* @api private
*/
function createUnwrapperFunction (funcPtr, type, isVariadic) {
var rtnType = type.retval || type[0] || 'v';
var argTypes = type.args || type[1] || [];
var unwrapper;
if (isVariadic) {
var func = ffi.VariadicForeignFunction(funcPtr, types.map(rtnType), types.mapArray(argTypes));
unwrapper = function() {
var newtypes = [];
// Detect the types coming in, make sure to ignore previously defined baseTypes,
// garner a list of these then send the function through the normal exec.
// The types system in objc, should probably be cleaned up considerably. This is
// somewhat faulty but since 95% of objects coming through are mostly ID/Class
// it works, we may have issues for function pointers/etc.
for(var i=argTypes.length; i < arguments.length; i++) {
if(arguments[i].type) newtypes.push(arguments[i].type)
else if(arguments[i].pointer) newtypes.push('@');
else if(typeof arguments[i] == 'function') newtypes.push('@?');
else if(typeof arguments[i] == 'string') newtypes.push('r*');
else if(typeof arguments[i] == 'number') newtypes.push('d');
else newtypes.push('?');
}
return wrapValue(func
.apply(null, types.mapArray(newtypes))
.apply(null, unwrapValues(arguments,argTypes.concat(newtypes))),
rtnType);
};
} else {
var func = ffi.ForeignFunction(funcPtr, types.map(rtnType), types.mapArray(argTypes));
unwrapper = function() {
return wrapValue(func.apply(null, unwrapValues(arguments, argTypes)), rtnType);
}
}
unwrapper.retval = rtnType;
unwrapper.args = argTypes;
unwrapper.pointer = funcPtr;
return unwrapper;
}
/**
* We have to simulate what the llvm compiler does when it encounters a Block
* literal expression (see `Block-ABI-Apple.txt` above).
* The "block literal" is the struct type for each Block instance.
*/
var __block_literal_1 = Struct({
isa: 'pointer',
flags: 'int32',
reserved: 'int32',
invoke: 'pointer',
descriptor: 'pointer'
});
/**
* The "block descriptor" is a static singleton struct. Probably used in more
* complex Block scenarios involving actual closure variables needing storage
* (in `NodObjC`, JavaScript closures are leveraged instead).
*/
var __block_descriptor_1 = Struct({
reserved: 'ulonglong',
Block_size: 'ulonglong'
});
// The class of the block instances; lazy-loaded
var BD = new __block_descriptor_1();
BD.reserved = 0;
BD.Block_size = __block_literal_1.size;
var CGB;
/**
* Creates a C block instance from a JS Function.
* Blocks are regular Objective-C objects in Obj-C, and can be sent messages;
* thus Block instances need are creted using the core.wrapId() function.
*
* @api private
*/
function createBlock (func, type) {
if (!func) return null;
else if (func.pointer) return func.pointer;
var bl = new __block_literal_1;
// Set the class of the instance
bl.isa = CGB || (CGB = dlopen().get('_NSConcreteGlobalBlock'));
// Global flags
bl.flags = 1 << 29;
bl.reserved = 0;
bl.invoke = createWrapperPointer(func, type);
bl.descriptor = BD.ref();
return bl.ref();
}
/**
* Creates an ID or Class depending on the encoded type, stores and
* manages cache for classes as well, this also wraps objects to
* create the objc(sel) type interface in JS.
*
* @api private
*/
function createObject (val, type, name) {
if (!val || ref.isNull(val)) return null;
var cache = objc.objc_getAssociatedObject(val, objc.objcStorageKey);
if (!ref.isNull(cache)) return cache.readObject(0);
var instance = (type == '@') ? new (require('./id'))(val)
: new (require('./class'))(val);
// store this ID JS reference into the Obj-C internal associated object store
var refn = ref.alloc('pointer');
refn.writeObject(instance, 0);
objc.objc_setAssociatedObject(val, objc.objcStorageKey, refn, 0);
return instance;
}
/*!
* Module exports.
*/
objc.createBlock = createBlock;
objc.createObject = createObject;
objc.createUnwrapperFunction = createUnwrapperFunction;
objc.createWrapperPointer = createWrapperPointer;
objc.Callback = ffi.Callback;
objc.ForeignFunction = ffi.ForeignFunction;
objc.Types = types;
objc.dlopen = dlopen;
objc.getClassList = getClassList;
objc.copyIvarList = copyIvarList;
objc.copyMethodList = copyMethodList;
objc.getMethodReturnType = getMethodReturnType;
objc.getStringAndFree = getStringAndFree;
objc.wrapValue = wrapValue;
objc.wrapValues = wrapValues;
objc.unwrapValues = unwrapValues;
objc.unwrapValue = unwrapValue;
objc.objcStorageKey = new Buffer(1);
module.exports = objc;