com_wrapper.c 16 KB

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