COM组件开发实践_#include

 Preface

      因为项目需要,开始从事ActiveX方面的工作,看了一些资料,可惜都是些COM原理方面的,没有切合实际动手的东西,在CodeProject上读完David Marcionek的文章【1】后,收获良多,但也遇到一些恼人的小问题,因此在其基础上就一些易错点做些小注解。本文版权归David Marcionek所有。

简介 

本文目的在于让你快速掌握ActiveX控件开发技术,将会展示开发ActiveX应该知道的基本概念,如方法,属性和事件,以及如何在一个ActiveX控件和一个web页面之间进行通信

在本文中,我们将创建一个ActiveX控件,当加载控件时,它会显示一个动画进度条,以便向用户表明控件正在加载。此控件会包含展示如何在控件和web页面间传递信息的功能。下面我们会使用VS2005一步步进行开发的。

创建一个ActiveX控件

为了创建一个ActiveX控件,如下所示:

1,创建一个"MFC ActiveX Control"项目,取名MyActiveX,

COM组件开发实践_#include_02

2,在"MFC ActiveX Control Wizard"对话框中,选中"Control Settings"

3,在"Create control based on"中选择"STATIC".我们将使用静态控件,因为我们只是显示从控件中获取的输出信息,并不接受输入信息。

4,在"Additional features"中,确保"Activates when visible"和"Flicker-free activation"被选中,"Has an About box dialog"不选中。

5,默认情况下,wizard会创建一个项目,使其在一个共享DLL中使用MFC.我们必须更改这种情况,因为除非所需的MFC DLL都已经在系统中安装了,否则ActiveX控件就不能运行。包含ActiveX控件的Web页面上出现红叉的一个原因就是此。在项目的属性中,"Configuration Properties"-->"General",将“Use of MFC” 改为“Use MFC in a Static Library”.

6,向导会创建如下几个类:

      1)CMyActiveXApp:这是ActiveX应用程序类,从COleControlModule类继承下来的。它是OLE控件模块对象继承自的基类,包含了初始化(InitInstance)和清理(ExitInstance)的代码

      2)CMyActiveXCtrl:从COleControl继承而来,这里是我们实现控件大部分功能的地方。

      3)CMyActiveXPropPage:从COlePropertyPage继承而来,用于管理控件的属性页对话框。向导已经为我们创建了一个默认的对话框来作为控件的属性页对话框。

COM组件开发实践_加载_03

增加动画GIF支持

      这里我们使用了一个CPictureEx类(具体代码见最后的“资源”部分),vs2005增加一个动画GIF资源有一个bug(其实在vs2008中也存在),我们可以使用下面这种技巧来回避它:

将ProcessingProgressBar.gif拷贝到项目文件夹下,然后更名为ProcessingProgressBar.gaf,在资源视图中,右键资源文件MyActiveX.rc,选择“添加资源”。在“添加资源”对话框中,按下”导入“按钮,并选择ProcessingProgressBar.gaf文件。在”自定义资源类型“对话框中输入“GIF”作为资源类型。这就会将GIF图片文件导入项目中。然后将导入的图片ID从IDR_GIF1 改为IDR_PROGRESSBAR.。

现在开始着手恢复原状,首先,打开MyActiveX.rc的源文件,找到IDR_PROGRESSBAR的定义,将其文件名改为 ProcessingProgressBar.gif”.同样地,把项目文件夹下的图片文件名也改回为“ProcessingProgressBar.gif”,最后在“解决方案资源管理器”视图中,选中ProcessingProgressBar.gaf,在其”属性“中,修改”相对路径“为” ."ProcessingProgressBar.gif”.

增加对话框

      现在,我们为进度条图像增加一个对话框。

1, 在“资源“视图中,右键”对话框“,选择”插入对话框“来创建一个默认的对话框。

2, 删除默认产生的“确定“和”取消“按钮,调整对话框大小为230*40。

3,更改对话框ID为IDD_MAINDIALOG,并修改对话框属性:Border—none, Style – Child, System Menu – False, Visible – True.

4,在对话框中加入一个图片控件,调整其大小为200*20,更改控件IDIDC_PROGRESSBAR,颜色为“white”

5,为对话框创建一个类,名为CMainDialog,

COM组件开发实践_web页面_04

 现在我们为类增加成员变量:

1,CMyActiveXCtrl类增加一个变量m_MainDialog,类型为CMainDialog

2, CMainDialog类增加一个变量m_ProgressBar,类型为CPictureEx,这里注意确保控件变量选中,并且对于的控件是”IDC_PROGRESSBAR”.

COM组件开发实践_控件_05

增加支持代码

好了,现在加入一些代码来绘制主对话框和进度条控件吧。

1,为CMyActiveXCtrl处理WM_CREATE事件的代码,在其中加入:

COM组件开发实践_控件_06m_MainDialog.Create(IDD_MAINDIALOG, this);

并在OnDraw函数中加入:

COM组件开发实践_控件_06m_MainDialog.MoveWindow(rcBounds, TRUE);
COM组件开发实践_控件_06CBrush brBackGnd(TranslateColor(AmbientBackColor()));
COM组件开发实践_控件_06pdc->FillRect(rcBounds, &brBackGnd);
COM组件开发实践_控件_06

     2.在CMainDialog类中,加入处理WM_CREATE事件的代码,在其中加入:

COM组件开发实践_控件_06if(m_ProgressBar.Load(MAKEINTRESOURCE(IDR_PROGRESSBAR),_T("GIF")))
COM组件开发实践_控件_06m_ProgressBar.Draw();
COM组件开发实践_控件_06

Ok,一个简单的ActiveX控件已经开发完毕,设置编译模式为“Release”模式,并构建整个应用程序。

创建一个Web页面作为ActiveX控件容器

      可以使用微软的ActiveX Control Pad。要利用它在Web页面中插入一个ActiveX控件,在<BODY>标记中右键,选择“Insert ActiveX Control”,选择你需要的就可以了。

COM组件开发实践_加载_14

COM组件开发实践_web页面_15

 直接打开Web页面或者放到IIS服务器上进行访问,一切顺利的话就可以看到下面的图像: 

COM组件开发实践_#include_16

注1:前面要求设置编译模式为“Release”,其实是为了避免运行时因为触及Assert出错而做的,否则会报错如下:

