xref: /PHP-8.0/ext/com_dotnet/com_com.c (revision e55f0c79)
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    | http://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 #ifdef HAVE_CONFIG_H
18 #include "config.h"
19 #endif
20 
21 #include "php.h"
22 #include "php_ini.h"
23 #include "ext/standard/info.h"
24 #include "php_com_dotnet.h"
25 #include "php_com_dotnet_internal.h"
26 #include "Zend/zend_exceptions.h"
27 
28 /* {{{ com_create_instance - ctor for COM class */
PHP_METHOD(com,__construct)29 PHP_METHOD(com, __construct)
30 {
31 	zval *object = getThis();
32 	zend_string *server_name = NULL;
33 	HashTable *server_params = NULL;
34 	php_com_dotnet_object *obj;
35 	char *module_name, *typelib_name = NULL;
36 	size_t module_name_len = 0, typelib_name_len = 0;
37 	zend_string *user_name = NULL, *password = NULL, *domain_name = NULL;
38 	OLECHAR *moniker;
39 	CLSID clsid;
40 	CLSCTX ctx = CLSCTX_SERVER;
41 	HRESULT res = E_FAIL;
42 	int mode = COMG(autoreg_case_sensitive) ? CONST_CS : 0;
43 	ITypeLib *TL = NULL;
44 	COSERVERINFO	info;
45 	COAUTHIDENTITY	authid = {0};
46 	COAUTHINFO		authinfo = {
47 		RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL,
48 		RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE,
49 		&authid, EOAC_NONE
50 	};
51 	zend_long cp = GetACP();
52 	const struct php_win32_cp *cp_it;
53 
54 	ZEND_PARSE_PARAMETERS_START(1, 4)
55 		Z_PARAM_STRING(module_name, module_name_len)
56 		Z_PARAM_OPTIONAL
57 		Z_PARAM_ARRAY_HT_OR_STR_OR_NULL(server_params, server_name)
58 		Z_PARAM_LONG(cp)
59 		Z_PARAM_STRING(typelib_name, typelib_name_len)
60 	ZEND_PARSE_PARAMETERS_END();
61 
62 	php_com_initialize();
63 	obj = CDNO_FETCH(object);
64 
65 	cp_it = php_win32_cp_get_by_id((DWORD)cp);
66 	if (!cp_it) {
67 		php_com_throw_exception(E_INVALIDARG, "Could not create COM object - invalid codepage!");
68 		RETURN_THROWS();
69 	}
70 	obj->code_page = (int)cp;
71 
72 	if (server_name) {
73 		ctx = CLSCTX_REMOTE_SERVER;
74 	} else if (server_params) {
75 		zval *tmp;
76 
77 		/* decode the data from the array */
78 
79 		if (NULL != (tmp = zend_hash_str_find(server_params,
80 				"Server", sizeof("Server")-1))) {
81 			server_name = zval_get_string(tmp);
82 			ctx = CLSCTX_REMOTE_SERVER;
83 		}
84 
85 		if (NULL != (tmp = zend_hash_str_find(server_params,
86 				"Username", sizeof("Username")-1))) {
87 			user_name = zval_get_string(tmp);
88 		}
89 
90 		if (NULL != (tmp = zend_hash_str_find(server_params,
91 				"Password", sizeof("Password")-1))) {
92 			password = zval_get_string(tmp);
93 		}
94 
95 		if (NULL != (tmp = zend_hash_str_find(server_params,
96 				"Domain", sizeof("Domain")-1))) {
97 			domain_name = zval_get_string(tmp);
98 		}
99 
100 		if (NULL != (tmp = zend_hash_str_find(server_params,
101 				"Flags", sizeof("Flags")-1))) {
102 			ctx = (CLSCTX)zval_get_long(tmp);
103 		}
104 	}
105 
106 	if (server_name && !COMG(allow_dcom)) {
107 		php_com_throw_exception(E_ERROR, "DCOM has been disabled by your administrator [com.allow_dcom=0]");
108 		RETURN_THROWS();
109 	}
110 
111 	moniker = php_com_string_to_olestring(module_name, module_name_len, obj->code_page);
112 
113 	/* if instantiating a remote object, either directly, or via
114 	 * a moniker, fill in the relevant info */
115 	if (server_name) {
116 		info.dwReserved1 = 0;
117 		info.dwReserved2 = 0;
118 		info.pwszName = php_com_string_to_olestring(ZSTR_VAL(server_name), ZSTR_LEN(server_name), obj->code_page);
119 
120 		if (user_name) {
121 			authid.User = (OLECHAR*) ZSTR_VAL(user_name);
122 			authid.UserLength = (ULONG) ZSTR_LEN(user_name);
123 
124 			if (password) {
125 				authid.Password = (OLECHAR*) ZSTR_VAL(password);
126 				authid.PasswordLength = (ULONG) ZSTR_LEN(password);
127 			} else {
128 				authid.Password = (OLECHAR*) "";
129 				authid.PasswordLength = 0;
130 			}
131 
132 			if (domain_name) {
133 				authid.Domain = (OLECHAR*) ZSTR_VAL(domain_name);
134 				authid.DomainLength = (ULONG) ZSTR_LEN(domain_name);
135 			} else {
136 				authid.Domain = (OLECHAR*) "";
137 				authid.DomainLength = 0;
138 			}
139 			authid.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;
140 			info.pAuthInfo = &authinfo;
141 		} else {
142 			info.pAuthInfo = NULL;
143 		}
144 	}
145 
146 	if (FAILED(CLSIDFromString(moniker, &clsid))) {
147 		/* try to use it as a moniker */
148 		IBindCtx *pBindCtx = NULL;
149 		IMoniker *pMoniker = NULL;
150 		ULONG ulEaten;
151 		BIND_OPTS2 bopt = {0};
152 
153 		if (SUCCEEDED(res = CreateBindCtx(0, &pBindCtx))) {
154 			if (server_name) {
155 				/* fill in the remote server info.
156 				 * MSDN docs indicate that this might be ignored in
157 				 * current win32 implementations, but at least we are
158 				 * doing the right thing in readiness for the day that
159 				 * it does work */
160 				bopt.cbStruct = sizeof(bopt);
161 				IBindCtx_GetBindOptions(pBindCtx, (BIND_OPTS*)&bopt);
162 				bopt.pServerInfo = &info;
163 				/* apparently, GetBindOptions will only ever return
164 				 * a regular BIND_OPTS structure.  My gut feeling is
165 				 * that it will modify the size field to reflect that
166 				 * so lets be safe and set it to the BIND_OPTS2 size
167 				 * again */
168 				bopt.cbStruct = sizeof(bopt);
169 				IBindCtx_SetBindOptions(pBindCtx, (BIND_OPTS*)&bopt);
170 			}
171 
172 			if (SUCCEEDED(res = MkParseDisplayName(pBindCtx, moniker, &ulEaten, &pMoniker))) {
173 				res = IMoniker_BindToObject(pMoniker, pBindCtx,
174 					NULL, &IID_IDispatch, (LPVOID*)&V_DISPATCH(&obj->v));
175 
176 				if (SUCCEEDED(res)) {
177 					V_VT(&obj->v) = VT_DISPATCH;
178 				}
179 
180 				IMoniker_Release(pMoniker);
181 			}
182 		}
183 		if (pBindCtx) {
184 			IBindCtx_Release(pBindCtx);
185 		}
186 	} else if (server_name) {
187 		MULTI_QI		qi;
188 
189 		qi.pIID = &IID_IDispatch;
190 		qi.pItf = NULL;
191 		qi.hr = S_OK;
192 
193 		res = CoCreateInstanceEx(&clsid, NULL, ctx, &info, 1, &qi);
194 
195 		if (SUCCEEDED(res)) {
196 			res = qi.hr;
197 			V_DISPATCH(&obj->v) = (IDispatch*)qi.pItf;
198 			V_VT(&obj->v) = VT_DISPATCH;
199 		}
200 	} else {
201 		res = CoCreateInstance(&clsid, NULL, CLSCTX_SERVER, &IID_IDispatch, (LPVOID*)&V_DISPATCH(&obj->v));
202 		if (SUCCEEDED(res)) {
203 			V_VT(&obj->v) = VT_DISPATCH;
204 		}
205 	}
206 
207 	if (server_name) {
208 		if (info.pwszName) efree(info.pwszName);
209 		if (server_params) zend_string_release(server_name);
210 	}
211 	if (user_name) zend_string_release(user_name);
212 	if (password) zend_string_release(password);
213 	if (domain_name) zend_string_release(domain_name);
214 
215 	efree(moniker);
216 
217 	if (FAILED(res)) {
218 		char *werr, *msg;
219 
220 		werr = php_win32_error_to_msg(res);
221 		spprintf(&msg, 0, "Failed to create COM object `%s': %s", module_name, werr);
222 		php_win32_error_msg_free(werr);
223 
224 		php_com_throw_exception(res, msg);
225 		efree(msg);
226 		RETURN_THROWS();
227 	}
228 
229 	/* we got the object and it lives ! */
230 
231 	/* see if it has TypeInfo available */
232 	if (FAILED(IDispatch_GetTypeInfo(V_DISPATCH(&obj->v), 0, LANG_NEUTRAL, &obj->typeinfo)) && typelib_name) {
233 		/* load up the library from the named file */
234 		TL = php_com_load_typelib_via_cache(typelib_name, obj->code_page);
235 
236 		if (TL) {
237 			if (COMG(autoreg_on)) {
238 				php_com_import_typelib(TL, mode, obj->code_page);
239 			}
240 
241 			/* cross your fingers... there is no guarantee that this ITypeInfo
242 			 * instance has any relation to this IDispatch instance... */
243 			ITypeLib_GetTypeInfo(TL, 0, &obj->typeinfo);
244 			ITypeLib_Release(TL);
245 		}
246 	} else if (obj->typeinfo && COMG(autoreg_on)) {
247 		UINT idx;
248 
249 		if (SUCCEEDED(ITypeInfo_GetContainingTypeLib(obj->typeinfo, &TL, &idx))) {
250 			/* check if the library is already in the cache by getting its name */
251 			BSTR name;
252 
253 			if (SUCCEEDED(ITypeLib_GetDocumentation(TL, -1, &name, NULL, NULL, NULL))) {
254 				typelib_name = php_com_olestring_to_string(name, &typelib_name_len, obj->code_page);
255 
256 				if (NULL != php_com_cache_typelib(TL, typelib_name, typelib_name_len)) {
257 					php_com_import_typelib(TL, mode, obj->code_page);
258 
259 					/* add a reference for the hash */
260 					ITypeLib_AddRef(TL);
261 				}
262 				efree(typelib_name);
263 			} else {
264 				/* try it anyway */
265 				php_com_import_typelib(TL, mode, obj->code_page);
266 			}
267 
268 			ITypeLib_Release(TL);
269 		}
270 	}
271 
272 }
273 /* }}} */
274 
275 /* {{{ Returns a handle to an already running instance of a COM object */
PHP_FUNCTION(com_get_active_object)276 PHP_FUNCTION(com_get_active_object)
277 {
278 	CLSID clsid;
279 	char *module_name;
280 	size_t module_name_len;
281 	zend_long code_page;
282 	zend_bool code_page_is_null = 1;
283 	IUnknown *unk = NULL;
284 	IDispatch *obj = NULL;
285 	HRESULT res;
286 	OLECHAR *module = NULL;
287 
288 	php_com_initialize();
289 	if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "s|l!",
290 				&module_name, &module_name_len, &code_page, &code_page_is_null)) {
291 		RETURN_THROWS();
292 	}
293 
294 	if (code_page_is_null) {
295 		code_page = COMG(code_page);
296 	}
297 
298 	module = php_com_string_to_olestring(module_name, module_name_len, (int)code_page);
299 
300 	res = CLSIDFromString(module, &clsid);
301 
302 	if (FAILED(res)) {
303 		php_com_throw_exception(res, NULL);
304 	} else {
305 		res = GetActiveObject(&clsid, NULL, &unk);
306 
307 		if (FAILED(res)) {
308 			php_com_throw_exception(res, NULL);
309 		} else {
310 			res = IUnknown_QueryInterface(unk, &IID_IDispatch, &obj);
311 
312 			if (FAILED(res)) {
313 				php_com_throw_exception(res, NULL);
314 			} else if (obj) {
315 				/* we got our dispatchable object */
316 				php_com_wrap_dispatch(return_value, obj, (int)code_page);
317 			}
318 		}
319 	}
320 
321 	if (obj) {
322 		IDispatch_Release(obj);
323 	}
324 	if (unk) {
325 		IUnknown_Release(obj);
326 	}
327 	efree(module);
328 }
329 /* }}} */
330 
331 /* Performs an Invoke on the given com object.
332  * returns a failure code and creates an exception if there was an error */
php_com_invoke_helper(php_com_dotnet_object * obj,DISPID id_member,WORD flags,DISPPARAMS * disp_params,VARIANT * v,int silent,int allow_noarg)333 HRESULT php_com_invoke_helper(php_com_dotnet_object *obj, DISPID id_member,
334 		WORD flags, DISPPARAMS *disp_params, VARIANT *v, int silent, int allow_noarg)
335 {
336 	HRESULT hr;
337 	unsigned int arg_err;
338 	EXCEPINFO e = {0};
339 
340 	hr = IDispatch_Invoke(V_DISPATCH(&obj->v), id_member,
341 		&IID_NULL, LOCALE_SYSTEM_DEFAULT, flags, disp_params, v, &e, &arg_err);
342 
343 	if (silent == 0 && FAILED(hr)) {
344 		char *source = NULL, *desc = NULL, *msg = NULL;
345 		size_t source_len, desc_len;
346 
347 		switch (hr) {
348 			case DISP_E_EXCEPTION:
349 				if (e.bstrSource) {
350 					source = php_com_olestring_to_string(e.bstrSource, &source_len, obj->code_page);
351 					SysFreeString(e.bstrSource);
352 				}
353 				if (e.bstrDescription) {
354 					desc = php_com_olestring_to_string(e.bstrDescription, &desc_len, obj->code_page);
355 					SysFreeString(e.bstrDescription);
356 				}
357 				if (PG(html_errors)) {
358 					spprintf(&msg, 0, "<b>Source:</b> %s<br/><b>Description:</b> %s",
359 						source ? source : "Unknown",
360 						desc ? desc : "Unknown");
361 				} else {
362 					spprintf(&msg, 0, "Source: %s\nDescription: %s",
363 						source ? source : "Unknown",
364 						desc ? desc : "Unknown");
365 				}
366 				if (desc) {
367 					efree(desc);
368 				}
369 				if (source) {
370 					efree(source);
371 				}
372 				if (e.bstrHelpFile) {
373 					SysFreeString(e.bstrHelpFile);
374 				}
375 				break;
376 
377 			case DISP_E_PARAMNOTFOUND:
378 			case DISP_E_TYPEMISMATCH:
379 				desc = php_win32_error_to_msg(hr);
380 				spprintf(&msg, 0, "Parameter %d: %s", arg_err, desc);
381 				php_win32_error_msg_free(desc);
382 				break;
383 
384 			case DISP_E_BADPARAMCOUNT:
385 				if ((disp_params->cArgs + disp_params->cNamedArgs == 0) && (allow_noarg == 1)) {
386 					/* if getting a property and they are missing all parameters,
387 					 * we want to create a proxy object for them; so lets not create an
388 					 * exception here */
389 					msg = NULL;
390 					break;
391 				}
392 				/* else fall through */
393 
394 			default:
395 				desc = php_win32_error_to_msg(hr);
396 				spprintf(&msg, 0, "Error [0x%08x] %s", hr, desc);
397 				php_win32_error_msg_free(desc);
398 				break;
399 		}
400 
401 		if (msg) {
402 			php_com_throw_exception(hr, msg);
403 			efree(msg);
404 		}
405 	}
406 
407 	return hr;
408 }
409 
410 /* map an ID to a name */
php_com_get_id_of_name(php_com_dotnet_object * obj,char * name,size_t namelen,DISPID * dispid)411 HRESULT php_com_get_id_of_name(php_com_dotnet_object *obj, char *name,
412 		size_t namelen, DISPID *dispid)
413 {
414 	OLECHAR *olename;
415 	HRESULT hr;
416 	zval *tmp;
417 
418 	if (namelen == -1) {
419 		namelen = strlen(name);
420 	}
421 
422 	if (obj->id_of_name_cache && NULL != (tmp = zend_hash_str_find(obj->id_of_name_cache, name, namelen))) {
423 		*dispid = (DISPID)Z_LVAL_P(tmp);
424 		return S_OK;
425 	}
426 
427 	olename = php_com_string_to_olestring(name, namelen, obj->code_page);
428 
429 	if (obj->typeinfo) {
430 		hr = ITypeInfo_GetIDsOfNames(obj->typeinfo, &olename, 1, dispid);
431 		if (FAILED(hr)) {
432 			HRESULT hr1 = hr;
433 			hr = IDispatch_GetIDsOfNames(V_DISPATCH(&obj->v), &IID_NULL, &olename, 1, LOCALE_SYSTEM_DEFAULT, dispid);
434 			if (SUCCEEDED(hr) && hr1 != E_NOTIMPL) {
435 				/* fall back on IDispatch direct */
436 				ITypeInfo_Release(obj->typeinfo);
437 				obj->typeinfo = NULL;
438 			}
439 		}
440 	} else {
441 		hr = IDispatch_GetIDsOfNames(V_DISPATCH(&obj->v), &IID_NULL, &olename, 1, LOCALE_SYSTEM_DEFAULT, dispid);
442 	}
443 	efree(olename);
444 
445 	if (SUCCEEDED(hr)) {
446 		zval tmp;
447 
448 		/* cache the mapping */
449 		if (!obj->id_of_name_cache) {
450 			ALLOC_HASHTABLE(obj->id_of_name_cache);
451 			zend_hash_init(obj->id_of_name_cache, 2, NULL, NULL, 0);
452 		}
453 		ZVAL_LONG(&tmp, *dispid);
454 		zend_hash_str_update(obj->id_of_name_cache, name, namelen, &tmp);
455 	}
456 
457 	return hr;
458 }
459 
460 /* the core of COM */
php_com_do_invoke_byref(php_com_dotnet_object * obj,zend_internal_function * f,WORD flags,VARIANT * v,int nargs,zval * args)461 int php_com_do_invoke_byref(php_com_dotnet_object *obj, zend_internal_function *f,
462 		WORD flags,	VARIANT *v, int nargs, zval *args)
463 {
464 	DISPID dispid, altdispid;
465 	DISPPARAMS disp_params;
466 	HRESULT hr;
467 	VARIANT *vargs = NULL, *byref_vals = NULL;
468 	int i, byref_count = 0, j;
469 
470 	/* assumption: that the active function (f) is the function we generated for the engine */
471 	if (!f) {
472 		return FAILURE;
473 	}
474 
475 	hr = php_com_get_id_of_name(obj, f->function_name->val, f->function_name->len, &dispid);
476 
477 	if (FAILED(hr)) {
478 		char *msg = NULL;
479 		char *winerr = php_win32_error_to_msg(hr);
480 		spprintf(&msg, 0, "Unable to lookup `%s': %s", f->function_name->val, winerr);
481 		php_win32_error_msg_free(winerr);
482 		php_com_throw_exception(hr, msg);
483 		efree(msg);
484 		return FAILURE;
485 	}
486 
487 
488 	if (nargs) {
489 		vargs = (VARIANT*)safe_emalloc(sizeof(VARIANT), nargs, 0);
490 	}
491 
492 	if (f->arg_info) {
493 		for (i = 0; i < nargs; i++) {
494 			if (ZEND_ARG_SEND_MODE(&f->arg_info[nargs - i - 1])) {
495 				byref_count++;
496 			}
497 		}
498 	}
499 
500 	if (byref_count) {
501 		byref_vals = (VARIANT*)safe_emalloc(sizeof(VARIANT), byref_count, 0);
502 		for (j = 0, i = 0; i < nargs; i++) {
503 			if (ZEND_ARG_SEND_MODE(&f->arg_info[nargs - i - 1])) {
504 				/* put the value into byref_vals instead */
505 				php_com_variant_from_zval(&byref_vals[j], &args[nargs - i - 1], obj->code_page);
506 
507 				/* if it is already byref, "move" it into the vargs array, otherwise
508 				 * make vargs a reference to this value */
509 				if (V_VT(&byref_vals[j]) & VT_BYREF) {
510 					memcpy(&vargs[i], &byref_vals[j], sizeof(vargs[i]));
511 					VariantInit(&byref_vals[j]); /* leave the variant slot empty to simplify cleanup */
512 				} else {
513 					VariantInit(&vargs[i]);
514 					V_VT(&vargs[i]) = V_VT(&byref_vals[j]) | VT_BYREF;
515 					/* union magic ensures that this works out */
516 					vargs[i].byref = &V_UINT(&byref_vals[j]);
517 				}
518 				j++;
519 			} else {
520 				php_com_variant_from_zval(&vargs[i], &args[nargs - i - 1], obj->code_page);
521 			}
522 		}
523 
524 	} else {
525 		/* Invoke'd args are in reverse order */
526 		for (i = 0; i < nargs; i++) {
527 			php_com_variant_from_zval(&vargs[i], &args[nargs - i - 1], obj->code_page);
528 		}
529 	}
530 
531 	disp_params.cArgs = nargs;
532 	disp_params.cNamedArgs = 0;
533 	disp_params.rgvarg = vargs;
534 	disp_params.rgdispidNamedArgs = NULL;
535 
536 	if (flags & DISPATCH_PROPERTYPUT) {
537 		altdispid = DISPID_PROPERTYPUT;
538 		disp_params.rgdispidNamedArgs = &altdispid;
539 		disp_params.cNamedArgs = 1;
540 	}
541 
542 	/* this will create an exception if needed */
543 	hr = php_com_invoke_helper(obj, dispid, flags, &disp_params, v, 0, 0);
544 
545 	/* release variants */
546 	if (vargs) {
547 		if (f && f->arg_info) {
548 			for (i = 0, j = 0; i < nargs; i++) {
549 				/* if this was byref, update the zval */
550 				if (ZEND_ARG_SEND_MODE(&f->arg_info[nargs - i - 1])) {
551 					zval *arg = &args[nargs - i - 1];
552 
553 					ZVAL_DEREF(arg);
554 					zval_ptr_dtor(arg);
555 					ZVAL_NULL(arg);
556 
557 					/* if the variant is pointing at the byref_vals, we need to map
558 					 * the pointee value as a zval; otherwise, the value is pointing
559 					 * into an existing PHP variant record */
560 					if (V_VT(&vargs[i]) & VT_BYREF) {
561 						if (vargs[i].byref == &V_UINT(&byref_vals[j])) {
562 							/* copy that value */
563 							php_com_zval_from_variant(arg, &byref_vals[j], obj->code_page);
564 						}
565 					} else {
566 						/* not sure if this can ever happen; the variant we marked as BYREF
567 						 * is no longer BYREF - copy its value */
568 						php_com_zval_from_variant(arg, &vargs[i], obj->code_page);
569 					}
570 					VariantClear(&byref_vals[j]);
571 					j++;
572 				}
573 				VariantClear(&vargs[i]);
574 			}
575 		} else {
576 			for (i = 0, j = 0; i < nargs; i++) {
577 				VariantClear(&vargs[i]);
578 			}
579 		}
580 		efree(vargs);
581 		if (byref_vals) efree(byref_vals);
582 	}
583 
584 	return SUCCEEDED(hr) ? SUCCESS : FAILURE;
585 }
586 
587 
588 
php_com_do_invoke_by_id(php_com_dotnet_object * obj,DISPID dispid,WORD flags,VARIANT * v,int nargs,zval * args,int silent,int allow_noarg)589 int php_com_do_invoke_by_id(php_com_dotnet_object *obj, DISPID dispid,
590 		WORD flags,	VARIANT *v, int nargs, zval *args, int silent, int allow_noarg)
591 {
592 	DISPID altdispid;
593 	DISPPARAMS disp_params;
594 	HRESULT hr;
595 	VARIANT *vargs = NULL;
596 	int i;
597 
598 	if (nargs) {
599 		vargs = (VARIANT*)safe_emalloc(sizeof(VARIANT), nargs, 0);
600 	}
601 
602 	/* Invoke'd args are in reverse order */
603 	for (i = 0; i < nargs; i++) {
604 		php_com_variant_from_zval(&vargs[i], &args[nargs - i - 1], obj->code_page);
605 	}
606 
607 	disp_params.cArgs = nargs;
608 	disp_params.cNamedArgs = 0;
609 	disp_params.rgvarg = vargs;
610 	disp_params.rgdispidNamedArgs = NULL;
611 
612 	if (flags & DISPATCH_PROPERTYPUT) {
613 		altdispid = DISPID_PROPERTYPUT;
614 		disp_params.rgdispidNamedArgs = &altdispid;
615 		disp_params.cNamedArgs = 1;
616 	}
617 
618 	/* this will create an exception if needed */
619 	hr = php_com_invoke_helper(obj, dispid, flags, &disp_params, v, silent, allow_noarg);
620 
621 	/* release variants */
622 	if (vargs) {
623 		for (i = 0; i < nargs; i++) {
624 			VariantClear(&vargs[i]);
625 		}
626 		efree(vargs);
627 	}
628 
629 	/* a bit of a hack this, but it's needed for COM array access. */
630 	if (hr == DISP_E_BADPARAMCOUNT)
631 		return hr;
632 
633 	return SUCCEEDED(hr) ? SUCCESS : FAILURE;
634 }
635 
php_com_do_invoke(php_com_dotnet_object * obj,char * name,size_t namelen,WORD flags,VARIANT * v,int nargs,zval * args,int allow_noarg)636 int php_com_do_invoke(php_com_dotnet_object *obj, char *name, size_t namelen,
637 		WORD flags,	VARIANT *v, int nargs, zval *args, int allow_noarg)
638 {
639 	DISPID dispid;
640 	HRESULT hr;
641 	char *msg = NULL;
642 
643 	hr = php_com_get_id_of_name(obj, name, namelen, &dispid);
644 
645 	if (FAILED(hr)) {
646 		char *winerr = php_win32_error_to_msg(hr);
647 		spprintf(&msg, 0, "Unable to lookup `%s': %s", name, winerr);
648 		php_win32_error_msg_free(winerr);
649 		php_com_throw_exception(hr, msg);
650 		efree(msg);
651 		return FAILURE;
652 	}
653 
654 	return php_com_do_invoke_by_id(obj, dispid, flags, v, nargs, args, 0, allow_noarg);
655 }
656 
657 /* {{{ Generate a globally unique identifier (GUID) */
PHP_FUNCTION(com_create_guid)658 PHP_FUNCTION(com_create_guid)
659 {
660 	GUID retval;
661 	OLECHAR *guid_string;
662 
663 	if (zend_parse_parameters_none() == FAILURE) {
664 		RETURN_THROWS();
665 	}
666 
667 	php_com_initialize();
668 	if (CoCreateGuid(&retval) == S_OK && StringFromCLSID(&retval, &guid_string) == S_OK) {
669 		size_t len;
670 		char *str;
671 
672 		str = php_com_olestring_to_string(guid_string, &len, CP_ACP);
673 		RETVAL_STRINGL(str, len);
674 		// TODO: avoid reallocation ???
675 		efree(str);
676 
677 		CoTaskMemFree(guid_string);
678 	} else {
679 		RETURN_FALSE;
680 	}
681 }
682 /* }}} */
683 
684 /* {{{ Connect events from a COM object to a PHP object */
PHP_FUNCTION(com_event_sink)685 PHP_FUNCTION(com_event_sink)
686 {
687 	zval *object, *sinkobject;
688 	zend_string *sink_str = NULL;
689 	HashTable *sink_ht = NULL;
690 	char *dispname = NULL, *typelibname = NULL;
691 	php_com_dotnet_object *obj;
692 	ITypeInfo *typeinfo = NULL;
693 
694 	ZEND_PARSE_PARAMETERS_START(2, 3)
695 		Z_PARAM_OBJECT_OF_CLASS(object, php_com_variant_class_entry)
696 		Z_PARAM_OBJECT(sinkobject)
697 		Z_PARAM_OPTIONAL
698 		Z_PARAM_ARRAY_HT_OR_STR_OR_NULL(sink_ht, sink_str)
699 	ZEND_PARSE_PARAMETERS_END();
700 
701 	RETVAL_FALSE;
702 
703 	php_com_initialize();
704 	obj = CDNO_FETCH(object);
705 
706 	if (sink_ht) {
707 		/* 0 => typelibname, 1 => dispname */
708 		zval *tmp;
709 
710 		if ((tmp = zend_hash_index_find(sink_ht, 0)) != NULL && Z_TYPE_P(tmp) == IS_STRING)
711 			typelibname = Z_STRVAL_P(tmp);
712 		if ((tmp = zend_hash_index_find(sink_ht, 1)) != NULL && Z_TYPE_P(tmp) == IS_STRING)
713 			dispname = Z_STRVAL_P(tmp);
714 	} else if (sink_str) {
715 		dispname = ZSTR_VAL(sink_str);
716 	}
717 
718 	typeinfo = php_com_locate_typeinfo(typelibname, obj, dispname, 1);
719 
720 	if (typeinfo) {
721 		HashTable *id_to_name;
722 
723 		ALLOC_HASHTABLE(id_to_name);
724 
725 		if (php_com_process_typeinfo(typeinfo, id_to_name, 0, &obj->sink_id, obj->code_page)) {
726 
727 			/* Create the COM wrapper for this sink */
728 			obj->sink_dispatch = php_com_wrapper_export_as_sink(sinkobject, &obj->sink_id, id_to_name);
729 
730 			/* Now hook it up to the source */
731 			php_com_object_enable_event_sink(obj, TRUE);
732 			RETVAL_TRUE;
733 
734 		} else {
735 			FREE_HASHTABLE(id_to_name);
736 		}
737 	}
738 
739 	if (typeinfo) {
740 		ITypeInfo_Release(typeinfo);
741 	}
742 
743 }
744 /* }}} */
745 
746 /* {{{ Print out a PHP class definition for a dispatchable interface */
PHP_FUNCTION(com_print_typeinfo)747 PHP_FUNCTION(com_print_typeinfo)
748 {
749 	zend_object *object_zpp;
750 	zend_string *typelib_name_zpp = NULL;
751 	char *ifacename = NULL;
752 	char *typelibname = NULL;
753 	size_t ifacelen;
754 	zend_bool wantsink = 0;
755 	php_com_dotnet_object *obj = NULL;
756 	ITypeInfo *typeinfo;
757 
758 	ZEND_PARSE_PARAMETERS_START(1, 3)
759 		Z_PARAM_OBJ_OF_CLASS_OR_STR(object_zpp, php_com_variant_class_entry, typelib_name_zpp)
760 		Z_PARAM_OPTIONAL
761 		Z_PARAM_STRING_OR_NULL(ifacename, ifacelen)
762 		Z_PARAM_BOOL(wantsink)
763 	ZEND_PARSE_PARAMETERS_END();
764 
765 	php_com_initialize();
766 	if (object_zpp) {
767 		obj = (php_com_dotnet_object*)object_zpp;
768 	} else {
769 		typelibname = ZSTR_VAL(typelib_name_zpp);
770 	}
771 
772 	typeinfo = php_com_locate_typeinfo(typelibname, obj, ifacename, wantsink ? 1 : 0);
773 	if (typeinfo) {
774 		php_com_process_typeinfo(typeinfo, NULL, 1, NULL, obj ? obj->code_page : COMG(code_page));
775 		ITypeInfo_Release(typeinfo);
776 		RETURN_TRUE;
777 	}
778 
779 	php_error_docref(NULL, E_WARNING, "Unable to find typeinfo using the parameters supplied");
780 	RETURN_FALSE;
781 }
782 /* }}} */
783 
784 /* {{{ Process COM messages, sleeping for up to timeoutms milliseconds */
PHP_FUNCTION(com_message_pump)785 PHP_FUNCTION(com_message_pump)
786 {
787 	zend_long timeoutms = 0;
788 	MSG msg;
789 	DWORD result;
790 
791 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &timeoutms) == FAILURE)
792 		RETURN_THROWS();
793 
794 	php_com_initialize();
795 	result = MsgWaitForMultipleObjects(0, NULL, FALSE, (DWORD)timeoutms, QS_ALLINPUT);
796 
797 	if (result == WAIT_OBJECT_0) {
798 		while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
799 			TranslateMessage(&msg);
800 			DispatchMessage(&msg);
801 		}
802 		/* we processed messages */
803 		RETVAL_TRUE;
804 	} else {
805 		/* we did not process messages (timed out) */
806 		RETVAL_FALSE;
807 	}
808 }
809 /* }}} */
810 
811 /* {{{ Loads a Typelibrary and registers its constants */
PHP_FUNCTION(com_load_typelib)812 PHP_FUNCTION(com_load_typelib)
813 {
814 	char *name;
815 	size_t namelen;
816 	ITypeLib *pTL = NULL;
817 	zend_bool cs = TRUE;
818 	int codepage = COMG(code_page);
819 
820 	if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "s|b", &name, &namelen, &cs)) {
821 		RETURN_THROWS();
822 	}
823 
824 	if (!cs) {
825 		php_error_docref(NULL, E_WARNING, "com_load_typelib(): Argument #2 ($case_insensitive) is ignored since declaration of case-insensitive constants is no longer supported");
826 	}
827 
828 	RETVAL_FALSE;
829 
830 	php_com_initialize();
831 	pTL = php_com_load_typelib_via_cache(name, codepage);
832 	if (pTL) {
833 		if (php_com_import_typelib(pTL, cs ? CONST_CS : 0, codepage) == SUCCESS) {
834 			RETVAL_TRUE;
835 		}
836 
837 		ITypeLib_Release(pTL);
838 		pTL = NULL;
839 	}
840 }
841 /* }}} */
842