写ActiveX控件,和WEB通信的一些心得1

摘自:http://hi.baidu.com/zzutligang/blog/item/b45992c364256c40b319a8fd.html

最近用VC++6.0写了一个ActiveX控件,实现了对加密狗和明华的非接触式ic卡读写器的操作封装,然后在web上使用。

控件写完了,经过了严格测试。但这个过程也遇到了不少难题,从网上搜了不少资料,但基本上都是一知半解,甚至有些根本就是错误的。

因此,在这里,进行一次总结。

第一:关于方法参数按地址传回值的问题。

这个问题很困扰我。我在网上搜了很多,有说可以,有的说不可以。目前我也不知道到底可不可以。但我的测试结果是不可以。

比如,控件一个方法func(long section, BSTR* parm),这种方式,在其他程序里,比如,vb,pb,c#这类的winform类型的应用上完全没问题,但在web上用javascript调用是不行的,浏览器总是提示类型不匹配。

解决办法:增加一个控件属性,将需要返回的数据放到属性里,执行完这个函数后,立即去访问那个属性。可以曲线解决这个问题。虽然不完美,但可以满足要求。

第二:就是控件编译注册后,在winform类型的开发工具上,都可以运行,但在web上加载的时候,会提示控件不安全,拒绝加载。

这个问题,其实是微软自欺欺人的一个做法。对着个东西,我保留自己的意见。

解决办法。首先在你的控件类名.cpp文件里的函数STDAPI DllRegisterServer(void)之前增加如下几个函数的定义:

// 创建组件种类 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

    // Make sure the provided description is not too long.     // Only copy the first 127 characters if it is.     int len = wcslen(catDescription);     if (len>127)         len = 127;     wcsncpy(catinfo.szDescription, catDescription, len);     // Make sure the description is null terminated.     catinfo.szDescription[len] = '\0';

    hr = pcr->RegisterCategories(1, &catinfo);         pcr->Release();

    return hr; }

// 注册组件种类 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(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; }

 

然后在函数STDAPI DllRegisterServer(void)的内部结尾处增加如下代码

HRESULT hr; // 标记控件初始化安全.     // 创建初始化安全组件种类     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;

    // 标记控件脚本安全     // 创建脚本安全组件种类     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;

 

在函数STDAPI DllUnregisterServer(void)的内部结尾处增加如下代码:

HRESULT hr; // 删除控件初始化安全入口.     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;

 

当然,还有一个方法,就是要让这个控件实现safe接口,好像比较麻烦,还不如这个方法来的简洁。

注意,增加如上代码,需要增加对

#include "comcat.h" #include "Objsafe.h"

这个两个头文件的引用。

第三:就是人见人头疼的控件自定义事件无法被web捕捉到得问题。

这个问题有如下几个方面

1、控件必须要实现接口IProvideClassInfo,浏览器在解析控件的时候,就是通过这个接口来遍历控件的所有自定义事件的,控件不实现这个接口,浏览器无法访问到这个控件的自定义事件,当然你就没办法捕捉到了,实现方法如下:

找到你的控件ctl.h文件,找到DECLARE_DYNCREATE(CYorkWebActiveXCtrl)这行宏定义,我的控件名是YorkWebActiveX,你找你自己的。然后再这行下增加如下代码:

//为了让浏览器捕捉到事件,增加如下代码,和CPP文件对应 DECLARE_INTERFACE_MAP()         BEGIN_INTERFACE_PART(ProvideClassInfo, IProvideClassInfo)             STDMETHOD_(HRESULT, GetClassInfo) (                   /*[out] */ITypeInfo** ppTI             );         END_INTERFACE_PART(ProvideClassInfo); /////////////////////////////////////////////////////////////

然后找到对应的cpp文件,找到UpdateRegistry函数的实现,在这个函数后面,注意,不是在这个函数内部,而是在这个函数后面,增加如下代码:

