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