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