COM组件开发实践_web页面_17

 跟踪调试后会发现: 

COM组件开发实践_加载_18

  可以看出是图片扩展控件加载时的顺序有些问题,但在浏览器中并不需要考虑如此多,因此这里忽略此Assert条件。

 注2:作者在这里没有对MyActiveX.idl文件进行讲解,我认为是一个不小的失误,也正是因为如此,才会导致一个很容易犯错的地方,当我们按照他的教程,仿照他的代码一步步进行完后,却发现在ActiveX测试容器中是可以运行通过的,但到了浏览器中却死活都是红叉叉。。。,就是因为作者忽略了其对MyActiveX.idl接口定义文件的修改进行解释。

注3:VS2008中没有ActiveX控件测试容器了,VS05以上的数字签名工具也改变了,因此使用VS2005可能更好

     我按照教程一步步模仿着做的时候,在上面这两点上纠缠了3个多小时才发现问题的原因。

     在下一篇文章中,将介绍如何对ActiveX控件进行数字签名并使其自注册和销毁来确保其安全性,此外还会介绍如何在ActiveX控件和Web页面间进行数据通信。

参考资源

 

1,A Complete ActiveX Web Control Tutorial By David Marcionek

2. Add GIF-animation to your MFC and ATL projects with the help of CPictureEx and CPictureExWnd by Oleg Bykov, CodeProject.

 

 

假设需求如下:底层是一个数学运算库DLL,中间是ActiveX控件(它调用底层的数学运算库DLL来完成控制层),界面层在测试时可以是一个exe程序,最后发布到IE浏览器上测试。

 

数学运算库DLL的开发

      新建一个Win32 DLL项目,加入一个头文件MyNum.h,在其中声明所有的数学函数(为简单起见,本文只考虑加法运算),代码如下:

COM组件开发实践_控件_06#ifndef MY_NUM_H
COM组件开发实践_控件_06#define MY_NUM_H
COM组件开发实践_控件_06int __stdcall AddNum(int,int);
COM组件开发实践_控件_06#endif
COM组件开发实践_控件_06

请注意这里的方法声明为__stdcall,而VC++默认的是__cdecl,由于组件的语言无关性要求调用和被调双方必须在函数调用的约定上一致,因此在后面加载DLL并获取此方法时也要求和你的声明一致。

      为了简单起见,加法方法的实现就放倒DLL入口点所在文件,代码如下:

COM组件开发实践_数字签名_24
COM组件开发实践_控件_06// NumDLL.cpp : 定义DLL 应用程序的入口点。
COM组件开发实践_控件_06//
COM组件开发实践_控件_06#include "stdafx.h"
COM组件开发实践_控件_06#include "MyNum.h"
COM组件开发实践_控件_06
COM组件开发实践_控件_06#ifdef _MANAGED
COM组件开发实践_控件_06#pragma managed(push, off)
COM组件开发实践_控件_06#endif
COM组件开发实践_控件_06
COM组件开发实践_控件_06int __stdcall AddNum(int Num1,int Num2)
COM组件开发实践_#include_35{
COM组件开发实践_web页面_36    return Num1+Num2;
COM组件开发实践_web页面_37}
COM组件开发实践_控件_06BOOL APIENTRY DllMain( HMODULE hModule,
COM组件开发实践_控件_06                       DWORD  ul_reason_for_call,
COM组件开发实践_控件_06                       LPVOID lpReserved
COM组件开发实践_控件_06                     )
COM组件开发实践_#include_35{
COM组件开发实践_web页面_36    return TRUE;
COM组件开发实践_web页面_37}
COM组件开发实践_控件_06
COM组件开发实践_控件_06#ifdef _MANAGED
COM组件开发实践_控件_06#pragma managed(pop)
COM组件开发实践_控件_06#endif
COM组件开发实践_控件_06
COM组件开发实践_数字签名_24

      为了能在其他程序中显示链接此DLL,我们为它加入一个.def文件,命名为NumDLL.def,列出此DLL导出的方法名称:

COM组件开发实践_控件_06LIBRARY    "NumDLL"
COM组件开发实践_控件_06EXPORTS
COM组件开发实践_控件_06    AddNum
COM组件开发实践_控件_06

至此我们的数学运算函数库DLL就完成了。

用ATL开发ActiveX控件

      开发ActiveX控件有两种方式,一是MFC,二是ATL,而后者是专门用于COM组件开发,因此更适合于ActiveX。因此这里选择后者,前者的开发示例参考我这篇文章(用VC++开发ActiveX 控件完全教程(一))。

      新建一个ATL项目,命名为”FuckATL”,接受默认设置。右键项目名,添加一个”ATL简单对象“,命名为CaluNumCtrl,点击下一步进入组件选项设置界面。

      修改类的头文件CaluNumCtrl.h如下:

