`
hyshucom
  • 浏览: 808576 次
文章分类
社区版块
存档分类
最新评论

用ATL库开发COM组件常见的几个问题 (ie不响应事件,解决悲剧的方法) .

 
阅读更多

使用ATL开发COM组件时有几个问题,可能会经常遇到,并且如果不知道的话,还很难找到解决的方法。我看到有些人在CSDN上也问到了相同的问题,但很少有人给出满意的答案,所以我将这几个常见问题写在下面,以免其他人再重复劳动。

一、 问:做的一个控件,在网页里面调用时,IE浏览器总是提示:“在此页上的ActiveX控件和本页上其它部分的交互可能不安全.你想允许这种交互吗?” 请问如何将这个提示去掉。

答:在你的控件中加入如下处理即可。在你的控件的类定义部分再多继承一个父类

public IObjectSafetyImpl<CCommunicationsLink,INTERFACESAFE_FOR_UNTRUSTED_CALLER|INTERFACESAFE_FOR_UNTRUSTED_DATA>

同时在接口映射中也多加一句:
BEGIN_COM_MAP(CXXX)
........
COM_INTERFACE_ENTRY(IObjectSafety)
END_COM_MAP()

进行如上处理,则IE就不会再提示了。


二、 问:我用VC的ATL库写的控件,用VB可以接收到事件,但是用IE网页却死活收不到事件?

答:一般有以下几种原因:
第一个是IE浏览器只能支持单套间的模式,你的控件只能是单套间的,不能是多套间。

第二个是IE浏览器不会主动调用Advise等注册事件的接口,所以你的控件的类定义必须再多继承以下的父类

public
IProvideClassInfo2Impl<&CLSID_Test, &DIID__ITestEvents, &LIBID_TESTLib,1,0>

同时接口映射中加入以下处理
BEGIN_COM_MAP(CTest)
//用来查询接口类信息
COM_INTERFACE_ENTRY(IProvideClassInfo)
COM_INTERFACE_ENTRY(IProvideClassInfo2)
END_COM_MAP()

这样IE才能够注册准备接收事件。
参考 http://support.microsoft.com/default.aspx?scid=kb;en-us;200839


第三是你在控件中激活事件的语句(象调用Fire_Event的地方)如果不在主线程里面,而是在另外开的线程里面激活,则要经过处理才行。具体的处理步骤在微软的知识库中提到了,网址是
http://support.microsoft.com/kb/q280512/
需要下载一个该网页上的文件ATLCPImplMT.h,在控件事件实现的文件中进行如下处理:包含该文件,加上一些小修改,示例代码如下

#include "ATLCPImplMT.h" // 包含此句

// 注意下面一句的继承类由IconnectionPointImpl变成了 IConnectionPointImplMT
template <class T>
class CProxy_ITestEvents : public IConnectionPointImplMT<T, &DIID__ITestEvents, CComDynamicUnkArray>
{
//Warning this class may be recreated by the wizard.
public:
HRESULT FireEvents(DISPID p_DispID, CComVariant* p_pvars, long p_lParNum)
{
CComVariant varResult;
T* pT = static_cast<T*>(this);
int nConnectionIndex;
int nConnections = m_vec.GetSize();

for (nConnectionIndex = 0; nConnectionIndex < nConnections; nConnectionIndex++)
{

// 屏蔽掉向导生成的这三句
// pT->Lock();
// CComPtr<IUnknown> sp = m_vec.GetAt(nConnectionIndex);
// pT->Unlock();

// 换成下面这两句
CComPtr<IUnknown> sp;
sp.Attach(GetInterfaceAt(nConnectionIndex));

IDispatch* pDispatch = reinterpret_cast<IDispatch*>(sp.p);
if (pDispatch != NULL)
{
VariantClear(&varResult);
DISPPARAMS disp = {p_pvars, NULL, p_lParNum, 0 };
pDispatch->Invoke(p_DispID, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &disp, &varResult, NULL, NULL);
}
}
return varResult.scode;
}


当然,还有象通过给主线程发消息、线程间列集等方法,不过我觉得这种操作起来更简单些,进行以上的处理后IE网页就可以接收事件了。

在网页中接收事件的语法大致如下:

vbs 中

sub 控件ID_On事件函数名(参数)
............
end sub

js 中
<script for="控件ID" event="事件函数名(参数)">
............
</script>


三、 问:我在控件里面有SAFEARRAY类型的数据,怎样通过事件传给IE网页或VB程序?

答:比如你的IDL定义如下:
dispinterface _ITestEvents
{
properties:
methods:
[id(1), helpstring("DataReceived")]
HRESULT DataReceived([in] SAFEARRAY(unsigned char)* Data);
};
此时如果收到数据了,需要通过事件将数据传出去。在事件处理的函数中定义如下
HRESULT Fire_DataReceived(SAFEARRAY * * Data)

你会发现ATL向导生成的代码根本无法将SAFEARRAY类型的数据传出去,其实可以将向导生成的代码进行一下修改即可

// pvars[0] = Data;

pvars[0].vt = VT_ARRAY | VT_BYREF | VT_UI1;
pvars[0].pparray = Data;

将原来的pvars[0] = Data;屏蔽掉,换成下面两句即可。注意其中VT_UI1跟你发送事件前,组织SAFEARRAY数据时的类型相同,比如你用下面的函数组织数据
SafeArrayCreate(VT_UI1, 1, pSab);
则是VT_UI1,如果是其他类型要同时对应起来。

使用ATL开发COM组件时有几个问题,可能会经常遇到,并且如果不知道的话,还很难找到解决的方法。我看到有些人在CSDN上也问到了相同的问题,但很少有人给出满意的答案,所以我将这几个常见问题写在下面,以免其他人再重复劳动。

一、 问:做的一个控件,在网页里面调用时,IE浏览器总是提示:“在此页上的ActiveX控件和本页上其它部分的交互可能不安全.你想允许这种交互吗?” 请问如何将这个提示去掉。

答:在你的控件中加入如下处理即可。在你的控件的类定义部分再多继承一个父类

public IObjectSafetyImpl<CCommunicationsLink,INTERFACESAFE_FOR_UNTRUSTED_CALLER|INTERFACESAFE_FOR_UNTRUSTED_DATA>

同时在接口映射中也多加一句:
BEGIN_COM_MAP(CXXX)
........
COM_INTERFACE_ENTRY(IObjectSafety)
END_COM_MAP()

进行如上处理,则IE就不会再提示了。


二、 问:我用VC的ATL库写的控件,用VB可以接收到事件,但是用IE网页却死活收不到事件?

答:一般有以下几种原因:
第一个是IE浏览器只能支持单套间的模式,你的控件只能是单套间的,不能是多套间。

第二个是IE浏览器不会主动调用Advise等注册事件的接口,所以你的控件的类定义必须再多继承以下的父类

public
IProvideClassInfo2Impl<&CLSID_Test, &DIID__ITestEvents, &LIBID_TESTLib,1,0>

同时接口映射中加入以下处理
BEGIN_COM_MAP(CTest)
//用来查询接口类信息
COM_INTERFACE_ENTRY(IProvideClassInfo)
COM_INTERFACE_ENTRY(IProvideClassInfo2)
END_COM_MAP()

这样IE才能够注册准备接收事件。
参考 http://support.microsoft.com/default.aspx?scid=kb;en-us;200839


第三是你在控件中激活事件的语句(象调用Fire_Event的地方)如果不在主线程里面,而是在另外开的线程里面激活,则要经过处理才行。具体的处理步骤在微软的知识库中提到了,网址是
http://support.microsoft.com/kb/q280512/
需要下载一个该网页上的文件ATLCPImplMT.h,在控件事件实现的文件中进行如下处理:包含该文件,加上一些小修改,示例代码如下

#include "ATLCPImplMT.h" // 包含此句

// 注意下面一句的继承类由IconnectionPointImpl变成了 IConnectionPointImplMT
template <class T>
class CProxy_ITestEvents : public IConnectionPointImplMT<T, &DIID__ITestEvents, CComDynamicUnkArray>
{
//Warning this class may be recreated by the wizard.
public:
HRESULT FireEvents(DISPID p_DispID, CComVariant* p_pvars, long p_lParNum)
{
CComVariant varResult;
T* pT = static_cast<T*>(this);
int nConnectionIndex;
int nConnections = m_vec.GetSize();

for (nConnectionIndex = 0; nConnectionIndex < nConnections; nConnectionIndex++)
{

// 屏蔽掉向导生成的这三句
// pT->Lock();
// CComPtr<IUnknown> sp = m_vec.GetAt(nConnectionIndex);
// pT->Unlock();

// 换成下面这两句
CComPtr<IUnknown> sp;
sp.Attach(GetInterfaceAt(nConnectionIndex));

IDispatch* pDispatch = reinterpret_cast<IDispatch*>(sp.p);
if (pDispatch != NULL)
{
VariantClear(&varResult);
DISPPARAMS disp = {p_pvars, NULL, p_lParNum, 0 };
pDispatch->Invoke(p_DispID, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &disp, &varResult, NULL, NULL);
}
}
return varResult.scode;
}


当然,还有象通过给主线程发消息、线程间列集等方法,不过我觉得这种操作起来更简单些,进行以上的处理后IE网页就可以接收事件了。

在网页中接收事件的语法大致如下:

vbs 中

sub 控件ID_On事件函数名(参数)
............
end sub

js 中
<script for="控件ID" event="事件函数名(参数)">
............
</script>


三、 问:我在控件里面有SAFEARRAY类型的数据,怎样通过事件传给IE网页或VB程序?

答:比如你的IDL定义如下:
dispinterface _ITestEvents
{
properties:
methods:
[id(1), helpstring("DataReceived")]
HRESULT DataReceived([in] SAFEARRAY(unsigned char)* Data);
};
此时如果收到数据了,需要通过事件将数据传出去。在事件处理的函数中定义如下
HRESULT Fire_DataReceived(SAFEARRAY * * Data)

你会发现ATL向导生成的代码根本无法将SAFEARRAY类型的数据传出去,其实可以将向导生成的代码进行一下修改即可

// pvars[0] = Data;

pvars[0].vt = VT_ARRAY | VT_BYREF | VT_UI1;
pvars[0].pparray = Data;

将原来的pvars[0] = Data;屏蔽掉,换成下面两句即可。注意其中VT_UI1跟你发送事件前,组织SAFEARRAY数据时的类型相同,比如你用下面的函数组织数据
SafeArrayCreate(VT_UI1, 1, pSab);
则是VT_UI1,如果是其他类型要同时对应起来。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics