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