COM组件开发实践_数字签名_24
COM组件开发实践_控件_06class ATL_NO_VTABLE CCaluNumCtrl :
COM组件开发实践_控件_06    public CComObjectRootEx<CComSingleThreadModel>,
COM组件开发实践_控件_06    public CComCoClass<CCaluNumCtrl, &CLSID_CaluNumCtrl>,
COM组件开发实践_控件_06    public ISupportErrorInfo,
COM组件开发实践_控件_06    public IConnectionPointContainerImpl<CCaluNumCtrl>,
COM组件开发实践_控件_06    public CProxy_ICaluNumCtrlEvents<CCaluNumCtrl>,
COM组件开发实践_控件_06    public IObjectWithSiteImpl<CCaluNumCtrl>,
COM组件开发实践_#include_35    public IDispatchImpl<ICaluNumCtrl, &IID_ICaluNumCtrl, &LIBID_FuckATLLib, /*wMajor =*/ 1, /*wMinor =*/ 0>
COM组件开发实践_#include_35{
COM组件开发实践_web页面_36public:
COM组件开发实践_web页面_36    typedef int (__stdcall*PtrAddNum)(int,int);
COM组件开发实践_web页面_36    PtrAddNum MyAddNum;
COM组件开发实践_web页面_36    CCaluNumCtrl()
COM组件开发实践_#include_69    {
COM组件开发实践_web页面_36        //加载数学运算库DLL
COM组件开发实践_web页面_36        handle = ::LoadLibrary(_T("D:\\dyk\\work\\NumDLL\\debug\\NumDLL.dll"));
COM组件开发实践_web页面_36        if (handle == NULL) 
COM组件开发实践_#include_69        {
COM组件开发实践_web页面_36            DWORD e = GetLastError();
COM组件开发实践_web页面_36            return;
COM组件开发实践_#include_76        }
COM组件开发实践_web页面_36        //获取加法运算函数指针
COM组件开发实践_web页面_36        MyAddNum = (PtrAddNum)GetProcAddress(handle,"AddNum");
COM组件开发实践_#include_76    }
COM组件开发实践_web页面_36DECLARE_REGISTRY_RESOURCEID(IDR_CALUNUMCTRL)
COM组件开发实践_web页面_36BEGIN_COM_MAP(CCaluNumCtrl)
COM组件开发实践_web页面_36    COM_INTERFACE_ENTRY(ICaluNumCtrl)
COM组件开发实践_web页面_36    COM_INTERFACE_ENTRY(IDispatch)
COM组件开发实践_web页面_36    COM_INTERFACE_ENTRY(ISupportErrorInfo)
COM组件开发实践_web页面_36    COM_INTERFACE_ENTRY(IConnectionPointContainer)
COM组件开发实践_web页面_36    COM_INTERFACE_ENTRY(IObjectWithSite)
COM组件开发实践_web页面_36END_COM_MAP()
COM组件开发实践_web页面_36
COM组件开发实践_web页面_36BEGIN_CONNECTION_POINT_MAP(CCaluNumCtrl)
COM组件开发实践_web页面_36    CONNECTION_POINT_ENTRY(__uuidof(_ICaluNumCtrlEvents))
COM组件开发实践_web页面_36END_CONNECTION_POINT_MAP()
COM组件开发实践_web页面_36// ISupportsErrorInfo
COM组件开发实践_web页面_36    STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid);
COM组件开发实践_web页面_36    DECLARE_PROTECT_FINAL_CONSTRUCT()
COM组件开发实践_web页面_36    HRESULT FinalConstruct()
COM组件开发实践_#include_69    {
COM组件开发实践_web页面_36        return S_OK;
COM组件开发实践_#include_76    }
COM组件开发实践_web页面_36    void FinalRelease()
COM组件开发实践_#include_69    {
COM组件开发实践_#include_76    }
COM组件开发实践_web页面_36//组件对外放出的加法方法
COM组件开发实践_web页面_36public:
COM组件开发实践_web页面_36    STDMETHOD(AddNumbers)(LONG Num1, LONG Num2, LONG* ReturnVal);    //下面是一个NUM属性,也是用于测试,包含了读写设置方法
COM组件开发实践_web页面_36public:
COM组件开发实践_web页面_36    STDMETHOD(get_NUM)(SHORT* pVal);
COM组件开发实践_web页面_36public:
COM组件开发实践_web页面_36    STDMETHOD(put_NUM)(SHORT newVal);
COM组件开发实践_web页面_36public:
COM组件开发实践_web页面_36    HMODULE handle;//数学函数库模块句柄
COM组件开发实践_web页面_37};
COM组件开发实践_控件_06
COM组件开发实践_数字签名_24

在控件实现文件CaluNumCtrl.cpp中,代码如下:

COM组件开发实践_数字签名_24
COM组件开发实践_控件_06STDMETHODIMP CCaluNumCtrl::AddNumbers(LONG Num1, LONG Num2, LONG* ReturnVal)
COM组件开发实践_#include_35{
COM组件开发实践_web页面_36    int sum = this->MyAddNum(static_cast<int>(Num1),static_cast<int>(Num2));
COM组件开发实践_web页面_36    *ReturnVal = static_cast<LONG>(sum);
COM组件开发实践_web页面_36    return S_OK;
COM组件开发实践_web页面_37}
COM组件开发实践_控件_06
COM组件开发实践_控件_06STDMETHODIMP CCaluNumCtrl::get_NUM(SHORT* pVal)
COM组件开发实践_#include_35{
COM组件开发实践_web页面_36    *pVal = 10;
COM组件开发实践_web页面_36    return S_OK;
COM组件开发实践_web页面_37}
COM组件开发实践_控件_06
COM组件开发实践_控件_06STDMETHODIMP CCaluNumCtrl::put_NUM(SHORT newVal)
COM组件开发实践_#include_35{
COM组件开发实践_web页面_36    // TODO: 在此添加实现代码
COM组件开发实践_web页面_36    
COM组件开发实践_web页面_36    return S_OK;
COM组件开发实践_web页面_37}
COM组件开发实践_控件_06
COM组件开发实践_数字签名_24

     好了,ActiveX控件仅仅是简单地调用底层的数学运算库DLL来完成运算,下面我们写一个exe程序对这个COM组件进行测试。

一个控制台测试程序

      建立一个最简单的控制台程序来进行测试,代码如下:

COM组件开发实践_数字签名_24
COM组件开发实践_控件_06#include "..\..\FuckATL\FuckATL\FuckATL.h"
COM组件开发实践_控件_06#include "..\..\FuckATL\FuckATL\FuckATL_i.c"
COM组件开发实践_控件_06#include <iostream>
COM组件开发实践_控件_06using  namespace std;
COM组件开发实践_控件_06
COM组件开发实践_控件_06void main(void)
COM组件开发实践_#include_35{
COM组件开发实践_web页面_36    // Declare and HRESULT and a pointer to the Simple_ATL interface
COM组件开发实践_web页面_36    HRESULT            hr;
COM组件开发实践_web页面_36    ICaluNumCtrl *IFirstATL = NULL;
COM组件开发实践_web页面_36    // Now we will intilize COM
COM组件开发实践_web页面_36    hr = CoInitialize(0);
COM组件开发实践_web页面_36    // Use the SUCCEDED macro and see if we can get a pointer to
COM组件开发实践_web页面_36    // the interface
COM组件开发实践_web页面_36    if(SUCCEEDED(hr))
COM组件开发实践_#include_69    {
COM组件开发实践_web页面_36        hr = CoCreateInstance( CLSID_CaluNumCtrl, NULL, CLSCTX_INPROC_SERVER,
COM组件开发实践_web页面_36            IID_ICaluNumCtrl, (void**) &IFirstATL);
COM组件开发实践_web页面_36        // If we succeeded then call the AddNumbers method, if it failed
COM组件开发实践_web页面_36        // then display an appropriate message to the user.
COM组件开发实践_web页面_36        if(SUCCEEDED(hr))
COM组件开发实践_#include_69        {
COM组件开发实践_web页面_36            long ReturnValue;
COM组件开发实践_web页面_36            hr = IFirstATL->AddNumbers(5, 7, &ReturnValue);
COM组件开发实践_web页面_36            cout << "The answer for 5 + 7 is: " << ReturnValue << endl;
COM组件开发实践_web页面_36            short num; 
COM组件开发实践_web页面_36            IFirstATL->get_NUM(&num);
COM组件开发实践_web页面_36            cout<<"num is: "<<num<<endl;
COM组件开发实践_web页面_36            hr = IFirstATL->Release();
COM组件开发实践_#include_76        }
COM组件开发实践_web页面_36        else
COM组件开发实践_#include_69        {
COM组件开发实践_web页面_36            cout << "CoCreateInstance Failed." << endl;
COM组件开发实践_#include_76        }
COM组件开发实践_#include_76    }
COM组件开发实践_web页面_36    // Uninitialize COM
COM组件开发实践_web页面_36    CoUninitialize();
COM组件开发实践_web页面_36    system("pause");
COM组件开发实践_web页面_37}
COM组件开发实践_控件_06
COM组件开发实践_数字签名_24

