com_wrapper.c 16 KB

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