写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,勉强解决这个问题。不知道有没有高人能解决这个问题。
好了,总结写完了。希望对我自己,还有有需要的人能有帮助

浙公网安备 33010602011771号