来到IE的世界

      最后我们将此ActiveX组件嵌入到html页面中,对其进行测试.新建一个html页面,代码如下:

COM组件开发实践_数字签名_24
COM组件开发实践_控件_06<HTML>
COM组件开发实践_控件_06<HEAD>
COM组件开发实践_控件_06<TITLE>New Page</TITLE>
COM组件开发实践_#include_35<script language="javascript">
COM组件开发实践_web页面_36    function doTest()
COM组件开发实践_#include_69    {
COM组件开发实践_web页面_36        var sum = FuckATL1.AddNumbers(3,4);
COM组件开发实践_web页面_36        alert(sum);
COM组件开发实践_web页面_37    }
COM组件开发实践_控件_06</script>
COM组件开发实践_控件_06</HEAD>
COM组件开发实践_控件_06<BODY>
COM组件开发实践_控件_06<OBJECT ID="FuckATL1" CLASSID="CLSID:7BF3B65F-A800-4604-AE6B-91844EFD5F05">
COM组件开发实践_控件_06</OBJECT>
COM组件开发实践_控件_06<input type="button" value="测试加法" id="btnOK" onclick="doTest();"></input>
COM组件开发实践_控件_06</BODY>
COM组件开发实践_控件_06</HTML>
COM组件开发实践_控件_06
COM组件开发实践_数字签名_24

     由于暂时先不考虑控件的安全性需要,因此会出现下面的警告信息,不过不要紧,这个问题以后再解决。

COM组件开发实践_web页面_198

测试结果如下:

COM组件开发实践_加载_199

  前面两篇文章分别介绍了MFC ActiveX应用程序和使用ATL开发ActiveX的简单实例,但还有两个问题需要解决:

1)标记ActiveX控件为安全的控件 2)对控件进行数字签名。本文将结合这两点进行简单的介绍。

Building a Safe ActiveX Control

      如何不想办法将控件标记为安全的,就会在Web页面与控件进行交互时出现如下图的警告信息:

COM组件开发实践_控件_200

     下面将分别介绍在MFC ActiveX和ATL中如何标记一个控件为安全的控件。

     要标记一个MFC ActiveX控件为安全,可以仿照下面代码修改而得:

COM组件开发实践_数字签名_24
// CardScan.cpp : CCardScanApp 和DLL 注册的实现。
#include "stdafx.h"
#include "CardScan.h"
#include "comcat.h"
#include "strsafe.h"
#include "objsafe.h"

CCardScanApp theApp;
const GUID CDECL BASED_CODE _tlid =
        { 0x29959268, 0x9729, 0x458E, { 0xA8, 0x39, 0xBB, 0x39, 0x2E, 0xCB, 0x7E, 0x37 } };
const WORD _wVerMajor = 1;
const WORD _wVerMinor = 0;
const CATID CLSID_SafeItem =
{0xB548F3C7,0x2135,0x4242,{0x92,0x0B,0xA7,0xBD,0xEE,0x6D,0x2B,0xA3}};

