借助动态代码生成技术在基于Webkit引擎的HTML5网页JS内调用易语

博客Markdown编辑器上线啦那些年我们追过的Wrox精品红皮计算机图书PMBOK第五版精讲视频教程火星人敏捷开发1001问

借助动态代码生成技术在基于Webkit引擎的HTML5网页JS内调用易语言函数

分类:

作者:庄晓立(Liigo)

日期:2015年3月3日夜

原创链接:

版权所有,转载请注明出处:

前两天我协助解决了一个技术问题,在此稍作记录和总结。

具体来说,就是在使用基于Webkit引擎的封装组件wke的过程中,需要把一个易语言函数注册给JavaScript引擎,让它可以在网页里被调用(就像在网页里调用普通JavaScript函数一样)。如果能做到这一点,就基本实现了从JavaScript传递参数到易语言、易语言返回值给JavaScript的双向沟通机制,以后有广泛的应用空间。

在整体思路上,还是蛮简单的。因为wke已经提供了颇为直观的接口函数(虽然严重缺乏文档):

#define JS_CALL __fastcalltypedef jsValue (JS_CALL *jsNativeFunction) (jsExecState es);WKE_API void jsBindFunction(const char* name, jsNativeFunction fn, unsigned int argCount);WKE_API void jsBindGetter(const char* name, jsNativeFunction fn); /*get property*/WKE_API void jsBindSetter(const char* name, jsNativeFunction fn); /*set property*/WKE_API int jsArgCount(jsExecState es);WKE_API jsType jsArgType(jsExecState es, int argIdx);WKE_API jsValue jsArg(jsExecState es, int argIdx);……

这里面最核心的函数是 jsBindFunction(),调用它就能注册一个新的JavaScript函数,只需提供函数名、实现回调函数、参数个数。在回调函数内部,通过 jsArgCount/jsArgType/jsArg 读取js传进来的参数,通过其他一些接口函数创建js值对象,都是一目了然的事情,这都不是事儿。

回调函数(fastcall)

首先卡在该回调函数的调用约定上:jsBindFunction的第二个参数,要求是 fastcall 调用约定的回调函数!可是易语言编译器根本就不支持编译生成fastcall调用约定的函数呀(仅支持stdcall)。fastcall 约定通过寄存器 ecx 和 edx 传递前两个参数,其余参数按照从右向左(从后往前)的顺序压栈,被调用者负责清理、平衡栈。这跟stdcall有一些类似但又明显不同。如果不管三七二十一盲目传递 stdcall 调用约定的回调函数进去,程序运行时非崩溃不可。

那怎么办呢?易语言编译器不支持fastcall,我们只好自食其力,纯手工生成二进制X86机器指令,人肉编译生成符合fastcall调用约定的回调函数。该函数声明的原型是:jsValue (__fastcall *jsNativeFunction) (jsExecState es),唯一个参数可从 ecx 寄存器中读取,没有入栈的参数,因而也不用平衡栈,直接 ret 就完事了。为了方便起见,我们引入两个易语言编写的函数:代理函数和用户函数,其中代理函数负责JS和易语言的类型转换,用户函数负责具体的执行逻辑,这两个函数毫无疑问都只能是stdcall调用约定(易语言编译器也不支持别的什么约定嘛)。下面设计我们的回调函数结构,以伪汇编代码来表示:

PUSH 用户函数地址PUSH ecxMOV eax, 代理函数地址CALL eaxRET这些伪汇编代码,要是用易语言写的话,其实就是一句话:返回(代理函数(es,用户函数))。(注:参数es是JavaScript引擎通过ecx寄存器传递进来的透明数据。)

易语言代码固然是简单,但因为编译器的限制,我们不能这么写。汇编代码稍微复杂一点,但我们仍然不能直接嵌入汇编(易语言编译器不支持)。只能手写机器码!把Intel指令集手册拿出来,查表,开工。既然是动态生成代码,当然需要先申请一块内存,然后把机器码填进去,然后把这块内存的首地址返回——这个内存的首地址也就是我们人肉编译生成的符合fastcall调用约定的回调函数的首地址。具体代码如下:

代理函数(stdcall)和用户函数(stdcall)

前面提到的代理函数,是一个很普通的易语言函数(stdcall),它负责解读JavaScript传递进来的参数,转换成易语言数据类型,转调易语言版的用户函数(也是stdcall),最后再把易语言用户函数的返回值转换为JavaScript类型后返回给JavaScript引擎。它接收两个参数,都是我们前面手工生成的回调函数传递进去的。代码如下:

代理函数的返回值是长整数型,也就是64位整数。根据 jsValue 的定义,它是64位指针,恰好可以用易语言的长整数表示。

JavaScript文本确定是UTF-8编码,转换到易语言文本之前,最好先执行编码转换(UTF-8 => GB18030),否则中文乱码。这一步骤非常简单,就作为课后作业吧。

我们完全可以改进这个代理函数,或者写另外一个代理函数,用于支持不同类型的用户函数(例如不同的参数类型和参数个数以及返回值类型)。

剩下的用户函数就更简单了,下面只是一个常规的示例(后面的测试代码就用到此函数):

把易语言函数注册为JavaScript函数

动态生成一个回调函数,作为参数传递给jsBindFunction即可:

函数调用次序总结快乐要有悲伤作陪,雨过应该就有天晴。

借助动态代码生成技术在基于Webkit引擎的HTML5网页JS内调用易语

相关文章:

你感兴趣的文章:

标签云: