在Window编程中,我们经常要依靠定时器来定时触发某些代码的执行。但在ATL 的ActiveX 编程中,定时器的使用受到了一定限制,下面,我就根据开发经验谈谈如何在ATL 的 ActiveX编程中使用定时器这一资源。
首先,为了优化的性能,当前ActiveX 控件 分两种有窗口和无窗口控件。
在有窗口控件中你所获得的编程资源几乎和普通的Windows编程没有两样,而定时器的使用也一样。在ATL中你只要在控件的构造函数中指定控件基类CComControlBase 的成员m_bWindowOnly 为 true 就可以创建一个有窗口的ActiveX控件。示例如下:
myControl : myControl(){
m_bWindowOnly = true;
}
这样就可以在程序中用 SetTimer/KillTimer 和 响应 WM_TIMER消息来设置并使用定时器。
当作为一个ATL程序员,我们当然不会放弃每一个优化程序性能的机会,下面我就来介绍一下在ATL中的无窗口ActiveX控件中使用定时器的几种方法:
第一种:使用回调函数。在VC中,由于MFC和ATL了包装,我们可以忽略窗口句柄参数,直接创建一个和窗口关联的定时器。当无窗口控件由于没有窗口句柄,直接调用CWindow的SetTimer函数会给你抛出一个ASSERT对话框来。这时候我们只好回到API来寻求帮助, 由于 API SetTimer在创建一个定时器时除了窗口句柄,还为我们提供了通过回调函数来响应定时事件,这样,我们就可以绕过无窗口控件的约束使用定时器了。
首先,定义一个定时器的回调函数:
VOID CALLBACK TimerProc(
HWND hwnd, // 定时器消息的窗口句柄
UINT message, // WM_TIMER 消息
UINT idTimer, // 定时器标志
DWORD dwTime) // 当前系统启动计时
{
//这里添加你的定时器处理代码
}
接下来,就可以使用SetTimer来使用定时器了
uResult = SetTimer(NULL, // handle to main window
ID_TIMER, // 定时器标识
1000, // 1 秒间隔
(TIMERPROC)TimerProc); // 回调函数
第二种:通常我们在使用定时器时,往往希望在定时事件出发时可以通知自己的ActiveX 控件,虽然上面方法可以,但在参数的传递上有些不便,这里我使用多线程来模拟实习之,作为ATL中的编程风格,在向实现类添加某些功能,通常使用模板基类来扩充,故首先定义如下基类:
//////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
//
// 多线程模拟定时器
// 因 无窗口ATICVX 控件不能直接使用Timer, 而且为了保证定时的准确和精度,所以使用之
// 无窗口还可以通过 回调过程方式使用 Timer 但精度不能保证,所以此处排出
//
template <class Derived, class T, const IID* piid>
class CTimer
{
public:
CTimer()
{
m_bTimerOn = FALSE;
}
HRESULT TimerOn(DWORD dwTimerInterval)
{
Derived* pDerived = ((Derived*)this);
m_dwTimerInterval = dwTimerInterval;
if (m_bTimerOn) // 已经启动,仅仅调整定时间隔
return S_OK;
m_bTimerOn = TRUE;
m_dwTimerInterval = dwTimerInterval;
m_pStream = NULL;
HRESULT hRes;
// 接口列集
hRes = CoMarshalInterThreadInterfaceInStream(*piid, (T*)pDerived, &m_pStream);
// 创建线程并传递 this 指针
m_hThread = CreateThread(NULL, 0, &_Apartment, (void*)this, 0, &m_dwThreadID);
return S_OK;
}
void TimerOff()
{
if (m_bTimerOn)
{
m_bTimerOn = FALSE;
AtlWaitWithMessageLoop(m_hThread);
}
}
// 实现函数
private:
static DWORD WINAPI _Apartment(void* pv)
{
CTimer<Derived, T, piid>* pThis = (CTimer<Derived, T, piid>*) pv;
pThis->Apartment();
return 0;
}
DWORD Apartment()
{
CoInitialize(NULL);
HRESULT hRes;
m_spT.Release();
//接口散集
if (m_pStream)
hRes = CoGetInterfaceAndReleaseStream(m_pStream, *piid, void**)&m_spT);
while(m_bTimerOn)
{
Sleep(m_dwTimerInterval);
if (!m_bTimerOn)
break;
m_spT->OnTimer();
}
m_spT.Release();
CoUninitialize();
return 0;
}
// 属性
public:
DWORD m_dwTimerInterval;
// 实现属性
private:
HANDLE m_hThread;
DWORD m_dwThreadID;
LPSTREAM m_pStream;
CComPtr<T> m_spT;
BOOL m_bTimerOn;
};
从基类中可以看到,在线程函数中定时调用子类的OnTimer接口函数。这样我们只要从CTimer 中派生,并在子类接口中定义OnTimer接口,并实现该接口函数,就可以获得定时的调用,而TimerOn则用来开始并指定定时间隔,而TimerOff则用来关闭定时器。子类的代码如下:
////////////////////////////////////////////////////////////////////////////
//
// 类: CMyControl
// 功能: 示例 ActiveX控件实现
//
class ATL_NO_VTABLE CMyControl :
// ... 其他接口
// 模拟定时器的支持
public CTimer<CMyControl , IMyControl, &IID_IMyControl>
{
public:
STDMETHOD(OnTimer)(void);
};
// 实现
STDMETHODIMP CCFuelWatchObj::OnTimer(void)
{
//定时响应代码
}
声明:方法二参考了MSDN中相关资料。
后记:实际上,在无窗口ActiveX控件中使用定时器的方法是很多,在这里只概述了两种。原本想就ATL中使用定时器这一功能进行比较详细描述,但由于中间工作原因间断,现在思源枯竭,只能写这些勒,如果这篇文章对于初次接触 ATL ActiveX 控件编程朋友有所参考,那在下就很感激了。