//{ 0x36299202, 0x9ef, 0x4abf,{ 0xad, 0xb9, 0x47, 0xc5, 0x99, 0xdb, 0xe7, 0x78}};
// CCardScanApp::InitInstance - DLL 初始化
BOOL CCardScanApp::InitInstance()
{
    BOOL bInit = COleControlModule::InitInstance();
    if (bInit)
    {
    }
    return bInit;
}
// CCardScanApp::ExitInstance - DLL 终止
int CCardScanApp::ExitInstance()
{
    return COleControlModule::ExitInstance();
}
HRESULT CreateComponentCategory(CATID catid, CHAR *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\{..catidCOM组件开发实践_控件_202}
    // 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(COLE2T(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;
    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, 
        _T("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, 
        _T("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;
    AFX_MANAGE_STATE(_afxModuleAddrThis);
    // 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;
    if (!AfxOleUnregisterTypeLib(_tlid, _wVerMajor, _wVerMinor))
        return ResultFromScode(SELFREG_E_TYPELIB);
    if (!COleObjectFactoryEx::UpdateRegistryAll(FALSE))
        return ResultFromScode(SELFREG_E_CLASS);
    return NOERROR;
}
COM组件开发实践_数字签名_24

     这里值得注意的一个地方是DllUnregisterServer函数,在这段代码中,我是将

hr=UnRegisterCLSIDInCategory(CLSID_SafeItem, CATID_SafeForInitializing);

hr=UnRegisterCLSIDInCategory(CLSID_SafeItem, CATID_SafeForScripting);

这两句代码放在

COM组件开发实践_数字签名_24
if (!AfxOleUnregisterTypeLib(_tlid, _wVerMajor, _wVerMinor))

           return ResultFromScode(SELFREG_E_TYPELIB);

      if (!COleObjectFactoryEx::UpdateRegistryAll(FALSE))

           return ResultFromScode(SELFREG_E_CLASS);
COM组件开发实践_数字签名_24

这两句代码的前面,如果你查阅MSDN,将会发现它上面的顺序和我是相反的,这应该是微软的一个错误代码,如果按照MSDN的代码来写,则你使用regsvr32 -u CardScan.ocx反注册时会报下面的错误:

COM组件开发实践_web页面_206

调整为我所说的顺序就没问题了。

2)要标记使用ATL写的ActiveX控件为安全的控件,这比MFC要简单的多,只需要在控件头文件中增加几行代码就可以了:

class ATL_NO_VTABLE CTestCtrl :
    …
    public IObjectSafetyImpl<CTestCtrl, INTERFACESAFE_FOR_UNTRUSTED_CALLER| INTERFACESAFE_FOR_UNTRUSTED_DATA>,

然后在COM映射表中增加一项:

BEGIN_COM_MAP(CTestCtrl)
    …
    COM_INTERFACE_ENTRY(IObjectSafety)
END_COM_MAP()

Building a Signed ActiveX Control

      ActiveX控件是个危险的东西,如果不对其合法性进行数字签名和验证,IE是会拒绝其安装的。

      工具包准备:CABARC.exe, cert2spc.exe, makecab.exe, makecert.exe, signcode.exe(或新版本中的signtool),以上小工具都可以在VS的安装路径下"Common7"Tools"Bin找到,或去微软官方网站上下载。

ActiveX控件的安装过程中,一部分工作就是自注册,这需要控件在VERSIONINFO结构中定义OLESelfRegister值,你可以对资源文件进行编辑如下

COM组件开发实践_数字签名_24
BEGIN
    BLOCK "StringFileInfo"
    BEGIN
        BLOCK "080403a8"
        BEGIN
            VALUE "CompanyName", "TODO: <公司名>"
            VALUE "FileDescription", "TODO: <文件说明>"
            VALUE "FileVersion", "1.0.0.1"
            VALUE "InternalName", "CardScan.ocx"
            VALUE "LegalCopyright", "TODO: (C) <公司名>。保留所有权利。"
            VALUE "OLESelfRegister", "\0"
            VALUE "OriginalFilename", "CardScan.ocx"
            VALUE "ProductName", "TODO: <产品名>"
            VALUE "ProductVersion", "1.0.0.1"
        END
    END
    BLOCK "VarFileInfo"
    BEGIN
        VALUE "Translation", 0x804, 936
    END
END
COM组件开发实践_数字签名_24

打包为CAB文件

因为ActiveX控件要放在网站上供客户下载到本地,因此压缩是必需的。一段典型的html代码如下:

<OBJECT ID="FuckATL1"  
CODEBASE ="http://localhost:8080/CardScan.cab"
CLASSID="CLSID:B548F3C7-2135-4242-920B-A7BDEE6D2BA3" WIDTH=300 HEIGHT=200
/>

CODEBASE就指明了要下载的压缩包,其中包含了oxc,dll控件等所需要的文件。

通常CAB文件包含了一个INF文件,它用来描述CAB文件的所有细节信息,下面举个简单例子,代码如下:

COM组件开发实践_数字签名_24
; Sample INF file for SCRIPTABLEACTIVEX.DLL
[version] 
; version signature (same for both NT and Win95) do not remove
signature="$CHICAGO$"
AdvancedINF=2.0  

[Add.Code]
CardScan.ocx=CardScan.ocx
CardScan.inf=CardScan.inf

[CardScan.ocx]
file-win32-x86=thiscab
clsid={B548F3C7-2135-4242-920B-A7BDEE6D2BA3} 
FileVersion=1,0,0,1 
RegisterServer=yes

[CardScan.inf]
file=thiscab
; end of INF file
COM组件开发实践_数字签名_24

至于打包就不赘述了,详尽的图解过程请看《如何给ActiveX数字签名(Step by Step, Delphi)

 



假设需求如下:底层是一个数学运算库DLL,中间是ActiveX控件(它调用底层的数学运算库DLL来完成控制层),界面层在测试时可以是一个exe程序,最后发布到IE浏览器上测试。

 

数学运算库DLL的开发

      新建一个Win32 DLL项目,加入一个头文件MyNum.h,在其中声明所有的数学函数(为简单起见,本文只考虑加法运算),代码如下:

COM组件开发实践_控件_06#ifndef MY_NUM_H
COM组件开发实践_控件_06#define MY_NUM_H
COM组件开发实践_控件_06int __stdcall AddNum(int,int);
COM组件开发实践_控件_06#endif
COM组件开发实践_控件_06

请注意这里的方法声明为__stdcall,而VC++默认的是__cdecl,由于组件的语言无关性要求调用和被调双方必须在函数调用的约定上一致,因此在后面加载DLL并获取此方法时也要求和你的声明一致。

      为了简单起见,加法方法的实现就放倒DLL入口点所在文件,代码如下:

COM组件开发实践_数字签名_24
COM组件开发实践_控件_06// NumDLL.cpp : 定义DLL 应用程序的入口点。
COM组件开发实践_控件_06//
COM组件开发实践_控件_06#include "stdafx.h"
COM组件开发实践_控件_06#include "MyNum.h"
COM组件开发实践_控件_06
COM组件开发实践_控件_06#ifdef _MANAGED
COM组件开发实践_控件_06#pragma managed(push, off)
COM组件开发实践_控件_06#endif
COM组件开发实践_控件_06
COM组件开发实践_控件_06int __stdcall AddNum(int Num1,int Num2)
COM组件开发实践_#include_35{
COM组件开发实践_web页面_36    return Num1+Num2;
COM组件开发实践_web页面_37}
COM组件开发实践_控件_06BOOL APIENTRY DllMain( HMODULE hModule,
COM组件开发实践_控件_06                       DWORD  ul_reason_for_call,
COM组件开发实践_控件_06                       LPVOID lpReserved
COM组件开发实践_控件_06                     )
COM组件开发实践_#include_35{
COM组件开发实践_web页面_36    return TRUE;
COM组件开发实践_web页面_37}
COM组件开发实践_控件_06
COM组件开发实践_控件_06#ifdef _MANAGED
COM组件开发实践_控件_06#pragma managed(pop)
COM组件开发实践_控件_06#endif
COM组件开发实践_控件_06
COM组件开发实践_数字签名_24

      为了能在其他程序中显示链接此DLL,我们为它加入一个.def文件,命名为NumDLL.def,列出此DLL导出的方法名称:

