原作者 David Marcionek.
翻译 免费打工仔
这个教程可以帮助你快速开发一个ActiveX控件。其中将要讲解关于ActiveX开发的一些基础概念,诸如方法(method)、属性(propertiy)和事件(event),以及ActiveX控件和网页之间的通讯方法。
目录[隐藏] |
http://www.codeproject.com/com/CompleteActiveX/MyActiveX.jpg
ActiveX是微软九十年代中期开发的一种技术,它允许你创建一个类似applet的应用程序,并允许你在微软的浏览器中下载和运行。这篇教程的阅读对象是那些希望使用Visuall C++开发第一个ActiveX程序但却不知道如何下手的人。当我自己尝试学习这种技术的时候,我发现关于ActiveX技术使用的信息大部分都没什么用处,是太老了或者缺失一些关键的信息。这让我建立一个可用ActiveX控件工程的过程困难重重。我写这篇文章的用意就是帮助你快速的建立一个ActiveX控件。其中将要讲解关于ActiveX开发的一些基础概念,诸如方法(method)、属性(propertiy)和事件(event),以及ActiveX控件和网页之间的通讯方法。并将要学习如何再Windows XP系统中的IE浏览器默认的安全设置中无警告的执行这个控件。
在这个指南中,我们将要创建一个ActiveX控件用来显示一个GIF动画表现的进度条,用来表示这个控件的载入和执行信息。这个控件的功能将会展示如何在网页和控件之间的信息。我们将会指导你如何使用微软Visual Studio 2005中一步一步的创建这个控件。
为了创建一个ActiveX控件,需要对微软Visual Studio 2005执行以下步骤:
http://www.codeproject.com/com/CompleteActiveX/image002.jpg
图1.新建项目对话框
http://www.codeproject.com/com/CompleteActiveX/image004.jpg
图2.MFC ActiveX控件向导
为了让这个ActiveX控件支持显示一个GIF动画表现的进度条功能,我们将要使用CPictureEx类型,它是Oleg Bykov在CodeProject Article提供的。这里提供一些相关的细节。首先,把源码文件pictureex.cpp和pictureex.h加入到你的工程,在解决方案资源管理其中右键单机工程,选择添加现有项目,然后选择同文件和源文件,填交到代码树中。
为了添加一个GIF资源到项目中,我们必须面对Visual Studio 2005(以及vs2003)的一个问题,它不允许导入GIF图像各式的文件到资源中。如果你这么做了,你对得到一个错误警告,你需要通过下面的途径来绕过这个问题:
现在,我们将要增加一个图形进程条对话框。
http://www.codeproject.com/com/CompleteActiveX/image006.jpg
图3.MFC类向导如图 – CMainDialog
现在我们可以向类中增加成员变量了。成员变量m_MainDialog是一个CMainDialog类型,m_ProgressBar是我们将要加入主对话框的进度条控件。
http://www.codeproject.com/com/CompleteActiveX/image008.jpg
Figure 4. 添加成员变量向导– m_ProgressBar
好了,现在我们需要卷起袖子写入一些代码了,用来绘制主对话框和进度条控件。
m_MainDialog.Create(IDD_MAINDIALOG, this);
在OnDraw方法中增加下列代码来对主对话框窗口填入背景色:
m_MainDialog.MoveWindow(rcBounds, TRUE); CBrush brBackGnd(TranslateColor(AmbientBackColor())); pdc->FillRect(rcBounds, &brBackGnd);
if (m_ProgressBar.Load(MAKEINTRESOURCE(IDR_PROGRESSBAR),_T("GIF"))) m_ProgressBar.Draw();
确定当前编译模式为Release,并编译MyActiveX ActiveX应用程序。
我们选择的用来快速创建测试网页的工具是微软的ActiveX Control Pad。你可以到微软去下载。
你也可以在其它的网站上面找到可用的下载链接,在你使用Visual Studio的系统上安装并运行这个工具。为了便于测试程序的效果,你还需要装上微软的IIS web服务器。
当你第一次运行ActiveX Control Pad,它会为你创建一个默认的HTML网页。为了插入ActiveX控件,右键单击HTML代码中<BODY>标签,然后选择ActiveX Control。在Insert ActiveX Control窗口中,选择在刚才用Visual Studio创建的MyActiveX Control,然后选择OK。
http://www.codeproject.com/com/CompleteActiveX/image010.jpg
图5. ActiveX Control Pad – Insert ActiveX Control
之后在ActiveX Control Pad中会显示两个矩形对话框,让你有机会修改你所选定的控件。属性对话框提供给你更改控件属性的方法,Edit ActiveX Control 对话框和意让你手工编辑这个控件。你可以关闭这两个窗口,我们之后会在HTML代码中更加细致的对其进行手动配置。你现在需要找到HTML代码中的OBJECT ID 标签,类似图6所示。把OBJECT ID标签里面的size参数改变到“WIDTH=350”和“HEIGHT=50”。然后储存HTML文件为myactivex.htm,并放置到IIS Web服务器的根文件夹中。
http://www.codeproject.com/com/CompleteActiveX/image012.jpg
图6. ActiveX Control Pad – MyActiveX ActiveX Control
开始测试ActiveX控件,在IE中载入网页http://localhost/myactivex.htm 。如果你得到了一些警告信息,只需要选择确定忽略它们。正确的结果是会在网页中显示一个GIF动画的进度条。如果没有,或者你在控件中得到了一个红色的X,那很可能是你对浏览器的安全设置把ActiveX下载或者运行给关闭了,这时候你应该打开IE的安全设置,把所有关于ActiveX的选项启用。
http://www.codeproject.com/com/CompleteActiveX/image014.jpg
图7. MyActiveX控件在IE中显示
接下来,我们需要构建ActiveX控件让其可以直接在IE浏览器中载入,而没有让然讨厌的错误信息。
为了构建一个已签名的ActiveX控件,你需要从某个机构购买一个签名代码证书,这些可以欠发证书的机构包括Thawte,Verisign,以及GeoTrust等。当你接受了这项服务的话,它们会帮助你验证程序不被篡改,并向你发放一个证书给你用来对你的ActiveX应用程序签名。我选择了Thawte提供的签名证书,它提供给你两个千名文件mycert.spc与mykey.pvk。
对ActiveX程序进行签名,我们需要把程序打包成CAB文件。让其可以从网页下载这个ActiveX控件并进行安装。在安装过程中包括了注册ActiveX组件的过程。为了实现这个功能,我们需要为ActiveX控件设置一个VERSIONINFO结构的OLESelfRegister值,在Visual Studio 2003中会帮助我们完成,而在VS2005中需要我们进行处理。我们需要编辑资源文件myactivex.rc加入下面OLESelfRegister值,如下:
VS_VERSION_INFO VERSIONINFO FILEVERSION 1,0,0,1 PRODUCTVERSION 1,0,0,1 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L #else FILEFLAGS 0x0L #endif FILEOS 0x4L FILETYPE 0x2L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904e4" BEGIN VALUE "CompanyName", "TODO: <Company name>" VALUE "FileDescription", "TODO: <File description>" VALUE "FileVersion", "1.0.0.1" VALUE "InternalName", "MyActiveX.ocx" VALUE "LegalCopyright", "TODO: (c) <Company name>. All rights reserved." VALUE "OLESelfRegister", "\0" VALUE "OriginalFilename", "MyActiveX.ocx" VALUE "ProductName", "TODO: <Product name>" VALUE "ProductVersion", "1.0.0.1" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1252 END END
在我们签名应用程序之前,ActiveX控件需要被打包成CAB文件。CAB文件中同时包含了一个INF文件被用来安装ActiveX 控件。为了构件CAB文件,你需要运行Cabinet Software Development Kit的cabarc.exe工具。下面提供了一个简单的INF文件例子,用来包装MyActiveX控件到CAB文件。你需要把CLSID行的值改成HTML文件中OBJECT ID标签所使用的值(之前用ActiveX Control Pad生成)。
[Add.Code] myactivex.ocx=myactivex.ocx myactivex.inf=myactivex.inf [myactivex.ocx] file=thiscab clsid={36299202-09EF-4ABF-ADB9-47C599DBE778} RegisterServer=yes FileVersion=1,0,0,0 [myactivex.inf] file=thiscab
建立CAB文件,向下面一样运行cabarc工具。 重要:确认在运行cabarc.exe时OCX和INF文件在相同的文件夹中,否则的话CAB文件从网页中下载之后会产生错误。这是一个导致警告的问题。
cabarc -s 6144 N myactivex.cab myactivex.ocx myactivex.inf
当对一个CAB文件进行签名的时候,你需要得到Microsoft MSDN上的signcode.exe工具。这里关系到“可信的签名和检查”将会在这个文章的后面提到。你使用signcode工具把从外面卖到的代码证书绑定到CAB文件中。下面是一个使用签名代码签名myactivex.cab文件的例子:
signcode -n "myactivex" -i http://www.myactivex.com -spc mycert.spc -v mykey.pvk -t http://timestamp.verisign.com/scripts/timstamp.dll myactivex.cab
在上面的例子中,http://www.myactivex.com 提供了这个给签名ActiveX控件提供更多用户信息的网页。
把这个CAB文件应用到网页中,首先拷贝myactivex.cab到你网页所在的文件夹,然后你必须修改网页中OBJECT ID标记中CODEBASE参数索引到你的CAB文件上来。参照图8的例子。如果你在IE中载入这个网页,它将会下载CAB文件并安装你的ActiveX控件,过程中并不会出现一个没有签名的警告。
http://www.codeproject.com/com/CompleteActiveX/image016.jpg
图8. ActiveX Control Pad – MyActiveX with CODEBASE
如果希望再IE载入控件的时候不对控件不安全作出警告的信息的话,你必须保证执行代码使用安全的初始化和脚本的Active控件。相关的细节可以参照微软MSDN上的文章“Safe Initialization and Scripting for ActiveX Controls”。本文的后面会给出链接地址。不过我在这篇文章里面看到了大量的错误和冗长的文章。其实只需要在代码中加入DllRegisterServer和DllUnregisterServer这两个方法。下面我们就一步步指导你把ActiveX控件改成安全的:
编辑MyActiveX.cpp文件加入下面代码。在你的ActiveX控件中CLSID_SafeItem的值需要等于你在MyActiveXCtrl.cpp文件中的IMPLEMENT_OLECREATE_EX,同时,这个值也要和你在网页中的OBJECT ID标记CLSID值相同。
#include "comcat.h" #include "strsafe.h" #include "objsafe.h" // CLSID_SafeItem - Necessary for safe ActiveX control // Id taken from IMPLEMENT_OLECREATE_EX function in xxxCtrl.cpp const CATID CLSID_SafeItem = { 0x36299202, 0x9ef, 0x4abf,{ 0xad, 0xb9, 0x47, 0xc5, 0x99, 0xdb, 0xe7, 0x78}}; // HRESULT CreateComponentCategory - Used to register ActiveX control as safe HRESULT CreateComponentCategory(CATID catid, WCHAR *catDescription) { ICatRegister *pcr = NULL ; HRESULT hr = S_OK ; hr = CoCreateInstance(CLSID_StdComponentCategoriesMgr, NULL, CLSCTX_INPROC_SERVER, IID_ICatRegister, (void**)&pcr); if (FAILED(hr)) return hr; // Make sure the HKCR\Component Categories\{..catid...} // key is registered. CATEGORYINFO catinfo; catinfo.catid = catid; catinfo.lcid = 0x0409 ; // english size_t len; // Make sure the provided description is not too long. // Only copy the first 127 characters if it is. // The second parameter of StringCchLength is the maximum // number of characters that may be read into catDescription. // There must be room for a NULL-terminator. The third parameter // contains the number of characters excluding the NULL-terminator. hr = StringCchLength(catDescription, STRSAFE_MAX_CCH, &len); if (SUCCEEDED(hr)) { if (len>127) { len = 127; } } else { // TODO: Write an error handler; } // The second parameter of StringCchCopy is 128 because you need // room for a NULL-terminator. hr = StringCchCopy(catinfo.szDescription, len + 1, catDescription); // Make sure the description is null terminated. catinfo.szDescription[len + 1] = '\0'; hr = pcr->RegisterCategories(1, &catinfo); pcr->Release(); return hr; } // HRESULT RegisterCLSIDInCategory - // Register your component categories information HRESULT RegisterCLSIDInCategory(REFCLSID clsid, CATID catid) { // Register your component categories information. ICatRegister *pcr = NULL ; HRESULT hr = S_OK ; hr = CoCreateInstance(CLSID_StdComponentCategoriesMgr, NULL, CLSCTX_INPROC_SERVER, IID_ICatRegister, (void**)&pcr); if (SUCCEEDED(hr)) { // Register this category as being "implemented" by the class. CATID rgcatid[1] ; rgcatid[0] = catid; hr = pcr->RegisterClassImplCategories(clsid, 1, rgcatid); } if (pcr != NULL) pcr->Release(); return hr; } // HRESULT UnRegisterCLSIDInCategory - Remove entries from the registry HRESULT UnRegisterCLSIDInCategory(REFCLSID clsid, CATID catid) { ICatRegister *pcr = NULL ; HRESULT hr = S_OK ; hr = CoCreateInstance(CLSID_StdComponentCategoriesMgr, NULL, CLSCTX_INPROC_SERVER, IID_ICatRegister, (void**)&pcr); if (SUCCEEDED(hr)) { // Unregister this category as being "implemented" by the class. CATID rgcatid[1] ; rgcatid[0] = catid; hr = pcr->UnRegisterClassImplCategories(clsid, 1, rgcatid); } if (pcr != NULL) pcr->Release(); return hr; }
改变DllRegisterServer方法:
STDAPI DllRegisterServer(void) { HRESULT hr; // HResult used by Safety Functions AFX_MANAGE_STATE(_afxModuleAddrThis); if (!AfxOleRegisterTypeLib(AfxGetInstanceHandle(), _tlid)) return ResultFromScode(SELFREG_E_TYPELIB); if (!COleObjectFactoryEx::UpdateRegistryAll(TRUE)) return ResultFromScode(SELFREG_E_CLASS); // Mark the control as safe for initializing. hr = CreateComponentCategory(CATID_SafeForInitializing, L"Controls safely initializable from persistent data!"); if (FAILED(hr)) return hr; hr = RegisterCLSIDInCategory(CLSID_SafeItem, CATID_SafeForInitializing); if (FAILED(hr)) return hr; // Mark the control as safe for scripting. hr = CreateComponentCategory(CATID_SafeForScripting, L"Controls safely scriptable!"); if (FAILED(hr)) return hr; hr = RegisterCLSIDInCategory(CLSID_SafeItem, CATID_SafeForScripting); if (FAILED(hr)) return hr; return NOERROR; }
修改DllUnregisterServer方法:
STDAPI DllUnregisterServer(void) { HRESULT hr; // HResult used by Safety Functions AFX_MANAGE_STATE(_afxModuleAddrThis); if (!AfxOleUnregisterTypeLib(_tlid, _wVerMajor, _wVerMinor)) return ResultFromScode(SELFREG_E_TYPELIB); if (!COleObjectFactoryEx::UpdateRegistryAll(FALSE)) return ResultFromScode(SELFREG_E_CLASS); // Remove entries from the registry. hr=UnRegisterCLSIDInCategory(CLSID_SafeItem, CATID_SafeForInitializing); if (FAILED(hr)) return hr; hr=UnRegisterCLSIDInCategory(CLSID_SafeItem, CATID_SafeForScripting); if (FAILED(hr)) return hr; return NOERROR; }
在ActiveX控件和网页之间传递消息需要通过属性、方法以及事件。为了便于展示这方面的技术,我们将要创建一个简单的网页,其中包含了简单的form元素,包括一个文本框与一个提交按键,文本通过一个输入参数属性被输入到ActiveX控件中。有一个控件方法把文本拷贝到输出参数属性上,然后激发一个事件把文本显示到网页中。我们需要做下列步骤:
http://www.codeproject.com/com/CompleteActiveX/image018.gif
图9. 添加属性向导
http://www.codeproject.com/com/CompleteActiveX/image020.jpg
Figure 10. 添加方法向导
http://www.codeproject.com/com/CompleteActiveX/image022.jpg
图11.添加事件向导
在上面的过程中,向导帮助我们创建了大多数的工作。我们只需要简单的添加几行代码来添加ActiveX控件拷贝文本和通知网页的事件。编辑文件MyActiveXCtrl.cpp,并且增加下面的代码来读入参数。
// Copy text from the input parameter to the output parameter m_OutputParameter = m_InputParameter; // Fire an event to notify web page FireParameterLoaded();
为了测试,下面使用ActiveX Control Pad工具来创建HTML代码:
<HTML> <HEAD> <TITLE>MyActiveX - Methods, Properties, and Events</TITLE> <SCRIPT LANGUAGE="JavaScript"> function PassParameter() { if (StringInput.value != " ") { MyActiveX1.InputParameter = StringInput.value; MyActiveX1.LoadParameter(); } } </SCRIPT> </HEAD> <BODY> < CENTER> MyActiveX - Methods, Properties, and Events Example < P>< /P> <OBJECT ID="MyActiveX1" WIDTH=350 HEIGHT=50 CLASSID="CLSID:36299202-09EF-4ABF-ADB9-47C599DBE778"> <PARAM NAME="_Version" VALUE="65536"> <PARAM NAME="_ExtentX" VALUE="2646"> <PARAM NAME="_ExtentY" VALUE="1323"> <PARAM NAME="_StockProps" VALUE="0"> </OBJECT> < p>< /p> Input Parameter: <INPUT TYPE ="text" NAME="StringInput" VALUE=" "> < p>< /p> <INPUT TYPE="button" NAME="Submit" VALUE="Submit" ONCLICK=PassParameter()> <SCRIPT FOR=MyActiveX1 EVENT=ParameterLoaded()> </SCRIPT> < /center> </BODY>
储存这些HTML代码到你的服务器上,并运行它。你将会看到一个网页显示一个进度条,并且拥有一个提交文本的form元素,其中包含一个文本输入框和一个提交按键。党提交后会显示一个新的页面,在其中显示“The parameter you entered is: ”,和你输入的文本。下面我们将会对网页HTML代码介绍一下概况。
当你按下提交,JavaScript脚本函数PassParameter将会被调用。在这个函数中在StringInput中的文本将会被拷贝到ActiveX控件的InputParameter属性中。接下来方法LoadParameter被调用,把InputParameter属性的值拷贝到OutputParameter中,最后调用FireParameterLoaded()来触发一个ActiveX事件。下面的HTML代码被用来处理这个事件。
<SCRIPT FOR=MyActiveX1 EVENT=ParameterLoaded()> </SCRIPT>
Click here to view David Marcionek's online profile.