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

作者:庄晓立(Liigo)

日期:2015年3月3日夜

原创链接:http://blog.csdn.net/liigo/article/details/44045177

版权所有,转载请注明出处:http://blog.csdn.net/liigo

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

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

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

#define JS_CALL __fastcall
typedef 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 ecx
MOV eax, 代理函数地址
CALL eax
RET

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

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

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

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

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

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

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

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

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

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

函数调用次序总结

到了该总结一下的时候了:我们借助动态代码生成技术,在运行时生成一个符合fastcall调用约定的回调函数(jsNativeFunction),通过jsBindFunction将其注册到Javascript引擎,同时赋予其一个JavaScript函数名。网页脚本调用此JS函数时,回调函数被调用,进而回调函数又调用了代理函数,代理函数又调用了用户函数,用户函数返回后,返回值又被逐层返回给JS引擎。

测试代码

首先注册一个测试用的JS函数,易语言代码:注册JS函数("plus1",用户函数示例)。

再先来一段HTML,加载到浏览器中:

<a href='#' onclick="document.getElementById('result').value=plus1('liigo');">link</a>
<p>
<textarea rows='6' cols='36' id='result'>hello</textarea>

当点击网页中的链接时,之前注册的JS函数 plus1 将被执行,进而易语言函数 用户函数示例 被调用。易函数返回的文本,成了 plus1 的返回值,最终输出到网页内的编辑框中。如果编辑框中文本显示为“liigo hohoho”,说明测试成功。

写在最后的思考

能否将前面的实现方案用易语言内置函数 “置入代码” 替换?我(Liigo)想,至少有两个阻力妨碍我们在此应用置入代码:1、置入代码只能作用于已经存在的函数,而不能运行时动态生成新的函数;2、置入代码是编译时行为,置入的代码不能包含可变量(例如上文的 CALL eax 恐怕就行不通)。勉强应用置入代码,也不是不行,只是会让每一个用户函数都非常复杂,既包含了置入代码,又包含了JS和易语言的类型转换,还包含了业务逻辑,没有任何封装性可言,易用性约等于零。

全文完。谢谢收看!知道我是谁吗?

大名鼎鼎的御前四品带刀护士!

时间: 2024-10-04 13:58:03

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

基于V8引擎的C++和JS的相互交互