COM组件开发实践_控件_06LIBRARY    "NumDLL"
COM组件开发实践_控件_06EXPORTS
COM组件开发实践_控件_06    AddNum
COM组件开发实践_控件_06

至此我们的数学运算函数库DLL就完成了。

用ATL开发ActiveX控件

      开发ActiveX控件有两种方式,一是MFC,二是ATL,而后者是专门用于COM组件开发,因此更适合于ActiveX。因此这里选择后者,前者的开发示例参考我这篇文章(用VC++开发ActiveX 控件完全教程(一))。

      新建一个ATL项目,命名为”FuckATL”,接受默认设置。右键项目名,添加一个”ATL简单对象“,命名为CaluNumCtrl,点击下一步进入组件选项设置界面。

      修改类的头文件CaluNumCtrl.h如下:

COM组件开发实践_数字签名_24
COM组件开发实践_控件_06class ATL_NO_VTABLE CCaluNumCtrl :
COM组件开发实践_控件_06    public CComObjectRootEx<CComSingleThreadModel>,
COM组件开发实践_控件_06    public CComCoClass<CCaluNumCtrl, &CLSID_CaluNumCtrl>,
COM组件开发实践_控件_06    public ISupportErrorInfo,
COM组件开发实践_控件_06    public IConnectionPointContainerImpl<CCaluNumCtrl>,
COM组件开发实践_控件_06    public CProxy_ICaluNumCtrlEvents<CCaluNumCtrl>,
COM组件开发实践_控件_06    public IObjectWithSiteImpl<CCaluNumCtrl>,
COM组件开发实践_#include_35    public IDispatchImpl<ICaluNumCtrl, &IID_ICaluNumCtrl, &LIBID_FuckATLLib, /*wMajor =*/ 1, /*wMinor =*/ 0>
COM组件开发实践_#include_35{
COM组件开发实践_web页面_36public:
COM组件开发实践_web页面_36    typedef int (__stdcall*PtrAddNum)(int,int);
COM组件开发实践_web页面_36    PtrAddNum MyAddNum;
COM组件开发实践_web页面_36    CCaluNumCtrl()
COM组件开发实践_#include_69    {
COM组件开发实践_web页面_36        //加载数学运算库DLL
COM组件开发实践_web页面_36        handle = ::LoadLibrary(_T("D:\\dyk\\work\\NumDLL\\debug\\NumDLL.dll"));
COM组件开发实践_web页面_36        if (handle == NULL) 
COM组件开发实践_#include_69        {
COM组件开发实践_web页面_36            DWORD e = GetLastError();
COM组件开发实践_web页面_36            return;
COM组件开发实践_#include_76        }
COM组件开发实践_web页面_36        //获取加法运算函数指针
COM组件开发实践_web页面_36        MyAddNum = (PtrAddNum)GetProcAddress(handle,"AddNum");
COM组件开发实践_#include_76    }
COM组件开发实践_web页面_36DECLARE_REGISTRY_RESOURCEID(IDR_CALUNUMCTRL)
COM组件开发实践_web页面_36BEGIN_COM_MAP(CCaluNumCtrl)
COM组件开发实践_web页面_36    COM_INTERFACE_ENTRY(ICaluNumCtrl)
COM组件开发实践_web页面_36    COM_INTERFACE_ENTRY(IDispatch)
COM组件开发实践_web页面_36    COM_INTERFACE_ENTRY(ISupportErrorInfo)
COM组件开发实践_web页面_36    COM_INTERFACE_ENTRY(IConnectionPointContainer)
COM组件开发实践_web页面_36    COM_INTERFACE_ENTRY(IObjectWithSite)
COM组件开发实践_web页面_36END_COM_MAP()
COM组件开发实践_web页面_36
COM组件开发实践_web页面_36BEGIN_CONNECTION_POINT_MAP(CCaluNumCtrl)
COM组件开发实践_web页面_36    CONNECTION_POINT_ENTRY(__uuidof(_ICaluNumCtrlEvents))
COM组件开发实践_web页面_36END_CONNECTION_POINT_MAP()
COM组件开发实践_web页面_36// ISupportsErrorInfo
COM组件开发实践_web页面_36    STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid);
COM组件开发实践_web页面_36    DECLARE_PROTECT_FINAL_CONSTRUCT()
COM组件开发实践_web页面_36    HRESULT FinalConstruct()
COM组件开发实践_#include_69    {
COM组件开发实践_web页面_36        return S_OK;
COM组件开发实践_#include_76    }
COM组件开发实践_web页面_36    void FinalRelease()
COM组件开发实践_#include_69    {
COM组件开发实践_#include_76    }
COM组件开发实践_web页面_36//组件对外放出的加法方法
COM组件开发实践_web页面_36public:
COM组件开发实践_web页面_36    STDMETHOD(AddNumbers)(LONG Num1, LONG Num2, LONG* ReturnVal);    //下面是一个NUM属性,也是用于测试,包含了读写设置方法
COM组件开发实践_web页面_36public:
COM组件开发实践_web页面_36    STDMETHOD(get_NUM)(SHORT* pVal);
COM组件开发实践_web页面_36public:
COM组件开发实践_web页面_36    STDMETHOD(put_NUM)(SHORT newVal);
COM组件开发实践_web页面_36public:
COM组件开发实践_web页面_36    HMODULE handle;//数学函数库模块句柄
COM组件开发实践_web页面_37};
COM组件开发实践_控件_06
COM组件开发实践_数字签名_24

在控件实现文件CaluNumCtrl.cpp中,代码如下:

