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