在C和Objective-C中绑定JavaScript

翻译:七夜小魔君、Jack★魏凡缤、肇庆、荣荣、crazy duck、紫夜行者

校对:u0u0

原文:JavaScript Bindings for C and Objective-C

引言

C/Objective-C语言中的JavaScript Bindings 是介于本地代码(C or Objective-C)和 JavaScript(JS)代码之间的“胶水”代码(封装代码)。JSB能实现JS和本地代码之间的 互相调用 。

这意味着你可以使用JS在你喜欢的本地库之间互相调用。例如,你可以在JS中创建一个 cocos2d的粒子系统,而它的逻辑和渲染是本地代码执行。或者你可以在JS中创建一个 Chipmunk的物理世界,而所有物理仿真,包括碰撞检测都是本地代码执行。

jsb-layer
JSB Layer

JS代码由Mozilla的JS虚拟机(VM)SpiderMonkey解析。使用SpiderMonekey最新的稳 定版本(本文使用的是v14.0.1)。JS VM通过JSB扩展支持自定义类型,自定义结构体和 Objective-C对象。

JSB有一套灵活的重命名规则用来设定哪些类、方法、函数和结构体将被解析或忽略,并确 定哪些方法是回调。为了方便的创建这些规则,它支持正则表达式。

这项技术正被Zynga公司用在游戏的快速开发和原型设计中。现在,让我们在cocos2d-iphone,Chipmunk和CoCosBuilder Reader上使用它。

主要特点

JSB的亮点:

  • 支持Objective-C/C的任何库
  • 自动生成JS Bindings(“胶水”代码)
  • 不需要修改库的源代码,或自动生成的代码
  • 有一套强大的规则可定制JS API 的生成
  • 自动在JS对象/类型 和 Objective-C对象/结构体/类型 之间相互转换
  • 在JS中支持本地对象的子类
  • 支持回调

创建自己的Bindings

简明步骤
1.下载JSB(git下载地址)

$git clone git://github.com/zynga/jsbindings.git

2.生成BridgeSupport 文件,假设项目名是CocosDenshion

$cd ~/src/CocosDenshion/CocosDenshion
$gen_bridge_metadata -F complete --no-64-bit -c '-DNDEBUG -I.' *.h -o ~/some/path/CocosDenshion.bridgesupport

3.生成补充文件

$cd ~/src/CocosDenshion/CocosDenshion
$~/jsb/generate_complement.py -o ~/some/path/CocosDenshion-complement.txt *.h

4.创建JSB配置文件

$vim ~/some/path/CocosDenshion_jsb.ini

5.运行JSB生成脚本

$~/jsb/generate_js_bindings.py -c ~/som/path/CocosDenshion_jsb.ini

6.导入刚才自动生成的文件和JSB源码文件到你的xcode项目中

7.导入SpiderMonkey和JRSwizzle到你的xcode项目
所有文件能在这里找到:

https://github.com/zynga/jsbindings/tree/master/configs/CocosDenshion
详细步骤

JSB中有一个名为generate_js_bindings.py的脚本, 这个脚本会生成“胶水”代码。脚本需 要一个配置文件,其中包含解析规则和BridgeSupport文件。

BridgeSupport文件由OS X下的gen_bridge_metada脚本生成,生成的是一些xml文件包 含了类名,方法名,参数,返回值,内部结构体,常量等信息.

gen_bridge_metada,内部使用clang 解析本地代码。输出的内容是非常可靠的,但不幸的 是,它不完整:丢失了类继承关系、协议和属性信息。这就是为什么JSB包含另一个python脚本generate_js_complement.py, 它生成丢失的信息。

当配置文件准备好,就可以运行generate_js_bindings.py 脚本生成“胶水”代码。

总结,JSB配置文件的结构是

  • 解析规则(可选):重命名规则、类的解析/忽略 等...
  • BridgeSupport文件(必需):类、方法、函数、结构信息
  • 补充文件(Objective - CGON):继承结构、协议和属性信息