COM组件开发实践_数字签名_24
COM组件开发实践_控件_06STDMETHODIMP CCaluNumCtrl::AddNumbers(LONG Num1, LONG Num2, LONG* ReturnVal)
COM组件开发实践_#include_35{
COM组件开发实践_web页面_36    int sum = this->MyAddNum(static_cast<int>(Num1),static_cast<int>(Num2));
COM组件开发实践_web页面_36    *ReturnVal = static_cast<LONG>(sum);
COM组件开发实践_web页面_36    return S_OK;
COM组件开发实践_web页面_37}
COM组件开发实践_控件_06
COM组件开发实践_控件_06STDMETHODIMP CCaluNumCtrl::get_NUM(SHORT* pVal)
COM组件开发实践_#include_35{
COM组件开发实践_web页面_36    *pVal = 10;
COM组件开发实践_web页面_36    return S_OK;
COM组件开发实践_web页面_37}
COM组件开发实践_控件_06
COM组件开发实践_控件_06STDMETHODIMP CCaluNumCtrl::put_NUM(SHORT newVal)
COM组件开发实践_#include_35{
COM组件开发实践_web页面_36    // TODO: 在此添加实现代码
COM组件开发实践_web页面_36    
COM组件开发实践_web页面_36    return S_OK;
COM组件开发实践_web页面_37}
COM组件开发实践_控件_06
COM组件开发实践_数字签名_24

     好了,ActiveX控件仅仅是简单地调用底层的数学运算库DLL来完成运算,下面我们写一个exe程序对这个COM组件进行测试。

一个控制台测试程序

      建立一个最简单的控制台程序来进行测试,代码如下:

COM组件开发实践_数字签名_24
COM组件开发实践_控件_06#include "..\..\FuckATL\FuckATL\FuckATL.h"
COM组件开发实践_控件_06#include "..\..\FuckATL\FuckATL\FuckATL_i.c"
COM组件开发实践_控件_06#include <iostream>
COM组件开发实践_控件_06using  namespace std;
COM组件开发实践_控件_06
COM组件开发实践_控件_06void main(void)
COM组件开发实践_#include_35{
COM组件开发实践_web页面_36    // Declare and HRESULT and a pointer to the Simple_ATL interface
COM组件开发实践_web页面_36    HRESULT            hr;
COM组件开发实践_web页面_36    ICaluNumCtrl *IFirstATL = NULL;
COM组件开发实践_web页面_36    // Now we will intilize COM
COM组件开发实践_web页面_36    hr = CoInitialize(0);
COM组件开发实践_web页面_36    // Use the SUCCEDED macro and see if we can get a pointer to
COM组件开发实践_web页面_36    // the interface
COM组件开发实践_web页面_36    if(SUCCEEDED(hr))
COM组件开发实践_#include_69    {
COM组件开发实践_web页面_36        hr = CoCreateInstance( CLSID_CaluNumCtrl, NULL, CLSCTX_INPROC_SERVER,
COM组件开发实践_web页面_36            IID_ICaluNumCtrl, (void**) &IFirstATL);
COM组件开发实践_web页面_36        // If we succeeded then call the AddNumbers method, if it failed
COM组件开发实践_web页面_36        // then display an appropriate message to the user.
COM组件开发实践_web页面_36        if(SUCCEEDED(hr))
COM组件开发实践_#include_69        {
COM组件开发实践_web页面_36            long ReturnValue;
COM组件开发实践_web页面_36            hr = IFirstATL->AddNumbers(5, 7, &ReturnValue);
COM组件开发实践_web页面_36            cout << "The answer for 5 + 7 is: " << ReturnValue << endl;
COM组件开发实践_web页面_36            short num; 
COM组件开发实践_web页面_36            IFirstATL->get_NUM(&num);
COM组件开发实践_web页面_36            cout<<"num is: "<<num<<endl;
COM组件开发实践_web页面_36            hr = IFirstATL->Release();
COM组件开发实践_#include_76        }
COM组件开发实践_web页面_36        else
COM组件开发实践_#include_69        {
COM组件开发实践_web页面_36            cout << "CoCreateInstance Failed." << endl;
COM组件开发实践_#include_76        }
COM组件开发实践_#include_76    }
COM组件开发实践_web页面_36    // Uninitialize COM
COM组件开发实践_web页面_36    CoUninitialize();
COM组件开发实践_web页面_36    system("pause");
COM组件开发实践_web页面_37}
COM组件开发实践_控件_06
COM组件开发实践_数字签名_24

来到IE的世界

      最后我们将此ActiveX组件嵌入到html页面中,对其进行测试.新建一个html页面,代码如下:

COM组件开发实践_数字签名_24
COM组件开发实践_控件_06<HTML>
COM组件开发实践_控件_06<HEAD>
COM组件开发实践_控件_06<TITLE>New Page</TITLE>
COM组件开发实践_#include_35<script language="javascript">
COM组件开发实践_web页面_36    function doTest()
COM组件开发实践_#include_69    {
COM组件开发实践_web页面_36        var sum = FuckATL1.AddNumbers(3,4);
COM组件开发实践_web页面_36        alert(sum);
COM组件开发实践_web页面_37    }
COM组件开发实践_控件_06</script>
COM组件开发实践_控件_06</HEAD>
COM组件开发实践_控件_06<BODY>
COM组件开发实践_控件_06<OBJECT ID="FuckATL1" CLASSID="CLSID:7BF3B65F-A800-4604-AE6B-91844EFD5F05">
COM组件开发实践_控件_06</OBJECT>
COM组件开发实践_控件_06<input type="button" value="测试加法" id="btnOK" onclick="doTest();"></input>
COM组件开发实践_控件_06</BODY>
COM组件开发实践_控件_06</HTML>
COM组件开发实践_控件_06
COM组件开发实践_数字签名_24

     由于暂时先不考虑控件的安全性需要,因此会出现下面的警告信息,不过不要紧,这个问题以后再解决。

COM组件开发实践_web页面_198

测试结果如下:

COM组件开发实践_加载_199

  前面两篇文章分别介绍了MFC ActiveX应用程序和使用ATL开发ActiveX的简单实例,但还有两个问题需要解决:

1)标记ActiveX控件为安全的控件 2)对控件进行数字签名。本文将结合这两点进行简单的介绍。

Building a Safe ActiveX Control

      如何不想办法将控件标记为安全的,就会在Web页面与控件进行交互时出现如下图的警告信息:

COM组件开发实践_控件_200

     下面将分别介绍在MFC ActiveX和ATL中如何标记一个控件为安全的控件。

     要标记一个MFC ActiveX控件为安全,可以仿照下面代码修改而得:

COM组件开发实践_数字签名_24
// CardScan.cpp : CCardScanApp 和DLL 注册的实现。
#include "stdafx.h"
#include "CardScan.h"
#include "comcat.h"
#include "strsafe.h"
#include "objsafe.h"

