一、现象
有大量的网友遇到过“IE浏览器点击二级网页链接却无法打开”的现象。一般的解决方案是重新注册有关的dll文件。
重新注册以下DLL文件,在"开始"->"运行"对话框中逐条输入以下命令.
regsvr32 Shdocvw.dll
regsvr32 Actxprxy.dll
regsvr32 Shell32.dll
regsvr32 Oleaut32.dll
regsvr32 Mshtml.dll
regsvr32 Urlmon.dll
regsvr32 browscui.dll
regsvr32 msjava.dll
可是,这种现象是如何产生的呢?我们开发人员需要注意些什么呢?
二、原因
动态链接库dll在注册时会将com组件的类和接口写入注册表。比如Mshtml.dll中的IHTMLElement接口写入到注册表的HKEY_CLASSES_ROOT/Interface中。IE在打开二级网页的链接时,需要查询IHTMLElement接口的信息。如果这个接口丢失了,就会出现问题。因此,只需要regsvr32 Mshtml.dll 就可以解决问题了。同样,如果是其他接口丢失,就需要重新注册对应的dll文件。那么,在什么情况之下会出现接口丢失呢?
三、背景
我在vista下开发PIMShell,一切正常。后来在xp下测试时,发现当程序卸载后,IE无法打开二级页面。原来程序大量使用了mshtml.dll中的IHTMLXXX接口,在安装时,把IHTMLXXX接口注册到自己名下,从而导致在卸载时IHTMLXXX接口也被删除,于是IE找不到所需要的IHTMLXXX接口了。然而vista采用了Side By Side的程序部署方式,所以,即使我的程序卸载了,IHTMLXXX接口依然存在,因此不存在这个问题。
那么,我们开发人员需要怎样做才能避免这个问题呢?
四、解决方案
针对用户而言,解决方案就是重新注册dll文件。这里要讨论的是针对开发人员的解决方案。因为,程序正确了,用户也就不会有此困扰了。
方案一:
开发com组件时,接口方法的参数不要出现任何IHTMLXXX接口,用IDispatch接口代替。如:
HRESULT GetElement(BSTR bstrName,IDispatch** ppElement)
{
CComPtr<IHTMLElement> pElement=this->__getElement(bstrName);
return pElement->QueryInterface(IID_IDispatch,(void**)ppElement);
}
因为开发的com组件接口均没有使用IHTMLXXX接口,那么在注册时也就不会改变注册表中IHTMLXXX接口的行为了。
方案二:
如果要想在接口方法的参数中使用IHTMLXXX接口,那么就必须明确指示IHTMLXXX接口来自于Mshtml.dll,而不属于自己。这样,在注册时也不会改变IHTMLXXX接口的行为。如何做呢?
1、在IDL文件中导入mshtml的类型库,并定义接口
library PIMShellCore
{
...
importlib("mshtml.tlb");
...
HRESULT GetElement([in] BSTR bstrName,[out,retval] MSHTML.IHTMLElement** ppElement);
...
};
2、GetElement实现
HRESULT GetElement(BSTR bstrName,IHTMLElement** ppElement)
{
CComPtr<IHTMLElement> pElement=this->__getElement(bstrName);
*ppElement=pElement.Detach();
return S_OK;
}
五、结论
虽然解决方案很简单,但为什么这个问题会大量存在呢?因为大多数的开发人员在开发ATL Com组件时采用属性化编程,从而不能使用类似MSHTML.IHTMLElement这样的语法,指示IHTMLElement接口来自mshtml.dll文件。幸运的是,visual studio 2008虽然支持属性化编程,但在新建ATL Com组件的向导中删除了“属性化”选项。所以,可以轻松的按照前面说的解决方案彻底解决“IE无法打开二级页面”的问题了。