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