基于什么原因略! 1. 脚本引擎的基本功能 V8只是一个JS引擎.去除它的特点功能出处,它必须要实现JS引擎的几个基础功能: 脚本执行: 脚本可能是一个表达式:一段js代码:或者一个文件 执行表达式返回js表达式对应的值 C++来取设JS的内容 获取JS内容的数据(包括基础数据类型.数组.日期等).对象(类的一个实例).类或函数 设置JS内容的数据 JS来取设C++的内容 C++为js动态添加类(例如:Date,可以通过new Date()来创建任意多个对象) C++为js动态添加全局对象(例如

基于webkit开发分享

因项目需要,需要将B/S上项目以桌面版的形式呈现,并实现控制操作系统锁屏功能,为此只有将其以类似于.NET的WebBrowser控件的方式嵌入winForm,但WebBrowser采用IE内核引擎,对HTML5以及CSS3尚不支持,同时受限于操作系统以及版本限制.为此必须转换思路,通过资料查询,主流浏览器很多,其解释引擎自然也不同,兼容性很好的Chrome采用webkit,FireFox采用GECKO,这两款引擎是我了解目前市场上兼容性比较好的解释引擎,虽说对html5支持程度并不全面,但对于项

基于JDK的动态代理技术详解

虽然对于Spring的基本思想Aop是基于动态代理和CGlib这一点很早就有所认识,但是什么是动态代理却不甚清楚.为了对Spring加深理解,我觉得好好学习一下java的动态代理是非常有必要的. 静态代理 在学习动态代理之前我先花一点时间了解一下静态代理,从静态代理出发了解代理到底是怎么一回事,以及了解静态代理的局限性,进而明白为什么要发展及使用动态代理技术. 相信使用过Spring框架的同学都知道Spring利用Aop完成声明式事务管理以及其他的代理增强,也就是在方法执行前后加上一些譬如时间.

MySQL技术内幕-InnoDB存储引擎-读书笔记(二)

MySQL技术内幕-InnoDB存储引擎-读书笔记(二) 作为php开发,使用mysql总是少不了的 系列文章博客链接 http://itsong.net/articles/466.html 第三章 文件 mysql与innodb几个类型的文件 参数文件,配置路径.初始化参数.内存大小等 日志文件,包括错误日志,二进制日志,慢查询日志,查询日志 socket文件,用unix域套接字,unix domain socket来进行连接时需要的文件,这一般是本机连接,比通常tcp快 pid文件,进程id

iOS中 动态热修补技术JSPatch 韩俊强的博客

所谓动态热修补就是把能够导致app 崩溃的严重bug,提交新版本到appstore 审核速度太慢影响用户使用,这时候就可以利用 JSPatch 可以让你用 JavaScript 书写原生 iOS APP.只需在项目引入极小的引擎,就可以使用 JavaScript 调用任何 Objective-C 的原生接口,获得脚本语言的优势:为项目动态添加模块,或替换项目原生代码动态修复 bug. 这里就不在赘述优缺点重点看实现! 每日更新关注:http://weibo.com/hanjunqiang  新浪

Impala中的代码生成技术

Cloudera Impala是一种为Hadoop生态系统打造的开源MPP(massive parallel processing)数据库,它主要为分析型查询负载而设计,而非OLTP.Impala能最大限度地利用现代硬件和高效查询执行的最新技术.LLVM下的运行时代码生成就是用来提升执行性能的技术之一. LLVM简介 LLVM是一个编译器及相关工具的库(toolchain),它不同于独立应用式(stand-alone)的传统编译器,LLVM是模块化且可重用的.它允许Impala这样的应用在运行的

《mysql技术内幕 InnoDB存储引擎(第二版)》阅读笔记

一.mysql架构 mysql是一个单进程多线程架构的数据库. 二.存储引擎 InnoDB: 支持事务 行锁 读操作无锁 4种隔离级别,默认为repeatable 自适应hash索引 每张表的存储都是按主键的顺序记性存放 支持全文索引(InnoDB1.2.x - mysql5.6) 支持MVCC(多版本并发控制)实现高并发 MyISAM: 不支持事务 表锁 支持全文索引 三.InnoDB体系架构 1.后台线程 Master Thread 负责将缓冲池中的数据异步刷新到磁盘,保证数据的一致性 IO

Spring AOP高级——源码实现(1)动态代理技术

在正式进入Spring AOP的源码实现前,我们需要准备一定的基础也就是面向切面编程的核心——动态代理. 动态代理实际上也是一种结构型的设计模式,JDK中已经为我们准备好了这种设计模式,不过这种JDK为我们提供的动态代理有2个缺点: 只能代理实现了接口的目标对象: 基于反射,效率低 鉴于以上2个缺点,于是就出现了第二种动态代理技术——CGLIB(Code Generation Library).这种代理技术一是不需要目标对象实现接口(这大大扩展了使用范围),二是它是基于字节码实现(这比反射效率高

基于CrossApp引擎和ghost博客系统的APP

技多不压身,即使没有用,也能装个逼. 开发这款APP,其实并不是给别人用的,更多的是给自己用.一直以来都是为APP开发数据接口,对APP开发并不了解,只知道业务需求需要什么数据,我提供什么接口来调取数据.如果止步如此,对技术的提升是没有帮助的. APP并不难,但也不简单,这款APP从学习CrossApp引擎到APP完成,总花掉了12天时间.并且还未对android适配调整,不过我也不打算适配了.android虚拟机实在太卡,还时不时的不响应(人生哲学:自身的发展,受限于当时环境影响). Cros