在ATL COM服务器中触发事件的典型代码就是这样(从this question复制并修剪一下):
HRESULT Fire_MessageTrigger()
{
HRESULT hr = S_OK;
T * pThis = static_cast<T *>(this);
int count = m_vec.GetSize();
for (int i = 0; i < count; i++)
{
pThis->Lock(); // I'm asking about this...
CComPtr<IUnknown> punkConnection = m_vec.GetAt(i);
pThis->Unlock(); // and this
IDispatch* pConnection = static_cast<IDispatch *>(punkConnection.p);
if (pConnection == 0)
continue;
DISPPARAMS params = { NULL, NULL, 0, 0 };
hr = pConnection->Invoke(2, IID_NULL, LOCALE_USER_DEFAULT,
DISPATCH_METHOD, ¶ms, 0, NULL, NULL);
}
return hr;
}
Lock()
和Unlock()
来电的目的是什么?
答案 0 :(得分:5)
围绕m_vec
成员变量(接收器列表)的线程安全措施。
您可以在并发线程上执行以下操作:
template <class T, const IID* piid, class CDV>
STDMETHODIMP IConnectionPointImpl<T, piid, CDV>::Advise(
_Inout_ IUnknown* pUnkSink,
_Out_ DWORD* pdwCookie)
{
// ...
pT->Lock();
*pdwCookie = m_vec.Add(p); // <<--- Modifying m_vec here
hRes = (*pdwCookie != NULL) ? S_OK : CONNECT_E_ADVISELIMIT;
pT->Unlock();
你的下一个问题是他们在访问m_vec.GetSize();
时没有锁定几行的原因。
这是他们这样做的方式:他们可以在并发向量访问的情况下错过单个事件传递,只要它不会创建内存损坏或其他未定义的行为。毕竟,订阅呼叫可能会在稍后发生并且无论如何都会错过此事件。这同样适用于事件取消订阅的情况(Unadvise
)。
此处的优先级是尽可能少地锁定,然后尽快解锁。请注意,接收器调用在解锁时发生,接收器列表可以进行修改,同时我们正在遍历调用它们的接收器列表。
UPD。 Visual Studio 2008始终生成Lock
/ Unlock
对。更新版本也是如此。从
“\ VC \ VCWizards \ CodeWiz \ ATL \ ImplementInterface \ HTML \ 1033 \ default.htm”文件:
strProxyMethod +=
"\t\tfor (int iConnection = 0; iConnection < cConnections; iConnection++)\r\n"+
"\t\t{\r\n"+
"\t\t\tpThis->Lock();\r\n"+
"\t\t\tCComPtr<IUnknown> punkConnection = m_vec.GetAt(iConnection);\r\n"+
"\t\t\tpThis->Unlock();\r\n\r\n";
早期的VS版本可以生成无锁代码,只有在从VS IDE手动重新生成代理类时才会更新代码。