xref: /PHP-5.6/ext/com_dotnet/com_handlers.c (revision 49493a2d)
1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 5                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) 1997-2016 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 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24 
25 #include "php.h"
26 #include "php_ini.h"
27 #include "ext/standard/info.h"
28 #include "php_com_dotnet.h"
29 #include "php_com_dotnet_internal.h"
30 #include "Zend/zend_exceptions.h"
31 
com_property_read(zval * object,zval * member,int type,const zend_literal * key TSRMLS_DC)32 static zval *com_property_read(zval *object, zval *member, int type, const zend_literal *key TSRMLS_DC)
33 {
34 	zval *return_value;
35 	php_com_dotnet_object *obj;
36 	VARIANT v;
37 	HRESULT res;
38 
39 	MAKE_STD_ZVAL(return_value);
40 	ZVAL_NULL(return_value);
41 	Z_SET_REFCOUNT_P(return_value, 0);
42 	Z_UNSET_ISREF_P(return_value);
43 
44 	obj = CDNO_FETCH(object);
45 
46 	if (V_VT(&obj->v) == VT_DISPATCH) {
47 		VariantInit(&v);
48 
49 		convert_to_string_ex(&member);
50 
51 		res = php_com_do_invoke(obj, Z_STRVAL_P(member), Z_STRLEN_P(member),
52 				DISPATCH_METHOD|DISPATCH_PROPERTYGET, &v, 0, NULL, 1 TSRMLS_CC);
53 
54 		if (res == SUCCESS) {
55 			php_com_zval_from_variant(return_value, &v, obj->code_page TSRMLS_CC);
56 			VariantClear(&v);
57 		} else if (res == DISP_E_BADPARAMCOUNT) {
58 			php_com_saproxy_create(object, return_value, member TSRMLS_CC);
59 		}
60 	} else {
61 		php_com_throw_exception(E_INVALIDARG, "this variant has no properties" TSRMLS_CC);
62 	}
63 
64 	return return_value;
65 }
66 
com_property_write(zval * object,zval * member,zval * value,const zend_literal * key TSRMLS_DC)67 static void com_property_write(zval *object, zval *member, zval *value, const zend_literal *key TSRMLS_DC)
68 {
69 	php_com_dotnet_object *obj;
70 	VARIANT v;
71 
72 	obj = CDNO_FETCH(object);
73 
74 	if (V_VT(&obj->v) == VT_DISPATCH) {
75 		VariantInit(&v);
76 
77 		convert_to_string_ex(&member);
78 		if (SUCCESS == php_com_do_invoke(obj, Z_STRVAL_P(member), Z_STRLEN_P(member),
79 				DISPATCH_PROPERTYPUT|DISPATCH_PROPERTYPUTREF, &v, 1, &value, 0 TSRMLS_CC)) {
80 			VariantClear(&v);
81 		}
82 	} else {
83 		php_com_throw_exception(E_INVALIDARG, "this variant has no properties" TSRMLS_CC);
84 	}
85 }
86 
com_read_dimension(zval * object,zval * offset,int type TSRMLS_DC)87 static zval *com_read_dimension(zval *object, zval *offset, int type TSRMLS_DC)
88 {
89 	zval *return_value;
90 	php_com_dotnet_object *obj;
91 	VARIANT v;
92 
93 	MAKE_STD_ZVAL(return_value);
94 	ZVAL_NULL(return_value);
95 	Z_SET_REFCOUNT_P(return_value, 0);
96 	Z_UNSET_ISREF_P(return_value);
97 
98 	obj = CDNO_FETCH(object);
99 
100 	if (V_VT(&obj->v) == VT_DISPATCH) {
101 		VariantInit(&v);
102 
103 		if (SUCCESS == php_com_do_invoke_by_id(obj, DISPID_VALUE,
104 				DISPATCH_METHOD|DISPATCH_PROPERTYGET, &v, 1, &offset, 0, 0 TSRMLS_CC)) {
105 			php_com_zval_from_variant(return_value, &v, obj->code_page TSRMLS_CC);
106 			VariantClear(&v);
107 		}
108 	} else if (V_ISARRAY(&obj->v)) {
109 		convert_to_long(offset);
110 
111 		if (SafeArrayGetDim(V_ARRAY(&obj->v)) == 1) {
112 			if (php_com_safearray_get_elem(&obj->v, &v, Z_LVAL_P(offset) TSRMLS_CC)) {
113 				php_com_wrap_variant(return_value, &v, obj->code_page TSRMLS_CC);
114 				VariantClear(&v);
115 			}
116 		} else {
117 			php_com_saproxy_create(object, return_value, offset TSRMLS_CC);
118 		}
119 
120 	} else {
121 		php_com_throw_exception(E_INVALIDARG, "this variant is not an array type" TSRMLS_CC);
122 	}
123 
124 	return return_value;
125 }
126 
com_write_dimension(zval * object,zval * offset,zval * value TSRMLS_DC)127 static void com_write_dimension(zval *object, zval *offset, zval *value TSRMLS_DC)
128 {
129 	php_com_dotnet_object *obj;
130 	zval *args[2];
131 	VARIANT v;
132 	HRESULT res;
133 
134 	obj = CDNO_FETCH(object);
135 
136 	if (V_VT(&obj->v) == VT_DISPATCH) {
137 		args[0] = offset;
138 		args[1] = value;
139 
140 		VariantInit(&v);
141 
142 		if (SUCCESS == php_com_do_invoke_by_id(obj, DISPID_VALUE,
143 				DISPATCH_METHOD|DISPATCH_PROPERTYPUT, &v, 2, args, 0, 0 TSRMLS_CC)) {
144 			VariantClear(&v);
145 		}
146 	} else if (V_ISARRAY(&obj->v)) {
147 		LONG indices = 0;
148 		VARTYPE vt;
149 
150 		if (SafeArrayGetDim(V_ARRAY(&obj->v)) == 1) {
151 			if (FAILED(SafeArrayGetVartype(V_ARRAY(&obj->v), &vt)) || vt == VT_EMPTY) {
152 				vt = V_VT(&obj->v) & ~VT_ARRAY;
153 			}
154 
155 			convert_to_long(offset);
156 			indices = Z_LVAL_P(offset);
157 
158 			VariantInit(&v);
159 			php_com_variant_from_zval(&v, value, obj->code_page TSRMLS_CC);
160 
161 			if (V_VT(&v) != vt) {
162 				VariantChangeType(&v, &v, 0, vt);
163 			}
164 
165 			if (vt == VT_VARIANT) {
166 				res = SafeArrayPutElement(V_ARRAY(&obj->v), &indices, &v);
167 			} else {
168 				res = SafeArrayPutElement(V_ARRAY(&obj->v), &indices, &v.lVal);
169 			}
170 
171 			VariantClear(&v);
172 
173 			if (FAILED(res)) {
174 				php_com_throw_exception(res, NULL TSRMLS_CC);
175 			}
176 
177 		} else {
178 			php_com_throw_exception(DISP_E_BADINDEX, "this variant has multiple dimensions; you can't set a new value without specifying *all* dimensions" TSRMLS_CC);
179 		}
180 
181 	} else {
182 		php_com_throw_exception(E_INVALIDARG, "this variant is not an array type" TSRMLS_CC);
183 	}
184 }
185 
186 #if 0
187 static void com_object_set(zval **property, zval *value TSRMLS_DC)
188 {
189 	/* Not yet implemented in the engine */
190 }
191 
192 static zval *com_object_get(zval *property TSRMLS_DC)
193 {
194 	/* Not yet implemented in the engine */
195 	return NULL;
196 }
197 #endif
198 
com_property_exists(zval * object,zval * member,int check_empty,const zend_literal * key TSRMLS_DC)199 static int com_property_exists(zval *object, zval *member, int check_empty, const zend_literal *key TSRMLS_DC)
200 {
201 	DISPID dispid;
202 	php_com_dotnet_object *obj;
203 
204 	obj = CDNO_FETCH(object);
205 
206 	if (V_VT(&obj->v) == VT_DISPATCH) {
207 		convert_to_string_ex(&member);
208 		if (SUCCEEDED(php_com_get_id_of_name(obj, Z_STRVAL_P(member), Z_STRLEN_P(member), &dispid TSRMLS_CC))) {
209 			/* TODO: distinguish between property and method! */
210 			return 1;
211 		}
212 	} else {
213 		/* TODO: check for safearray */
214 	}
215 
216 	return 0;
217 }
218 
com_dimension_exists(zval * object,zval * member,int check_empty TSRMLS_DC)219 static int com_dimension_exists(zval *object, zval *member, int check_empty TSRMLS_DC)
220 {
221 	php_error_docref(NULL TSRMLS_CC, E_WARNING, "Operation not yet supported on a COM object");
222 	return 0;
223 }
224 
com_property_delete(zval * object,zval * member,const zend_literal * key TSRMLS_DC)225 static void com_property_delete(zval *object, zval *member, const zend_literal *key TSRMLS_DC)
226 {
227 	php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot delete properties from a COM object");
228 }
229 
com_dimension_delete(zval * object,zval * offset TSRMLS_DC)230 static void com_dimension_delete(zval *object, zval *offset TSRMLS_DC)
231 {
232 	php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot delete properties from a COM object");
233 }
234 
com_properties_get(zval * object TSRMLS_DC)235 static HashTable *com_properties_get(zval *object TSRMLS_DC)
236 {
237 	/* TODO: use type-info to get all the names and values ?
238 	 * DANGER: if we do that, there is a strong possibility for
239 	 * infinite recursion when the hash is displayed via var_dump().
240 	 * Perhaps it is best to leave it un-implemented.
241 	 */
242 	return NULL;
243 }
244 
function_dtor(void * pDest)245 static void function_dtor(void *pDest)
246 {
247 	zend_internal_function *f = (zend_internal_function*)pDest;
248 
249 	efree((char*)f->function_name);
250 	if (f->arg_info) {
251 		efree(f->arg_info);
252 	}
253 }
254 
PHP_FUNCTION(com_method_handler)255 static PHP_FUNCTION(com_method_handler)
256 {
257 	Z_OBJ_HANDLER_P(getThis(), call_method)(
258 			((zend_internal_function*)EG(current_execute_data)->function_state.function)->function_name,
259 			INTERNAL_FUNCTION_PARAM_PASSTHRU);
260 }
261 
com_method_get(zval ** object_ptr,char * name,int len,const zend_literal * key TSRMLS_DC)262 static union _zend_function *com_method_get(zval **object_ptr, char *name, int len, const zend_literal *key TSRMLS_DC)
263 {
264 	zend_internal_function f, *fptr = NULL;
265 	php_com_dotnet_object *obj;
266 	union _zend_function *func;
267 	DISPID dummy;
268 	zval *object = *object_ptr;
269 
270 	obj = CDNO_FETCH(object);
271 
272 	if (V_VT(&obj->v) != VT_DISPATCH) {
273 		return NULL;
274 	}
275 
276 	if (FAILED(php_com_get_id_of_name(obj, name, len, &dummy TSRMLS_CC))) {
277 		return NULL;
278 	}
279 
280 	/* check cache */
281 	if (obj->method_cache == NULL || FAILURE == zend_hash_find(obj->method_cache, name, len, (void**)&fptr)) {
282 		f.type = ZEND_OVERLOADED_FUNCTION;
283 		f.num_args = 0;
284 		f.arg_info = NULL;
285 		f.scope = obj->ce;
286 		f.fn_flags = ZEND_ACC_CALL_VIA_HANDLER;
287 		f.function_name = estrndup(name, len);
288 		f.handler = PHP_FN(com_method_handler);
289 
290 		fptr = &f;
291 
292 		if (obj->typeinfo) {
293 			/* look for byref params */
294 			ITypeComp *comp;
295 			ITypeInfo *TI = NULL;
296 			DESCKIND kind;
297 			BINDPTR bindptr;
298 			OLECHAR *olename;
299 			ULONG lhash;
300 			int i;
301 
302 			if (SUCCEEDED(ITypeInfo_GetTypeComp(obj->typeinfo, &comp))) {
303 				olename = php_com_string_to_olestring(name, len, obj->code_page TSRMLS_CC);
304 				lhash = LHashValOfNameSys(SYS_WIN32, LOCALE_SYSTEM_DEFAULT, olename);
305 
306 				if (SUCCEEDED(ITypeComp_Bind(comp, olename, lhash, INVOKE_FUNC, &TI, &kind, &bindptr))) {
307 					switch (kind) {
308 						case DESCKIND_FUNCDESC:
309 							f.arg_info = ecalloc(bindptr.lpfuncdesc->cParams, sizeof(zend_arg_info));
310 
311 							for (i = 0; i < bindptr.lpfuncdesc->cParams; i++) {
312 								f.arg_info[i].allow_null = 1;
313 								if (bindptr.lpfuncdesc->lprgelemdescParam[i].paramdesc.wParamFlags & PARAMFLAG_FOUT) {
314 									f.arg_info[i].pass_by_reference = 1;
315 								}
316 							}
317 
318 							f.num_args = bindptr.lpfuncdesc->cParams;
319 
320 							ITypeInfo_ReleaseFuncDesc(TI, bindptr.lpfuncdesc);
321 							break;
322 
323 							/* these should not happen, but *might* happen if the user
324 							 * screws up; lets avoid a leak in that case */
325 						case DESCKIND_VARDESC:
326 							ITypeInfo_ReleaseVarDesc(TI, bindptr.lpvardesc);
327 							break;
328 						case DESCKIND_TYPECOMP:
329 							ITypeComp_Release(bindptr.lptcomp);
330 							break;
331 
332 						case DESCKIND_NONE:
333 							break;
334 					}
335 					if (TI) {
336 						ITypeInfo_Release(TI);
337 					}
338 				}
339 				ITypeComp_Release(comp);
340 				efree(olename);
341 			}
342 		}
343 
344 		if (fptr) {
345 			/* save this method in the cache */
346 			if (!obj->method_cache) {
347 				ALLOC_HASHTABLE(obj->method_cache);
348 				zend_hash_init(obj->method_cache, 2, NULL, function_dtor, 0);
349 			}
350 
351 			zend_hash_update(obj->method_cache, name, len, &f, sizeof(f), (void**)&fptr);
352 		}
353 	}
354 
355 	if (fptr) {
356 		/* duplicate this into a new chunk of emalloc'd memory,
357 		 * since the engine will efree it */
358 		func = emalloc(sizeof(*fptr));
359 		memcpy(func, fptr, sizeof(*fptr));
360 
361 		return func;
362 	}
363 
364 	return NULL;
365 }
366 
com_call_method(const char * method,INTERNAL_FUNCTION_PARAMETERS)367 static int com_call_method(const char *method, INTERNAL_FUNCTION_PARAMETERS)
368 {
369 	zval ***args = NULL;
370 	php_com_dotnet_object *obj;
371 	int nargs;
372 	VARIANT v;
373 	int ret = FAILURE;
374 
375 	obj = CDNO_FETCH(getThis());
376 
377 	if (V_VT(&obj->v) != VT_DISPATCH) {
378 		return FAILURE;
379 	}
380 
381 	nargs = ZEND_NUM_ARGS();
382 
383 	if (nargs) {
384 		args = (zval ***)safe_emalloc(sizeof(zval *), nargs, 0);
385 		zend_get_parameters_array_ex(nargs, args);
386 	}
387 
388 	VariantInit(&v);
389 
390 	if (SUCCESS == php_com_do_invoke_byref(obj, (char*)method, -1, DISPATCH_METHOD|DISPATCH_PROPERTYGET, &v, nargs, args TSRMLS_CC)) {
391 		php_com_zval_from_variant(return_value, &v, obj->code_page TSRMLS_CC);
392 		ret = SUCCESS;
393 		VariantClear(&v);
394 	}
395 
396 	if (args) {
397 		efree(args);
398 	}
399 
400 	return ret;
401 }
402 
com_constructor_get(zval * object TSRMLS_DC)403 static union _zend_function *com_constructor_get(zval *object TSRMLS_DC)
404 {
405 	php_com_dotnet_object *obj;
406 	static zend_internal_function c, d, v;
407 
408 	obj = CDNO_FETCH(object);
409 
410 #define POPULATE_CTOR(f, fn)	\
411 	f.type = ZEND_INTERNAL_FUNCTION; \
412 	f.function_name = (char *) obj->ce->name; \
413 	f.scope = obj->ce; \
414 	f.arg_info = NULL; \
415 	f.num_args = 0; \
416 	f.fn_flags = 0; \
417 	f.handler = ZEND_FN(fn); \
418 	return (union _zend_function*)&f;
419 
420 	switch (obj->ce->name[0]) {
421 #if HAVE_MSCOREE_H
422 		case 'd':
423 			POPULATE_CTOR(d, com_dotnet_create_instance);
424 #endif
425 
426 		case 'c':
427 			POPULATE_CTOR(c, com_create_instance);
428 
429 		case 'v':
430 			POPULATE_CTOR(v, com_variant_create_instance);
431 
432 		default:
433 			return NULL;
434 	}
435 }
436 
com_class_entry_get(const zval * object TSRMLS_DC)437 static zend_class_entry *com_class_entry_get(const zval *object TSRMLS_DC)
438 {
439 	php_com_dotnet_object *obj;
440 	obj = CDNO_FETCH(object);
441 
442 	return obj->ce;
443 }
444 
com_class_name_get(const zval * object,const char ** class_name,zend_uint * class_name_len,int parent TSRMLS_DC)445 static int com_class_name_get(const zval *object, const char **class_name, zend_uint *class_name_len, int parent TSRMLS_DC)
446 {
447 	php_com_dotnet_object *obj;
448 	obj = CDNO_FETCH(object);
449 
450 	*class_name = estrndup(obj->ce->name, obj->ce->name_length);
451 	*class_name_len = obj->ce->name_length;
452 
453 	return 0;
454 }
455 
456 /* This compares two variants for equality */
com_objects_compare(zval * object1,zval * object2 TSRMLS_DC)457 static int com_objects_compare(zval *object1, zval *object2 TSRMLS_DC)
458 {
459 	php_com_dotnet_object *obja, *objb;
460 	int ret;
461 	/* strange header bug problem here... the headers define the proto without the
462 	 * flags parameter.  However, the MSDN docs state that there is a flags parameter,
463 	 * and my VC6 won't link unless the code uses the version with 4 parameters.
464 	 * So, we have this declaration here to fix it */
465 	STDAPI VarCmp(LPVARIANT pvarLeft, LPVARIANT pvarRight, LCID lcid, DWORD flags);
466 
467 	obja = CDNO_FETCH(object1);
468 	objb = CDNO_FETCH(object2);
469 
470 	switch (VarCmp(&obja->v, &objb->v, LOCALE_SYSTEM_DEFAULT, 0)) {
471 		case VARCMP_LT:
472 			ret = -1;
473 			break;
474 		case VARCMP_GT:
475 			ret = 1;
476 			break;
477 		case VARCMP_EQ:
478 			ret = 0;
479 			break;
480 		default:
481 			/* either or both operands are NULL...
482 			 * not 100% sure how to handle this */
483 			ret = -2;
484 	}
485 
486 	return ret;
487 }
488 
com_object_cast(zval * readobj,zval * writeobj,int type TSRMLS_DC)489 static int com_object_cast(zval *readobj, zval *writeobj, int type TSRMLS_DC)
490 {
491 	php_com_dotnet_object *obj;
492 	VARIANT v;
493 	VARTYPE vt = VT_EMPTY;
494 	HRESULT res = S_OK;
495 
496 	obj = CDNO_FETCH(readobj);
497 	ZVAL_NULL(writeobj);
498 	VariantInit(&v);
499 
500 	if (V_VT(&obj->v) == VT_DISPATCH) {
501 		if (SUCCESS != php_com_do_invoke_by_id(obj, DISPID_VALUE,
502 				DISPATCH_METHOD|DISPATCH_PROPERTYGET, &v, 0, NULL, 1, 0 TSRMLS_CC)) {
503 			VariantCopy(&v, &obj->v);
504 		}
505 	} else {
506 		VariantCopy(&v, &obj->v);
507 	}
508 
509 	switch(type) {
510 		case IS_LONG:
511 			vt = VT_INT;
512 			break;
513 		case IS_DOUBLE:
514 			vt = VT_R8;
515 			break;
516 		case IS_BOOL:
517 			vt = VT_BOOL;
518 			break;
519 		case IS_STRING:
520 			vt = VT_BSTR;
521 			break;
522 		default:
523 			;
524 	}
525 
526 	if (vt != VT_EMPTY && vt != V_VT(&v)) {
527 		res = VariantChangeType(&v, &v, 0, vt);
528 	}
529 
530 	if (SUCCEEDED(res)) {
531 		php_com_zval_from_variant(writeobj, &v, obj->code_page TSRMLS_CC);
532 	}
533 
534 	VariantClear(&v);
535 
536 	if (SUCCEEDED(res)) {
537 		return SUCCESS;
538 	}
539 
540 	return zend_std_cast_object_tostring(readobj, writeobj, type TSRMLS_CC);
541 }
542 
com_object_count(zval * object,long * count TSRMLS_DC)543 static int com_object_count(zval *object, long *count TSRMLS_DC)
544 {
545 	php_com_dotnet_object *obj;
546 	LONG ubound = 0, lbound = 0;
547 
548 	obj = CDNO_FETCH(object);
549 
550 	if (!V_ISARRAY(&obj->v)) {
551 		return FAILURE;
552 	}
553 
554 	SafeArrayGetLBound(V_ARRAY(&obj->v), 1, &lbound);
555 	SafeArrayGetUBound(V_ARRAY(&obj->v), 1, &ubound);
556 
557 	*count = ubound - lbound + 1;
558 
559 	return SUCCESS;
560 }
561 
562 zend_object_handlers php_com_object_handlers = {
563 	ZEND_OBJECTS_STORE_HANDLERS,
564 	com_property_read,
565 	com_property_write,
566 	com_read_dimension,
567 	com_write_dimension,
568 	NULL,
569 	NULL, /* com_object_get, */
570 	NULL, /* com_object_set, */
571 	com_property_exists,
572 	com_property_delete,
573 	com_dimension_exists,
574 	com_dimension_delete,
575 	com_properties_get,
576 	com_method_get,
577 	com_call_method,
578 	com_constructor_get,
579 	com_class_entry_get,
580 	com_class_name_get,
581 	com_objects_compare,
582 	com_object_cast,
583 	com_object_count,
584 	NULL,									/* get_debug_info */
585 	NULL,									/* get_closure */
586 	NULL,									/* get_gc */
587 };
588 
php_com_object_enable_event_sink(php_com_dotnet_object * obj,int enable TSRMLS_DC)589 void php_com_object_enable_event_sink(php_com_dotnet_object *obj, int enable TSRMLS_DC)
590 {
591 	if (obj->sink_dispatch) {
592 		IConnectionPointContainer *cont;
593 		IConnectionPoint *point;
594 
595 		if (SUCCEEDED(IDispatch_QueryInterface(V_DISPATCH(&obj->v),
596 				&IID_IConnectionPointContainer, (void**)&cont))) {
597 
598 			if (SUCCEEDED(IConnectionPointContainer_FindConnectionPoint(cont,
599 					&obj->sink_id, &point))) {
600 
601 				if (enable) {
602 					IConnectionPoint_Advise(point, (IUnknown*)obj->sink_dispatch, &obj->sink_cookie);
603 				} else {
604 					IConnectionPoint_Unadvise(point, obj->sink_cookie);
605 				}
606 				IConnectionPoint_Release(point);
607 			}
608 			IConnectionPointContainer_Release(cont);
609 		}
610 	}
611 }
612 
php_com_object_free_storage(void * object TSRMLS_DC)613 void php_com_object_free_storage(void *object TSRMLS_DC)
614 {
615 	php_com_dotnet_object *obj = (php_com_dotnet_object*)object;
616 
617 	if (obj->typeinfo) {
618 		ITypeInfo_Release(obj->typeinfo);
619 		obj->typeinfo = NULL;
620 	}
621 
622 	if (obj->sink_dispatch) {
623 		php_com_object_enable_event_sink(obj, FALSE TSRMLS_CC);
624 		IDispatch_Release(obj->sink_dispatch);
625 		obj->sink_dispatch = NULL;
626 	}
627 
628 	VariantClear(&obj->v);
629 
630 	if (obj->method_cache) {
631 		zend_hash_destroy(obj->method_cache);
632 		FREE_HASHTABLE(obj->method_cache);
633 	}
634 	if (obj->id_of_name_cache) {
635 		zend_hash_destroy(obj->id_of_name_cache);
636 		FREE_HASHTABLE(obj->id_of_name_cache);
637 	}
638 	efree(obj);
639 }
640 
php_com_object_clone(void * object,void ** clone_ptr TSRMLS_DC)641 void php_com_object_clone(void *object, void **clone_ptr TSRMLS_DC)
642 {
643 	php_com_dotnet_object *cloneobj, *origobject;
644 
645 	origobject = (php_com_dotnet_object*)object;
646 	cloneobj = (php_com_dotnet_object*)emalloc(sizeof(php_com_dotnet_object));
647 
648 	memcpy(cloneobj, origobject, sizeof(*cloneobj));
649 
650 	/* VariantCopy will perform VariantClear; we don't want to clobber
651 	 * the IDispatch that we memcpy'd, so we init a new variant in the
652 	 * clone structure */
653 	VariantInit(&cloneobj->v);
654 	/* We use the Indirection-following version of the API since we
655 	 * want to clone as much as possible */
656 	VariantCopyInd(&cloneobj->v, &origobject->v);
657 
658 	if (cloneobj->typeinfo) {
659 		ITypeInfo_AddRef(cloneobj->typeinfo);
660 	}
661 
662 	*clone_ptr = cloneobj;
663 }
664 
php_com_object_new(zend_class_entry * ce TSRMLS_DC)665 zend_object_value php_com_object_new(zend_class_entry *ce TSRMLS_DC)
666 {
667 	php_com_dotnet_object *obj;
668 	zend_object_value retval;
669 
670 	php_com_initialize(TSRMLS_C);
671 	obj = emalloc(sizeof(*obj));
672 	memset(obj, 0, sizeof(*obj));
673 
674 	VariantInit(&obj->v);
675 	obj->code_page = CP_ACP;
676 	obj->ce = ce;
677 	obj->zo.ce = ce;
678 
679 	retval.handle = zend_objects_store_put(obj, NULL, php_com_object_free_storage, php_com_object_clone TSRMLS_CC);
680 	retval.handlers = &php_com_object_handlers;
681 
682 	return retval;
683 }
684