首页 > 代码库 > ATL - JavaScript混合编程
ATL - JavaScript混合编程
JavaScript混合编程- ATL
最后更新日期:2014-5-10
环境:Windows8.1 64bit英文版,Visual Studio 2013 Professional Update1英文版
阅读前提:COM的基本概念
内容简介
ATL(ActiveTemplate Library)是微软为了简化COM编程提供的一套C++模板,这里介绍如何用ATL建立一个简单的轻量级COM服务供JavaScript脚本调用,使我们对ATL的使用有个概念。
Hello,World
使用Administrator账号启动VisualStudio否则注册COM服务会失败。
新建Solution命名为ATLTutorial1,[VisualC++]->[ATL Project]->[Application type:选择Dynamic LinkLibrary(DLL)]->[Support options:勾选Allow merging of proxy/stubcode]->[Finish],建立COM服务程序。
[Allow merging of proxy/stub code]选项,会把你的所有组件(二进制代码)打包到一个DLL文件中,proxy/stub指的是代理和存根。
你还需要为COM服务添加COM Class,[Class View]窗口中右键单击项目名称[ATLTutorial1]->[Add]->[Class]在弹出窗口中选择[ATL]->[ATLSimple Object]启动ATL Simple Object向导,在short name中输入HelloWorld, Threading model中选择Apartment,Aggregation选择No,Interface选择Dual,Support勾选Connection Points、IObjectWithSite(IE object support)两个选项,建立HelloWorld类。
这个向导[1]在IDL文件中暴露出你的HelloWorld类,[2]新建IHelloWorld类定义你的HelloWorld的接口[3]新建CHelloWorld类,实现你在IHelloWorld接口中定义的功能。在外部的客户程序是通过你在IDL文件HelloWorld类的uuid值来实例化你的类。
Interface选项
Interface的Dual选项,让你的Class继承IDispatch接口和自定义接口(CustomInterface),Dual即双接口的意思。IDispatch是由OLE自动化协议暴露出来的接口。Automation (IDispatch) 接口允许客户程序在运行的时候找出COM对象支持哪些属性和方法,并提供如何调用COM对象属性和方法的必要信息。由于客户程序编译的时候不需要知道COM对象的成员,这样脚本程序,比如说在IE(Internet Explorer)上运行的Java Script就可以调用你的COM对象了。 自定义接口(CustomInterface)使客户程序能够基于VTABLE调用你的对象,这比基于IDispatch的调用性能开销要少,下图是IDispatch接口的棒棒糖。
Threading Model(线程模型)
COM的核心是尽可能向客户程序hide对象的细节,客户程序只要知道如何调用COM对象就可以了,其中要hide的一个细节就是COM对象是否线程安全,所以Microsoft定义了Apartment(公寓)的概念来简化这个细节。
ThreadingModel(线程模型)Apartment(公寓)定义了COM对象的执行上下文,客户程序中的某根线程通过调用CoInitializa(或CoInitializeEx或OleInitialize)进入Apartment(公寓)初始化里面(全部) 的COM对象。COM要求客户,如果要通过接口指针调用COM对象中的方法,必须在Apartment(公寓)里进行。换言之,想要从当前线程中调用某个COM对象,要先用CoCreateInstance方法初始化这个COM对象, CoCreateInstance会根据你对象的线程模型(Threading Model属性)建立Apartment。一个Apartment(公寓)里可以有任意多个COM对象。
COM定义了两种Apartment(公寓),单线程公寓STA(Single-threadedapartment)和多线程公寓MTA(multi-threaded apartment)。
STA只允许一根线程访问你的COM对象所以是线程安全的,所以你COM对象的方法不会被多个线程同时访问,一个进程中可以有多个单线程公寓(STA),存放在里面的数据只会被单根线程访问。
MTA允许多线程访问你的COM对象,但是一个进程中只能有一个多线程公寓(MTA),存放在里面的数据会被多根线程调用。
Threading Model(线程模型)选择Apartment选项,指明我们的这个COM对象是线程不安全的,必须在初始化它的线程中调用,系统会为你的COM对象做好线程保护。但是,当多个进程调用你的DLL时,你得为COM对象的数据加锁了,这样才能防止数据同步问题。
Single or blank选项指示我们的COM类只能在客户的主线程中调用,你不需要为你的对象做同步工作,系统保证对你对象的调用时串行的。
Both选项告诉系统和客户我们的类是线程安全的,可以使用和客户程序一样的Apartment(公寓)类型,系统不会对你的对象进行线程保护,所以你必须为自己对象中的数据做好同步,由于避免了系统对整个对象做同步,所以提高了性能。
Free选项类似Both,但是要求系统把它放在多线程公寓中(MTA),放在MTA中的对象,它的方法可能会同时被多个线程调用。
Neutral选项告诉系统我们的Class要放在Neutral Apartment中。Neutral Apartment是COM+中出现的Apartment,使你的Class能被任意线程调用,而自己不需要做同步工作,但是一个进程中只能有一个Neutral Apartment。
ThreadingModel中常用的是Apartment选项和Both选项。
Support选项
Connecton Points选项,使你的COM类继承IConnectonPointImpl接口,并执行以下四个步骤,[1]在IDL(接口定义语言)文件中定义回调接口[2]使用ATL proxygenerator(代理生成器)创建proxy(代理)类[3]把创建的proxy(代理)类添加到你的COM对象[4]为你的COM对象connection point map(连接点映射),添加connection point(连接点)。
ISupportErrorInfo选项,让你的错误信息沿着调用路径往上正确传播,选择支持ISupportErrorInfo会让你的对象继承ISuportErrorInfoImpl接口,如果你要做个OLE Automation对象,必须继承这个接口进行差错处理。
IObjectWithSite(IE object support)选项,让你的对象能够在IE中,和它的容器(containersite)通讯,例如,Java Script调用了你写的COM对象,你的COM对象也能检索Java Script所在Web页面中的元素。
Aggregation选项
我们一般选择No,即不需要COM对象间的继承,如何使用Aggregation(聚合)可以参考下面的一篇资料。
《Aggregation explained》
http://www.codeproject.com/Articles/17455/Aggregation-explained
Attribute选项
一般不勾选它,即我们按照经典方式新建ATL对象,如果你经常需要编写ATL程序,可以研究下ATL属性(Attribute)编程,它通过一些关键词简化ATL 对象的开发,但是有Attribute属性的ATL对象间不能相互继承。
添加方法
在[Class View]中你可以看到向导为你生成了三个IHelloWorld,右键单击最上面的IHelloWorld,[Add]->[Add Function...]弹出添加方法窗口,添加SayHello方法,为方法添加“[in]BSTR msg”和“[out,retval] BSTR* result”两个参数,进入新添加的方法,并修改源代码(HelloWorld.cpp文件),修改后的样子如下。
#include<string>
usingnamespacestd;
// CHelloWorld
STDMETHODIMPCHelloWorld::SayHello(BSTRmsg,BSTR*result)
{
wstringhead= L"收到来自JavaScript的信息=>";
wstringcontent=head+OLE2W(msg);
*result=W2BSTR(content.c_str());
returnS_OK;
}
传入字符串不需要带指针类型(BSTR类型),但是返回字符串给调用者,需要变量类型为指针型,即BSTR*类型。
为了让IE不要弹出警告窗口,,还需要为我们的CHelloWorld类添加IObjectSafetyImpl接口继承。双击[Class View]窗口中的CHelloWorld打开HelloWorld.h文件,找到public IObjectWithSiteImpl<CHelloWorld>,在下面插入代码行
publicIObjectSafetyImpl<CHelloWorld,INTERFACESAFE_FOR_UNTRUSTED_CALLER|INTERFACESAFE_FOR_UNTRUSTED_DATA>,
找到“COM_INTERFACE_ENTRY(IObjectWithSite)”在下面插入
COM_INTERFACE_ENTRY(IObjectSafety)
按[F6]Build Solution,Visual Studio会自动注册你的ATL对象。
测试我们的第一个ATL对象
在[Class View]中双击你刚才新添加方法的IHelloWorld接口,打开ATLTutorial1.idl文件,这是COM对象的接口定义文件。 在“libraryATLTutorial1Lib”中找到COM对象类HelloWorld,coclass HelloWorld前面的uuid值就是HelloWorld对象在系统中的名称,我们在HTML中需要通过这个名称实例化这个对象。
IDL文件,library后面的ATLTutorial1Lib是我们ATL库的名称,在系统的COM对象列表中找到这个库,展开它后可以看见我们的COM类HelloWorld,进一步展开可以看到它继承了IHelloWorld接口。
右键单击[SolutionExplorer]中的Solution名称,为当前Solution添加test.htm文件,内容如下:
<HTML>
<HEAD>
<METANAME="GENERATOR"Content="MicrosoftVisual Studio">
<TITLE></TITLE>
</HEAD>
<BODY>
<objectclassid="clsid:9147B0AF-2579-4E4F-87B0-FA2CAD630DD0"id="objHelloWorld"name="objHelloWorld"></object>
<scriptlanguage="JavaScript"type="text/javascript">
function testSayHelloMethod(){
var a= objHelloWorld.SayHello("My name iskagula!");
alert(a);
}
</script>
<inputtype=buttonvalue="测试我们的第一个ATL对象的方法"onclick="testSayHelloMethod()"/>
</BODY>
</HTML>
如果你要不借助Wizard(向导)添加、删除或修改HelloWorld类的方法,只需要修改ATLTutorial1.idl、HelloWorld.h、HelloWorld.cpp这三个文件就可以了。ATLTutorial1.idl定义你的方法如何暴露给外部,HelloWorld.h声明你的方法,HelloWorld.cpp实现你的方法。
以后想要发布DLL,建议你在当前Solution中添加SetupProject,制作安装文件非常方便。
从HTML跟踪你的代码
在[SolutionExplorer]中选中ATL项目名称,[Alt]+[Enter]快捷键打开项目属性页,[Configuration Properties]->[Debugging]->[Debugger to Launch]中选择[WebBrowser Debugger],在[HTTP URL]中填入你htm的位置,如果测试文件test.htm在D分区的MyProject目录下,可以填“file:///D:\MyProject\test.htm”,按[F5]就可以从HTML文件跟踪你ATL对象的源代码了。
从C#项目中跟踪
在你ATL项目所在的Solution中添加C#的Win32控制台项目,为项目添加[COM]->[TypeLibraries]->[ ATLTutorial1Lib]的引用,其中ATLTutorial1Lib是我们ATL库的库名,紧跟在ATLTutorial1.idl文件的library关键词后面,修改Program.cs文件内容如下:
staticvoid Main(string[] args)
{
ATLTutorial1Lib.HelloWorld hw =new ATLTutorial1Lib.HelloWorld();
Debug.WriteLine("===>"+hw.SayHello("abc"));
Console.ReadKey();
}
打开C#项目的属性页,勾选[Debug]->[EnableDebuggers]->[Enable native code debugging]。
打开Solution属性页,[CommonProperties]->[Project Dependencies]设置你C#的项目依赖于你的ATL项目。
把C#项目设置为启动项目,按[F5]启动Debug,就可以调试你ATL对象的代码,如果ATL代码做了修改,VisualStudio会先build你的ATL项目,再启动你的C#程序。
ATL还需要注意下面几个Class的使用
CComPtr<T>
CComPtr<T>是ATL提供的智能指针,用来减少编写释放对象所需要的代码,使代码更加简洁,示例代码如下:
//建立spDoc智能指针
CComPtr<IHTMLDocument2> spDoc;
//sPDoc智能指针,得到对象
browser->get_Document((IDispatch**)&spDoc);
//使用spDoc智能指针
spDoc->get_Script(&pScript);
//代码段结束后,spDoc会自动释放所指向的对象,不需要你写其它额外的代码
CComBSTR
BSTR类型是ATL的字符串类型,用来同外部传递字符串。标准BSTR是一个有长度前缀和null结束符的OLECHAR数组,ATL 类 CComBSTR 提供对 BSTR 数据类型的包装。有些COM对象的方法调用要求传入CComBSTR类的字符串对象,但是我们的C++代码一般用std::wstring类型的字符串,下面是 std::wstring类型和CComBSTR类型对象的互转。
//std::wstring的实例转CComBSTR实例
CComBSTR bstrMember(::W2BSTR(name.c_str()));//std::wstringname
//或则用下面的方式转
BSTRbs2 = SysAllocStringLen(name.data(), name.size());
CComBSTR bstrMember(bs2);
//CComBSTR实例转std::wstring实例
CComBSTRbs(L"ddd");
std::wstringws(bs, SysStringLen(bs));
CComBSTR类在超出范围后会自动释放它所分配的内存。
CComVariant
VARIANT是个struct对象,虽然可以容纳不同数据类型,但是在同JavaScript的混合编程中,主要用于传递用户对象。 CComVariant则是VARIANT的封装。具体可以参考
《VARIANT 与 CComVariant 的使用》
http://blog.csdn.net/tangaowen/article/details/6553305
当调用者不知道COM对象方法的参数类型时往往可以使用CComVariant类型的参数代替。
Q 如何从ATL中调用JavaScript中的方法?参考下面的链接
《ATL回调JavaScript》
http://blog.csdn.net/lee353086/article/details/8853820
Q如何从ATL对象中返回数组?参考下面的链接
《从ATL中返回字符串数组到JavaScript的示例》
http://blog.csdn.net/lee353086/article/details/7557333
Q 如何在ATL中检索HTML中的元素?参考下面的链接
《ATL中对DOM中的元素进行枚举的例子》
http://blog.csdn.net/lee353086/article/details/9342353
Q 如何把ATL发布成CAB包?参考下面的链接
《VS2013编写ATL简单对象在PHP中使用》
http://blog.csdn.net/sjg20010414/article/details/20045179
参考资料
[1]《Active TemplateLibrary》
http://resources.esri.com/help/9.3/arcgisdesktop/com/COM/VCpp/ATLDiscussion.htm
[2]《ATL and MFC changesand fixes in Visual Studio 2013》
http://blogs.msdn.com/b/vcblog/archive/2013/08/20/atl-and-mfc-changes-and-fixes-in-visual-studio-2013.aspx
[3]《ATL COM DesktopComponents》
http://msdn.microsoft.com/en-us/library/t9adwcde.aspx
[4]《IE11 EnhancedProtected Mode 解决BHO与高权限进程通信问题》
http://blog.csdn.net/yangjian8915/article/details/11812303
ATL - JavaScript混合编程