作者:yurunsun@gmail.com 新浪微博@孙雨润 新浪博客 优快云博客日期:2012年11月2日
上一节一起搭建了Windows JS开发运行环境,并演示了一些实用的小例子,下面一起研究如何实用脚本的内置对象和任意COM组件。
1. WSH内置对象模型
WSH对象模型由一下几个对象组成:
- WScript
WshArguments
- WshNamed
- WshUnnamed
WshController
- WshRemote
WshNetwork
WshShell
- WshShortcut
- WshUrlShortCut
- WshEnvironment
- WshSpecialFolders
- WshScriptExec
对象的详细文档可以猛击此处下载。看起来很高深的样子,其实对我们有用的只有几个对象,下面挑出有用的对象以及对象的属性方法一个个解释,其余请查阅文档。
1.1 WScript 对象
WScript是WSH根对象,不需要在调用其属性和方法之前进行实例化,始终可在任何脚本文件中使用。
-
CreateObject 方法
WScript.CreateObject(strProgID[,strPrefix])
strProgID就是COM技术中的术语ProgID,暂时把它理解成对象的名字。第二个位于中括号内的是可选参数,这两个参数将在后面COM组件一并解释。
现在我们只需要知道,这个方法就是简单的创建一个名字是strProgID参数的对象。例如:
var oIE = WScript.CreateObject("InternetExplorer.Application"); //创建IE对象 var oWordApp = WScript.CreateObject("Word.Application"); //创建MS-Word对象 var oWordDoc = WScript.CreateObject("Word.Document"); //创建MS-Word文档对象 var oWMP = WScript.CreateObject("WMPlayer.OCX"); //创建WindowsMediaPlayer播放器对象 var oQQMusic = WScript.CreateObject("QzoneMusic.MusicPlayer"); //创建QQ控件音乐播放器对象 var o360yun = WScript.CreateObject("CloudShell.ShellContextMenu"); //创建360云盘右键菜单对象
返回值就是创建好的对象,至于这个对象定义了哪些属性和方法,我们暂时可以通过在IDE中利用提示名字猜一下,当然这不是正常途径;正确的方法还是放在下边COM组件部分解释。
-
Echo 方法
WScript.Echo [Arg1] [,Arg2] [,Arg3] ...
将文本输出到消息框中(WScript.exe)或命令控制台窗口(CScript.exe)。
-
Sleep 方法
Sleep(intTime)
运行脚本的线程被挂起,释放它所占用的 CPU。超过指定的时间间隔后,脚本继续执行。如果运行异步操作和多过程,或者您的脚本中包括由事件触发的代码,Sleep 方法就尤其有用;事件通知稍后解释。
-
Arguments 属性如同C语言main函数传入命令行参数一样,从Arguments中可以读到脚本执行时的参数:
objArgs = WScript.Arguments; for (i = 0; i < objArgs.length; i++) { WScript.Echo(objArgs(i)); }
-
StdIn/StdOut 属性 (CScript.exe环境专用)返回一个标准输入/输出流的对象.
var stdin = WScript.StdIn; var stdout = WScript.StdOut; while (!stdin.AtEndOfStream) { var str = stdin.ReadLine(); stdout.WriteLine("Line " + (stdin.Line - 1) + ": " + str); }
1.2 WScript.WshShell 对象
提供对本地 Windows 外壳程序的访问。创建方式:
var sh = WScript.CreateObject(WScript.Shell);
-
AppActivate 方法
激活应用程序窗口。在确定要激活哪个应用程序时,指定的标题将与正在运行的每个应用程序的标题字符串相比较。如果不存在完全匹配的标题,则将激活标题字符串以 title 开头的所有应用程序。如果还是找不到任何应用程序,则将激活标题字符串以 title 结尾的所有应用程序。如果存在多个名为 title 的应用程序实例,则将随机激活一个实例。
var WshShell = WScript.CreateObject("WScript.Shell"); WshShell.Run("calc"); WScript.Sleep(100); WshShell.AppActivate("Calculator");
-
Popup 方法
在弹出式消息框中显示文本。弹出的button、logo对应的数字可以在上边提供下载的文档中找到。
var WshShell = WScript.CreateObject("WScript.Shell"); var BtnCode = WshShell.Popup("Do you feel alright?", 7, "Answer This Question:", 4 + 32); switch(BtnCode) { case 6: WScript.Echo("Glad to hear you feel alright."); break; case 7: WScript.Echo("Hope you're feeling better soon."); break; case -1: WScript.Echo("Is there anybody out there?"); break; }
-
Run 方法
object.Run(strCommand, [intWindowStyle], [bWaitOnReturn])
strCommand 表示要运行的命令行的字符串值,必须包括要传递到可执行文件的所有参数。intWindowStyle表示程序窗口外观的整数值,bWaitOnReturn 可选。布尔值,表示在继续执行脚本中的下一条语句之前,脚本是否等待执行完程序。
var WshShell = WScript.CreateObject("WScript.Shell") var ret = WshShell.Run("notepad " + WScript.ScriptFullName, 1, true);
-
SendKeys 方法
将一个或多个键击发送到活动窗口(模拟按键),制作外挂、自动登录的利器。各个特殊键盘的名字可以在文档中查阅。
2. “当我们CreateObject的时候,我们Create了什么
在介绍WScript.CreateObject(strProgID)
时提到了传入的参数就是COM技术中的术语ProgID,而Create出来的对象就是COM组件的一个实例。先来重新回顾一下调用这个函数创建对象的例子:
var oIE = WScript.CreateObject("InternetExplorer.Application"); //创建IE对象
var oWordApp = WScript.CreateObject("Word.Application"); //创建MS-Word对象
var oWordDoc = WScript.CreateObject("Word.Document"); //创建MS-Word文档对象
var oWMP = WScript.CreateObject("WMPlayer.OCX"); //创建WindowsMediaPlayer播放器对象
var oQQMusic = WScript.CreateObject("QzoneMusic.MusicPlayer"); //创建QQ控件音乐播放器对象
var o360yun = WScript.CreateObject("CloudShell.ShellContextMenu"); //创建360云盘右键菜单对象
相信大家好奇的是:
- 为什么在js中根据一个字符串就能得到一个IE的对象,甚至能调用IE的方法去浏览网页、前进、后退?
- 这个对象和真正的
C:\Program Files (x86)\Internet Explorer\iexplorer.exe
有什么联系和区别? - 如何查看这个对象的全部方法、属性?
- 只能调用方法访问属性吗?网页加载完成、网页加载失败404这些通知如何得到?
- 还有哪些类似的对象,再哪里能得到所有可用对象名字及其详细信息?
- 自己能否创建这样的对象,供js调用?
接下来会一一解释这些问题。
2.1 什么是COM组件
考虑一下几个问题:
- QQ的新闻弹窗、YY的活动中心弹窗,都是窗口内嵌浏览器。这个浏览器是每个公司都要重新开发吗?
- 假如QQ有一个屏蔽关键词的模块,用户安装了QQIM和QQTalk,这个模块如何共用?
- 之前用C++开发的模块现在用C#还能可以调用么,脚本可以调用么?
第一个问题听起来与dll动态链接库有点儿类似,直观的想法是Windows开发一个浏览器dll,应用程序加载dll便可以调用其提供的方法。问题是调用者如何得知该dll提供的方法名字、参数、返回值?恐怕只能依赖文档解决;如果说第一个问题是由于不同公司交流不便,那么第二个问题(这个是虚构的)中同一家公司如何做到组件复用呢?QQTalk要把路径设置到QQ安装目录下了吗?至于第三个问题就更有些棘手了。
20年前微软为了解决这些问题,提出了对象复用的一套标准 —— COM (Component Object Model),这里的Object不同于OOP语言中的对象概念,而是建立在二进制可执行文件基础上的。这套标准是概括起来是这样的:
-
用一种类似协议的伪代码方式,描述调用接口的名字、出入参数、返回值:
interface IWebBrowser2 : IWebBrowserApp { [id(0x000001f4), helpstring("Navigates to a URL or file or pidl.")] HRESULT Navigate2( [in] VARIANT* URL, [in, optional] VARIANT* Flags, [in, optional] VARIANT* TargetFrameName, [in, optional] VARIANT* PostData, [in, optional] VARIANT* Headers); [id(0x000001f5), helpstring("IOleCommandTarget::QueryStatus")] HRESULT QueryStatusWB( [in] OLECMDID cmdID, [out, retval] OLECMDF* pcmdf);
-
每个COM组件实现若干个接口,从一个接口可以转到另一个接口
-
每个COM组件向操作系统注册自己的接口信息,以便使用者获得自己的资料。可以使用外部工具:
C:\>regsvr32.exe dllname.dll
-
每个COM组件用全球唯一的ID(CLSID)标识自己,并提供工厂创建方法创建自身。除了CLSID,还允许使用ProgID为自己命名,二者一一对应
- 每个COM组件使用引用计数控制生存期
- 为了支持跨语言互调(自动化技术),实现一个特定的接口
IDispatch
。在这个接口中,参数使用特定的VARIANT
结构体作为弱类型变量,调用函数统一经过一个特定的方法Invoke
再根据方法名字转发 - 为支持反向事件通知(COM回调用户代码),实现一个特定的接口
IConnectionPoint
作为连接点
2.2 问题的答案
对什么是COM有一个大概的了解后,之前提出的问题会一一有答案了。
-
为什么在js中根据一个字符串就能得到一个IE的对象,甚至能调用IE的方法去浏览网页、前进、后退?
答:因为IE这个COM组件实现了自动化。首先创建了语言无关的二进制对象;然后实现了
IDispatch
接口,将不同语言的函数调用转移到Inovke
中根据方法名字再次分发;然后在操作系统注册了自己的接口描述,使得IDE能够提示可供调用的方法名称 -
这个对象和真正的
C:\Program Files (x86)\Internet Explorer\iexplorer.exe
有什么联系和区别?答:iexplorer.exe是一个可执行程序,由多个COM组件构成。
InternetExplorer.Application
是其中一个 -
如何查看这个对象的全部方法、属性?
答:对于已经注册的COM组件,我们可以通过注册表读取键值,但更实用的是使用操作系统提供的查看工具oleview.exe。我的位于
C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\oleview.exe
目录下. 工具界面截图:
-
只能调用方法访问属性吗?网页加载完成、网页加载失败404这些通知如何得到?
答:对于可连接对象,我们能够做到让COM组件回调用户的代码。而WSH已经把一切做好,在js中我们按照如下方式处理。还记得
WScript.CreateObject(strProgID[,strPrefix])
的第二个可选参数strPrefix。文档上是这样描述的:该对象的输出接口将连接到脚本文件。事件函数由该前缀和事件名称组成。如果您创建对象时未提供 strPrefix 参数,则仍可通过 ConnectObject 方法同步发生在该对象上的事件。当对象引发事件时,WSH 将调用在事件名称开头附加了 strPrefix 的方法。
以IE组件为例,查阅oleview.exe中Internet Explore组件的
DWebBrowserEvents/DWebBrowserEvents2
接口或者MSDN文档我们能看到很多事件通知,我们从中选取有代表性的3个:[id(0x000000fb), helpstring("A new, hidden, non-navigated WebBrowser window is needed.")] void NewWindow2([in, out] IDispatch** ppDisp, [in, out] VARIANT_BOOL* Cancel); [id(0x000000fc), helpstring("Fired when the document being navigated to becomes visible and enters the navigation stack.")] void NavigateComplete2([in] IDispatch* pDisp, [in] VARIANT* URL); [id(0x00000067), helpstring("Fired when application is quiting.")] void Quit([in, out] VARIANT_BOOL* Cancel);
绑定事件的代码如下:
var objIE = WScript.CreateObject("InternetExplorer.Application", "objIE_"); objIE.Visible = true; var pending = true; while (pending){ WScript.Sleep(100); } function objIE_NewWindow2(pDisp, Cancel){ WScript.Echo("You've tried to open a new window..."); } function objIE_NavigateComplete2(pDisp, URL){ WScript.Echo("You just navigated to", URL); } function objIE_DocumentComplete2(pDisp, URL){ WScript.Echo("Document complete of", URL); } function objIE_OnQuit(){ WScript.Echo("You just quit "); pending = false; }
-
还有哪些类似的对象,再哪里能得到所有可用对象名字及其详细信息?
答:在oleview.exe中可以查看本机注册的所有COM
-
自己能否创建这样的对象,供js调用?
答:遵守COM的协定,可以非常方便的使用VC/VB/C#等语言创建二进制兼容的COM组件,供js调用