CCardScanApp theApp;
const GUID CDECL BASED_CODE _tlid =
        { 0x29959268, 0x9729, 0x458E, { 0xA8, 0x39, 0xBB, 0x39, 0x2E, 0xCB, 0x7E, 0x37 } };
const WORD _wVerMajor = 1;
const WORD _wVerMinor = 0;
const CATID CLSID_SafeItem =
{0xB548F3C7,0x2135,0x4242,{0x92,0x0B,0xA7,0xBD,0xEE,0x6D,0x2B,0xA3}};

//{ 0x36299202, 0x9ef, 0x4abf,{ 0xad, 0xb9, 0x47, 0xc5, 0x99, 0xdb, 0xe7, 0x78}};
// CCardScanApp::InitInstance - DLL 初始化
BOOL CCardScanApp::InitInstance()
{
    BOOL bInit = COleControlModule::InitInstance();
    if (bInit)
    {
    }
    return bInit;
}
// CCardScanApp::ExitInstance - DLL 终止
int CCardScanApp::ExitInstance()
{
    return COleControlModule::ExitInstance();
}
HRESULT CreateComponentCategory(CATID catid, CHAR *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\{..catidCOM组件开发实践_控件_202}
    // 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(COLE2T(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;
    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, 
        _T("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, 
        _T("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;
    AFX_MANAGE_STATE(_afxModuleAddrThis);
    // 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;
    if (!AfxOleUnregisterTypeLib(_tlid, _wVerMajor, _wVerMinor))
        return ResultFromScode(SELFREG_E_TYPELIB);
    if (!COleObjectFactoryEx::UpdateRegistryAll(FALSE))
        return ResultFromScode(SELFREG_E_CLASS);
    return NOERROR;
}
COM组件开发实践_数字签名_24

     这里值得注意的一个地方是DllUnregisterServer函数,在这段代码中,我是将

hr=UnRegisterCLSIDInCategory(CLSID_SafeItem, CATID_SafeForInitializing);

hr=UnRegisterCLSIDInCategory(CLSID_SafeItem, CATID_SafeForScripting);

这两句代码放在

COM组件开发实践_数字签名_24
if (!AfxOleUnregisterTypeLib(_tlid, _wVerMajor, _wVerMinor))

           return ResultFromScode(SELFREG_E_TYPELIB);

      if (!COleObjectFactoryEx::UpdateRegistryAll(FALSE))

           return ResultFromScode(SELFREG_E_CLASS);
COM组件开发实践_数字签名_24

这两句代码的前面,如果你查阅MSDN,将会发现它上面的顺序和我是相反的,这应该是微软的一个错误代码,如果按照MSDN的代码来写,则你使用regsvr32 -u CardScan.ocx反注册时会报下面的错误:

COM组件开发实践_web页面_206

调整为我所说的顺序就没问题了。

2)要标记使用ATL写的ActiveX控件为安全的控件,这比MFC要简单的多,只需要在控件头文件中增加几行代码就可以了:

class ATL_NO_VTABLE CTestCtrl :
    …
    public IObjectSafetyImpl<CTestCtrl, INTERFACESAFE_FOR_UNTRUSTED_CALLER| INTERFACESAFE_FOR_UNTRUSTED_DATA>,

然后在COM映射表中增加一项:

BEGIN_COM_MAP(CTestCtrl)
    …
    COM_INTERFACE_ENTRY(IObjectSafety)
END_COM_MAP()

Building a Signed ActiveX Control

      ActiveX控件是个危险的东西,如果不对其合法性进行数字签名和验证,IE是会拒绝其安装的。

      工具包准备:CABARC.exe, cert2spc.exe, makecab.exe, makecert.exe, signcode.exe(或新版本中的signtool),以上小工具都可以在VS的安装路径下"Common7"Tools"Bin找到,或去微软官方网站上下载。

ActiveX控件的安装过程中,一部分工作就是自注册,这需要控件在VERSIONINFO结构中定义OLESelfRegister值,你可以对资源文件进行编辑如下

COM组件开发实践_数字签名_24
BEGIN
    BLOCK "StringFileInfo"
    BEGIN
        BLOCK "080403a8"
        BEGIN
            VALUE "CompanyName", "TODO: <公司名>"
            VALUE "FileDescription", "TODO: <文件说明>"
            VALUE "FileVersion", "1.0.0.1"
            VALUE "InternalName", "CardScan.ocx"
            VALUE "LegalCopyright", "TODO: (C) <公司名>。保留所有权利。"
            VALUE "OLESelfRegister", "\0"
            VALUE "OriginalFilename", "CardScan.ocx"
            VALUE "ProductName", "TODO: <产品名>"
            VALUE "ProductVersion", "1.0.0.1"
        END
    END
    BLOCK "VarFileInfo"
    BEGIN
        VALUE "Translation", 0x804, 936
    END
END
COM组件开发实践_数字签名_24

打包为CAB文件

因为ActiveX控件要放在网站上供客户下载到本地,因此压缩是必需的。一段典型的html代码如下:

<OBJECT ID="FuckATL1"  
CODEBASE ="http://localhost:8080/CardScan.cab"
CLASSID="CLSID:B548F3C7-2135-4242-920B-A7BDEE6D2BA3" WIDTH=300 HEIGHT=200
/>

CODEBASE就指明了要下载的压缩包,其中包含了oxc,dll控件等所需要的文件。

通常CAB文件包含了一个INF文件,它用来描述CAB文件的所有细节信息,下面举个简单例子,代码如下:

COM组件开发实践_数字签名_24
; Sample INF file for SCRIPTABLEACTIVEX.DLL
[version] 
; version signature (same for both NT and Win95) do not remove
signature="$CHICAGO$"
AdvancedINF=2.0  

[Add.Code]
CardScan.ocx=CardScan.ocx
CardScan.inf=CardScan.inf

[CardScan.ocx]
file-win32-x86=thiscab
clsid={B548F3C7-2135-4242-920B-A7BDEE6D2BA3} 
FileVersion=1,0,0,1 
RegisterServer=yes

[CardScan.inf]
file=thiscab
; end of INF file
COM组件开发实践_数字签名_24

至于打包就不赘述了,详尽的图解过程请看《如何给ActiveX数字签名(Step by Step, Delphi)