webidl binder
还是以一个CSS3 animation开始
See the Pen Waiting by 攻伤菊菊长 (@THISISAGOODNAME) on CodePen.
- WebIDL Binder
- A quick example
- Using C++ classes in JavaScript
- Pointers, References, Value types (Ref and Value)
- Const
- Un-deletable classes (NoDelete)
- Defining inner classes and classes inside namespaces (Prefix)
- Operators
- enums
- Sub-classing C++ base classes in JavaScript (JSImplementation)
- Pointers and comparisons
- NULL
- void*
- WebIDL types
- Test and example code
WebIDL Binder
WebIDL Binder是一个简单轻量的C++绑定方法,通过该方法编译过的C++代码可以在JavaScript中以和普通的JavaScript类库相同的方法调用内部的方法。
WebIDL Binder使用WebIDL(中文Wiki)来定义,一种专门用来粘合 C++ 和 JavaScript 的接口定义语言。他是 C++ 类库向 JavaScript 移植的常见选择,因为它是底层的,它也很容易进行优化。
Binder支持一个 C++ 类型的子集。虽然只是子集,但是被众多类库移植后证明是够用的 – 甚至包括 Box2D 和 Bullet 物理引擎。
A quick example
使用WebIDL Binder来粘合语言需要三步
- 创建一个WebIDL文件来描述C++接口
- 使用Binder生成C++和JavaScript的“胶水”代码
- 将“胶水”代码和工程文件一起,使用Emscripten编译
创建WebIDL文件
第一步是创建WebIDL文件来描述你将要粘合的C++类型。这个文件应该会和头文件中的信息重复,这种格式既能简化代码解析,也能简化代码的表示。
举个例子,下面的C++类
下面的IDL文件是用来描述上面的两个类的
IDL定义和C++源文件之间的映射关系非常明显。需要注意几件事
- IDL类定义包括了一个和接口同名返回值为
void
的函数。这个构造器可以在JavaScript中直接调用来生成实例,但是必须在IDL中定义,即使C++使用了默认构造器(比如上例中的Foo
) - 在WebIDL中使用的类型名不一定和他们在C++中定义时相同(举个例子,上例中
int
被映射为long
)。更多关于映射的信息见WebIDL types
结构体
structs
和类定义的方法相同 – 同样都使用interface
关键字
生成胶水代码
粘合剂生成器(bindings generator)(tools/webidl_binder.py)接收一个WebIDL文件名为输入,输出输入文件的文件名,并创建C++和JavaScript的胶水代码文件。
举个例子,为了使用IDL文件my_classes.idl生成胶水代码文件glue.cpp和glue.js,需要使用如下的命令
python tools/webidl_binder.py my_classes.idl glue
编译工程(使用胶水代码)
为了在项目中使用胶水代码文件(glue.cpp
和glue.js
)
- 在编译项目使用的 emcc 命令中添加
--post-js glue.js
,post-js参数用来在编译时添加胶水代码 - 创建一个文件,比如my_glue_wrapper.cpp,用来
#include
你需要粘合的类的头文件以及 glue.cpp 文件,类似如下的内容
由粘合剂生成器(bindings generator)产生的C++胶水代码不包含他们粘合的类的头文件是因为他们无法在WebIDL文件中表示,所以需要创建一个新文件来包含头文件和胶水代码文件。如果不想创建这个新文件,可以把使用到的类的头文件引用添加到glue.cpp的头部,但是每次IDL文件重新编译时该过程都要重来一次
- 在最终的emcc命令中添加my_glue_wrapper.cpp文件
最终的emcc文件同时包括C++和JavaScript胶水文件,因为这两者其实一开始就是要一起使用的
./emcc my_classes.cpp my_glue_wrapper.cpp --post-js glue.js -o output.js
现在输出文件包含了在JavaScript中创建C++类的实例的一切
Using C++ classes in JavaScript
当绑定完成后,C++对象可以在JavaScript直接使用,就像他们本来就是JavaScript原生对象。继续上面的例子,你可以创建Foo
和Bar
对象的实例并调用他们的内部方法
使用Module对象来获取对象 虽然一般情况下对象在全局命令空间,但也存在例外(比如你使用closure编译器来压缩和包装编译后的代码来避免污染全局命名空间)。你也可以修改Module的名字,比如使用
var MyModuleName = Module;
JavaScript有自动垃圾回收功能,在包装的C++对象不再被使用后,会启用gc。如果C++对象不需要特别指明被清理(甚至没有析构函数),你什么都不用做。
如果一个C++对象确实需要被清理,你需要明确调用Module.destroy(obj)
来调用析构函数,然后删除所有引用对象,以便它可以被垃圾收集。举个例子,如果要清楚Bar
被分配的内存:
当在JavaScript中创建C++对象时,需要显示调用C++构造器。但是没有办法得知某个JavaScript对象要被垃圾回收,所以胶水语言不能自动调用析构函数
通常你需要销毁你自己创建的对象,取决于你移植的类库
Pointers, References, Value types (Ref and Value)
C++的参数和返回类型可以是指针、引用或值类型(在堆栈上分配)。IDL文件使用不同的修饰词来代表每种情况。
在WebIDL中,没有修饰词的参数和返回值都被假定为C++中的指针
引用需要修饰词[Ref]
如果一个引用省略了
[Ref]
修饰词,生成的胶水代码不会被编译(在尝试转换引用转换成对象时失败,因为emscripten把它当做了一个指针)
如果C++返回一个对象(而不是引用或指针)那么返回值需要修饰词[Value]
。这将为该类分配一个静态(单例)实例,并返回它。你应该立即使用它,并在使用后不再使用。
Const
C++中使用const
修饰的参数和返回值类型可以在IDL中使用[Const]
来指定。
例如,下面的代码片段显示了C++和IDL的一个函数返回一个指针常量对象。
对象中使用常量指定的属性需要readonly
关键字而不是[Const]
,例如
这会在绑定时产生一个get_numericalConstant()
方法,但是没有相对应的setter方法
多重修饰词是可以出现的。比如,一个返回常量引用的方法需要在IDL中使用
[Ref, Const]
来修饰
Un-deletable classes (NoDelete)
如果一个类不能被删除(析构函数是私有的),需要在IDL文件中添加[NoDelete]
修饰
Defining inner classes and classes inside namespaces (Prefix)
在某个命名空间(或者另一个类)中定义的类需要在IDL中使用Prefix
来指定范围。之后当类在C++胶水语言中被音乐,都需要前缀。
举个例子,下面的IDL定义确保Inner
类指的是MyNameSpace::Inner
Operators
可以使用[Operator=]
来粘合C++操作符:
- 操作符的命名是任意的,
add
只是用来举例 - 现在仅限包含
=
的操作符,比如:+=
、-=
、*=
enums
枚举类型在C++和IDL中定义非常类似
当枚举类型在命名空间内部时,稍有复杂
当枚举在一个类中定义时,枚举和类的接口定义是分离的
Sub-classing C++ base classes in JavaScript (JSImplementation)
WebIDL Binder允许C++基类在JavaScript中作为子类,在下面的IDL片段中,[JSImplementation="Base"]
表示相关接口(ImplJS
)是C++类Base
的一个JavaScript实现
在实行的粘合和编译过程后,你可以在JavaScript用如下方法运行接口
当C++代码中有指向Base
实例的指针并调用virtualFunc()
时,会到达上面定义的JavaScript代码
- 你必须在IDL中列举
JSImplementation
类(ImplJS
)的全部方法,不然编译会出错 - 你也需要在IDL文件中提供
Base
类的接口定义
Pointers and comparisons
所以绑定方法都应该接收包装好的对象(包含一个原始指针)而不是直接接收原始指针。通常你不需要处理原始指针(这些通常是简单的内存地址或者整数形式表示)。如果你需要处理原始指针,在编译好的代码中使用下面的函数会很有用
wrapPointer(ptr, Class)
- 接收一个原始指针(一个整数),返回一个包装好的对象
如果不传递
Class
参数,会被假定为root类(很有可能不是你想要的)
getPointer(object)
- 返回一个原始指针castObject(object, Class)
- 返回另一个类,但有相同指针的包装compare(object1, object2)
- 比较两个对象的指针
一个特定类的指针总有一个单独的包对象。这运行你在其他地方使用普通的JavaScript代码来给对象添加数据(比如object.attribute = someData)
compare()
应该代替指针的直接比较,因为有可能出现不同的包对象使用通一个个指针(一个类是另一个类的子类的时候)
NULL
所有返回指针、引用或者对象的绑定函数会返回一个包装好的指针。因为通过返回包,你可以获取输出并传递给另一个绑定方法,而且不需要检查参数的类型。
但是在返回NULL
指针时你可能会疑惑。在使用粘合时,返回的指针可能是NULL
(是一个全局实例,表示0的指针)而不是null
(JavaScript内建的null对象)或者是数值0
void*
void*
类型在IDL文件中使用VoidPtr
来表示,你也可以用nay
类型来表示。
两者的区别是VoidPtr
更像一个指针,可以用来获取一个包对象,而any
更像一个32位正式(这也是Emscripten编译后的原始指针的存在形式)
WebIDL types
WebIDL中的类型名和C++不完全相同,以下是一些常见类型的映射
C++ | IDL |
---|---|
bool |
boolean |
float |
float |
double |
double |
char |
byte |
char* |
DOMString (represents a JavaScript string) |
unsigned char |
octet |
unsigned short int |
unsigned short |
unsigned short |
unsigned short |
unsigned long |
unsigned long |
int |
long |
void |
void |
void* |
any or VoidPtr (see void*) |
WebIDL的详细文档可以参考W3C规范
Test and example code
test_webidl,其中涵盖了绝大部分情况。