index
“glue” code generation

配置规则

有一套强大的配置规则可以转换本地API到自定义的JS API。让我们看看其中一些规则.

重命名规则:
重命名规则允许我们重命名方法名,类名,函数名或结构名。下面示例代码默认的JS API:

// CCAnimation (from cocos2d-iphone v2.0)
+(id) animationWithAnimationFrames:(NSArray*)arrayOfAnimationFrames delayPerUnit:(float)delayPerUnit loops:(NSUInteger)loops;

将是:

// ugly
cc.CCAnimation.animationWithAnimationFrames_delayPerUnit_loops_( frames, delay, loops );

我们可以利用一套简单的规则把这个JS API命名为如下所示的函数:

// more JS friendly
cc.Animation.create( frames, delay, loops );

为了做到这一点,我们要把前缀CC从类名中移除, 因为它已经使用了名为cc的JS名字空间。
obj_class_prefix_to_remove = CC
最后,我们添加一个方法的命名规则:
method_properties = CCAnimation # animationWithAnimationFrames:delayPerUnit:loops: = name:"create",

合并规则:
但是CCAnimation的其它构造函数会发生什么呢?

// CCAnimation supports 4 different constructors
+(id) animationWithSpriteFrames:(NSArray*)arrayOfSpriteFrameNames;
+(id) animationWithSpriteFrames:(NSArray*)arrayOfSpriteFrameNames delay:(float)delay;
+(id) animationWithAnimationFrames:(NSArray*)arrayOfAnimationFrames delayPerUnit:(float)delayPerUnit loops:(NSUInteger)loops;

我们应该做的是创建一个规则,把四个构造函数合并为一个。JSB会根据参数的个数正确 执行. 这个规则如下所示:
method_properties = CCAnimation # animationWithAnimationFrames:delayPerUnit:loops: = name:"create"; merge: "animation" | "animationWithSpriteFrames:" | "animationWithSpriteFrames:delay:",

JS的代码将看起来像这样:

// calls [CCAnimation animation]
var anim = cc.Animation.create();
// calls [CCAnimation animnationWithSpriteFrames:]
var anim = cc.Animation.create(array);
// calls [CCAnimation animnationWithSpriteFrames:delay:]
var anim = cc.Animation.create(array, delay);
// calls [CCAnimation animationWithAnimationFrames:delayPerUnit:loops:]
var anim = cc.Animation.create(array, delay, loops);

回调规则:
JSB支持回调方法,为了把一个函数注册为回调函数,你需要在配置文件中添加一条回调 规则. 例如:
method_properties = CCNode#onEnter = callback,

你也可以重命名回调函数:
method_properties = CCLayer # ccTouchesBegan:withEvent: = callback; name:"onTouchesBegan",

示例配置:
进一步学习了解配置文件, 可参考下面的例子。

 

JS Bingdings内幕(“胶水”代码)

JS代码绑定允许本地代码和JS代码互相调用。让我们来看看细节:

JS调用本地函数
下面的代码将调用本地C函数ccpAdd():

var p1 = cc.p(0,0);
var p2 = cc.p(1,1);
// cc.pAdd is a "wrapped" function, and it will call the cocos2d ccpAdd() C function
var ret = cc.pAdd(p1, p2);

让我们看看本地ccpAdd的声明

CGPoint ccpAdd(const CGPoint v1, const CGPoint v2);

当执行cc.pAdd时,它会调用“粘合剂”的功能代码JSB_ccpAdd。JSB_ccpAdd做了以下事情:

  • 把JS参数转换为本地参数
  • 调用本地的ccpAdd()函数
  • 把本地的返回值转换成JS的返回值
  • 转换参数或返回值时 如果遇到错误,则失败

function-call-flow
function call flow

JS调用本地实例/类方法
可以从JS调用实例或类的方法。内部逻辑和调用本机函数相似。让我们来看看:

