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