Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

455 lines
18 KiB

  1. //+-------------------------------------------------------------------------
  2. //
  3. // Microsoft Windows
  4. // Copyright (C) Microsoft Corporation, 1992 - 1999
  5. //
  6. // File: scriptevents.h
  7. //
  8. // Contents: Implementation of script events thru connection points
  9. //
  10. // History: 11-Feb-99 AudriusZ Created
  11. //
  12. //--------------------------------------------------------------------------
  13. /*
  14. This file provides means to implement connection-point based events with
  15. the ability to fire them to running scripts. That is an alternative to
  16. wizard-based implementation provided by ATL.
  17. IDEA
  18. The idea behind this implementation is to create the class that
  19. implements the methods from the event interface. Each such method will
  20. have a string literal equivalent to method name. Whenever such a method
  21. is called, it will use that string literal to find the dispatch ID of
  22. the interface method and will use it to forward the call to event sinks.
  23. The special measures need to be taken to assure correct mapping of methods,
  24. proper parameter types and count. The provided implementation puts these
  25. tasks on the compiler, since it's more accurate than the human. Deriving
  26. the class from the event interface implemented assures it. To implement
  27. method mapping the class uses CComTypeInfoHolder defined in ATL
  28. to do the actual mapping.
  29. The benefits come with a price - the event interface needs to be dual
  30. (while the scripts require it to be dispinterface only) - that requires
  31. defining 2 interfaces in IDL. Mapping also means slightly increased method
  32. call time.
  33. REQUIREMENTS / USAGE
  34. 1. You need to define hidden dual interface (with the name constructed by prepending
  35. underscore to the name of dispinterface) in the IDL file, like this:
  36. [
  37. uuid(YYYYYYYYYYY), dual, helpstring("")
  38. ]
  39. interface _DocEvents : IDispatch
  40. {
  41. [id(1), helpstring("Occurs when a document is initialized")]
  42. HRESULT OnInitialize( [in] PDOCUMENT pDocument);
  43. [id(2), helpstring("Occurs when a document is destroyed")]
  44. HRESULT OnDestroy( [in] PDOCUMENT pDocument);
  45. };
  46. 2. You need to define the dispinterface to be used as event source, like
  47. this:
  48. [
  49. uuid(XXXXXXXXXXX) , helpstring("DocEvents Interface")
  50. ]
  51. dispinterface DocEvents
  52. {
  53. interface _DocEvents;
  54. };
  55. 3. Your com object needs to derive from IConnectionPointContainerImpl and
  56. define CONNECTION_POINT_MAP.
  57. 4. You need to provide the specialization of the event proxy, mapping all
  58. the methods from the event interface, like this:
  59. DISPATCH_CALL_MAP_BEGIN(DocEvents)
  60. DISPATCH_CALL1( OnInitialize, PDOCUMENT )
  61. DISPATCH_CALL1( OnDestroy, PDOCUMENT )
  62. DISPATCH_CALL_MAP_END()
  63. Note: this must be defined at global scope (probably *.h file) and visible
  64. from wherever ScFireComEvent is executed.
  65. 5. (optional) If any of the parameters in the event interface refers to
  66. coclass in IDL, you need to map (cast) the types to the interface
  67. pointer inside the proxy, like this:
  68. DISPATCH_CALL_MAP_BEGIN(DocEvents)
  69. DISPATCH_PARAM_CAST ( PDOCUMENT, IDispatch * );
  70. DISPATCH_CALL1( OnInitialize, PDOCUMENT )
  71. DISPATCH_CALL1( OnDestroy, PDOCUMENT )
  72. DISPATCH_CALL_MAP_END()
  73. 6. where needed you may fire the evens like this:
  74. sc = ScFireComEvent(m_sp_Document, DocEvents , OnDestroy(pDocument));
  75. 7. to avoid creating com objects (parameters to events) when there is no one
  76. interested in receiving them use ScHasSinks macro:
  77. sc = ScHasSinks(m_sp_Document, DocEvents);
  78. if (sc == SC(S_OK))
  79. {
  80. // create com objects and fire the event
  81. }
  82. COMPARISON this ATL
  83. Manual changes yes semi
  84. Dual interface required not required
  85. Call to method 1st time Maps the name direct
  86. Type library required at runtime only to generate
  87. After method is added require to add Require to regenerate
  88. the method to proxy proxy file
  89. After disp ID is changed no changes Require to regen.
  90. Compiling changed IDL Won't compile if proxy Fail on runtime
  91. doesn't fit interface
  92. fire from anywhere from proxy only
  93. coclass param casting provides not support
  94. USED CLASS HIERARCHY
  95. CEventDispatcherBase [ type-invariant stuff]
  96. /\
  97. ||
  98. CEventDispatcher<_dual_interface> [ interface type parametrized stuff]
  99. /\
  100. ||
  101. CEventProxy<_dispinterface> [ client provided call map defines this]
  102. /\
  103. ||
  104. CScriptEvent<_dispinterface> [ this one gets instantiated by ScFireComEvent ]
  105. */
  106. #include "eventlock.h"
  107. /***************************************************************************\
  108. *
  109. * CLASS: CEventDispatcherBase
  110. *
  111. * PURPOSE: base class for CEventDispatcher template
  112. * implements functionality not dependant on template parameters
  113. *
  114. \***************************************************************************/
  115. class CEventDispatcherBase
  116. {
  117. protected:
  118. // c-tor
  119. CEventDispatcherBase() : m_bEventSourceExists(false) {}
  120. // Parameter cast template
  121. // Provided to allow parameter casting for parameters of specified types
  122. // in proxy using DISPATCH_PARAM_CAST macro.
  123. // Default (specified here) casts every type to itself
  124. template <typename _param_type>
  125. struct ParamCast
  126. {
  127. typedef _param_type PT;
  128. };
  129. // Parameter cast function (see comment above)
  130. template <typename _param_type>
  131. inline ParamCast<_param_type>::PT& CastParam(_param_type& arg) const
  132. {
  133. return reinterpret_cast<ParamCast<_param_type>::PT&>(arg);
  134. }
  135. // sets connection point container to be used to forward events
  136. void SetContainer(LPUNKNOWN pComObject);
  137. // forward the call using IDispatch of event sinks
  138. SC ScInvokeOnConnections(const REFIID riid, DISPID dispid, CComVariant *pVar, int count, CEventBuffer& buffer) const;
  139. // returns S_FALSE if no sinks are registered with the object.
  140. SC ScHaveSinksRegisteredForInterface(const REFIID riid);
  141. private:
  142. // there are two options: com object does not exist (that's ok)
  143. // and it does not implement container (error)
  144. // we have 2 variables to deal with both situations
  145. bool m_bEventSourceExists;
  146. IConnectionPointContainerPtr m_spContainer;
  147. };
  148. /***************************************************************************\
  149. *
  150. * CLASS: CEventDispatcher
  151. *
  152. * PURPOSE: Fully equipped call dispather
  153. * Main functionality of it is to implement ScMapMethodToID
  154. * and ScInvokeOnConnections - two functions required to call
  155. * method by name thru IDispatch::Invoke()
  156. * They are use by DISPATCH_CALL# macros
  157. *
  158. \***************************************************************************/
  159. template <typename _dual_interface>
  160. class CEventDispatcher : public CEventDispatcherBase
  161. {
  162. protected:
  163. typedef _dual_interface DualIntf;
  164. // Maps method name to dispId; Employs CComTypeInfoHolder to do the job
  165. SC ScMapMethodToID(LPCWSTR strMethod, DISPID& dispid)
  166. {
  167. DECLARE_SC(sc, TEXT("ScMapMethodToID"));
  168. // this cast is needed just because of bad parameter type defined in GetIDsOfNames
  169. LPOLESTR strName = const_cast<LPOLESTR>(strMethod);
  170. // rely on CComTypeInfoHolder to do it
  171. sc = m_TypeInfo.GetIDsOfNames( IID_NULL, &strName, 1, LOCALE_NEUTRAL, &dispid );
  172. if (sc)
  173. return sc;
  174. return sc;
  175. }
  176. private:
  177. // be aware - this member depents on template parameter
  178. static CComTypeInfoHolder m_TypeInfo;
  179. };
  180. /***************************************************************************\
  181. *
  182. * CEventDispatcher static member initialization
  183. *
  184. \***************************************************************************/
  185. const WORD wVersionMajor = 1;
  186. const WORD wVersionMinor = 0;
  187. template <typename _dual_interface>
  188. CComTypeInfoHolder CEventDispatcher<_dual_interface>::m_TypeInfo =
  189. { &__uuidof(_dual_interface), &LIBID_MMC20, wVersionMajor, wVersionMinor, NULL, 0, NULL, 0 };
  190. /***************************************************************************\
  191. *
  192. * CLASS: CEventProxy
  193. *
  194. * PURPOSE: Every event interface should have specialization of this class,
  195. * describing mapped (dual interface - to - dispinterface) methods.
  196. * Here it is just declared - not defined.
  197. *
  198. \***************************************************************************/
  199. template <typename _dispinterface> class CEventProxy;
  200. /***************************************************************************\
  201. *
  202. * CLASS: CScriptEvent
  203. *
  204. * PURPOSE: The object that will be constructed to fire actual event.
  205. * It is provided solely to do initialization by constuctor,
  206. * coz that allowes us to use single unnamed object in the single
  207. * statement to both construct it and invoke a method on it.
  208. *
  209. \***************************************************************************/
  210. template <typename _dispinterface>
  211. class CScriptEvent: public CEventProxy<_dispinterface>
  212. {
  213. public:
  214. CScriptEvent(LPUNKNOWN pComObject)
  215. {
  216. SetContainer(pComObject);
  217. }
  218. // returns S_FALSE if no sinks are registered with the object.
  219. SC ScHaveSinksRegistered()
  220. {
  221. return ScHaveSinksRegisteredForInterface(__uuidof(_dispinterface));
  222. }
  223. };
  224. /***************************************************************************\
  225. *
  226. * MACROS USED TO IMPLEMENT EVENT PROXY
  227. *
  228. \***************************************************************************/
  229. /***************************************************************************\
  230. *
  231. * MACRO: DISPATCH_CALL_MAP_BEGIN
  232. *
  233. * defines the begining of the call map.
  234. * NOTE : assumes that dual interface has the same name as the dispinterface
  235. * with single '_' prepended
  236. \***************************************************************************/
  237. #define DISPATCH_CALL_MAP_BEGIN(_dispinterface) \
  238. template<> class CEventProxy<_dispinterface> : public CEventDispatcher<_##_dispinterface> {
  239. /***************************************************************************\
  240. *
  241. * MACRO: DISPATCH_CALL_MAP_END
  242. *
  243. * defines the end of the call map.
  244. \***************************************************************************/
  245. #define DISPATCH_CALL_MAP_END() };
  246. /***************************************************************************\
  247. *
  248. * MACRO: DISPATCH_PARAM_CAST
  249. *
  250. * defines type mapping to be used prior to IDispatch::Invoke
  251. * It is provided to deal with objects defined as coclass
  252. * in IDL file, which cannot implecitly be converted to any interface.
  253. * ( if we do not change it CComVariant will treat it as bool type )
  254. *
  255. \***************************************************************************/
  256. #define DISPATCH_PARAM_CAST(from,to) \
  257. public: template <> struct CEventDispatcherBase::ParamCast<from> { typedef to PT; }
  258. /***************************************************************************\
  259. *
  260. * MACRO: DISPATCH_CALL_PROLOG
  261. *
  262. * used as first part of every DISPATCH_CALL# macro
  263. *
  264. \***************************************************************************/
  265. #define DISPATCH_CALL_PROLOG(_method, _param_list) \
  266. /* Implement pure method to be able to instantiate the class */ \
  267. /* it should not be used - so it's declared private */ \
  268. private: STDMETHOD(_method) _param_list \
  269. { \
  270. /* retrieving the pointer to base won't compile if method is */ \
  271. /* removed from the interface (that's what we want to catch) */ \
  272. HRESULT (STDMETHODCALLTYPE DualIntf::*pm)_param_list = DualIntf::_method; \
  273. return E_NOTIMPL; \
  274. } \
  275. /* Implement invocation in Sc* method used by SC_FIRE* macro */ \
  276. /* _param_list represents parameter list with brackets */ \
  277. public: SC Sc##_method _param_list { \
  278. DECLARE_SC(sc, TEXT("DISPATCH_CALL::Sc") TEXT(#_method)); \
  279. /* following lines gets dispid on the first call only. */ \
  280. /* succeeding calls will reuse it or error from the 1st call */ \
  281. static DISPID dispid = 0; \
  282. static SC sc_map = ScMapMethodToID(L#_method, dispid); \
  283. if (sc_map) return sc = sc_map;
  284. /***************************************************************************\
  285. *
  286. * MACRO: DISPATCH_CALL_EPILOG
  287. *
  288. * used as last part of every DISPATCH_CALL# macro
  289. *
  290. \***************************************************************************/
  291. #define DISPATCH_CALL_EPILOG(_pvar, _count) \
  292. /* Get the proper event buffer for locked scenarios */ \
  293. CEventBuffer& buffer = GetEventBuffer(); \
  294. /* just invoke the method on the sinks */ \
  295. return sc = ScInvokeOnConnections(__uuidof(_dispinterface), dispid, _pvar, _count, buffer); }
  296. /***************************************************************************\
  297. *
  298. * MACRO: DISPATCH_CALL0
  299. *
  300. * used to map an event interface method with 0 parameters
  301. *
  302. \***************************************************************************/
  303. #define DISPATCH_CALL0(_method) \
  304. DISPATCH_CALL_PROLOG(_method, ()) \
  305. DISPATCH_CALL_EPILOG(NULL, 0)
  306. /***************************************************************************\
  307. *
  308. * MACRO: DISPATCH_CALL1
  309. *
  310. * used to map an event interface method with 1 parameter
  311. *
  312. \***************************************************************************/
  313. #define DISPATCH_CALL1(_method, P1) \
  314. DISPATCH_CALL_PROLOG(_method, (P1 param1)) \
  315. CComVariant var[] = { CastParam(param1) }; \
  316. DISPATCH_CALL_EPILOG(var, countof(var))
  317. /***************************************************************************\
  318. *
  319. * MACRO: DISPATCH_CALL2
  320. *
  321. * used to map an event interface method with 2 parameters
  322. *
  323. \***************************************************************************/
  324. #define DISPATCH_CALL2(_method, P1, P2) \
  325. DISPATCH_CALL_PROLOG(_method, (P1 param1, P2 param2)) \
  326. CComVariant var[] = { CastParam(param2), CastParam(param1) }; \
  327. DISPATCH_CALL_EPILOG(var, countof(var))
  328. /***************************************************************************\
  329. *
  330. * MACRO: DISPATCH_CALL3
  331. *
  332. * used to map an event interface method with 3 parameters
  333. *
  334. \***************************************************************************/
  335. #define DISPATCH_CALL3(_method, P1, P2, P3) \
  336. DISPATCH_CALL_PROLOG(_method, (P1 param1, P2 param2, P3 param3)) \
  337. CComVariant var[] = { CastParam(param3), CastParam(param2), CastParam(param1) }; \
  338. DISPATCH_CALL_EPILOG(var, countof(var))
  339. /***************************************************************************\
  340. *
  341. * MACRO: DISPATCH_CALL4
  342. *
  343. * used to map an event interface method with 4 parameters
  344. *
  345. \***************************************************************************/
  346. #define DISPATCH_CALL4(_method, P1, P2, P3, P4) \
  347. DISPATCH_CALL_PROLOG(_method, (P1 param1, P2 param2, P3 param3, P4 param4)) \
  348. CComVariant var[] = { CastParam(param4), CastParam(param3), CastParam(param2), CastParam(param1) }; \
  349. DISPATCH_CALL_EPILOG(var, countof(var))
  350. /***************************************************************************\
  351. *
  352. * MACRO: DISPATCH_CALL5
  353. *
  354. * used to map an event interface method with 5 parameters
  355. *
  356. \***************************************************************************/
  357. #define DISPATCH_CALL5(_method, P1, P2, P3, P4, P5) \
  358. DISPATCH_CALL_PROLOG(_method, (P1 param1, P2 param2, P3 param3, P4 param4, P5 param5)) \
  359. CComVariant var[] = { CastParam(param5), CastParam(param4), CastParam(param3), CastParam(param2), CastParam(param1) }; \
  360. DISPATCH_CALL_EPILOG(var, countof(var))
  361. /***************************************************************************\
  362. *
  363. * MACRO: ScFireComEvent
  364. *
  365. * used to fire script event. Note that _p_com_object may be NULL
  366. *
  367. \***************************************************************************/
  368. #ifdef DBG
  369. extern CTraceTag tagComEvents;
  370. #endif
  371. #define ScFireComEvent(_p_com_object, _dispinterface, _function_call) \
  372. CScriptEvent<_dispinterface>(_p_com_object).Sc##_function_call; \
  373. Trace(tagComEvents, _T(#_function_call));
  374. /***************************************************************************\
  375. *
  376. * MACRO: ScHasSinks
  377. *
  378. * used to determine if there are sinks connected
  379. * ( to avoid creating com object when ScFireComEvent would result in no calls )
  380. *
  381. \***************************************************************************/
  382. #define ScHasSinks(_p_com_object, _dispinterface) \
  383. ((_p_com_object) == NULL) ? SC(S_FALSE) : \
  384. CScriptEvent<_dispinterface>(_p_com_object).ScHaveSinksRegistered();