//创建一个精灵并设置其位置为200,200
var sprite = cc.Sprite.create('image.png');
sprite.setPosition( cc.p(200,200) );
cc.Sprite.create("image.png")

将调用“胶水”函数
JSB_CCSprite_spriteWithFile_rect__static
做了以下事情:

  • JS字符串转换成本地字符串
  • 通过调用 [CCSprite spriteWithFile:@"image.png"]创建一个本地实例
  • 本地实例转换成JS对象
  • 新创建的实例添加到字典,使用JS对象作为key
  • 返回转换后的JS实例

sprite.setPosition(cc.p(200,200)) 将调用“胶水”函数JSB_CCNode_setPosition_,
做了以下事情:

  • 通过JS对象作为key,从字典中获取本机实例。
  • JS对象转换成CGPoint
  • 调用[instance setPosition:p]
  • setPosition没有返回值,它返回一个“void”对象给JS

class-instantiation-flow
class instantiation flow
instance-method-flow
instance method flow

从本地调用JS代码
回调函数
可以生成两种类型的回调:

  • 在JS中触发回调而在本地执行(前面提到过)
  • 在本地触发回调并执行JS代码。也就是所谓的”回调”

例如,cocos2d-iphone的onEnter,onExit,update回调函数。onEnter在本地触发,JS中 可重写这个方法. 在下面这个例子里,JS函数onEnte会被本地代码调用:

var MyLayer = cc.Layer.extend({
ctor:function () {
cc.associateWithNative( this, cc.Layer );
this.init();
},

onEnter:function() {
cc.log("onEnter called");
},
});

JSB支持回调,且不需要修改被解析库的源代码。JSB要做的是,把原始回调方法和JSB回调方法交换。

完整的流程:

  • JSB注册所有交换方法为回调
  • 在交换方法调用时:
    *调用本地回调函数
    *然后调用JS回调函数(如果有)

callbacks-flow
callbacks flow

脚本执行:

本地代码调用JS脚本,方法如下:

// JSBCore is responsible registering the native objects in JS, among other things
if( ! [[JSBCore sharedInstance] runScript:@"my_js_script.js"] )
  NSLog(@"Error running script");

谁在使用JSB?
JSB已成功应用于下列开源项目:

cocos2d-iphone的开发分支有3个demo工程使用了JSB:

  • JS Tests: cocos2d JS bindings tests
  • JS Watermelon With Me: a simple physics game that uses JS bindings for cocos2d, Chipmunk, CocosDenshion and CocosBuilder Reader.
  • JS MoonWarriors: A top-down shooter that uses JS bindings for cocos2d

值得一提的是,得益于一套强大的规则,cocos2d-iphone生成的JS API和
cocos2d-HTML5 API是相同的。
Moon Warriors 可以使用cocos2d­iphone+JSB 或 cocos2d­html5运行,无需修改一行代码. 试试web版本:

Moon Warriors Web

Bugs/局限性
这篇博章时,JSB有下面的bugs或局限。最新清单请访问JSB Homepage.

  • 没有JS调试器。SpiderMonKey 15发布后,会添加远程调试功能。
  • 没有JS分析工具
  • 本地对象控制JS对象的生命周期
    这意味着本机对象被释放了,而JS对象仍然存在。
    在某些情况下JS对象可能指向一个已经被释放的本机对象, 从而产生逻辑错误。
  • 回调函数不支持返回值. 支持参数有限。
  • 脚本无法解析BridgeSupport常量。
  • 避免使用OS X 10.6(或更老)系统的gen_bridge_metada。建议使用OS X 10.8(Mountain Lion)的gen_bridge_metada来生成BridgeSupport文件。
  • 从头做一个新项目是不容易的。Xcode模板即将来临。在此之际,更简单的方法就是通过复制JS“targets”来绑定cocos2d-iphone v2.1。

标签: jsb

?>