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.

657 lines
17 KiB

  1. /*
  2. +----------------------------------------------------------------------+
  3. | PHP Version 5 |
  4. +----------------------------------------------------------------------+
  5. | Copyright (c) 1997-2009 The PHP Group |
  6. +----------------------------------------------------------------------+
  7. | This source file is subject to version 3.01 of the PHP license, |
  8. | that is bundled with this package in the file LICENSE, and is |
  9. | available through the world-wide-web at the following url: |
  10. | http://www.php.net/license/3_01.txt |
  11. | If you did not receive a copy of the PHP license and are unable to |
  12. | obtain it through the world-wide-web, please send a note to |
  13. | license@php.net so we can mail you a copy immediately. |
  14. +----------------------------------------------------------------------+
  15. | Author: Wez Furlong <wez@thebrainroom.com> |
  16. +----------------------------------------------------------------------+
  17. */
  18. /* $Id$ */
  19. /* This module exports a PHP object as a COM object by wrapping it
  20. * using IDispatchEx */
  21. #ifdef HAVE_CONFIG_H
  22. #include "config.h"
  23. #endif
  24. #include "php.h"
  25. #include "php_ini.h"
  26. #include "ext/standard/info.h"
  27. #include "php_com_dotnet.h"
  28. #include "php_com_dotnet_internal.h"
  29. typedef struct {
  30. /* This first part MUST match the declaration
  31. * of interface IDispatchEx */
  32. CONST_VTBL struct IDispatchExVtbl *lpVtbl;
  33. /* now the PHP stuff */
  34. DWORD engine_thread; /* for sanity checking */
  35. zval *object; /* the object exported */
  36. LONG refcount; /* COM reference count */
  37. HashTable *dispid_to_name; /* keep track of dispid -> name mappings */
  38. HashTable *name_to_dispid; /* keep track of name -> dispid mappings */
  39. GUID sinkid; /* iid that we "implement" for event sinking */
  40. int id;
  41. } php_dispatchex;
  42. static int le_dispatch;
  43. static void disp_destructor(php_dispatchex *disp);
  44. static void dispatch_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC)
  45. {
  46. php_dispatchex *disp = (php_dispatchex *)rsrc->ptr;
  47. disp_destructor(disp);
  48. }
  49. int php_com_wrapper_minit(INIT_FUNC_ARGS)
  50. {
  51. le_dispatch = zend_register_list_destructors_ex(dispatch_dtor,
  52. NULL, "com_dotnet_dispatch_wrapper", module_number);
  53. return le_dispatch;
  54. }
  55. /* {{{ trace */
  56. static inline void trace(char *fmt, ...)
  57. {
  58. va_list ap;
  59. char buf[4096];
  60. snprintf(buf, sizeof(buf), "T=%08x ", GetCurrentThreadId());
  61. OutputDebugString(buf);
  62. va_start(ap, fmt);
  63. vsnprintf(buf, sizeof(buf), fmt, ap);
  64. OutputDebugString(buf);
  65. va_end(ap);
  66. }
  67. /* }}} */
  68. #ifdef ZTS
  69. # define TSRMLS_FIXED() TSRMLS_FETCH();
  70. #else
  71. # define TSRMLS_FIXED()
  72. #endif
  73. #define FETCH_DISP(methname) \
  74. TSRMLS_FIXED() \
  75. php_dispatchex *disp = (php_dispatchex*)This; \
  76. if (COMG(rshutdown_started)) { \
  77. trace(" PHP Object:%p (name:unknown) %s\n", disp->object, methname); \
  78. } else { \
  79. trace(" PHP Object:%p (name:%s) %s\n", disp->object, Z_OBJCE_P(disp->object)->name, methname); \
  80. } \
  81. if (GetCurrentThreadId() != disp->engine_thread) { \
  82. return RPC_E_WRONG_THREAD; \
  83. }
  84. static HRESULT STDMETHODCALLTYPE disp_queryinterface(
  85. IDispatchEx *This,
  86. /* [in] */ REFIID riid,
  87. /* [iid_is][out] */ void **ppvObject)
  88. {
  89. FETCH_DISP("QueryInterface");
  90. if (IsEqualGUID(&IID_IUnknown, riid) ||
  91. IsEqualGUID(&IID_IDispatch, riid) ||
  92. IsEqualGUID(&IID_IDispatchEx, riid) ||
  93. IsEqualGUID(&disp->sinkid, riid)) {
  94. *ppvObject = This;
  95. InterlockedIncrement(&disp->refcount);
  96. return S_OK;
  97. }
  98. *ppvObject = NULL;
  99. return E_NOINTERFACE;
  100. }
  101. static ULONG STDMETHODCALLTYPE disp_addref(IDispatchEx *This)
  102. {
  103. FETCH_DISP("AddRef");
  104. return InterlockedIncrement(&disp->refcount);
  105. }
  106. static ULONG STDMETHODCALLTYPE disp_release(IDispatchEx *This)
  107. {
  108. ULONG ret;
  109. FETCH_DISP("Release");
  110. ret = InterlockedDecrement(&disp->refcount);
  111. trace("-- refcount now %d\n", ret);
  112. if (ret == 0) {
  113. /* destroy it */
  114. if (disp->id)
  115. zend_list_delete(disp->id);
  116. }
  117. return ret;
  118. }
  119. static HRESULT STDMETHODCALLTYPE disp_gettypeinfocount(
  120. IDispatchEx *This,
  121. /* [out] */ UINT *pctinfo)
  122. {
  123. FETCH_DISP("GetTypeInfoCount");
  124. *pctinfo = 0;
  125. return S_OK;
  126. }
  127. static HRESULT STDMETHODCALLTYPE disp_gettypeinfo(
  128. IDispatchEx *This,
  129. /* [in] */ UINT iTInfo,
  130. /* [in] */ LCID lcid,
  131. /* [out] */ ITypeInfo **ppTInfo)
  132. {
  133. FETCH_DISP("GetTypeInfo");
  134. *ppTInfo = NULL;
  135. return DISP_E_BADINDEX;
  136. }
  137. static HRESULT STDMETHODCALLTYPE disp_getidsofnames(
  138. IDispatchEx *This,
  139. /* [in] */ REFIID riid,
  140. /* [size_is][in] */ LPOLESTR *rgszNames,
  141. /* [in] */ UINT cNames,
  142. /* [in] */ LCID lcid,
  143. /* [size_is][out] */ DISPID *rgDispId)
  144. {
  145. UINT i;
  146. HRESULT ret = S_OK;
  147. FETCH_DISP("GetIDsOfNames");
  148. for (i = 0; i < cNames; i++) {
  149. char *name;
  150. unsigned int namelen;
  151. zval **tmp;
  152. name = php_com_olestring_to_string(rgszNames[i], &namelen, COMG(code_page) TSRMLS_CC);
  153. /* Lookup the name in the hash */
  154. if (zend_hash_find(disp->name_to_dispid, name, namelen+1, (void**)&tmp) == FAILURE) {
  155. ret = DISP_E_UNKNOWNNAME;
  156. rgDispId[i] = 0;
  157. } else {
  158. rgDispId[i] = Z_LVAL_PP(tmp);
  159. }
  160. efree(name);
  161. }
  162. return ret;
  163. }
  164. static HRESULT STDMETHODCALLTYPE disp_invoke(
  165. IDispatchEx *This,
  166. /* [in] */ DISPID dispIdMember,
  167. /* [in] */ REFIID riid,
  168. /* [in] */ LCID lcid,
  169. /* [in] */ WORD wFlags,
  170. /* [out][in] */ DISPPARAMS *pDispParams,
  171. /* [out] */ VARIANT *pVarResult,
  172. /* [out] */ EXCEPINFO *pExcepInfo,
  173. /* [out] */ UINT *puArgErr)
  174. {
  175. return This->lpVtbl->InvokeEx(This, dispIdMember,
  176. lcid, wFlags, pDispParams,
  177. pVarResult, pExcepInfo, NULL);
  178. }
  179. static HRESULT STDMETHODCALLTYPE disp_getdispid(
  180. IDispatchEx *This,
  181. /* [in] */ BSTR bstrName,
  182. /* [in] */ DWORD grfdex,
  183. /* [out] */ DISPID *pid)
  184. {
  185. HRESULT ret = DISP_E_UNKNOWNNAME;
  186. char *name;
  187. unsigned int namelen;
  188. zval **tmp;
  189. FETCH_DISP("GetDispID");
  190. name = php_com_olestring_to_string(bstrName, &namelen, COMG(code_page) TSRMLS_CC);
  191. trace("Looking for %s, namelen=%d in %p\n", name, namelen, disp->name_to_dispid);
  192. /* Lookup the name in the hash */
  193. if (zend_hash_find(disp->name_to_dispid, name, namelen+1, (void**)&tmp) == SUCCESS) {
  194. trace("found it\n");
  195. *pid = Z_LVAL_PP(tmp);
  196. ret = S_OK;
  197. }
  198. efree(name);
  199. return ret;
  200. }
  201. static HRESULT STDMETHODCALLTYPE disp_invokeex(
  202. IDispatchEx *This,
  203. /* [in] */ DISPID id,
  204. /* [in] */ LCID lcid,
  205. /* [in] */ WORD wFlags,
  206. /* [in] */ DISPPARAMS *pdp,
  207. /* [out] */ VARIANT *pvarRes,
  208. /* [out] */ EXCEPINFO *pei,
  209. /* [unique][in] */ IServiceProvider *pspCaller)
  210. {
  211. zval **name;
  212. UINT i;
  213. zval *retval = NULL;
  214. zval ***params = NULL;
  215. HRESULT ret = DISP_E_MEMBERNOTFOUND;
  216. FETCH_DISP("InvokeEx");
  217. if (SUCCESS == zend_hash_index_find(disp->dispid_to_name, id, (void**)&name)) {
  218. /* TODO: add support for overloaded objects */
  219. trace("-- Invoke: %d %20s [%d] flags=%08x args=%d\n", id, Z_STRVAL_PP(name), Z_STRLEN_PP(name), wFlags, pdp->cArgs);
  220. /* convert args into zvals.
  221. * Args are in reverse order */
  222. if (pdp->cArgs) {
  223. params = (zval ***)safe_emalloc(sizeof(zval **), pdp->cArgs, 0);
  224. for (i = 0; i < pdp->cArgs; i++) {
  225. VARIANT *arg;
  226. zval *zarg;
  227. arg = &pdp->rgvarg[ pdp->cArgs - 1 - i];
  228. trace("alloc zval for arg %d VT=%08x\n", i, V_VT(arg));
  229. ALLOC_INIT_ZVAL(zarg);
  230. php_com_wrap_variant(zarg, arg, COMG(code_page) TSRMLS_CC);
  231. params[i] = (zval**)emalloc(sizeof(zval**));
  232. *params[i] = zarg;
  233. }
  234. }
  235. trace("arguments processed, prepare to do some work\n");
  236. /* TODO: if PHP raises an exception here, we should catch it
  237. * and expose it as a COM exception */
  238. if (wFlags & DISPATCH_PROPERTYGET) {
  239. retval = zend_read_property(Z_OBJCE_P(disp->object), disp->object, Z_STRVAL_PP(name), Z_STRLEN_PP(name)+1, 1 TSRMLS_CC);
  240. } else if (wFlags & DISPATCH_PROPERTYPUT) {
  241. zend_update_property(Z_OBJCE_P(disp->object), disp->object, Z_STRVAL_PP(name), Z_STRLEN_PP(name)+1, *params[0] TSRMLS_CC);
  242. } else if (wFlags & DISPATCH_METHOD) {
  243. zend_try {
  244. if (SUCCESS == call_user_function_ex(EG(function_table), &disp->object, *name,
  245. &retval, pdp->cArgs, params, 1, NULL TSRMLS_CC)) {
  246. ret = S_OK;
  247. trace("function called ok\n");
  248. /* Copy any modified values to callers copy of variant*/
  249. for (i = 0; i < pdp->cArgs; i++) {
  250. php_com_dotnet_object *obj = CDNO_FETCH(*params[i]);
  251. VARIANT *srcvar = &obj->v;
  252. VARIANT *dstvar = &pdp->rgvarg[ pdp->cArgs - 1 - i];
  253. if ((V_VT(dstvar) & VT_BYREF) && obj->modified ) {
  254. trace("percolate modified value for arg %d VT=%08x\n", i, V_VT(dstvar));
  255. php_com_copy_variant(dstvar, srcvar TSRMLS_CC);
  256. }
  257. }
  258. } else {
  259. trace("failed to call func\n");
  260. ret = DISP_E_EXCEPTION;
  261. }
  262. } zend_catch {
  263. trace("something blew up\n");
  264. ret = DISP_E_EXCEPTION;
  265. } zend_end_try();
  266. } else {
  267. trace("Don't know how to handle this invocation %08x\n", wFlags);
  268. }
  269. /* release arguments */
  270. if (params) {
  271. for (i = 0; i < pdp->cArgs; i++) {
  272. zval_ptr_dtor(params[i]);
  273. efree(params[i]);
  274. }
  275. efree(params);
  276. }
  277. /* return value */
  278. if (retval) {
  279. if (pvarRes) {
  280. VariantInit(pvarRes);
  281. php_com_variant_from_zval(pvarRes, retval, COMG(code_page) TSRMLS_CC);
  282. }
  283. zval_ptr_dtor(&retval);
  284. } else if (pvarRes) {
  285. VariantInit(pvarRes);
  286. }
  287. } else {
  288. trace("InvokeEx: I don't support DISPID=%d\n", id);
  289. }
  290. return ret;
  291. }
  292. static HRESULT STDMETHODCALLTYPE disp_deletememberbyname(
  293. IDispatchEx *This,
  294. /* [in] */ BSTR bstrName,
  295. /* [in] */ DWORD grfdex)
  296. {
  297. FETCH_DISP("DeleteMemberByName");
  298. /* TODO: unset */
  299. return S_FALSE;
  300. }
  301. static HRESULT STDMETHODCALLTYPE disp_deletememberbydispid(
  302. IDispatchEx *This,
  303. /* [in] */ DISPID id)
  304. {
  305. FETCH_DISP("DeleteMemberByDispID");
  306. /* TODO: unset */
  307. return S_FALSE;
  308. }
  309. static HRESULT STDMETHODCALLTYPE disp_getmemberproperties(
  310. IDispatchEx *This,
  311. /* [in] */ DISPID id,
  312. /* [in] */ DWORD grfdexFetch,
  313. /* [out] */ DWORD *pgrfdex)
  314. {
  315. FETCH_DISP("GetMemberProperties");
  316. return DISP_E_UNKNOWNNAME;
  317. }
  318. static HRESULT STDMETHODCALLTYPE disp_getmembername(
  319. IDispatchEx *This,
  320. /* [in] */ DISPID id,
  321. /* [out] */ BSTR *pbstrName)
  322. {
  323. zval *name;
  324. FETCH_DISP("GetMemberName");
  325. if (SUCCESS == zend_hash_index_find(disp->dispid_to_name, id, (void**)&name)) {
  326. OLECHAR *olestr = php_com_string_to_olestring(Z_STRVAL_P(name), Z_STRLEN_P(name), COMG(code_page) TSRMLS_CC);
  327. *pbstrName = SysAllocString(olestr);
  328. efree(olestr);
  329. return S_OK;
  330. } else {
  331. return DISP_E_UNKNOWNNAME;
  332. }
  333. }
  334. static HRESULT STDMETHODCALLTYPE disp_getnextdispid(
  335. IDispatchEx *This,
  336. /* [in] */ DWORD grfdex,
  337. /* [in] */ DISPID id,
  338. /* [out] */ DISPID *pid)
  339. {
  340. ulong next = id+1;
  341. FETCH_DISP("GetNextDispID");
  342. while(!zend_hash_index_exists(disp->dispid_to_name, next))
  343. next++;
  344. if (zend_hash_index_exists(disp->dispid_to_name, next)) {
  345. *pid = next;
  346. return S_OK;
  347. }
  348. return S_FALSE;
  349. }
  350. static HRESULT STDMETHODCALLTYPE disp_getnamespaceparent(
  351. IDispatchEx *This,
  352. /* [out] */ IUnknown **ppunk)
  353. {
  354. FETCH_DISP("GetNameSpaceParent");
  355. *ppunk = NULL;
  356. return E_NOTIMPL;
  357. }
  358. static struct IDispatchExVtbl php_dispatch_vtbl = {
  359. disp_queryinterface,
  360. disp_addref,
  361. disp_release,
  362. disp_gettypeinfocount,
  363. disp_gettypeinfo,
  364. disp_getidsofnames,
  365. disp_invoke,
  366. disp_getdispid,
  367. disp_invokeex,
  368. disp_deletememberbyname,
  369. disp_deletememberbydispid,
  370. disp_getmemberproperties,
  371. disp_getmembername,
  372. disp_getnextdispid,
  373. disp_getnamespaceparent
  374. };
  375. /* enumerate functions and properties of the object and assign
  376. * dispatch ids */
  377. static void generate_dispids(php_dispatchex *disp TSRMLS_DC)
  378. {
  379. HashPosition pos;
  380. char *name = NULL;
  381. zval *tmp;
  382. int namelen;
  383. int keytype;
  384. ulong pid;
  385. if (disp->dispid_to_name == NULL) {
  386. ALLOC_HASHTABLE(disp->dispid_to_name);
  387. ALLOC_HASHTABLE(disp->name_to_dispid);
  388. zend_hash_init(disp->name_to_dispid, 0, NULL, ZVAL_PTR_DTOR, 0);
  389. zend_hash_init(disp->dispid_to_name, 0, NULL, ZVAL_PTR_DTOR, 0);
  390. }
  391. /* properties */
  392. if (Z_OBJPROP_P(disp->object)) {
  393. zend_hash_internal_pointer_reset_ex(Z_OBJPROP_P(disp->object), &pos);
  394. while (HASH_KEY_NON_EXISTANT != (keytype =
  395. zend_hash_get_current_key_ex(Z_OBJPROP_P(disp->object), &name,
  396. &namelen, &pid, 0, &pos))) {
  397. char namebuf[32];
  398. if (keytype == HASH_KEY_IS_LONG) {
  399. snprintf(namebuf, sizeof(namebuf), "%d", pid);
  400. name = namebuf;
  401. namelen = strlen(namebuf)+1;
  402. }
  403. zend_hash_move_forward_ex(Z_OBJPROP_P(disp->object), &pos);
  404. /* Find the existing id */
  405. if (zend_hash_find(disp->name_to_dispid, name, namelen, (void**)&tmp) == SUCCESS)
  406. continue;
  407. /* add the mappings */
  408. MAKE_STD_ZVAL(tmp);
  409. ZVAL_STRINGL(tmp, name, namelen-1, 1);
  410. pid = zend_hash_next_free_element(disp->dispid_to_name);
  411. zend_hash_index_update(disp->dispid_to_name, pid, (void*)&tmp, sizeof(zval *), NULL);
  412. MAKE_STD_ZVAL(tmp);
  413. ZVAL_LONG(tmp, pid);
  414. zend_hash_update(disp->name_to_dispid, name, namelen, (void*)&tmp, sizeof(zval *), NULL);
  415. }
  416. }
  417. /* functions */
  418. if (Z_OBJCE_P(disp->object)) {
  419. zend_hash_internal_pointer_reset_ex(&Z_OBJCE_P(disp->object)->function_table, &pos);
  420. while (HASH_KEY_NON_EXISTANT != (keytype =
  421. zend_hash_get_current_key_ex(&Z_OBJCE_P(disp->object)->function_table,
  422. &name, &namelen, &pid, 0, &pos))) {
  423. char namebuf[32];
  424. if (keytype == HASH_KEY_IS_LONG) {
  425. snprintf(namebuf, sizeof(namebuf), "%d", pid);
  426. name = namebuf;
  427. namelen = strlen(namebuf) + 1;
  428. }
  429. zend_hash_move_forward_ex(Z_OBJPROP_P(disp->object), &pos);
  430. /* Find the existing id */
  431. if (zend_hash_find(disp->name_to_dispid, name, namelen, (void**)&tmp) == SUCCESS)
  432. continue;
  433. /* add the mappings */
  434. MAKE_STD_ZVAL(tmp);
  435. ZVAL_STRINGL(tmp, name, namelen-1, 1);
  436. pid = zend_hash_next_free_element(disp->dispid_to_name);
  437. zend_hash_index_update(disp->dispid_to_name, pid, (void*)&tmp, sizeof(zval *), NULL);
  438. MAKE_STD_ZVAL(tmp);
  439. ZVAL_LONG(tmp, pid);
  440. zend_hash_update(disp->name_to_dispid, name, namelen, (void*)&tmp, sizeof(zval *), NULL);
  441. }
  442. }
  443. }
  444. static php_dispatchex *disp_constructor(zval *object TSRMLS_DC)
  445. {
  446. php_dispatchex *disp = (php_dispatchex*)CoTaskMemAlloc(sizeof(php_dispatchex));
  447. trace("constructing a COM wrapper for PHP object %p (%s)\n", object, Z_OBJCE_P(object)->name);
  448. if (disp == NULL)
  449. return NULL;
  450. memset(disp, 0, sizeof(php_dispatchex));
  451. disp->engine_thread = GetCurrentThreadId();
  452. disp->lpVtbl = &php_dispatch_vtbl;
  453. disp->refcount = 1;
  454. if (object)
  455. Z_ADDREF_P(object);
  456. disp->object = object;
  457. disp->id = zend_list_insert(disp, le_dispatch);
  458. return disp;
  459. }
  460. static void disp_destructor(php_dispatchex *disp)
  461. {
  462. TSRMLS_FETCH();
  463. /* Object store not available during request shutdown */
  464. if (COMG(rshutdown_started)) {
  465. trace("destroying COM wrapper for PHP object %p (name:unknown)\n", disp->object);
  466. } else {
  467. trace("destroying COM wrapper for PHP object %p (name:%s)\n", disp->object, Z_OBJCE_P(disp->object)->name);
  468. }
  469. disp->id = 0;
  470. if (disp->refcount > 0)
  471. CoDisconnectObject((IUnknown*)disp, 0);
  472. zend_hash_destroy(disp->dispid_to_name);
  473. zend_hash_destroy(disp->name_to_dispid);
  474. FREE_HASHTABLE(disp->dispid_to_name);
  475. FREE_HASHTABLE(disp->name_to_dispid);
  476. if (disp->object)
  477. zval_ptr_dtor(&disp->object);
  478. CoTaskMemFree(disp);
  479. }
  480. PHPAPI IDispatch *php_com_wrapper_export_as_sink(zval *val, GUID *sinkid,
  481. HashTable *id_to_name TSRMLS_DC)
  482. {
  483. php_dispatchex *disp = disp_constructor(val TSRMLS_CC);
  484. HashPosition pos;
  485. char *name = NULL;
  486. zval *tmp, **ntmp;
  487. int namelen;
  488. int keytype;
  489. ulong pid;
  490. disp->dispid_to_name = id_to_name;
  491. memcpy(&disp->sinkid, sinkid, sizeof(disp->sinkid));
  492. /* build up the reverse mapping */
  493. ALLOC_HASHTABLE(disp->name_to_dispid);
  494. zend_hash_init(disp->name_to_dispid, 0, NULL, ZVAL_PTR_DTOR, 0);
  495. zend_hash_internal_pointer_reset_ex(id_to_name, &pos);
  496. while (HASH_KEY_NON_EXISTANT != (keytype =
  497. zend_hash_get_current_key_ex(id_to_name, &name, &namelen, &pid, 0, &pos))) {
  498. if (keytype == HASH_KEY_IS_LONG) {
  499. zend_hash_get_current_data_ex(id_to_name, (void**)&ntmp, &pos);
  500. MAKE_STD_ZVAL(tmp);
  501. ZVAL_LONG(tmp, pid);
  502. zend_hash_update(disp->name_to_dispid, Z_STRVAL_PP(ntmp),
  503. Z_STRLEN_PP(ntmp)+1, (void*)&tmp, sizeof(zval *), NULL);
  504. }
  505. zend_hash_move_forward_ex(id_to_name, &pos);
  506. }
  507. return (IDispatch*)disp;
  508. }
  509. PHPAPI IDispatch *php_com_wrapper_export(zval *val TSRMLS_DC)
  510. {
  511. php_dispatchex *disp = NULL;
  512. if (Z_TYPE_P(val) != IS_OBJECT) {
  513. return NULL;
  514. }
  515. if (php_com_is_valid_object(val TSRMLS_CC)) {
  516. /* pass back its IDispatch directly */
  517. php_com_dotnet_object *obj = CDNO_FETCH(val);
  518. if (obj == NULL)
  519. return NULL;
  520. if (V_VT(&obj->v) == VT_DISPATCH && V_DISPATCH(&obj->v)) {
  521. IDispatch_AddRef(V_DISPATCH(&obj->v));
  522. return V_DISPATCH(&obj->v);
  523. }
  524. return NULL;
  525. }
  526. disp = disp_constructor(val TSRMLS_CC);
  527. generate_dispids(disp TSRMLS_CC);
  528. return (IDispatch*)disp;
  529. }