如何使用aardio更好的玩耍electron
这个支持库我仅写了15kb的体积,aardio小到极致,electron大到极致,但没有关系,aardio拥抱一切。
使用aardio嵌入electron的一些优势:
1、chromium + node.js这一系列方案中,electron 是其中做的最好的,至少耍CEF这些东西好几十条大街。
2、chromium 在网页呈现是做的最好的,但是JS与系统交互的通力非常弱,不能直接调用C/C++的接口函数,也无法直接调用系统API,有时候其他语言很简单的事,但用JS非常麻烦,这在Web上没有问题,但在桌面开发上是硬伤,这方面恰恰是 aardio可以优势互补的地方。
3、electron带了一大堆DLL,有60多个DLL,不知道怎么想的没有搞个子目录,electron里写软件,始终都是用 electron.exe启动,修改图标、版本信息都比较痛苦,如果使用 aardio嵌入,这都不是问题,我把 electron.exe 改名为了 electron.dll ,就想当于多了个DLL.,而且exe文件也不用放到一大堆DLL一起。
4、aardio提供了全自动下载,安装electron运行时的功能,几句代码写一个EXE成为可能。而且大家可以共享公共的运行时,体积大就不再是问题(当然,也可以自带electron),这就是说你只要几百KB的EXE就可以扔给别人用,而且可以使用 electron,顶多就是用户第一次使用下载一下,这也并不是问题,难道从你自己这里下载不是下载?!这种模式更方便。
5、electron体积非常大,而aardio小到极致,而且electron 不仅仅是自己大,添加模块,模块依赖模块,模块的安装、编译都是非常痛苦的事,但 aardio很多东西就是几句代码就可以做到,这样可以避免开发软件时体积进一步的膨胀。
6、在aardio中嵌入以后,可以全部被转换到最简单的单线程异步编程。aardio可以方便的远程调用electron中的JS函数,当然JS函数也可以选程调用aardio函数,交互调用非常方便。虽然electron是用C++写的,你用C++控制 electron,可能还没有用 aardio去控制更方便。
7、这个支持库还会提供一个嵌入式的HTTP服务器,WebSocket服务器,随机分配端口,不会与其他软件冲突,aardio,js都可以自动获取这个随机的服务器URL,所有事情后台全部做好。
那么嵌入electron的缺点也简单说一下:
1、无论electron做的多好,这种臃肿的架构,无法替代小轻快的原生桌面应用。
2、electron不支持XP,我在WIN2008上试了一下都不支持,只能支持WIN7,WIN2008 R2以上的系统,这倒没什么问题,这部份陈旧的操作系统在中国的市场份额也只有10%,你能让 90%的用户使用你的软件都几乎是不可能了,更何必在乎那 10%在用着老旧系统几乎无利可图的平台呢?!
3、体积大! 如果你正好就是喜欢大,那就不是问题。
4、使用JS你的代码是没有秘密的,这不像aardio十年来无人能反编译源码,而且关键是aardio嵌入C,C++组件是非常方便的。但这对JS是硬伤。
在分享这个支持库的用法以前,先简单谈一谈我是怎么实现这个功能的,大家可以看看,我没写几句代码就轻松的降服 electron。
首先第一步,我想到的是把electron的主窗口嵌入到aardio窗口中,这看起来很简单,但是 electron却并不太高兴不愿意让我们这么干,而这个一定要在创建窗口的时候去设置,不然窗口显示出来,再让他嵌入,这个体验就非常不好了。
那么,首先我用C语言写了一个假的CreateWindowEx函数,为了方便直接在aardio中编译这个DLL,这个C语言的源码以及编译代码我已经放到了标准库里,文件位置: ~\lib\electron\.build\CreateWindowExHook.aardio ,然后在 aardio中用如下代码启动electron并注入DLL钩子:
import process.apiHook;
var prcs = process.apiHook( "\electron\electron.exe" );
//先给electron安装钩子
var hookInfo = prcs.install("User32.dll","CreateWindowExW","CreateWindowExHook.dll","_CreateWindowExHook@48");
//调用外部进程中的API函数
SetCreateWindowExPtr = prcs.process.remoteApi("void(addr addrTrampoline,addr hwndParent)","CreateWindowExHook.dll","SetCreateWindowExPtr","cdecl")
SetCreateWindowExPtr(
hookInfo.addrTrampoline,//这是真正的CreateWindowEx函数指针地址
winform.hwnd
);
//然后让electron进程继续运行
prcs.resume();
这样我们就通过API钩子轻松的拿到了 electron创建的窗口句柄,在electron创建主窗口的时候,就强行改掉他的样式,并将他捉进aardio窗口内。
第二步,就考虑传一些数据给 electron了,electron没有提供方法让你可以干这事,不过electron可以取环境变量,于是我们可以拿这个做文章,环境变量不仅仅可以放字符串哦,完全可以把对象序列化为JSON,再到 electron里反序列化回来,当然,这种事情我全给写好了,大家现在可以直接传对象过去。
第三步,考虑怎么让aardio与electron交互调用,electron可不仅仅是多线程,他已经是多进程了,他自己交互就很吃力了,压根没有考虑为其他语言提供可能性,怎么办呢,JS可以使用WebSocket,那么可以通过WebSocket实现RPC,然后找了半天node.js的一些WebSocket/RPC的库用了都不理想,依赖库一堆,安装麻烦,用起来有的还没有反应,这不符合我拎上几句代码走江湖的惯性思维。关键是这些JS实现的RPC都长这风格:
var startEnviron = require('startEnviron');
var wsRpc = require('json-rpc-ws');
var client = wsRpc.createClient();
client.connect(startEnviron.wsUrl, function connected () {
client.send('hello', ['JS传过来的参数1', 'JS传过来的参数2'], function mirrorReply (error, reply) {
client.send('aardio.print', [reply]);
});
});
其实这让人感觉更像是一个WebSocket加JSON自动转换器。
我希望可以像在aardio中那样更方便的调用RPC函数,例如可以这样写:
client.hello('JS传过来的参数1', 'JS传过来的参数2')
这样写起来方便,可读性也好的多,但是没有办法,JS不能愉快的实现这样的效果。
即然JS解决不了这个问题,那就发挥我一惯的方法了,用最粗鲁的方式解决问题。首先我在aardio中制定了一个aasdl这个接口描述语言( http://bbs.aardio.com/doc/aasdl/ ),然后我首先改进aardio的RPC模块支持aasdl,然后我决定不找那些node.js模块了,我觉得真是太大了,几句代码能搞定的事,为什么要搞那么多模块,怪不得他们把electron这个东西搞的这么大体积。
然后我在node.js中写了一个模块,在连接RPC服务器以后,立即获取aasdl,并转换为JS中的函数,最后一切麻烦的细节都被这个几十行的代码搞定了。然后在 electron中可以这样写JS
aardio = require("aardio");
//调用aardio中的函数,
aardio.test.hello( "你点击了" + e.target.innerHTML )
//调用aardio中其他对象的成员函数
aardio.test.hello( "你点击了" + e.target.innerHTML )
并且,你不需要安装 aardio这个JS模块,aardio会自动的把他复制到正确的位置,aardio从来不会麻烦用户去装任何东西。
好吧,该切入正题了,下面我们上调用 electron的演示源码( 你可以在新版 aardio范例-> Web应用 -> electron 找到这个示例)
import win.ui;
/*DSG{{*/
var winform = win.form(text="在aardio中嵌入electron";right=1278;bottom=767;bgcolor=16777215;clipch=1)
winform.add()
/*}}*/
import electron.runtime;//如果希望自动安装electron到系统目录,请先导入
import electron.app;//导入electron扩展库
var app = electron.app(winform);//创建electron进程
//可以直接在这里写electron加载主进程的main.js启动文件
app.jsMain =/**
const aardio = require('aardio') //导入aardio模块
const {app, BrowserWindow} = require('electron')
let win
function createWindow () {
win = new BrowserWindow( aardio.startEnviron.browserWindow)
win.loadURL(aardio.startEnviron.indexUrl)
win.webContents.openDevTools()
win.on('closed', () => {
win = null
})
}
app.on('ready', createWindow)
app.on('window-all-closed', () => {
app.quit();
})
**/
//可选在这里直接指定index.js的代码,实际开发请写到工程文件里
app.html = /**
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>aardio嵌入electron演示</title>
</head>
<body>
<h1>aardio嵌入electron演示!</h1>
<button id="aardio">点这里调用aardio代码</button>
<?
response.write("在electron中执行aardio代码")
?>
</body>
<script type="text/javascript">
aardio = require("aardio");
//响应按钮点击事件
document.querySelector("#aardio").onclick = function(e){
//调用aardio中的函数,hello要调用的函数名字,后面可以跟任意个调用参数
aardio.ex.hello( "你点击了" + e.target.innerHTML ).then(
function(message){
alert(message);
}
)
}
//aardio服务端发起调查任务
aardio.on("getUrl",function(){
return document.location.href;
});
</script>
</html>
**/
/*
在下面的external对象中指定允许electron渲染进程中使用JS直接调用的函数
下面的external 会直接转换为js中的aardio对象,在JS中require('aardio')就可以使用
*/
app.external = {
hello = function(...){
winform.msgbox( web.json.stringify({...},true,false) )
return "electron,我是aardio,一起玩怎么样!?"
}
ex = {
hello = function(...){
winform.msgbox( web.json.stringify({...},true,false) )
return "electron,我是aardio,你最近过的还好吗?!"
}
}
}
/*
启动electron,下面使用一个aardio表对象指定的启动参数,
在electron的主进程、渲染进程中都可以直接通过 aardio.startEnviron 访问。
*/
app.start(
//electron打开的第一个页面,必须指定应用程序目录下的aardio代码文件
indexUrl = "/res/main.aardio";
//指定electron创建浏览器窗口的启动参数
browserWindow = {
width = 800;
height = 600;
frame = false;
resizable = false;
center = false;
titleBarStyle = "hidden";
webPreferences = {
nodeIntegration = true;
}
}
);
//调查任务结束,JS代码汇报结果到这里
app.callback("getUrl",function(hSocket,result,err){
winform.msgbox(result,"打开了页面")
})
winform.setTimeout(
function(){
//对所有页面发起调查任务,
app.survey("getUrl");
},2000
)
winform.show()
win.loopMessage();