/////////////////////////////////////////////////////////////////////// //为了实现让web浏览器能捕捉到事件,增加如下代码 BEGIN_INTERFACE_MAP( CYorkWebActiveXCtrl, COleControl )              INTERFACE_PART(CYorkWebActiveXCtrl, IID_IProvideClassInfo, ProvideClassInfo)
        END_INTERFACE_MAP()

ULONG FAR EXPORT CYorkWebActiveXCtrl::XProvideClassInfo::AddRef() {
    METHOD_PROLOGUE(CYorkWebActiveXCtrl, ProvideClassInfo) return pThis->ExternalAddRef(); }

ULONG FAR EXPORT CYorkWebActiveXCtrl::XProvideClassInfo::Release() {
METHOD_PROLOGUE(CYorkWebActiveXCtrl, ProvideClassInfo) return pThis->ExternalRelease(); }

HRESULT STDMETHODCALLTYPE CYorkWebActiveXCtrl::XProvideClassInfo::QueryInterface (REFIID iid, void FAR* FAR* ppvObj) { METHOD_PROLOGUE(CYorkWebActiveXCtrl, ProvideClassInfo)
return (HRESULT)pThis->ExternalQueryInterface(&iid, ppvObj);
}

HRESULT STDMETHODCALLTYPE CYorkWebActiveXCtrl::XProvideClassInfo::GetClassInfo (                /* [out] */ITypeInfo** ppTypeInfo ) { METHOD_PROLOGUE(CYorkWebActiveXCtrl, ProvideClassInfo)

HRESULT hr; CLSID clsid; pThis->GetClassID(&clsid);

hr = pThis->GetTypeInfoOfGuid(GetUserDefaultLCID(), clsid, ppTypeInfo);

return hr;
} /////////////////////////////////////////////////////////////////////////////

 

好了,现在你的控件的事件就可以被浏览器搜到了。

浏览器可以找到你的控件的自定义事件,你就可以在你的控件里去Fire你的控件事件了。

如果你的控件不涉及到多线程,那到现在,你的控件就可以很完美的运行了。

但如果你的控件开启了一个新线程,并且要在这个线程里去触发一个自定义事件,你会发现,好好的事件,在web上又不工作了,烦人啊。怎么办?老方法:曲线救国!!!

解决办法:在控件里定义一个消息号

//消息号 #define WM_THREADFIREEVENT_ICFIND WM_APP+101

然后增加一个消息映射函数,让控件接到这个消息的时候,就执行那个消息映射函数,然后在那个消息映射函数里,去Fire你的自定义事件,就ok了。

好了,看似到这里,问题已经很完美的解决了。但我还有一个问题没解决:

本来我的控件不需要有界面,于是我在创建控件工程的时候,选择了运行时不可见,但上面通过发消息触发事件的解决办法就完全不可行了,控件根本就收不到post过来的那个消息。极其郁闷啊!

也就是说如果控件的这个定义:

static const DWORD BASED_CODE _dwYorkWebActiveXOleMisc = OLEMISC_ACTIVATEWHENVISIBLE | OLEMISC_SETCLIENTSITEFIRST | OLEMISC_INSIDEOUT | OLEMISC_CANTLINKINSIDE | OLEMISC_RECOMPOSEONRESIZE;

有 OLEMISC_INVISIBLEATRUNTIME  这个成员,控件在运行时就不可见,但就不能捕捉到消息,当然也就不能触发自定义事件了。

如果去掉这个成员,那控件就运行完美了,但web页面上会出现控件的样子,默认就是一个椭圆,很不协调,我现在就是通过将web页面上的这个控件的高度和宽度都设置为0,勉强解决这个问题。不知道有没有高人能解决这个问题。

好了,总结写完了。希望对我自己,还有有需要的人能有帮助

posted @ 2013-01-04 14:45  cityhunter2008  阅读(139)  评论(0)    收藏  举报