diff options
author | Lorry Tar Creator <lorry-tar-importer@baserock.org> | 2013-03-14 05:42:27 +0000 |
---|---|---|
committer | <> | 2013-04-03 16:25:08 +0000 |
commit | c4dd7a1a684490673e25aaf4fabec5df138854c4 (patch) | |
tree | 4d57c44caae4480efff02b90b9be86f44bf25409 /ext/com_dotnet | |
download | php2-master.tar.gz |
Imported from /home/lorry/working-area/delta_php2/php-5.4.13.tar.bz2.HEADphp-5.4.13master
Diffstat (limited to 'ext/com_dotnet')
26 files changed, 7783 insertions, 0 deletions
diff --git a/ext/com_dotnet/CREDITS b/ext/com_dotnet/CREDITS new file mode 100644 index 0000000..8dd06fa --- /dev/null +++ b/ext/com_dotnet/CREDITS @@ -0,0 +1,2 @@ +COM and .Net +Wez Furlong diff --git a/ext/com_dotnet/README b/ext/com_dotnet/README new file mode 100644 index 0000000..0d9db40 --- /dev/null +++ b/ext/com_dotnet/README @@ -0,0 +1,71 @@ +This is the new php5 COM module. + +It is not 100% backwards compatible with PHP 4 ext/com, but you should not miss +the "features" that have not been retained. + +This module exposes 3 classes: variant, com and dotnet(*). +com and dotnet classes are descendants of the variant class; the only +difference between the three are their constructors. Once instantiated, the +module doesn't make a distinction between them. + +COM errrors are mapped to exceptions; you should protect your COM code using +the try..catch construct if you want to be able to handle error conditions. + +Be warned that due to the way the ZE2 currently works, exceptions are only +"armed" at the time they are detected, but do not "detonate" until the end of +the statement. So, code like this: + + $obj->foo[43]->bar(); + +Where the foo[43] access triggers an exception will continue to call the bar() +method on a null object and cause a fatal php error. + +Default properties and array access: + +$obj = new COM("..."); +$obj[1]->foo(); + +The code above will use the type information for the object to determine its +default property and then access it. In PHP 4, it was hard-coded to use the +"Items" member, which was wrong. + +The default property will also be used by the casting support to determine the +value for the object. + +Variants: + +This implementation of COM takes a simpler approach than the PHP 4 version; +we only map a few native types to COM and vice-versa, leaving the more complex +things as variants. This allows greater consistency of data when passing +parameters to and from COM objects (no data will be lost). In addition, a +large number of the variant API has been mapped to PHP space so that you can +use it for working with the special variant decimal, currency and date time +types. This could be used as a replacement for the bcmath extension, for +example. + +You can use the new object casting hook to for a php-native representation of +a variant object: + +$a = new variant(4); +$b = new variant(6); +$c = variant_add($a, $b); +echo $c; // outputs 10 as a string, instead of Object + +Sample Script: + +<?php +$word = new COM("word.application"); +print "Loaded Word, version {$word->Version}\n"; +$word->Visible = 1; +$word->Documents->Add(); +$word->Selection->TypeText("This is a test..."); +$word->Documents[1]->SaveAs("Useless test.doc"); +$word->Quit(); +?> + +TODO: + +- documentation + +* dotnet support requires that you have the mscoree.h header from the .net sdk + when you build the module. diff --git a/ext/com_dotnet/com_com.c b/ext/com_dotnet/com_com.c new file mode 100644 index 0000000..fe48ee9 --- /dev/null +++ b/ext/com_dotnet/com_com.c @@ -0,0 +1,844 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2013 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong <wez@thebrainroom.com> | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "php_com_dotnet.h" +#include "php_com_dotnet_internal.h" +#include "Zend/zend_exceptions.h" + +/* {{{ com_create_instance - ctor for COM class */ +PHP_FUNCTION(com_create_instance) +{ + zval *object = getThis(); + zval *server_params = NULL; + php_com_dotnet_object *obj; + char *module_name, *typelib_name = NULL, *server_name = NULL; + char *user_name = NULL, *domain_name = NULL, *password = NULL; + int module_name_len, typelib_name_len, server_name_len, + user_name_len, domain_name_len, password_len; + OLECHAR *moniker; + CLSID clsid; + CLSCTX ctx = CLSCTX_SERVER; + HRESULT res = E_FAIL; + int mode = COMG(autoreg_case_sensitive) ? CONST_CS : 0; + ITypeLib *TL = NULL; + COSERVERINFO info; + COAUTHIDENTITY authid = {0}; + COAUTHINFO authinfo = { + RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, + RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, + &authid, EOAC_NONE + }; + + php_com_initialize(TSRMLS_C); + obj = CDNO_FETCH(object); + + if (FAILURE == zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, + ZEND_NUM_ARGS() TSRMLS_CC, "s|s!ls", + &module_name, &module_name_len, &server_name, &server_name_len, + &obj->code_page, &typelib_name, &typelib_name_len) && + FAILURE == zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, + ZEND_NUM_ARGS() TSRMLS_CC, "sa|ls", + &module_name, &module_name_len, &server_params, &obj->code_page, + &typelib_name, &typelib_name_len)) { + + php_com_throw_exception(E_INVALIDARG, "Could not create COM object - invalid arguments!" TSRMLS_CC); + ZVAL_NULL(object); + return; + } + + if (server_name) { + ctx = CLSCTX_REMOTE_SERVER; + } else if (server_params) { + zval **tmp; + + /* decode the data from the array */ + + if (SUCCESS == zend_hash_find(HASH_OF(server_params), + "Server", sizeof("Server"), (void**)&tmp)) { + convert_to_string_ex(tmp); + server_name = Z_STRVAL_PP(tmp); + server_name_len = Z_STRLEN_PP(tmp); + ctx = CLSCTX_REMOTE_SERVER; + } + + if (SUCCESS == zend_hash_find(HASH_OF(server_params), + "Username", sizeof("Username"), (void**)&tmp)) { + convert_to_string_ex(tmp); + user_name = Z_STRVAL_PP(tmp); + user_name_len = Z_STRLEN_PP(tmp); + } + + if (SUCCESS == zend_hash_find(HASH_OF(server_params), + "Password", sizeof("Password"), (void**)&tmp)) { + convert_to_string_ex(tmp); + password = Z_STRVAL_PP(tmp); + password_len = Z_STRLEN_PP(tmp); + } + + if (SUCCESS == zend_hash_find(HASH_OF(server_params), + "Domain", sizeof("Domain"), (void**)&tmp)) { + convert_to_string_ex(tmp); + domain_name = Z_STRVAL_PP(tmp); + domain_name_len = Z_STRLEN_PP(tmp); + } + + if (SUCCESS == zend_hash_find(HASH_OF(server_params), + "Flags", sizeof("Flags"), (void**)&tmp)) { + convert_to_long_ex(tmp); + ctx = (CLSCTX)Z_LVAL_PP(tmp); + } + } + + if (server_name && !COMG(allow_dcom)) { + php_com_throw_exception(E_ERROR, "DCOM has been disabled by your administrator [com.allow_dcom=0]" TSRMLS_CC); + return; + } + + moniker = php_com_string_to_olestring(module_name, module_name_len, obj->code_page TSRMLS_CC); + + /* if instantiating a remote object, either directly, or via + * a moniker, fill in the relevant info */ + if (server_name) { + info.dwReserved1 = 0; + info.dwReserved2 = 0; + info.pwszName = php_com_string_to_olestring(server_name, server_name_len, obj->code_page TSRMLS_CC); + + if (user_name) { + authid.User = php_com_string_to_olestring(user_name, -1, obj->code_page TSRMLS_CC); + authid.UserLength = user_name_len; + + if (password) { + authid.Password = (OLECHAR*)password; + authid.PasswordLength = password_len; + } else { + authid.Password = (OLECHAR*)""; + authid.PasswordLength = 0; + } + + if (domain_name) { + authid.Domain = (OLECHAR*)domain_name; + authid.DomainLength = domain_name_len; + } else { + authid.Domain = (OLECHAR*)""; + authid.DomainLength = 0; + } + authid.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI; + info.pAuthInfo = &authinfo; + } else { + info.pAuthInfo = NULL; + } + } + + if (FAILED(CLSIDFromString(moniker, &clsid))) { + /* try to use it as a moniker */ + IBindCtx *pBindCtx = NULL; + IMoniker *pMoniker = NULL; + ULONG ulEaten; + BIND_OPTS2 bopt = {0}; + + if (SUCCEEDED(res = CreateBindCtx(0, &pBindCtx))) { + if (server_name) { + /* fill in the remote server info. + * MSDN docs indicate that this might be ignored in + * current win32 implementations, but at least we are + * doing the right thing in readiness for the day that + * it does work */ + bopt.cbStruct = sizeof(bopt); + IBindCtx_GetBindOptions(pBindCtx, (BIND_OPTS*)&bopt); + bopt.pServerInfo = &info; + /* apparently, GetBindOptions will only ever return + * a regular BIND_OPTS structure. My gut feeling is + * that it will modify the size field to reflect that + * so lets be safe and set it to the BIND_OPTS2 size + * again */ + bopt.cbStruct = sizeof(bopt); + IBindCtx_SetBindOptions(pBindCtx, (BIND_OPTS*)&bopt); + } + + if (SUCCEEDED(res = MkParseDisplayName(pBindCtx, moniker, &ulEaten, &pMoniker))) { + res = IMoniker_BindToObject(pMoniker, pBindCtx, + NULL, &IID_IDispatch, (LPVOID*)&V_DISPATCH(&obj->v)); + + if (SUCCEEDED(res)) { + V_VT(&obj->v) = VT_DISPATCH; + } + + IMoniker_Release(pMoniker); + } + } + if (pBindCtx) { + IBindCtx_Release(pBindCtx); + } + } else if (server_name) { + MULTI_QI qi; + + qi.pIID = &IID_IDispatch; + qi.pItf = NULL; + qi.hr = S_OK; + + res = CoCreateInstanceEx(&clsid, NULL, ctx, &info, 1, &qi); + + if (SUCCEEDED(res)) { + res = qi.hr; + V_DISPATCH(&obj->v) = (IDispatch*)qi.pItf; + V_VT(&obj->v) = VT_DISPATCH; + } + } else { + res = CoCreateInstance(&clsid, NULL, CLSCTX_SERVER, &IID_IDispatch, (LPVOID*)&V_DISPATCH(&obj->v)); + if (SUCCEEDED(res)) { + V_VT(&obj->v) = VT_DISPATCH; + } + } + + if (server_name) { + STR_FREE((char*)info.pwszName); + STR_FREE((char*)authid.User); + } + + efree(moniker); + + if (FAILED(res)) { + char *werr, *msg; + + werr = php_win32_error_to_msg(res); + spprintf(&msg, 0, "Failed to create COM object `%s': %s", module_name, werr); + LocalFree(werr); + + php_com_throw_exception(res, msg TSRMLS_CC); + efree(msg); + ZVAL_NULL(object); + return; + } + + /* we got the object and it lives ! */ + + /* see if it has TypeInfo available */ + if (FAILED(IDispatch_GetTypeInfo(V_DISPATCH(&obj->v), 0, LANG_NEUTRAL, &obj->typeinfo)) && typelib_name) { + /* load up the library from the named file */ + int cached; + + TL = php_com_load_typelib_via_cache(typelib_name, obj->code_page, &cached TSRMLS_CC); + + if (TL) { + if (COMG(autoreg_on) && !cached) { + php_com_import_typelib(TL, mode, obj->code_page TSRMLS_CC); + } + + /* cross your fingers... there is no guarantee that this ITypeInfo + * instance has any relation to this IDispatch instance... */ + ITypeLib_GetTypeInfo(TL, 0, &obj->typeinfo); + ITypeLib_Release(TL); + } + } else if (obj->typeinfo && COMG(autoreg_on)) { + int idx; + + if (SUCCEEDED(ITypeInfo_GetContainingTypeLib(obj->typeinfo, &TL, &idx))) { + /* check if the library is already in the cache by getting its name */ + BSTR name; + + if (SUCCEEDED(ITypeLib_GetDocumentation(TL, -1, &name, NULL, NULL, NULL))) { + typelib_name = php_com_olestring_to_string(name, &typelib_name_len, obj->code_page TSRMLS_CC); + + if (SUCCESS == zend_ts_hash_add(&php_com_typelibraries, typelib_name, typelib_name_len+1, (void*)&TL, sizeof(ITypeLib*), NULL)) { + php_com_import_typelib(TL, mode, obj->code_page TSRMLS_CC); + + /* add a reference for the hash */ + ITypeLib_AddRef(TL); + } + + } else { + /* try it anyway */ + php_com_import_typelib(TL, mode, obj->code_page TSRMLS_CC); + } + + ITypeLib_Release(TL); + } + } + +} +/* }}} */ + +/* {{{ proto object com_get_active_object(string progid [, int code_page ]) + Returns a handle to an already running instance of a COM object */ +PHP_FUNCTION(com_get_active_object) +{ + CLSID clsid; + char *module_name; + int module_name_len; + long code_page = COMG(code_page); + IUnknown *unk = NULL; + IDispatch *obj = NULL; + HRESULT res; + OLECHAR *module = NULL; + + php_com_initialize(TSRMLS_C); + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", + &module_name, &module_name_len, &code_page)) { + php_com_throw_exception(E_INVALIDARG, "Invalid arguments!" TSRMLS_CC); + return; + } + + module = php_com_string_to_olestring(module_name, module_name_len, code_page TSRMLS_CC); + + res = CLSIDFromString(module, &clsid); + + if (FAILED(res)) { + php_com_throw_exception(res, NULL TSRMLS_CC); + } else { + res = GetActiveObject(&clsid, NULL, &unk); + + if (FAILED(res)) { + php_com_throw_exception(res, NULL TSRMLS_CC); + } else { + res = IUnknown_QueryInterface(unk, &IID_IDispatch, &obj); + + if (FAILED(res)) { + php_com_throw_exception(res, NULL TSRMLS_CC); + } else if (obj) { + /* we got our dispatchable object */ + php_com_wrap_dispatch(return_value, obj, code_page TSRMLS_CC); + } + } + } + + if (obj) { + IDispatch_Release(obj); + } + if (unk) { + IUnknown_Release(obj); + } + efree(module); +} +/* }}} */ + +/* Performs an Invoke on the given com object. + * returns a failure code and creates an exception if there was an error */ +HRESULT php_com_invoke_helper(php_com_dotnet_object *obj, DISPID id_member, + WORD flags, DISPPARAMS *disp_params, VARIANT *v, int silent, int allow_noarg TSRMLS_DC) +{ + HRESULT hr; + unsigned int arg_err; + EXCEPINFO e = {0}; + + hr = IDispatch_Invoke(V_DISPATCH(&obj->v), id_member, + &IID_NULL, LOCALE_SYSTEM_DEFAULT, flags, disp_params, v, &e, &arg_err); + + if (silent == 0 && FAILED(hr)) { + char *source = NULL, *desc = NULL, *msg = NULL; + int source_len, desc_len; + + switch (hr) { + case DISP_E_EXCEPTION: + if (e.bstrSource) { + source = php_com_olestring_to_string(e.bstrSource, &source_len, obj->code_page TSRMLS_CC); + SysFreeString(e.bstrSource); + } + if (e.bstrDescription) { + desc = php_com_olestring_to_string(e.bstrDescription, &desc_len, obj->code_page TSRMLS_CC); + SysFreeString(e.bstrDescription); + } + if (PG(html_errors)) { + spprintf(&msg, 0, "<b>Source:</b> %s<br/><b>Description:</b> %s", + source ? source : "Unknown", + desc ? desc : "Unknown"); + } else { + spprintf(&msg, 0, "Source: %s\nDescription: %s", + source ? source : "Unknown", + desc ? desc : "Unknown"); + } + if (desc) { + efree(desc); + } + if (source) { + efree(source); + } + if (e.bstrHelpFile) { + SysFreeString(e.bstrHelpFile); + } + break; + + case DISP_E_PARAMNOTFOUND: + case DISP_E_TYPEMISMATCH: + desc = php_win32_error_to_msg(hr); + spprintf(&msg, 0, "Parameter %d: %s", arg_err, desc); + LocalFree(desc); + break; + + case DISP_E_BADPARAMCOUNT: + if ((disp_params->cArgs + disp_params->cNamedArgs == 0) && (allow_noarg == 1)) { + /* if getting a property and they are missing all parameters, + * we want to create a proxy object for them; so lets not create an + * exception here */ + msg = NULL; + break; + } + /* else fall through */ + + default: + desc = php_win32_error_to_msg(hr); + spprintf(&msg, 0, "Error [0x%08x] %s", hr, desc); + LocalFree(desc); + break; + } + + if (msg) { + php_com_throw_exception(hr, msg TSRMLS_CC); + efree(msg); + } + } + + return hr; +} + +/* map an ID to a name */ +HRESULT php_com_get_id_of_name(php_com_dotnet_object *obj, char *name, + int namelen, DISPID *dispid TSRMLS_DC) +{ + OLECHAR *olename; + HRESULT hr; + DISPID *dispid_ptr; + + if (namelen == -1) { + namelen = strlen(name); + } + + if (obj->id_of_name_cache && SUCCESS == zend_hash_find(obj->id_of_name_cache, name, namelen, (void**)&dispid_ptr)) { + *dispid = *dispid_ptr; + return S_OK; + } + + olename = php_com_string_to_olestring(name, namelen, obj->code_page TSRMLS_CC); + + if (obj->typeinfo) { + hr = ITypeInfo_GetIDsOfNames(obj->typeinfo, &olename, 1, dispid); + if (FAILED(hr)) { + hr = IDispatch_GetIDsOfNames(V_DISPATCH(&obj->v), &IID_NULL, &olename, 1, LOCALE_SYSTEM_DEFAULT, dispid); + if (SUCCEEDED(hr)) { + /* fall back on IDispatch direct */ + ITypeInfo_Release(obj->typeinfo); + obj->typeinfo = NULL; + } + } + } else { + hr = IDispatch_GetIDsOfNames(V_DISPATCH(&obj->v), &IID_NULL, &olename, 1, LOCALE_SYSTEM_DEFAULT, dispid); + } + efree(olename); + + if (SUCCEEDED(hr)) { + /* cache the mapping */ + if (!obj->id_of_name_cache) { + ALLOC_HASHTABLE(obj->id_of_name_cache); + zend_hash_init(obj->id_of_name_cache, 2, NULL, NULL, 0); + } + zend_hash_update(obj->id_of_name_cache, name, namelen, dispid, sizeof(*dispid), NULL); + } + + return hr; +} + +/* the core of COM */ +int php_com_do_invoke_byref(php_com_dotnet_object *obj, char *name, int namelen, + WORD flags, VARIANT *v, int nargs, zval ***args TSRMLS_DC) +{ + DISPID dispid, altdispid; + DISPPARAMS disp_params; + HRESULT hr; + VARIANT *vargs = NULL, *byref_vals = NULL; + int i, byref_count = 0, j; + zend_internal_function *f = (zend_internal_function*)EG(current_execute_data)->function_state.function; + + /* assumption: that the active function (f) is the function we generated for the engine */ + if (!f || f->arg_info == NULL) { + f = NULL; + } + + hr = php_com_get_id_of_name(obj, name, namelen, &dispid TSRMLS_CC); + + if (FAILED(hr)) { + char *winerr = NULL; + char *msg = NULL; + winerr = php_win32_error_to_msg(hr); + spprintf(&msg, 0, "Unable to lookup `%s': %s", name, winerr); + LocalFree(winerr); + php_com_throw_exception(hr, msg TSRMLS_CC); + efree(msg); + return FAILURE; + } + + + if (nargs) { + vargs = (VARIANT*)safe_emalloc(sizeof(VARIANT), nargs, 0); + } + + if (f) { + for (i = 0; i < nargs; i++) { + if (f->arg_info[nargs - i - 1].pass_by_reference) { + byref_count++; + } + } + } + + if (byref_count) { + byref_vals = (VARIANT*)safe_emalloc(sizeof(VARIANT), byref_count, 0); + for (j = 0, i = 0; i < nargs; i++) { + if (f->arg_info[nargs - i - 1].pass_by_reference) { + /* put the value into byref_vals instead */ + php_com_variant_from_zval(&byref_vals[j], *args[nargs - i - 1], obj->code_page TSRMLS_CC); + + /* if it is already byref, "move" it into the vargs array, otherwise + * make vargs a reference to this value */ + if (V_VT(&byref_vals[j]) & VT_BYREF) { + memcpy(&vargs[i], &byref_vals[j], sizeof(vargs[i])); + VariantInit(&byref_vals[j]); /* leave the variant slot empty to simplify cleanup */ + } else { + VariantInit(&vargs[i]); + V_VT(&vargs[i]) = V_VT(&byref_vals[j]) | VT_BYREF; + /* union magic ensures that this works out */ + vargs[i].byref = &V_UINT(&byref_vals[j]); + } + j++; + } else { + php_com_variant_from_zval(&vargs[i], *args[nargs - i - 1], obj->code_page TSRMLS_CC); + } + } + + } else { + /* Invoke'd args are in reverse order */ + for (i = 0; i < nargs; i++) { + php_com_variant_from_zval(&vargs[i], *args[nargs - i - 1], obj->code_page TSRMLS_CC); + } + } + + disp_params.cArgs = nargs; + disp_params.cNamedArgs = 0; + disp_params.rgvarg = vargs; + disp_params.rgdispidNamedArgs = NULL; + + if (flags & DISPATCH_PROPERTYPUT) { + altdispid = DISPID_PROPERTYPUT; + disp_params.rgdispidNamedArgs = &altdispid; + disp_params.cNamedArgs = 1; + } + + /* this will create an exception if needed */ + hr = php_com_invoke_helper(obj, dispid, flags, &disp_params, v, 0, 0 TSRMLS_CC); + + /* release variants */ + if (vargs) { + for (i = 0, j = 0; i < nargs; i++) { + /* if this was byref, update the zval */ + if (f && f->arg_info[nargs - i - 1].pass_by_reference) { + SEPARATE_ZVAL_IF_NOT_REF(args[nargs - i - 1]); + + /* if the variant is pointing at the byref_vals, we need to map + * the pointee value as a zval; otherwise, the value is pointing + * into an existing PHP variant record */ + if (V_VT(&vargs[i]) & VT_BYREF) { + if (vargs[i].byref == &V_UINT(&byref_vals[j])) { + /* copy that value */ + php_com_zval_from_variant(*args[nargs - i - 1], &byref_vals[j], + obj->code_page TSRMLS_CC); + } + } else { + /* not sure if this can ever happen; the variant we marked as BYREF + * is no longer BYREF - copy its value */ + php_com_zval_from_variant(*args[nargs - i - 1], &vargs[i], + obj->code_page TSRMLS_CC); + } + VariantClear(&byref_vals[j]); + j++; + } + VariantClear(&vargs[i]); + } + efree(vargs); + } + + return SUCCEEDED(hr) ? SUCCESS : FAILURE; +} + + + +int 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 TSRMLS_DC) +{ + DISPID altdispid; + DISPPARAMS disp_params; + HRESULT hr; + VARIANT *vargs = NULL; + int i; + + if (nargs) { + vargs = (VARIANT*)safe_emalloc(sizeof(VARIANT), nargs, 0); + } + + /* Invoke'd args are in reverse order */ + for (i = 0; i < nargs; i++) { + php_com_variant_from_zval(&vargs[i], args[nargs - i - 1], obj->code_page TSRMLS_CC); + } + + disp_params.cArgs = nargs; + disp_params.cNamedArgs = 0; + disp_params.rgvarg = vargs; + disp_params.rgdispidNamedArgs = NULL; + + if (flags & DISPATCH_PROPERTYPUT) { + altdispid = DISPID_PROPERTYPUT; + disp_params.rgdispidNamedArgs = &altdispid; + disp_params.cNamedArgs = 1; + } + + /* this will create an exception if needed */ + hr = php_com_invoke_helper(obj, dispid, flags, &disp_params, v, silent, allow_noarg TSRMLS_CC); + + /* release variants */ + if (vargs) { + for (i = 0; i < nargs; i++) { + VariantClear(&vargs[i]); + } + efree(vargs); + } + + /* a bit of a hack this, but it's needed for COM array access. */ + if (hr == DISP_E_BADPARAMCOUNT) + return hr; + + return SUCCEEDED(hr) ? SUCCESS : FAILURE; +} + +int php_com_do_invoke(php_com_dotnet_object *obj, char *name, int namelen, + WORD flags, VARIANT *v, int nargs, zval **args, int allow_noarg TSRMLS_DC) +{ + DISPID dispid; + HRESULT hr; + char *winerr = NULL; + char *msg = NULL; + + hr = php_com_get_id_of_name(obj, name, namelen, &dispid TSRMLS_CC); + + if (FAILED(hr)) { + winerr = php_win32_error_to_msg(hr); + spprintf(&msg, 0, "Unable to lookup `%s': %s", name, winerr); + LocalFree(winerr); + php_com_throw_exception(hr, msg TSRMLS_CC); + efree(msg); + return FAILURE; + } + + return php_com_do_invoke_by_id(obj, dispid, flags, v, nargs, args, 0, allow_noarg TSRMLS_CC); +} + +/* {{{ proto string com_create_guid() + Generate a globally unique identifier (GUID) */ +PHP_FUNCTION(com_create_guid) +{ + GUID retval; + OLECHAR *guid_string; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + php_com_initialize(TSRMLS_C); + if (CoCreateGuid(&retval) == S_OK && StringFromCLSID(&retval, &guid_string) == S_OK) { + Z_TYPE_P(return_value) = IS_STRING; + Z_STRVAL_P(return_value) = php_com_olestring_to_string(guid_string, &Z_STRLEN_P(return_value), CP_ACP TSRMLS_CC); + + CoTaskMemFree(guid_string); + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool com_event_sink(object comobject, object sinkobject [, mixed sinkinterface]) + Connect events from a COM object to a PHP object */ +PHP_FUNCTION(com_event_sink) +{ + zval *object, *sinkobject, *sink=NULL; + char *dispname = NULL, *typelibname = NULL; + zend_bool gotguid = 0; + php_com_dotnet_object *obj; + ITypeInfo *typeinfo = NULL; + + RETVAL_FALSE; + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Oo|z/", + &object, php_com_variant_class_entry, &sinkobject, &sink)) { + RETURN_FALSE; + } + + php_com_initialize(TSRMLS_C); + obj = CDNO_FETCH(object); + + if (sink && Z_TYPE_P(sink) == IS_ARRAY) { + /* 0 => typelibname, 1 => dispname */ + zval **tmp; + + if (zend_hash_index_find(Z_ARRVAL_P(sink), 0, (void**)&tmp) == SUCCESS) + typelibname = Z_STRVAL_PP(tmp); + if (zend_hash_index_find(Z_ARRVAL_P(sink), 1, (void**)&tmp) == SUCCESS) + dispname = Z_STRVAL_PP(tmp); + } else if (sink != NULL) { + convert_to_string(sink); + dispname = Z_STRVAL_P(sink); + } + + typeinfo = php_com_locate_typeinfo(typelibname, obj, dispname, 1 TSRMLS_CC); + + if (typeinfo) { + HashTable *id_to_name; + + ALLOC_HASHTABLE(id_to_name); + + if (php_com_process_typeinfo(typeinfo, id_to_name, 0, &obj->sink_id, obj->code_page TSRMLS_CC)) { + + /* Create the COM wrapper for this sink */ + obj->sink_dispatch = php_com_wrapper_export_as_sink(sinkobject, &obj->sink_id, id_to_name TSRMLS_CC); + + /* Now hook it up to the source */ + php_com_object_enable_event_sink(obj, TRUE TSRMLS_CC); + RETVAL_TRUE; + + } else { + FREE_HASHTABLE(id_to_name); + } + } + + if (typeinfo) { + ITypeInfo_Release(typeinfo); + } + +} +/* }}} */ + +/* {{{ proto bool com_print_typeinfo(object comobject | string typelib, string dispinterface, bool wantsink) + Print out a PHP class definition for a dispatchable interface */ +PHP_FUNCTION(com_print_typeinfo) +{ + zval *arg1; + char *ifacename = NULL; + char *typelibname = NULL; + int ifacelen; + zend_bool wantsink = 0; + php_com_dotnet_object *obj = NULL; + ITypeInfo *typeinfo; + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z/|s!b", &arg1, &ifacename, + &ifacelen, &wantsink)) { + RETURN_FALSE; + } + + php_com_initialize(TSRMLS_C); + if (Z_TYPE_P(arg1) == IS_OBJECT) { + CDNO_FETCH_VERIFY(obj, arg1); + } else { + convert_to_string(arg1); + typelibname = Z_STRVAL_P(arg1); + } + + typeinfo = php_com_locate_typeinfo(typelibname, obj, ifacename, wantsink ? 1 : 0 TSRMLS_CC); + if (typeinfo) { + php_com_process_typeinfo(typeinfo, NULL, 1, NULL, obj ? obj->code_page : COMG(code_page) TSRMLS_CC); + ITypeInfo_Release(typeinfo); + RETURN_TRUE; + } else { + zend_error(E_WARNING, "Unable to find typeinfo using the parameters supplied"); + } + RETURN_FALSE; +} +/* }}} */ + +/* {{{ proto bool com_message_pump([int timeoutms]) + Process COM messages, sleeping for up to timeoutms milliseconds */ +PHP_FUNCTION(com_message_pump) +{ + long timeoutms = 0; + MSG msg; + DWORD result; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|l", &timeoutms) == FAILURE) + RETURN_FALSE; + + php_com_initialize(TSRMLS_C); + result = MsgWaitForMultipleObjects(0, NULL, FALSE, timeoutms, QS_ALLINPUT); + + if (result == WAIT_OBJECT_0) { + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + /* we processed messages */ + RETVAL_TRUE; + } else { + /* we did not process messages (timed out) */ + RETVAL_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool com_load_typelib(string typelib_name [, int case_insensitive]) + Loads a Typelibrary and registers its constants */ +PHP_FUNCTION(com_load_typelib) +{ + char *name; + int namelen; + ITypeLib *pTL = NULL; + zend_bool cs = TRUE; + int codepage = COMG(code_page); + int cached = 0; + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|b", &name, &namelen, &cs)) { + return; + } + + RETVAL_FALSE; + + php_com_initialize(TSRMLS_C); + pTL = php_com_load_typelib_via_cache(name, codepage, &cached TSRMLS_CC); + if (pTL) { + if (cached) { + RETVAL_TRUE; + } else if (php_com_import_typelib(pTL, cs ? CONST_CS : 0, codepage TSRMLS_CC) == SUCCESS) { + RETVAL_TRUE; + } + + ITypeLib_Release(pTL); + pTL = NULL; + } +} +/* }}} */ + + + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/ext/com_dotnet/com_dotnet.c b/ext/com_dotnet/com_dotnet.c new file mode 100644 index 0000000..7fbdae1 --- /dev/null +++ b/ext/com_dotnet/com_dotnet.c @@ -0,0 +1,322 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2013 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong <wez@thebrainroom.com> | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" + +#if HAVE_MSCOREE_H +# include "php_ini.h" +# include "ext/standard/info.h" +# include "php_com_dotnet.h" +# include "php_com_dotnet_internal.h" +# include "Zend/zend_exceptions.h" +# include <mscoree.h> + +/* Since there is no official public mscorlib.h header file, and since + * generating your own version from the elusive binary .tlb file takes a lot of + * hacking and results in a 3MB header file (!), we opt for this slightly + * voodoo approach. The following is just enough definition to be able to + * reach the _AppDomain::CreateInstance method that we need to use to be able + * to fire up .Net objects. We used to use IDispatch for this, but it would + * not always work. + * + * The following info was obtained using OleView to export the IDL from + * mscorlib.tlb. Note that OleView is unable to generate C headers for this + * particular tlb... hence this mess. + */ + +const GUID IID_mscorlib_System_AppDomain = { +0x05F696DC, 0x2B29, 0x3663, {0xAD, 0x8B, 0xC4, 0x38, 0x9C, 0xF2, 0xA7, 0x13 }}; + +typedef struct _Imscorlib_System_AppDomain IAppDomain; + +struct _Imscorlib_System_AppDomainVtbl { + BEGIN_INTERFACE + + HRESULT ( STDMETHODCALLTYPE *QueryInterface )( + IAppDomain * This, + /* [in] */ REFIID riid, + /* [iid_is][out] */ void **ppvObject); + + ULONG ( STDMETHODCALLTYPE *AddRef )( + IAppDomain * This); + + ULONG ( STDMETHODCALLTYPE *Release )( + IAppDomain * This); + + /* this is padding to get CreateInstance into the correct position */ +#define DUMMY_METHOD(x) HRESULT ( STDMETHODCALLTYPE *dummy_##x )(IAppDomain *This) + + DUMMY_METHOD(GetTypeInfoCount); + DUMMY_METHOD(GetTypeInfo); + DUMMY_METHOD(GetIDsOfNames); + DUMMY_METHOD(Invoke); + DUMMY_METHOD(ToString); + DUMMY_METHOD(Equals); + DUMMY_METHOD(GetHashCode); + DUMMY_METHOD(GetType); + DUMMY_METHOD(InitializeLifetimeService); + DUMMY_METHOD(GetLifetimeService); + DUMMY_METHOD(Evidence); + DUMMY_METHOD(add_DomainUnload); + DUMMY_METHOD(remove_DomainUnload); + DUMMY_METHOD(add_AssemblyLoad); + DUMMY_METHOD(remove_AssemblyLoad); + DUMMY_METHOD(add_ProcessExit); + DUMMY_METHOD(remove_ProcessExit); + DUMMY_METHOD(add_TypeResolve); + DUMMY_METHOD(remove_TypeResolve); + DUMMY_METHOD(add_ResourceResolve); + DUMMY_METHOD(remove_ResourceResolve); + DUMMY_METHOD(add_AssemblyResolve); + DUMMY_METHOD(remove_AssemblyResolve); + DUMMY_METHOD(add_UnhandledException); + DUMMY_METHOD(remove_UnhandledException); + DUMMY_METHOD(DefineDynamicAssembly); + DUMMY_METHOD(DefineDynamicAssembly_2); + DUMMY_METHOD(DefineDynamicAssembly_3); + DUMMY_METHOD(DefineDynamicAssembly_4); + DUMMY_METHOD(DefineDynamicAssembly_5); + DUMMY_METHOD(DefineDynamicAssembly_6); + DUMMY_METHOD(DefineDynamicAssembly_7); + DUMMY_METHOD(DefineDynamicAssembly_8); + DUMMY_METHOD(DefineDynamicAssembly_9); + + HRESULT ( STDMETHODCALLTYPE *CreateInstance )(IAppDomain * This, BSTR AssemblyName, BSTR typeName, IUnknown **pRetVal); + HRESULT ( STDMETHODCALLTYPE *CreateInstanceFrom )(IAppDomain * This, BSTR AssemblyFile, BSTR typeName, IUnknown **pRetVal); + + /* more methods live here */ + + END_INTERFACE +}; + +struct _Imscorlib_System_AppDomain { + struct _Imscorlib_System_AppDomainVtbl *lpVtbl; +}; + + +struct dotnet_runtime_stuff { + ICorRuntimeHost *dotnet_host; + IAppDomain *dotnet_domain; + DISPID create_instance; +}; + +static HRESULT dotnet_init(char **p_where TSRMLS_DC) +{ + HRESULT hr; + struct dotnet_runtime_stuff *stuff; + IUnknown *unk = NULL; + char *where = ""; + + stuff = malloc(sizeof(*stuff)); + if (!stuff) { + return S_FALSE; + } + memset(stuff, 0, sizeof(*stuff)); + + where = "CoCreateInstance"; + hr = CoCreateInstance(&CLSID_CorRuntimeHost, NULL, CLSCTX_ALL, + &IID_ICorRuntimeHost, (LPVOID*)&stuff->dotnet_host); + + if (FAILED(hr)) + goto out; + + /* fire up the host and get the domain object */ + where = "ICorRuntimeHost_Start\n"; + hr = ICorRuntimeHost_Start(stuff->dotnet_host); + if (FAILED(hr)) + goto out; + + where = "ICorRuntimeHost_GetDefaultDomain"; + hr = ICorRuntimeHost_GetDefaultDomain(stuff->dotnet_host, &unk); + if (FAILED(hr)) + goto out; + + where = "QI: System._AppDomain"; + hr = IUnknown_QueryInterface(unk, &IID_mscorlib_System_AppDomain, (LPVOID*)&stuff->dotnet_domain); + if (FAILED(hr)) + goto out; + + COMG(dotnet_runtime_stuff) = stuff; + +out: + if (unk) { + IUnknown_Release(unk); + } + if (COMG(dotnet_runtime_stuff) == NULL) { + /* clean up */ + if (stuff->dotnet_domain) { + IUnknown_Release(stuff->dotnet_domain); + } + if (stuff->dotnet_host) { + ICorRuntimeHost_Stop(stuff->dotnet_host); + ICorRuntimeHost_Release(stuff->dotnet_host); + } + free(stuff); + + *p_where = where; + + return hr; + } + + return S_OK; +} + +/* {{{ com_dotnet_create_instance - ctor for DOTNET class */ +PHP_FUNCTION(com_dotnet_create_instance) +{ + zval *object = getThis(); + php_com_dotnet_object *obj; + char *assembly_name, *datatype_name; + int assembly_name_len, datatype_name_len; + struct dotnet_runtime_stuff *stuff; + OLECHAR *oleassembly, *oletype; + BSTR oleassembly_sys, oletype_sys; + HRESULT hr; + int ret = FAILURE; + char *where = ""; + IUnknown *unk = NULL; + + php_com_initialize(TSRMLS_C); + if (COMG(dotnet_runtime_stuff) == NULL) { + hr = dotnet_init(&where TSRMLS_CC); + if (FAILED(hr)) { + char buf[1024]; + char *err = php_win32_error_to_msg(hr); + snprintf(buf, sizeof(buf), "Failed to init .Net runtime [%s] %s", where, err); + if (err) + LocalFree(err); + php_com_throw_exception(hr, buf TSRMLS_CC); + ZVAL_NULL(object); + return; + } + } + + stuff = (struct dotnet_runtime_stuff*)COMG(dotnet_runtime_stuff); + + obj = CDNO_FETCH(object); + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss|l", + &assembly_name, &assembly_name_len, + &datatype_name, &datatype_name_len, + &obj->code_page)) { + php_com_throw_exception(E_INVALIDARG, "Could not create .Net object - invalid arguments!" TSRMLS_CC); + ZVAL_NULL(object); + return; + } + + oletype = php_com_string_to_olestring(datatype_name, datatype_name_len, obj->code_page TSRMLS_CC); + oleassembly = php_com_string_to_olestring(assembly_name, assembly_name_len, obj->code_page TSRMLS_CC); + oletype_sys = SysAllocString(oletype); + oleassembly_sys = SysAllocString(oleassembly); + where = "CreateInstance"; + hr = stuff->dotnet_domain->lpVtbl->CreateInstance(stuff->dotnet_domain, oleassembly_sys, oletype_sys, &unk); + efree(oletype); + efree(oleassembly); + SysFreeString(oletype_sys); + SysFreeString(oleassembly_sys); + + if (SUCCEEDED(hr)) { + VARIANT unwrapped; + IObjectHandle *handle = NULL; + + where = "QI: IObjectHandle"; + hr = IUnknown_QueryInterface(unk, &IID_IObjectHandle, &handle); + + if (SUCCEEDED(hr)) { + where = "IObjectHandle_Unwrap"; + hr = IObjectHandle_Unwrap(handle, &unwrapped); + if (SUCCEEDED(hr)) { + + if (V_VT(&unwrapped) == VT_UNKNOWN) { + where = "Unwrapped, QI for IDispatch"; + hr = IUnknown_QueryInterface(V_UNKNOWN(&unwrapped), &IID_IDispatch, &V_DISPATCH(&obj->v)); + + if (SUCCEEDED(hr)) { + V_VT(&obj->v) = VT_DISPATCH; + + /* get its type-info */ + IDispatch_GetTypeInfo(V_DISPATCH(&obj->v), 0, LANG_NEUTRAL, &obj->typeinfo); + ret = SUCCESS; + } + } else if (V_VT(&unwrapped) == VT_DISPATCH) { + /* unwrapped is now the dispatch pointer we want */ + V_DISPATCH(&obj->v) = V_DISPATCH(&unwrapped); + V_VT(&obj->v) = VT_DISPATCH; + + /* get its type-info */ + IDispatch_GetTypeInfo(V_DISPATCH(&obj->v), 0, LANG_NEUTRAL, &obj->typeinfo); + + ret = SUCCESS; + } else { + /* shouldn't happen, but let's be ready for it */ + VariantClear(&unwrapped); + hr = E_INVALIDARG; + } + } + IObjectHandle_Release(handle); + } + IUnknown_Release(unk); + } + + if (ret == FAILURE) { + char buf[1024]; + char *err = php_win32_error_to_msg(hr); + snprintf(buf, sizeof(buf), "Failed to instantiate .Net object [%s] [0x%08x] %s", where, hr, err); + if (err && err[0]) { + LocalFree(err); + } + php_com_throw_exception(hr, buf TSRMLS_CC); + ZVAL_NULL(object); + return; + } +} +/* }}} */ + +void php_com_dotnet_mshutdown(TSRMLS_D) +{ + struct dotnet_runtime_stuff *stuff = COMG(dotnet_runtime_stuff); + + if (stuff->dotnet_domain) { + IDispatch_Release(stuff->dotnet_domain); + } + if (stuff->dotnet_host) { + ICorRuntimeHost_Stop(stuff->dotnet_host); + ICorRuntimeHost_Release(stuff->dotnet_host); + stuff->dotnet_host = NULL; + } + free(stuff); + COMG(dotnet_runtime_stuff) = NULL; +} + +void php_com_dotnet_rshutdown(TSRMLS_D) +{ + struct dotnet_runtime_stuff *stuff = COMG(dotnet_runtime_stuff); + + if (stuff->dotnet_domain) { + IDispatch_Release(stuff->dotnet_domain); + stuff->dotnet_domain = NULL; + } +} + +#endif /* HAVE_MSCOREE_H */ diff --git a/ext/com_dotnet/com_extension.c b/ext/com_dotnet/com_extension.c new file mode 100644 index 0000000..1257974 --- /dev/null +++ b/ext/com_dotnet/com_extension.c @@ -0,0 +1,520 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2013 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong <wez@thebrainroom.com> | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "php_com_dotnet.h" +#include "php_com_dotnet_internal.h" +#include "Zend/zend_exceptions.h" + +ZEND_DECLARE_MODULE_GLOBALS(com_dotnet) +static PHP_GINIT_FUNCTION(com_dotnet); + +TsHashTable php_com_typelibraries; + +zend_class_entry + *php_com_variant_class_entry, + *php_com_exception_class_entry, + *php_com_saproxy_class_entry; + +/* {{{ arginfo */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_variant_set, 0, 0, 2) + ZEND_ARG_INFO(0, variant) + ZEND_ARG_INFO(0, value) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_variant_add, 0, 0, 2) + ZEND_ARG_INFO(0, left) + ZEND_ARG_INFO(0, right) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_variant_cat, 0, 0, 2) + ZEND_ARG_INFO(0, left) + ZEND_ARG_INFO(0, right) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_variant_sub, 0, 0, 2) + ZEND_ARG_INFO(0, left) + ZEND_ARG_INFO(0, right) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_variant_mul, 0, 0, 2) + ZEND_ARG_INFO(0, left) + ZEND_ARG_INFO(0, right) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_variant_and, 0, 0, 2) + ZEND_ARG_INFO(0, left) + ZEND_ARG_INFO(0, right) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_variant_div, 0, 0, 2) + ZEND_ARG_INFO(0, left) + ZEND_ARG_INFO(0, right) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_variant_eqv, 0, 0, 2) + ZEND_ARG_INFO(0, left) + ZEND_ARG_INFO(0, right) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_variant_idiv, 0, 0, 2) + ZEND_ARG_INFO(0, left) + ZEND_ARG_INFO(0, right) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_variant_imp, 0, 0, 2) + ZEND_ARG_INFO(0, left) + ZEND_ARG_INFO(0, right) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_variant_mod, 0, 0, 2) + ZEND_ARG_INFO(0, left) + ZEND_ARG_INFO(0, right) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_variant_or, 0, 0, 2) + ZEND_ARG_INFO(0, left) + ZEND_ARG_INFO(0, right) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_variant_pow, 0, 0, 2) + ZEND_ARG_INFO(0, left) + ZEND_ARG_INFO(0, right) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_variant_xor, 0, 0, 2) + ZEND_ARG_INFO(0, left) + ZEND_ARG_INFO(0, right) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_variant_abs, 0, 0, 1) + ZEND_ARG_INFO(0, left) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_variant_fix, 0, 0, 1) + ZEND_ARG_INFO(0, left) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_variant_int, 0, 0, 1) + ZEND_ARG_INFO(0, left) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_variant_neg, 0, 0, 1) + ZEND_ARG_INFO(0, left) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_variant_not, 0, 0, 1) + ZEND_ARG_INFO(0, left) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_variant_round, 0, 0, 2) + ZEND_ARG_INFO(0, left) + ZEND_ARG_INFO(0, decimals) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_variant_cmp, 0, 0, 2) + ZEND_ARG_INFO(0, left) + ZEND_ARG_INFO(0, right) + ZEND_ARG_INFO(0, lcid) + ZEND_ARG_INFO(0, flags) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_variant_date_to_timestamp, 0, 0, 1) + ZEND_ARG_INFO(0, variant) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_variant_date_from_timestamp, 0, 0, 1) + ZEND_ARG_INFO(0, timestamp) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_variant_get_type, 0, 0, 1) + ZEND_ARG_INFO(0, variant) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_variant_set_type, 0, 0, 2) + ZEND_ARG_INFO(0, variant) + ZEND_ARG_INFO(0, type) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_variant_cast, 0, 0, 2) + ZEND_ARG_INFO(0, variant) + ZEND_ARG_INFO(0, type) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_com_get_active_object, 0, 0, 1) + ZEND_ARG_INFO(0, progid) + ZEND_ARG_INFO(0, code_page) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_com_create_guid, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_com_event_sink, 0, 0, 2) + ZEND_ARG_INFO(0, comobject) + ZEND_ARG_INFO(0, sinkobject) + ZEND_ARG_INFO(0, sinkinterface) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_com_print_typeinfo, 0, 0, 1) + ZEND_ARG_INFO(0, comobject) + ZEND_ARG_INFO(0, dispinterface) + ZEND_ARG_INFO(0, wantsink) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_com_message_pump, 0, 0, 0) + ZEND_ARG_INFO(0, timeoutms) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_com_load_typelib, 0, 0, 1) + ZEND_ARG_INFO(0, typelib_name) + ZEND_ARG_INFO(0, case_insensitive) +ZEND_END_ARG_INFO() +/* }}} */ + +const zend_function_entry com_dotnet_functions[] = { + PHP_FE(variant_set, arginfo_variant_set) + PHP_FE(variant_add, arginfo_variant_add) + PHP_FE(variant_cat, arginfo_variant_cat) + PHP_FE(variant_sub, arginfo_variant_sub) + PHP_FE(variant_mul, arginfo_variant_mul) + PHP_FE(variant_and, arginfo_variant_and) + PHP_FE(variant_div, arginfo_variant_div) + PHP_FE(variant_eqv, arginfo_variant_eqv) + PHP_FE(variant_idiv, arginfo_variant_idiv) + PHP_FE(variant_imp, arginfo_variant_imp) + PHP_FE(variant_mod, arginfo_variant_mod) + PHP_FE(variant_or, arginfo_variant_or) + PHP_FE(variant_pow, arginfo_variant_pow) + PHP_FE(variant_xor, arginfo_variant_xor) + PHP_FE(variant_abs, arginfo_variant_abs) + PHP_FE(variant_fix, arginfo_variant_fix) + PHP_FE(variant_int, arginfo_variant_int) + PHP_FE(variant_neg, arginfo_variant_neg) + PHP_FE(variant_not, arginfo_variant_not) + PHP_FE(variant_round, arginfo_variant_round) + PHP_FE(variant_cmp, arginfo_variant_cmp) + PHP_FE(variant_date_to_timestamp, arginfo_variant_date_to_timestamp) + PHP_FE(variant_date_from_timestamp, arginfo_variant_date_from_timestamp) + PHP_FE(variant_get_type, arginfo_variant_get_type) + PHP_FE(variant_set_type, arginfo_variant_set_type) + PHP_FE(variant_cast, arginfo_variant_cast) + /* com_com.c */ + PHP_FE(com_create_guid, arginfo_com_create_guid) + PHP_FE(com_event_sink, arginfo_com_event_sink) + PHP_FE(com_print_typeinfo, arginfo_com_print_typeinfo) + PHP_FE(com_message_pump, arginfo_com_message_pump) + PHP_FE(com_load_typelib, arginfo_com_load_typelib) + PHP_FE(com_get_active_object, arginfo_com_get_active_object) + PHP_FE_END +}; + +/* {{{ com_dotnet_module_entry + */ +zend_module_entry com_dotnet_module_entry = { + STANDARD_MODULE_HEADER, + "com_dotnet", + com_dotnet_functions, + PHP_MINIT(com_dotnet), + PHP_MSHUTDOWN(com_dotnet), + PHP_RINIT(com_dotnet), + PHP_RSHUTDOWN(com_dotnet), + PHP_MINFO(com_dotnet), + "0.1", + PHP_MODULE_GLOBALS(com_dotnet), + PHP_GINIT(com_dotnet), + NULL, + NULL, + STANDARD_MODULE_PROPERTIES_EX +}; +/* }}} */ + +#ifdef COMPILE_DL_COM_DOTNET +ZEND_GET_MODULE(com_dotnet) +#endif + +/* {{{ PHP_INI + */ + +/* com.typelib_file is the path to a file containing a + * list of typelibraries to register *persistently*. + * lines starting with ; are comments + * append #cis to end of typelib name to cause its constants + * to be loaded case insensitively */ +static PHP_INI_MH(OnTypeLibFileUpdate) +{ + FILE *typelib_file; + char *typelib_name_buffer; + char *strtok_buf = NULL; + int cached; + + if (!new_value || !new_value[0] || (typelib_file = VCWD_FOPEN(new_value, "r"))==NULL) { + return FAILURE; + } + + typelib_name_buffer = (char *) emalloc(sizeof(char)*1024); + + while (fgets(typelib_name_buffer, 1024, typelib_file)) { + ITypeLib *pTL; + char *typelib_name; + char *modifier, *ptr; + int mode = CONST_CS | CONST_PERSISTENT; /* CONST_PERSISTENT is ok here */ + + if (typelib_name_buffer[0]==';') { + continue; + } + typelib_name = php_strtok_r(typelib_name_buffer, "\r\n", &strtok_buf); /* get rid of newlines */ + if (typelib_name == NULL) { + continue; + } + typelib_name = php_strtok_r(typelib_name, "#", &strtok_buf); + modifier = php_strtok_r(NULL, "#", &strtok_buf); + if (modifier != NULL) { + if (!strcmp(modifier, "cis") || !strcmp(modifier, "case_insensitive")) { + mode &= ~CONST_CS; + } + } + + /* Remove leading/training white spaces on search_string */ + while (isspace(*typelib_name)) {/* Ends on '\0' in worst case */ + typelib_name ++; + } + ptr = typelib_name + strlen(typelib_name) - 1; + while ((ptr != typelib_name) && isspace(*ptr)) { + *ptr = '\0'; + ptr--; + } + + if ((pTL = php_com_load_typelib_via_cache(typelib_name, COMG(code_page), &cached TSRMLS_CC)) != NULL) { + if (!cached) { + php_com_import_typelib(pTL, mode, COMG(code_page) TSRMLS_CC); + } + ITypeLib_Release(pTL); + } + } + + efree(typelib_name_buffer); + fclose(typelib_file); + + return SUCCESS; +} + +PHP_INI_BEGIN() + STD_PHP_INI_ENTRY("com.allow_dcom", "0", PHP_INI_SYSTEM, OnUpdateBool, allow_dcom, zend_com_dotnet_globals, com_dotnet_globals) + STD_PHP_INI_ENTRY("com.autoregister_verbose", "0", PHP_INI_ALL, OnUpdateBool, autoreg_verbose, zend_com_dotnet_globals, com_dotnet_globals) + STD_PHP_INI_ENTRY("com.autoregister_typelib", "0", PHP_INI_ALL, OnUpdateBool, autoreg_on, zend_com_dotnet_globals, com_dotnet_globals) + STD_PHP_INI_ENTRY("com.autoregister_casesensitive", "1", PHP_INI_ALL, OnUpdateBool, autoreg_case_sensitive, zend_com_dotnet_globals, com_dotnet_globals) + STD_PHP_INI_ENTRY("com.code_page", "", PHP_INI_ALL, OnUpdateLong, code_page, zend_com_dotnet_globals, com_dotnet_globals) + PHP_INI_ENTRY("com.typelib_file", "", PHP_INI_SYSTEM, OnTypeLibFileUpdate) +PHP_INI_END() +/* }}} */ + +/* {{{ PHP_GINIT_FUNCTION + */ +static PHP_GINIT_FUNCTION(com_dotnet) +{ + memset(com_dotnet_globals, 0, sizeof(*com_dotnet_globals)); + com_dotnet_globals->code_page = CP_ACP; +} +/* }}} */ + +/* {{{ PHP_MINIT_FUNCTION + */ +PHP_MINIT_FUNCTION(com_dotnet) +{ + zend_class_entry ce, *tmp; + + php_com_wrapper_minit(INIT_FUNC_ARGS_PASSTHRU); + php_com_persist_minit(INIT_FUNC_ARGS_PASSTHRU); + + INIT_CLASS_ENTRY(ce, "com_exception", NULL); + php_com_exception_class_entry = zend_register_internal_class_ex(&ce, zend_exception_get_default(TSRMLS_C), NULL TSRMLS_CC); + php_com_exception_class_entry->ce_flags |= ZEND_ACC_FINAL; +/* php_com_exception_class_entry->constructor->common.fn_flags |= ZEND_ACC_PROTECTED; */ + + INIT_CLASS_ENTRY(ce, "com_safearray_proxy", NULL); + php_com_saproxy_class_entry = zend_register_internal_class(&ce TSRMLS_CC); + php_com_saproxy_class_entry->ce_flags |= ZEND_ACC_FINAL; +/* php_com_saproxy_class_entry->constructor->common.fn_flags |= ZEND_ACC_PROTECTED; */ + php_com_saproxy_class_entry->get_iterator = php_com_saproxy_iter_get; + + INIT_CLASS_ENTRY(ce, "variant", NULL); + ce.create_object = php_com_object_new; + php_com_variant_class_entry = zend_register_internal_class(&ce TSRMLS_CC); + php_com_variant_class_entry->get_iterator = php_com_iter_get; + + INIT_CLASS_ENTRY(ce, "com", NULL); + ce.create_object = php_com_object_new; + tmp = zend_register_internal_class_ex(&ce, php_com_variant_class_entry, "variant" TSRMLS_CC); + tmp->get_iterator = php_com_iter_get; + + zend_ts_hash_init(&php_com_typelibraries, 0, NULL, php_com_typelibrary_dtor, 1); + +#if HAVE_MSCOREE_H + INIT_CLASS_ENTRY(ce, "dotnet", NULL); + ce.create_object = php_com_object_new; + tmp = zend_register_internal_class_ex(&ce, php_com_variant_class_entry, "variant" TSRMLS_CC); + tmp->get_iterator = php_com_iter_get; +#endif + + REGISTER_INI_ENTRIES(); + +#define COM_CONST(x) REGISTER_LONG_CONSTANT(#x, x, CONST_CS|CONST_PERSISTENT) + + COM_CONST(CLSCTX_INPROC_SERVER); + COM_CONST(CLSCTX_INPROC_HANDLER); + COM_CONST(CLSCTX_LOCAL_SERVER); + COM_CONST(CLSCTX_REMOTE_SERVER); + COM_CONST(CLSCTX_SERVER); + COM_CONST(CLSCTX_ALL); + +#if 0 + COM_CONST(DISPATCH_METHOD); + COM_CONST(DISPATCH_PROPERTYGET); + COM_CONST(DISPATCH_PROPERTYPUT); +#endif + + COM_CONST(VT_NULL); + COM_CONST(VT_EMPTY); + COM_CONST(VT_UI1); + COM_CONST(VT_I1); + COM_CONST(VT_UI2); + COM_CONST(VT_I2); + COM_CONST(VT_UI4); + COM_CONST(VT_I4); + COM_CONST(VT_R4); + COM_CONST(VT_R8); + COM_CONST(VT_BOOL); + COM_CONST(VT_ERROR); + COM_CONST(VT_CY); + COM_CONST(VT_DATE); + COM_CONST(VT_BSTR); + COM_CONST(VT_DECIMAL); + COM_CONST(VT_UNKNOWN); + COM_CONST(VT_DISPATCH); + COM_CONST(VT_VARIANT); + COM_CONST(VT_INT); + COM_CONST(VT_UINT); + COM_CONST(VT_ARRAY); + COM_CONST(VT_BYREF); + + COM_CONST(CP_ACP); + COM_CONST(CP_MACCP); + COM_CONST(CP_OEMCP); + COM_CONST(CP_UTF7); + COM_CONST(CP_UTF8); + COM_CONST(CP_SYMBOL); + COM_CONST(CP_THREAD_ACP); + + COM_CONST(VARCMP_LT); + COM_CONST(VARCMP_EQ); + COM_CONST(VARCMP_GT); + COM_CONST(VARCMP_NULL); + + COM_CONST(NORM_IGNORECASE); + COM_CONST(NORM_IGNORENONSPACE); + COM_CONST(NORM_IGNORESYMBOLS); + COM_CONST(NORM_IGNOREWIDTH); + COM_CONST(NORM_IGNOREKANATYPE); +#ifdef NORM_IGNOREKASHIDA + COM_CONST(NORM_IGNOREKASHIDA); +#endif + COM_CONST(DISP_E_DIVBYZERO); + COM_CONST(DISP_E_OVERFLOW); + COM_CONST(DISP_E_BADINDEX); + COM_CONST(MK_E_UNAVAILABLE); + + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MSHUTDOWN_FUNCTION + */ +PHP_MSHUTDOWN_FUNCTION(com_dotnet) +{ + UNREGISTER_INI_ENTRIES(); +#if HAVE_MSCOREE_H + if (COMG(dotnet_runtime_stuff)) { + php_com_dotnet_mshutdown(TSRMLS_C); + } +#endif + + zend_ts_hash_destroy(&php_com_typelibraries); + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_RINIT_FUNCTION + */ +PHP_RINIT_FUNCTION(com_dotnet) +{ + COMG(rshutdown_started) = 0; + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_RSHUTDOWN_FUNCTION + */ +PHP_RSHUTDOWN_FUNCTION(com_dotnet) +{ +#if HAVE_MSCOREE_H + if (COMG(dotnet_runtime_stuff)) { + php_com_dotnet_rshutdown(TSRMLS_C); + } +#endif + COMG(rshutdown_started) = 1; + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MINFO_FUNCTION + */ +PHP_MINFO_FUNCTION(com_dotnet) +{ + php_info_print_table_start(); + + php_info_print_table_header(2, "COM support", "enabled"); + php_info_print_table_header(2, "DCOM support", COMG(allow_dcom) ? "enabled" : "disabled"); + +#if HAVE_MSCOREE_H + php_info_print_table_header(2, ".Net support", "enabled"); +#else + php_info_print_table_header(2, ".Net support", "not present in this build"); +#endif + + php_info_print_table_end(); + + DISPLAY_INI_ENTRIES(); +} +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/ext/com_dotnet/com_handlers.c b/ext/com_dotnet/com_handlers.c new file mode 100644 index 0000000..4bdbf52 --- /dev/null +++ b/ext/com_dotnet/com_handlers.c @@ -0,0 +1,683 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2013 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong <wez@thebrainroom.com> | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "php_com_dotnet.h" +#include "php_com_dotnet_internal.h" +#include "Zend/zend_exceptions.h" + +static zval *com_property_read(zval *object, zval *member, int type, const zend_literal *key TSRMLS_DC) +{ + zval *return_value; + php_com_dotnet_object *obj; + VARIANT v; + HRESULT res; + + MAKE_STD_ZVAL(return_value); + ZVAL_NULL(return_value); + Z_SET_REFCOUNT_P(return_value, 0); + Z_UNSET_ISREF_P(return_value); + + obj = CDNO_FETCH(object); + + if (V_VT(&obj->v) == VT_DISPATCH) { + VariantInit(&v); + + convert_to_string_ex(&member); + + res = php_com_do_invoke(obj, Z_STRVAL_P(member), Z_STRLEN_P(member), + DISPATCH_METHOD|DISPATCH_PROPERTYGET, &v, 0, NULL, 1 TSRMLS_CC); + + if (res == SUCCESS) { + php_com_zval_from_variant(return_value, &v, obj->code_page TSRMLS_CC); + VariantClear(&v); + } else if (res == DISP_E_BADPARAMCOUNT) { + php_com_saproxy_create(object, return_value, member TSRMLS_CC); + } + } else { + php_com_throw_exception(E_INVALIDARG, "this variant has no properties" TSRMLS_CC); + } + + return return_value; +} + +static void com_property_write(zval *object, zval *member, zval *value, const zend_literal *key TSRMLS_DC) +{ + php_com_dotnet_object *obj; + VARIANT v; + + obj = CDNO_FETCH(object); + + if (V_VT(&obj->v) == VT_DISPATCH) { + VariantInit(&v); + + convert_to_string_ex(&member); + if (SUCCESS == php_com_do_invoke(obj, Z_STRVAL_P(member), Z_STRLEN_P(member), + DISPATCH_PROPERTYPUT|DISPATCH_PROPERTYPUTREF, &v, 1, &value, 0 TSRMLS_CC)) { + VariantClear(&v); + } + } else { + php_com_throw_exception(E_INVALIDARG, "this variant has no properties" TSRMLS_CC); + } +} + +static zval *com_read_dimension(zval *object, zval *offset, int type TSRMLS_DC) +{ + zval *return_value; + php_com_dotnet_object *obj; + VARIANT v; + + MAKE_STD_ZVAL(return_value); + ZVAL_NULL(return_value); + Z_SET_REFCOUNT_P(return_value, 0); + Z_UNSET_ISREF_P(return_value); + + obj = CDNO_FETCH(object); + + if (V_VT(&obj->v) == VT_DISPATCH) { + VariantInit(&v); + + if (SUCCESS == php_com_do_invoke_by_id(obj, DISPID_VALUE, + DISPATCH_METHOD|DISPATCH_PROPERTYGET, &v, 1, &offset, 0, 0 TSRMLS_CC)) { + php_com_zval_from_variant(return_value, &v, obj->code_page TSRMLS_CC); + VariantClear(&v); + } + } else if (V_ISARRAY(&obj->v)) { + convert_to_long(offset); + + if (SafeArrayGetDim(V_ARRAY(&obj->v)) == 1) { + if (php_com_safearray_get_elem(&obj->v, &v, Z_LVAL_P(offset) TSRMLS_CC)) { + php_com_wrap_variant(return_value, &v, obj->code_page TSRMLS_CC); + VariantClear(&v); + } + } else { + php_com_saproxy_create(object, return_value, offset TSRMLS_CC); + } + + } else { + php_com_throw_exception(E_INVALIDARG, "this variant is not an array type" TSRMLS_CC); + } + + return return_value; +} + +static void com_write_dimension(zval *object, zval *offset, zval *value TSRMLS_DC) +{ + php_com_dotnet_object *obj; + zval *args[2]; + VARIANT v; + HRESULT res; + + obj = CDNO_FETCH(object); + + if (V_VT(&obj->v) == VT_DISPATCH) { + args[0] = offset; + args[1] = value; + + VariantInit(&v); + + if (SUCCESS == php_com_do_invoke_by_id(obj, DISPID_VALUE, + DISPATCH_METHOD|DISPATCH_PROPERTYPUT, &v, 2, args, 0, 0 TSRMLS_CC)) { + VariantClear(&v); + } + } else if (V_ISARRAY(&obj->v)) { + LONG indices = 0; + VARTYPE vt; + + if (SafeArrayGetDim(V_ARRAY(&obj->v)) == 1) { + if (FAILED(SafeArrayGetVartype(V_ARRAY(&obj->v), &vt)) || vt == VT_EMPTY) { + vt = V_VT(&obj->v) & ~VT_ARRAY; + } + + convert_to_long(offset); + indices = Z_LVAL_P(offset); + + VariantInit(&v); + php_com_variant_from_zval(&v, value, obj->code_page TSRMLS_CC); + + if (V_VT(&v) != vt) { + VariantChangeType(&v, &v, 0, vt); + } + + if (vt == VT_VARIANT) { + res = SafeArrayPutElement(V_ARRAY(&obj->v), &indices, &v); + } else { + res = SafeArrayPutElement(V_ARRAY(&obj->v), &indices, &v.lVal); + } + + VariantClear(&v); + + if (FAILED(res)) { + php_com_throw_exception(res, NULL TSRMLS_CC); + } + + } else { + 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); + } + + } else { + php_com_throw_exception(E_INVALIDARG, "this variant is not an array type" TSRMLS_CC); + } +} + +#if 0 +static void com_object_set(zval **property, zval *value TSRMLS_DC) +{ + /* Not yet implemented in the engine */ +} + +static zval *com_object_get(zval *property TSRMLS_DC) +{ + /* Not yet implemented in the engine */ + return NULL; +} +#endif + +static int com_property_exists(zval *object, zval *member, int check_empty, const zend_literal *key TSRMLS_DC) +{ + DISPID dispid; + php_com_dotnet_object *obj; + + obj = CDNO_FETCH(object); + + if (V_VT(&obj->v) == VT_DISPATCH) { + convert_to_string_ex(&member); + if (SUCCEEDED(php_com_get_id_of_name(obj, Z_STRVAL_P(member), Z_STRLEN_P(member), &dispid TSRMLS_CC))) { + /* TODO: distinguish between property and method! */ + return 1; + } + } else { + /* TODO: check for safearray */ + } + + return 0; +} + +static int com_dimension_exists(zval *object, zval *member, int check_empty TSRMLS_DC) +{ + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Operation not yet supported on a COM object"); + return 0; +} + +static void com_property_delete(zval *object, zval *member, const zend_literal *key TSRMLS_DC) +{ + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot delete properties from a COM object"); +} + +static void com_dimension_delete(zval *object, zval *offset TSRMLS_DC) +{ + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot delete properties from a COM object"); +} + +static HashTable *com_properties_get(zval *object TSRMLS_DC) +{ + /* TODO: use type-info to get all the names and values ? + * DANGER: if we do that, there is a strong possibility for + * infinite recursion when the hash is displayed via var_dump(). + * Perhaps it is best to leave it un-implemented. + */ + return NULL; +} + +static void function_dtor(void *pDest) +{ + zend_internal_function *f = (zend_internal_function*)pDest; + + efree((char*)f->function_name); + if (f->arg_info) { + efree(f->arg_info); + } +} + +static PHP_FUNCTION(com_method_handler) +{ + Z_OBJ_HANDLER_P(getThis(), call_method)( + ((zend_internal_function*)EG(current_execute_data)->function_state.function)->function_name, + INTERNAL_FUNCTION_PARAM_PASSTHRU); +} + +static union _zend_function *com_method_get(zval **object_ptr, char *name, int len, const zend_literal *key TSRMLS_DC) +{ + zend_internal_function f, *fptr = NULL; + php_com_dotnet_object *obj; + union _zend_function *func; + DISPID dummy; + zval *object = *object_ptr; + + obj = CDNO_FETCH(object); + + if (V_VT(&obj->v) != VT_DISPATCH) { + return NULL; + } + + if (FAILED(php_com_get_id_of_name(obj, name, len, &dummy TSRMLS_CC))) { + return NULL; + } + + /* check cache */ + if (obj->method_cache == NULL || FAILURE == zend_hash_find(obj->method_cache, name, len, (void**)&fptr)) { + f.type = ZEND_OVERLOADED_FUNCTION; + f.num_args = 0; + f.arg_info = NULL; + f.scope = obj->ce; + f.fn_flags = ZEND_ACC_CALL_VIA_HANDLER; + f.function_name = estrndup(name, len); + f.handler = PHP_FN(com_method_handler); + + fptr = &f; + + if (obj->typeinfo) { + /* look for byref params */ + ITypeComp *comp; + ITypeInfo *TI = NULL; + DESCKIND kind; + BINDPTR bindptr; + OLECHAR *olename; + ULONG lhash; + int i; + + if (SUCCEEDED(ITypeInfo_GetTypeComp(obj->typeinfo, &comp))) { + olename = php_com_string_to_olestring(name, len, obj->code_page TSRMLS_CC); + lhash = LHashValOfNameSys(SYS_WIN32, LOCALE_SYSTEM_DEFAULT, olename); + + if (SUCCEEDED(ITypeComp_Bind(comp, olename, lhash, INVOKE_FUNC, &TI, &kind, &bindptr))) { + switch (kind) { + case DESCKIND_FUNCDESC: + f.arg_info = ecalloc(bindptr.lpfuncdesc->cParams, sizeof(zend_arg_info)); + + for (i = 0; i < bindptr.lpfuncdesc->cParams; i++) { + f.arg_info[i].allow_null = 1; + if (bindptr.lpfuncdesc->lprgelemdescParam[i].paramdesc.wParamFlags & PARAMFLAG_FOUT) { + f.arg_info[i].pass_by_reference = 1; + } + } + + f.num_args = bindptr.lpfuncdesc->cParams; + + ITypeInfo_ReleaseFuncDesc(TI, bindptr.lpfuncdesc); + break; + + /* these should not happen, but *might* happen if the user + * screws up; lets avoid a leak in that case */ + case DESCKIND_VARDESC: + ITypeInfo_ReleaseVarDesc(TI, bindptr.lpvardesc); + break; + case DESCKIND_TYPECOMP: + ITypeComp_Release(bindptr.lptcomp); + break; + + case DESCKIND_NONE: + break; + } + if (TI) { + ITypeInfo_Release(TI); + } + } + ITypeComp_Release(comp); + efree(olename); + } + } + + if (fptr) { + /* save this method in the cache */ + if (!obj->method_cache) { + ALLOC_HASHTABLE(obj->method_cache); + zend_hash_init(obj->method_cache, 2, NULL, function_dtor, 0); + } + + zend_hash_update(obj->method_cache, name, len, &f, sizeof(f), (void**)&fptr); + } + } + + if (fptr) { + /* duplicate this into a new chunk of emalloc'd memory, + * since the engine will efree it */ + func = emalloc(sizeof(*fptr)); + memcpy(func, fptr, sizeof(*fptr)); + + return func; + } + + return NULL; +} + +static int com_call_method(const char *method, INTERNAL_FUNCTION_PARAMETERS) +{ + zval ***args = NULL; + php_com_dotnet_object *obj; + int nargs; + VARIANT v; + int ret = FAILURE; + + obj = CDNO_FETCH(getThis()); + + if (V_VT(&obj->v) != VT_DISPATCH) { + return FAILURE; + } + + nargs = ZEND_NUM_ARGS(); + + if (nargs) { + args = (zval ***)safe_emalloc(sizeof(zval *), nargs, 0); + zend_get_parameters_array_ex(nargs, args); + } + + VariantInit(&v); + + if (SUCCESS == php_com_do_invoke_byref(obj, (char*)method, -1, DISPATCH_METHOD|DISPATCH_PROPERTYGET, &v, nargs, args TSRMLS_CC)) { + php_com_zval_from_variant(return_value, &v, obj->code_page TSRMLS_CC); + ret = SUCCESS; + VariantClear(&v); + } + + if (args) { + efree(args); + } + + return ret; +} + +static union _zend_function *com_constructor_get(zval *object TSRMLS_DC) +{ + php_com_dotnet_object *obj; + static zend_internal_function c, d, v; + + obj = CDNO_FETCH(object); + +#define POPULATE_CTOR(f, fn) \ + f.type = ZEND_INTERNAL_FUNCTION; \ + f.function_name = (char *) obj->ce->name; \ + f.scope = obj->ce; \ + f.arg_info = NULL; \ + f.num_args = 0; \ + f.fn_flags = 0; \ + f.handler = ZEND_FN(fn); \ + return (union _zend_function*)&f; + + switch (obj->ce->name[0]) { +#if HAVE_MSCOREE_H + case 'd': + POPULATE_CTOR(d, com_dotnet_create_instance); +#endif + + case 'c': + POPULATE_CTOR(c, com_create_instance); + + case 'v': + POPULATE_CTOR(v, com_variant_create_instance); + + default: + return NULL; + } +} + +static zend_class_entry *com_class_entry_get(const zval *object TSRMLS_DC) +{ + php_com_dotnet_object *obj; + obj = CDNO_FETCH(object); + + return obj->ce; +} + +static int com_class_name_get(const zval *object, const char **class_name, zend_uint *class_name_len, int parent TSRMLS_DC) +{ + php_com_dotnet_object *obj; + obj = CDNO_FETCH(object); + + *class_name = estrndup(obj->ce->name, obj->ce->name_length); + *class_name_len = obj->ce->name_length; + + return 0; +} + +/* This compares two variants for equality */ +static int com_objects_compare(zval *object1, zval *object2 TSRMLS_DC) +{ + php_com_dotnet_object *obja, *objb; + int ret; + /* strange header bug problem here... the headers define the proto without the + * flags parameter. However, the MSDN docs state that there is a flags parameter, + * and my VC6 won't link unless the code uses the version with 4 parameters. + * So, we have this declaration here to fix it */ + STDAPI VarCmp(LPVARIANT pvarLeft, LPVARIANT pvarRight, LCID lcid, DWORD flags); + + obja = CDNO_FETCH(object1); + objb = CDNO_FETCH(object2); + + switch (VarCmp(&obja->v, &objb->v, LOCALE_SYSTEM_DEFAULT, 0)) { + case VARCMP_LT: + ret = -1; + break; + case VARCMP_GT: + ret = 1; + break; + case VARCMP_EQ: + ret = 0; + break; + default: + /* either or both operands are NULL... + * not 100% sure how to handle this */ + ret = -2; + } + + return ret; +} + +static int com_object_cast(zval *readobj, zval *writeobj, int type TSRMLS_DC) +{ + php_com_dotnet_object *obj; + VARIANT v; + VARTYPE vt = VT_EMPTY; + HRESULT res = S_OK; + + obj = CDNO_FETCH(readobj); + ZVAL_NULL(writeobj); + VariantInit(&v); + + if (V_VT(&obj->v) == VT_DISPATCH) { + if (SUCCESS != php_com_do_invoke_by_id(obj, DISPID_VALUE, + DISPATCH_METHOD|DISPATCH_PROPERTYGET, &v, 0, NULL, 1, 0 TSRMLS_CC)) { + VariantCopy(&v, &obj->v); + } + } else { + VariantCopy(&v, &obj->v); + } + + switch(type) { + case IS_LONG: + vt = VT_INT; + break; + case IS_DOUBLE: + vt = VT_R8; + break; + case IS_BOOL: + vt = VT_BOOL; + break; + case IS_STRING: + vt = VT_BSTR; + break; + default: + ; + } + + if (vt != VT_EMPTY && vt != V_VT(&v)) { + res = VariantChangeType(&v, &v, 0, vt); + } + + if (SUCCEEDED(res)) { + php_com_zval_from_variant(writeobj, &v, obj->code_page TSRMLS_CC); + } + + VariantClear(&v); + + if (SUCCEEDED(res)) { + return SUCCESS; + } + + return zend_std_cast_object_tostring(readobj, writeobj, type TSRMLS_CC); +} + +static int com_object_count(zval *object, long *count TSRMLS_DC) +{ + php_com_dotnet_object *obj; + LONG ubound = 0, lbound = 0; + + obj = CDNO_FETCH(object); + + if (!V_ISARRAY(&obj->v)) { + return FAILURE; + } + + SafeArrayGetLBound(V_ARRAY(&obj->v), 1, &lbound); + SafeArrayGetUBound(V_ARRAY(&obj->v), 1, &ubound); + + *count = ubound - lbound + 1; + + return SUCCESS; +} + +zend_object_handlers php_com_object_handlers = { + ZEND_OBJECTS_STORE_HANDLERS, + com_property_read, + com_property_write, + com_read_dimension, + com_write_dimension, + NULL, + NULL, /* com_object_get, */ + NULL, /* com_object_set, */ + com_property_exists, + com_property_delete, + com_dimension_exists, + com_dimension_delete, + com_properties_get, + com_method_get, + com_call_method, + com_constructor_get, + com_class_entry_get, + com_class_name_get, + com_objects_compare, + com_object_cast, + com_object_count, + NULL, /* get_debug_info */ + NULL, /* get_closure */ + NULL, /* get_gc */ +}; + +void php_com_object_enable_event_sink(php_com_dotnet_object *obj, int enable TSRMLS_DC) +{ + if (obj->sink_dispatch) { + IConnectionPointContainer *cont; + IConnectionPoint *point; + + if (SUCCEEDED(IDispatch_QueryInterface(V_DISPATCH(&obj->v), + &IID_IConnectionPointContainer, (void**)&cont))) { + + if (SUCCEEDED(IConnectionPointContainer_FindConnectionPoint(cont, + &obj->sink_id, &point))) { + + if (enable) { + IConnectionPoint_Advise(point, (IUnknown*)obj->sink_dispatch, &obj->sink_cookie); + } else { + IConnectionPoint_Unadvise(point, obj->sink_cookie); + } + IConnectionPoint_Release(point); + } + IConnectionPointContainer_Release(cont); + } + } +} + +void php_com_object_free_storage(void *object TSRMLS_DC) +{ + php_com_dotnet_object *obj = (php_com_dotnet_object*)object; + + if (obj->typeinfo) { + ITypeInfo_Release(obj->typeinfo); + obj->typeinfo = NULL; + } + + if (obj->sink_dispatch) { + php_com_object_enable_event_sink(obj, FALSE TSRMLS_CC); + IDispatch_Release(obj->sink_dispatch); + obj->sink_dispatch = NULL; + } + + VariantClear(&obj->v); + + if (obj->method_cache) { + zend_hash_destroy(obj->method_cache); + FREE_HASHTABLE(obj->method_cache); + } + if (obj->id_of_name_cache) { + zend_hash_destroy(obj->id_of_name_cache); + FREE_HASHTABLE(obj->id_of_name_cache); + } + efree(obj); +} + +void php_com_object_clone(void *object, void **clone_ptr TSRMLS_DC) +{ + php_com_dotnet_object *cloneobj, *origobject; + + origobject = (php_com_dotnet_object*)object; + cloneobj = (php_com_dotnet_object*)emalloc(sizeof(php_com_dotnet_object)); + + memcpy(cloneobj, origobject, sizeof(*cloneobj)); + + /* VariantCopy will perform VariantClear; we don't want to clobber + * the IDispatch that we memcpy'd, so we init a new variant in the + * clone structure */ + VariantInit(&cloneobj->v); + /* We use the Indirection-following version of the API since we + * want to clone as much as possible */ + VariantCopyInd(&cloneobj->v, &origobject->v); + + if (cloneobj->typeinfo) { + ITypeInfo_AddRef(cloneobj->typeinfo); + } + + *clone_ptr = cloneobj; +} + +zend_object_value php_com_object_new(zend_class_entry *ce TSRMLS_DC) +{ + php_com_dotnet_object *obj; + zend_object_value retval; + + php_com_initialize(TSRMLS_C); + obj = emalloc(sizeof(*obj)); + memset(obj, 0, sizeof(*obj)); + + VariantInit(&obj->v); + obj->code_page = CP_ACP; + obj->ce = ce; + obj->zo.ce = ce; + + retval.handle = zend_objects_store_put(obj, NULL, php_com_object_free_storage, php_com_object_clone TSRMLS_CC); + retval.handlers = &php_com_object_handlers; + + return retval; +} diff --git a/ext/com_dotnet/com_iterator.c b/ext/com_dotnet/com_iterator.c new file mode 100644 index 0000000..ce4bdd6 --- /dev/null +++ b/ext/com_dotnet/com_iterator.c @@ -0,0 +1,250 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2013 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong <wez@thebrainroom.com> | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "php_com_dotnet.h" +#include "php_com_dotnet_internal.h" +#include "Zend/zend_exceptions.h" + +struct php_com_iterator { + zend_object_iterator iter; + IEnumVARIANT *ev; + ulong key; + VARIANT v; /* cached element */ + int code_page; + VARIANT safe_array; + VARTYPE sa_type; + LONG sa_max; + zval *zdata; +}; + +static void com_iter_dtor(zend_object_iterator *iter TSRMLS_DC) +{ + struct php_com_iterator *I = (struct php_com_iterator*)iter->data; + + if (I->ev) { + IEnumVARIANT_Release(I->ev); + } + VariantClear(&I->v); + VariantClear(&I->safe_array); + if (I->zdata) { + zval_ptr_dtor((zval**)&I->zdata); + } + efree(I); +} + +static int com_iter_valid(zend_object_iterator *iter TSRMLS_DC) +{ + struct php_com_iterator *I = (struct php_com_iterator*)iter->data; + + if (I->zdata) { + return SUCCESS; + } + + return FAILURE; +} + +static void com_iter_get_data(zend_object_iterator *iter, zval ***data TSRMLS_DC) +{ + struct php_com_iterator *I = (struct php_com_iterator*)iter->data; + + *data = &I->zdata; +} + +static int com_iter_get_key(zend_object_iterator *iter, char **str_key, uint *str_key_len, + ulong *int_key TSRMLS_DC) +{ + struct php_com_iterator *I = (struct php_com_iterator*)iter->data; + + if (I->key == (ulong)-1) { + return HASH_KEY_NON_EXISTANT; + } + *int_key = I->key; + return HASH_KEY_IS_LONG; +} + +static int com_iter_move_forwards(zend_object_iterator *iter TSRMLS_DC) +{ + struct php_com_iterator *I = (struct php_com_iterator*)iter->data; + unsigned long n_fetched; + zval *ptr; + + /* release current cached element */ + VariantClear(&I->v); + + if (I->zdata) { + zval_ptr_dtor((zval**)&I->zdata); + I->zdata = NULL; + } + + if (I->ev) { + /* Get the next element */ + if (SUCCEEDED(IEnumVARIANT_Next(I->ev, 1, &I->v, &n_fetched)) && n_fetched > 0) { + I->key++; + } else { + /* indicate that there are no more items */ + I->key = (ulong)-1; + return FAILURE; + } + } else { + /* safe array */ + if (I->key >= (ULONG) I->sa_max) { + I->key = (ulong)-1; + return FAILURE; + } + I->key++; + if (php_com_safearray_get_elem(&I->safe_array, &I->v, (LONG)I->key TSRMLS_CC) == 0) { + I->key = (ulong)-1; + return FAILURE; + } + } + + MAKE_STD_ZVAL(ptr); + php_com_zval_from_variant(ptr, &I->v, I->code_page TSRMLS_CC); + /* php_com_wrap_variant(ptr, &I->v, I->code_page TSRMLS_CC); */ + I->zdata = ptr; + return SUCCESS; +} + + +static zend_object_iterator_funcs com_iter_funcs = { + com_iter_dtor, + com_iter_valid, + com_iter_get_data, + com_iter_get_key, + com_iter_move_forwards, + NULL +}; + +zend_object_iterator *php_com_iter_get(zend_class_entry *ce, zval *object, int by_ref TSRMLS_DC) +{ + php_com_dotnet_object *obj; + struct php_com_iterator *I; + IEnumVARIANT *iev = NULL; + DISPPARAMS dp; + VARIANT v; + unsigned long n_fetched; + zval *ptr; + + if (by_ref) { + zend_error(E_ERROR, "An iterator cannot be used with foreach by reference"); + } + + obj = CDNO_FETCH(object); + + if (V_VT(&obj->v) != VT_DISPATCH && !V_ISARRAY(&obj->v)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "variant is not an object or array VT=%d", V_VT(&obj->v)); + return NULL; + } + + memset(&dp, 0, sizeof(dp)); + VariantInit(&v); + + I = (struct php_com_iterator*)ecalloc(1, sizeof(*I)); + I->iter.funcs = &com_iter_funcs; + I->iter.data = I; + I->code_page = obj->code_page; + I->zdata = NULL; + VariantInit(&I->safe_array); + VariantInit(&I->v); + + if (V_ISARRAY(&obj->v)) { + LONG bound; + UINT dims; + + dims = SafeArrayGetDim(V_ARRAY(&obj->v)); + + if (dims != 1) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Can only handle single dimension variant arrays (this array has %d)", dims); + goto fail; + } + + /* same semantics as foreach on a PHP array; + * make a copy and enumerate that copy */ + VariantCopy(&I->safe_array, &obj->v); + + /* determine the key value for the array */ + SafeArrayGetLBound(V_ARRAY(&I->safe_array), 1, &bound); + SafeArrayGetUBound(V_ARRAY(&I->safe_array), 1, &I->sa_max); + + /* pre-fetch the element */ + if (php_com_safearray_get_elem(&I->safe_array, &I->v, bound TSRMLS_CC)) { + I->key = bound; + MAKE_STD_ZVAL(ptr); + php_com_zval_from_variant(ptr, &I->v, I->code_page TSRMLS_CC); + I->zdata = ptr; + } else { + I->key = (ulong)-1; + } + + } else { + /* can we enumerate it? */ + if (FAILED(IDispatch_Invoke(V_DISPATCH(&obj->v), DISPID_NEWENUM, + &IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD|DISPATCH_PROPERTYGET, + &dp, &v, NULL, NULL))) { + goto fail; + } + + /* get something useful out of it */ + if (V_VT(&v) == VT_UNKNOWN) { + IUnknown_QueryInterface(V_UNKNOWN(&v), &IID_IEnumVARIANT, (void**)&iev); + } else if (V_VT(&v) == VT_DISPATCH) { + IDispatch_QueryInterface(V_DISPATCH(&v), &IID_IEnumVARIANT, (void**)&iev); + } + + VariantClear(&v); + + if (iev == NULL) { + goto fail; + } + + I->ev = iev; + + /* Get the first element now */ + if (SUCCEEDED(IEnumVARIANT_Next(I->ev, 1, &I->v, &n_fetched)) && n_fetched > 0) { + /* indicate that we have element 0 */ + I->key = 0; + MAKE_STD_ZVAL(ptr); + php_com_zval_from_variant(ptr, &I->v, I->code_page TSRMLS_CC); + I->zdata = ptr; + } else { + /* indicate that there are no more items */ + I->key = (ulong)-1; + } + } + + return &I->iter; + +fail: + if (I) { + VariantClear(&I->safe_array); + VariantClear(&I->v); + efree(I); + } + return NULL; +} + diff --git a/ext/com_dotnet/com_misc.c b/ext/com_dotnet/com_misc.c new file mode 100644 index 0000000..023c5b8 --- /dev/null +++ b/ext/com_dotnet/com_misc.c @@ -0,0 +1,144 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2013 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong <wez@thebrainroom.com> | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "php_com_dotnet.h" +#include "php_com_dotnet_internal.h" +#include "Zend/zend_exceptions.h" + +void php_com_throw_exception(HRESULT code, char *message TSRMLS_DC) +{ + int free_msg = 0; + if (message == NULL) { + message = php_win32_error_to_msg(code); + free_msg = 1; + } + zend_throw_exception(php_com_exception_class_entry, message, (long)code TSRMLS_CC); + if (free_msg) { + LocalFree(message); + } +} + +PHP_COM_DOTNET_API void php_com_wrap_dispatch(zval *z, IDispatch *disp, + int codepage TSRMLS_DC) +{ + php_com_dotnet_object *obj; + + obj = emalloc(sizeof(*obj)); + memset(obj, 0, sizeof(*obj)); + obj->code_page = codepage; + obj->ce = php_com_variant_class_entry; + obj->zo.ce = php_com_variant_class_entry; + + VariantInit(&obj->v); + V_VT(&obj->v) = VT_DISPATCH; + V_DISPATCH(&obj->v) = disp; + + IDispatch_AddRef(V_DISPATCH(&obj->v)); + IDispatch_GetTypeInfo(V_DISPATCH(&obj->v), 0, LANG_NEUTRAL, &obj->typeinfo); + + Z_TYPE_P(z) = IS_OBJECT; + z->value.obj.handle = zend_objects_store_put(obj, NULL, php_com_object_free_storage, php_com_object_clone TSRMLS_CC); + z->value.obj.handlers = &php_com_object_handlers; +} + +PHP_COM_DOTNET_API void php_com_wrap_variant(zval *z, VARIANT *v, + int codepage TSRMLS_DC) +{ + php_com_dotnet_object *obj; + + obj = emalloc(sizeof(*obj)); + memset(obj, 0, sizeof(*obj)); + obj->code_page = codepage; + obj->ce = php_com_variant_class_entry; + obj->zo.ce = php_com_variant_class_entry; + + VariantInit(&obj->v); + VariantCopyInd(&obj->v, v); + obj->modified = 0; + + if ((V_VT(&obj->v) == VT_DISPATCH) && (V_DISPATCH(&obj->v) != NULL)) { + IDispatch_GetTypeInfo(V_DISPATCH(&obj->v), 0, LANG_NEUTRAL, &obj->typeinfo); + } + + Z_TYPE_P(z) = IS_OBJECT; + + z->value.obj.handle = zend_objects_store_put(obj, NULL, php_com_object_free_storage, php_com_object_clone TSRMLS_CC); + z->value.obj.handlers = &php_com_object_handlers; +} + +/* this is a convenience function for fetching a particular + * element from a (possibly multi-dimensional) safe array */ +PHP_COM_DOTNET_API int php_com_safearray_get_elem(VARIANT *array, VARIANT *dest, LONG dim1 TSRMLS_DC) +{ + UINT dims; + LONG lbound, ubound; + LONG indices[1]; + VARTYPE vt; + + if (!V_ISARRAY(array)) { + return 0; + } + + dims = SafeArrayGetDim(V_ARRAY(array)); + + if (dims != 1) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Can only handle single dimension variant arrays (this array has %d)", dims); + return 0; + } + + if (FAILED(SafeArrayGetVartype(V_ARRAY(array), &vt)) || vt == VT_EMPTY) { + vt = V_VT(array) & ~VT_ARRAY; + } + + /* determine the bounds */ + SafeArrayGetLBound(V_ARRAY(array), 1, &lbound); + SafeArrayGetUBound(V_ARRAY(array), 1, &ubound); + + /* check bounds */ + if (dim1 < lbound || dim1 > ubound) { + php_com_throw_exception(DISP_E_BADINDEX, "index out of bounds" TSRMLS_CC); + return 0; + } + + /* now fetch that element */ + VariantInit(dest); + + indices[0] = dim1; + + if (vt == VT_VARIANT) { + SafeArrayGetElement(V_ARRAY(array), indices, dest); + } else { + V_VT(dest) = vt; + /* store the value into "lVal" member of the variant. + * This works because it is a union; since we know the variant + * type, we end up with a working variant */ + SafeArrayGetElement(V_ARRAY(array), indices, &dest->lVal); + } + + return 1; +} diff --git a/ext/com_dotnet/com_olechar.c b/ext/com_dotnet/com_olechar.c new file mode 100644 index 0000000..e456df2 --- /dev/null +++ b/ext/com_dotnet/com_olechar.c @@ -0,0 +1,101 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2013 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong <wez@thebrainroom.com> | + | Harald Radi <h.radi@nme.at> | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "php_com_dotnet.h" +#include "php_com_dotnet_internal.h" + + +PHP_COM_DOTNET_API OLECHAR *php_com_string_to_olestring(char *string, uint string_len, int codepage TSRMLS_DC) +{ + OLECHAR *olestring = NULL; + DWORD flags = codepage == CP_UTF8 ? 0 : MB_PRECOMPOSED | MB_ERR_INVALID_CHARS; + BOOL ok; + + if (string_len == -1) { + /* determine required length for the buffer (includes NUL terminator) */ + string_len = MultiByteToWideChar(codepage, flags, string, -1, NULL, 0); + } else { + /* allow room for NUL terminator */ + string_len++; + } + + if (string_len > 0) { + olestring = (OLECHAR*)safe_emalloc(string_len, sizeof(OLECHAR), 0); + ok = MultiByteToWideChar(codepage, flags, string, string_len, olestring, string_len); + } else { + ok = FALSE; + olestring = (OLECHAR*)emalloc(sizeof(OLECHAR)); + *olestring = 0; + } + + if (!ok) { + char *msg = php_win32_error_to_msg(GetLastError()); + + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Could not convert string to unicode: `%s'", msg); + + LocalFree(msg); + } + + return olestring; +} + +PHP_COM_DOTNET_API char *php_com_olestring_to_string(OLECHAR *olestring, uint *string_len, int codepage TSRMLS_DC) +{ + char *string; + uint length = 0; + BOOL ok; + + length = WideCharToMultiByte(codepage, 0, olestring, -1, NULL, 0, NULL, NULL); + + if (length) { + string = (char*)safe_emalloc(length, sizeof(char), 0); + length = WideCharToMultiByte(codepage, 0, olestring, -1, string, length, NULL, NULL); + ok = length > 0; + } else { + string = (char*)emalloc(sizeof(char)); + *string = '\0'; + ok = FALSE; + length = 0; + } + + if (!ok) { + char *msg = php_win32_error_to_msg(GetLastError()); + + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Could not convert string from unicode: `%s'", msg); + + LocalFree(msg); + } + + if (string_len) { + *string_len = length-1; + } + + return string; +} diff --git a/ext/com_dotnet/com_persist.c b/ext/com_dotnet/com_persist.c new file mode 100644 index 0000000..8953a41 --- /dev/null +++ b/ext/com_dotnet/com_persist.c @@ -0,0 +1,777 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2013 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong <wez@thebrainroom.com> | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +/* Infrastructure for working with persistent COM objects. + * Implements: IStream* wrapper for PHP streams. + * TODO: Magic __wakeup and __sleep handlers for serialization + * (can wait till 5.1) */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "php_com_dotnet.h" +#include "php_com_dotnet_internal.h" +#include "Zend/zend_exceptions.h" + +/* {{{ expose php_stream as a COM IStream */ + +typedef struct { + CONST_VTBL struct IStreamVtbl *lpVtbl; + DWORD engine_thread; + LONG refcount; + php_stream *stream; + int id; +} php_istream; + +static int le_istream; +static void istream_destructor(php_istream *stm TSRMLS_DC); + +static void istream_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC) +{ + php_istream *stm = (php_istream *)rsrc->ptr; + istream_destructor(stm TSRMLS_CC); +} + +#define FETCH_STM() \ + php_istream *stm = (php_istream*)This; \ + TSRMLS_FETCH(); \ + if (GetCurrentThreadId() != stm->engine_thread) \ + return RPC_E_WRONG_THREAD; + +#define FETCH_STM_EX() \ + php_istream *stm = (php_istream*)This; \ + if (GetCurrentThreadId() != stm->engine_thread) \ + return RPC_E_WRONG_THREAD; + +static HRESULT STDMETHODCALLTYPE stm_queryinterface( + IStream *This, + /* [in] */ REFIID riid, + /* [iid_is][out] */ void **ppvObject) +{ + FETCH_STM_EX(); + + if (IsEqualGUID(&IID_IUnknown, riid) || + IsEqualGUID(&IID_IStream, riid)) { + *ppvObject = This; + InterlockedIncrement(&stm->refcount); + return S_OK; + } + + *ppvObject = NULL; + return E_NOINTERFACE; +} + +static ULONG STDMETHODCALLTYPE stm_addref(IStream *This) +{ + FETCH_STM_EX(); + + return InterlockedIncrement(&stm->refcount); +} + +static ULONG STDMETHODCALLTYPE stm_release(IStream *This) +{ + ULONG ret; + FETCH_STM(); + + ret = InterlockedDecrement(&stm->refcount); + if (ret == 0) { + /* destroy it */ + if (stm->id) + zend_list_delete(stm->id); + } + return ret; +} + +static HRESULT STDMETHODCALLTYPE stm_read(IStream *This, void *pv, ULONG cb, ULONG *pcbRead) +{ + int nread; + FETCH_STM(); + + nread = php_stream_read(stm->stream, pv, cb); + + if (pcbRead) { + *pcbRead = nread > 0 ? nread : 0; + } + if (nread > 0) { + return S_OK; + } + return S_FALSE; +} + +static HRESULT STDMETHODCALLTYPE stm_write(IStream *This, void const *pv, ULONG cb, ULONG *pcbWritten) +{ + int nwrote; + FETCH_STM(); + + nwrote = php_stream_write(stm->stream, pv, cb); + + if (pcbWritten) { + *pcbWritten = nwrote > 0 ? nwrote : 0; + } + if (nwrote > 0) { + return S_OK; + } + return S_FALSE; +} + +static HRESULT STDMETHODCALLTYPE stm_seek(IStream *This, LARGE_INTEGER dlibMove, + DWORD dwOrigin, ULARGE_INTEGER *plibNewPosition) +{ + off_t offset; + int whence; + int ret; + FETCH_STM(); + + switch (dwOrigin) { + case STREAM_SEEK_SET: whence = SEEK_SET; break; + case STREAM_SEEK_CUR: whence = SEEK_CUR; break; + case STREAM_SEEK_END: whence = SEEK_END; break; + default: + return STG_E_INVALIDFUNCTION; + } + + if (dlibMove.HighPart) { + /* we don't support 64-bit offsets */ + return STG_E_INVALIDFUNCTION; + } + + offset = (off_t) dlibMove.QuadPart; + + ret = php_stream_seek(stm->stream, offset, whence); + + if (plibNewPosition) { + plibNewPosition->QuadPart = (ULONGLONG)(ret >= 0 ? ret : 0); + } + + return ret >= 0 ? S_OK : STG_E_INVALIDFUNCTION; +} + +static HRESULT STDMETHODCALLTYPE stm_set_size(IStream *This, ULARGE_INTEGER libNewSize) +{ + FETCH_STM(); + + if (libNewSize.HighPart) { + return STG_E_INVALIDFUNCTION; + } + + if (php_stream_truncate_supported(stm->stream)) { + int ret = php_stream_truncate_set_size(stm->stream, (size_t)libNewSize.QuadPart); + + if (ret == 0) { + return S_OK; + } + } + + return STG_E_INVALIDFUNCTION; +} + +static HRESULT STDMETHODCALLTYPE stm_copy_to(IStream *This, IStream *pstm, ULARGE_INTEGER cb, + ULARGE_INTEGER *pcbRead, ULARGE_INTEGER *pcbWritten) +{ + FETCH_STM_EX(); + + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE stm_commit(IStream *This, DWORD grfCommitFlags) +{ + FETCH_STM(); + + php_stream_flush(stm->stream); + + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE stm_revert(IStream *This) +{ + /* NOP */ + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE stm_lock_region(IStream *This, + ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD lockType) +{ + return STG_E_INVALIDFUNCTION; +} + +static HRESULT STDMETHODCALLTYPE stm_unlock_region(IStream *This, + ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD lockType) +{ + return STG_E_INVALIDFUNCTION; +} + +static HRESULT STDMETHODCALLTYPE stm_stat(IStream *This, + STATSTG *pstatstg, DWORD grfStatFlag) +{ + return STG_E_INVALIDFUNCTION; +} + +static HRESULT STDMETHODCALLTYPE stm_clone(IStream *This, IStream **ppstm) +{ + return STG_E_INVALIDFUNCTION; +} + +static struct IStreamVtbl php_istream_vtbl = { + stm_queryinterface, + stm_addref, + stm_release, + stm_read, + stm_write, + stm_seek, + stm_set_size, + stm_copy_to, + stm_commit, + stm_revert, + stm_lock_region, + stm_unlock_region, + stm_stat, + stm_clone +}; + +static void istream_destructor(php_istream *stm TSRMLS_DC) +{ + if (stm->id) { + int id = stm->id; + stm->id = 0; + zend_list_delete(id); + return; + } + + if (stm->refcount > 0) { + CoDisconnectObject((IUnknown*)stm, 0); + } + + zend_list_delete(stm->stream->rsrc_id); + + CoTaskMemFree(stm); +} +/* }}} */ + +PHP_COM_DOTNET_API IStream *php_com_wrapper_export_stream(php_stream *stream TSRMLS_DC) +{ + php_istream *stm = (php_istream*)CoTaskMemAlloc(sizeof(*stm)); + + if (stm == NULL) + return NULL; + + memset(stm, 0, sizeof(*stm)); + stm->engine_thread = GetCurrentThreadId(); + stm->lpVtbl = &php_istream_vtbl; + stm->refcount = 1; + stm->stream = stream; + + zend_list_addref(stream->rsrc_id); + stm->id = zend_list_insert(stm, le_istream TSRMLS_CC); + + return (IStream*)stm; +} + +#define CPH_ME(fname, arginfo) PHP_ME(com_persist, fname, arginfo, ZEND_ACC_PUBLIC) +#define CPH_SME(fname, arginfo) PHP_ME(com_persist, fname, arginfo, ZEND_ACC_ALLOW_STATIC|ZEND_ACC_PUBLIC) +#define CPH_METHOD(fname) static PHP_METHOD(com_persist, fname) + +#define CPH_FETCH() php_com_persist_helper *helper = (php_com_persist_helper*)zend_object_store_get_object(getThis() TSRMLS_CC); + +#define CPH_NO_OBJ() if (helper->unk == NULL) { php_com_throw_exception(E_INVALIDARG, "No COM object is associated with this helper instance" TSRMLS_CC); return; } + +typedef struct { + zend_object std; + long codepage; + IUnknown *unk; + IPersistStream *ips; + IPersistStreamInit *ipsi; + IPersistFile *ipf; +} php_com_persist_helper; + +static zend_object_handlers helper_handlers; +static zend_class_entry *helper_ce; + +static inline HRESULT get_persist_stream(php_com_persist_helper *helper) +{ + if (!helper->ips && helper->unk) { + return IUnknown_QueryInterface(helper->unk, &IID_IPersistStream, &helper->ips); + } + return helper->ips ? S_OK : E_NOTIMPL; +} + +static inline HRESULT get_persist_stream_init(php_com_persist_helper *helper) +{ + if (!helper->ipsi && helper->unk) { + return IUnknown_QueryInterface(helper->unk, &IID_IPersistStreamInit, &helper->ipsi); + } + return helper->ipsi ? S_OK : E_NOTIMPL; +} + +static inline HRESULT get_persist_file(php_com_persist_helper *helper) +{ + if (!helper->ipf && helper->unk) { + return IUnknown_QueryInterface(helper->unk, &IID_IPersistFile, &helper->ipf); + } + return helper->ipf ? S_OK : E_NOTIMPL; +} + + +/* {{{ proto string COMPersistHelper::GetCurFile() + Determines the filename into which an object will be saved, or false if none is set, via IPersistFile::GetCurFile */ +CPH_METHOD(GetCurFileName) +{ + HRESULT res; + OLECHAR *olename = NULL; + CPH_FETCH(); + + CPH_NO_OBJ(); + + res = get_persist_file(helper); + if (helper->ipf) { + res = IPersistFile_GetCurFile(helper->ipf, &olename); + + if (res == S_OK) { + Z_TYPE_P(return_value) = IS_STRING; + Z_STRVAL_P(return_value) = php_com_olestring_to_string(olename, + &Z_STRLEN_P(return_value), helper->codepage TSRMLS_CC); + CoTaskMemFree(olename); + return; + } else if (res == S_FALSE) { + CoTaskMemFree(olename); + RETURN_FALSE; + } + php_com_throw_exception(res, NULL TSRMLS_CC); + } else { + php_com_throw_exception(res, NULL TSRMLS_CC); + } +} +/* }}} */ + + +/* {{{ proto bool COMPersistHelper::SaveToFile(string filename [, bool remember]) + Persist object data to file, via IPersistFile::Save */ +CPH_METHOD(SaveToFile) +{ + HRESULT res; + char *filename, *fullpath = NULL; + int filename_len; + zend_bool remember = TRUE; + OLECHAR *olefilename = NULL; + CPH_FETCH(); + + CPH_NO_OBJ(); + + res = get_persist_file(helper); + if (helper->ipf) { + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "p!|b", + &filename, &filename_len, &remember)) { + php_com_throw_exception(E_INVALIDARG, "Invalid arguments" TSRMLS_CC); + return; + } + + if (filename) { + fullpath = expand_filepath(filename, NULL TSRMLS_CC); + if (!fullpath) { + RETURN_FALSE; + } + + if (php_check_open_basedir(fullpath TSRMLS_CC)) { + efree(fullpath); + RETURN_FALSE; + } + + olefilename = php_com_string_to_olestring(filename, strlen(fullpath), helper->codepage TSRMLS_CC); + efree(fullpath); + } + res = IPersistFile_Save(helper->ipf, olefilename, remember); + if (SUCCEEDED(res)) { + if (!olefilename) { + res = IPersistFile_GetCurFile(helper->ipf, &olefilename); + if (S_OK == res) { + IPersistFile_SaveCompleted(helper->ipf, olefilename); + CoTaskMemFree(olefilename); + olefilename = NULL; + } + } else if (remember) { + IPersistFile_SaveCompleted(helper->ipf, olefilename); + } + } + + if (olefilename) { + efree(olefilename); + } + + if (FAILED(res)) { + php_com_throw_exception(res, NULL TSRMLS_CC); + } + + } else { + php_com_throw_exception(res, NULL TSRMLS_CC); + } +} +/* }}} */ + +/* {{{ proto bool COMPersistHelper::LoadFromFile(string filename [, int flags]) + Load object data from file, via IPersistFile::Load */ +CPH_METHOD(LoadFromFile) +{ + HRESULT res; + char *filename, *fullpath; + int filename_len; + long flags = 0; + OLECHAR *olefilename; + CPH_FETCH(); + + CPH_NO_OBJ(); + + res = get_persist_file(helper); + if (helper->ipf) { + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "p|l", + &filename, &filename_len, &flags)) { + php_com_throw_exception(E_INVALIDARG, "Invalid arguments" TSRMLS_CC); + return; + } + + if (!(fullpath = expand_filepath(filename, NULL TSRMLS_CC))) { + RETURN_FALSE; + } + + if (php_check_open_basedir(fullpath TSRMLS_CC)) { + efree(fullpath); + RETURN_FALSE; + } + + olefilename = php_com_string_to_olestring(fullpath, strlen(fullpath), helper->codepage TSRMLS_CC); + efree(fullpath); + + res = IPersistFile_Load(helper->ipf, olefilename, flags); + efree(olefilename); + + if (FAILED(res)) { + php_com_throw_exception(res, NULL TSRMLS_CC); + } + + } else { + php_com_throw_exception(res, NULL TSRMLS_CC); + } +} +/* }}} */ + +/* {{{ proto int COMPersistHelper::GetMaxStreamSize() + Gets maximum stream size required to store the object data, via IPersistStream::GetSizeMax (or IPersistStreamInit::GetSizeMax) */ +CPH_METHOD(GetMaxStreamSize) +{ + HRESULT res; + ULARGE_INTEGER size; + CPH_FETCH(); + + CPH_NO_OBJ(); + + res = get_persist_stream_init(helper); + if (helper->ipsi) { + res = IPersistStreamInit_GetSizeMax(helper->ipsi, &size); + } else { + res = get_persist_stream(helper); + if (helper->ips) { + res = IPersistStream_GetSizeMax(helper->ips, &size); + } else { + php_com_throw_exception(res, NULL TSRMLS_CC); + return; + } + } + + if (res != S_OK) { + php_com_throw_exception(res, NULL TSRMLS_CC); + } else { + /* TODO: handle 64 bit properly */ + RETURN_LONG((LONG)size.QuadPart); + } +} +/* }}} */ + +/* {{{ proto int COMPersistHelper::InitNew() + Initializes the object to a default state, via IPersistStreamInit::InitNew */ +CPH_METHOD(InitNew) +{ + HRESULT res; + CPH_FETCH(); + + CPH_NO_OBJ(); + + res = get_persist_stream_init(helper); + if (helper->ipsi) { + res = IPersistStreamInit_InitNew(helper->ipsi); + + if (res != S_OK) { + php_com_throw_exception(res, NULL TSRMLS_CC); + } else { + RETURN_TRUE; + } + } else { + php_com_throw_exception(res, NULL TSRMLS_CC); + } +} +/* }}} */ + +/* {{{ proto mixed COMPersistHelper::LoadFromStream(resource stream) + Initializes an object from the stream where it was previously saved, via IPersistStream::Load or OleLoadFromStream */ +CPH_METHOD(LoadFromStream) +{ + zval *zstm; + php_stream *stream; + IStream *stm = NULL; + HRESULT res; + CPH_FETCH(); + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &zstm)) { + php_com_throw_exception(E_INVALIDARG, "invalid arguments" TSRMLS_CC); + return; + } + + php_stream_from_zval_no_verify(stream, &zstm); + + if (stream == NULL) { + php_com_throw_exception(E_INVALIDARG, "expected a stream" TSRMLS_CC); + return; + } + + stm = php_com_wrapper_export_stream(stream TSRMLS_CC); + if (stm == NULL) { + php_com_throw_exception(E_UNEXPECTED, "failed to wrap stream" TSRMLS_CC); + return; + } + + res = S_OK; + RETVAL_TRUE; + + if (helper->unk == NULL) { + IDispatch *disp = NULL; + + /* we need to create an object and load using OleLoadFromStream */ + res = OleLoadFromStream(stm, &IID_IDispatch, &disp); + + if (SUCCEEDED(res)) { + php_com_wrap_dispatch(return_value, disp, COMG(code_page) TSRMLS_CC); + } + } else { + res = get_persist_stream_init(helper); + if (helper->ipsi) { + res = IPersistStreamInit_Load(helper->ipsi, stm); + } else { + res = get_persist_stream(helper); + if (helper->ips) { + res = IPersistStreamInit_Load(helper->ipsi, stm); + } + } + } + IStream_Release(stm); + + if (FAILED(res)) { + php_com_throw_exception(res, NULL TSRMLS_CC); + RETURN_NULL(); + } +} +/* }}} */ + +/* {{{ proto int COMPersistHelper::SaveToStream(resource stream) + Saves the object to a stream, via IPersistStream::Save */ +CPH_METHOD(SaveToStream) +{ + zval *zstm; + php_stream *stream; + IStream *stm = NULL; + HRESULT res; + CPH_FETCH(); + + CPH_NO_OBJ(); + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &zstm)) { + php_com_throw_exception(E_INVALIDARG, "invalid arguments" TSRMLS_CC); + return; + } + + php_stream_from_zval_no_verify(stream, &zstm); + + if (stream == NULL) { + php_com_throw_exception(E_INVALIDARG, "expected a stream" TSRMLS_CC); + return; + } + + stm = php_com_wrapper_export_stream(stream TSRMLS_CC); + if (stm == NULL) { + php_com_throw_exception(E_UNEXPECTED, "failed to wrap stream" TSRMLS_CC); + return; + } + + res = get_persist_stream_init(helper); + if (helper->ipsi) { + res = IPersistStreamInit_Save(helper->ipsi, stm, TRUE); + } else { + res = get_persist_stream(helper); + if (helper->ips) { + res = IPersistStream_Save(helper->ips, stm, TRUE); + } + } + + IStream_Release(stm); + + if (FAILED(res)) { + php_com_throw_exception(res, NULL TSRMLS_CC); + return; + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto int COMPersistHelper::__construct([object com_object]) + Creates a persistence helper object, usually associated with a com_object */ +CPH_METHOD(__construct) +{ + php_com_dotnet_object *obj = NULL; + zval *zobj = NULL; + CPH_FETCH(); + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|O!", + &zobj, php_com_variant_class_entry)) { + php_com_throw_exception(E_INVALIDARG, "invalid arguments" TSRMLS_CC); + return; + } + + if (!zobj) { + return; + } + + obj = CDNO_FETCH(zobj); + + if (V_VT(&obj->v) != VT_DISPATCH || V_DISPATCH(&obj->v) == NULL) { + php_com_throw_exception(E_INVALIDARG, "parameter must represent an IDispatch COM object" TSRMLS_CC); + return; + } + + /* it is always safe to cast an interface to IUnknown */ + helper->unk = (IUnknown*)V_DISPATCH(&obj->v); + IUnknown_AddRef(helper->unk); + helper->codepage = obj->code_page; +} +/* }}} */ + + + + +static const zend_function_entry com_persist_helper_methods[] = { + CPH_ME(__construct, NULL) + CPH_ME(GetCurFileName, NULL) + CPH_ME(SaveToFile, NULL) + CPH_ME(LoadFromFile, NULL) + CPH_ME(GetMaxStreamSize, NULL) + CPH_ME(InitNew, NULL) + CPH_ME(LoadFromStream, NULL) + CPH_ME(SaveToStream, NULL) + PHP_FE_END +}; + +static void helper_free_storage(void *obj TSRMLS_DC) +{ + php_com_persist_helper *object = (php_com_persist_helper*)obj; + + if (object->ipf) { + IPersistFile_Release(object->ipf); + } + if (object->ips) { + IPersistStream_Release(object->ips); + } + if (object->ipsi) { + IPersistStreamInit_Release(object->ipsi); + } + if (object->unk) { + IUnknown_Release(object->unk); + } + zend_object_std_dtor(&object->std TSRMLS_CC); + efree(object); +} + + +static void helper_clone(void *obj, void **clone_ptr TSRMLS_DC) +{ + php_com_persist_helper *clone, *object = (php_com_persist_helper*)obj; + + clone = emalloc(sizeof(*object)); + memcpy(clone, object, sizeof(*object)); + *clone_ptr = clone; + + zend_object_std_init(&clone->std, object->std.ce TSRMLS_CC); + + if (clone->ipf) { + IPersistFile_AddRef(clone->ipf); + } + if (clone->ips) { + IPersistStream_AddRef(clone->ips); + } + if (clone->ipsi) { + IPersistStreamInit_AddRef(clone->ipsi); + } + if (clone->unk) { + IUnknown_AddRef(clone->unk); + } +} + +static zend_object_value helper_new(zend_class_entry *ce TSRMLS_DC) +{ + php_com_persist_helper *helper; + zend_object_value retval; + + helper = emalloc(sizeof(*helper)); + memset(helper, 0, sizeof(*helper)); + + zend_object_std_init(&helper->std, helper_ce TSRMLS_CC); + + retval.handle = zend_objects_store_put(helper, NULL, helper_free_storage, helper_clone TSRMLS_CC); + retval.handlers = &helper_handlers; + + return retval; +} + +int php_com_persist_minit(INIT_FUNC_ARGS) +{ + zend_class_entry ce; + + memcpy(&helper_handlers, zend_get_std_object_handlers(), sizeof(helper_handlers)); + helper_handlers.clone_obj = NULL; + + INIT_CLASS_ENTRY(ce, "COMPersistHelper", com_persist_helper_methods); + ce.create_object = helper_new; + helper_ce = zend_register_internal_class(&ce TSRMLS_CC); + helper_ce->ce_flags |= ZEND_ACC_FINAL; + + le_istream = zend_register_list_destructors_ex(istream_dtor, + NULL, "com_dotnet_istream_wrapper", module_number); + + return SUCCESS; +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/ext/com_dotnet/com_saproxy.c b/ext/com_dotnet/com_saproxy.c new file mode 100644 index 0000000..ad92849 --- /dev/null +++ b/ext/com_dotnet/com_saproxy.c @@ -0,0 +1,586 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2013 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong <wez@thebrainroom.com> | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +/* This module implements a SafeArray proxy which is used internally + * by the engine when resolving multi-dimensional array accesses on + * SafeArray types. + * In addition, the proxy is now able to handle properties of COM objects + * that smell like PHP arrays. + * */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "php_com_dotnet.h" +#include "php_com_dotnet_internal.h" +#include "Zend/zend_exceptions.h" + +typedef struct { + /* the object we a proxying for; we hold a refcount to it */ + zval *zobj; + php_com_dotnet_object *obj; + + /* how many dimensions we are indirecting to get into this element */ + LONG dimensions; + + /* this is an array whose size_is(dimensions) */ + zval **indices; + +} php_com_saproxy; + +typedef struct { + zend_object_iterator iter; + zval *proxy_obj; + php_com_saproxy *proxy; + LONG key; + LONG imin, imax; + LONG *indices; +} php_com_saproxy_iter; + +#define SA_FETCH(zv) (php_com_saproxy*)zend_object_store_get_object(zv TSRMLS_CC) + +static inline void clone_indices(php_com_saproxy *dest, php_com_saproxy *src, int ndims) +{ + int i; + + for (i = 0; i < ndims; i++) { + MAKE_STD_ZVAL(dest->indices[i]); + *dest->indices[i] = *src->indices[i]; + zval_copy_ctor(dest->indices[i]); + } +} + +static zval *saproxy_property_read(zval *object, zval *member, int type, const zend_literal *key TSRMLS_DC) +{ + zval *return_value; + + MAKE_STD_ZVAL(return_value); + ZVAL_NULL(return_value); + + php_com_throw_exception(E_INVALIDARG, "safearray has no properties" TSRMLS_CC); + + return return_value; +} + +static void saproxy_property_write(zval *object, zval *member, zval *value, const zend_literal *key TSRMLS_DC) +{ + php_com_throw_exception(E_INVALIDARG, "safearray has no properties" TSRMLS_CC); +} + +static zval *saproxy_read_dimension(zval *object, zval *offset, int type TSRMLS_DC) +{ + php_com_saproxy *proxy = SA_FETCH(object); + zval *return_value; + UINT dims, i; + SAFEARRAY *sa; + LONG ubound, lbound; + HRESULT res; + + MAKE_STD_ZVAL(return_value); + ZVAL_NULL(return_value); + + if (V_VT(&proxy->obj->v) == VT_DISPATCH) { + VARIANT v; + zval **args; + + /* prop-get using first dimension as the property name, + * all subsequent dimensions and the offset as parameters */ + + args = safe_emalloc(proxy->dimensions + 1, sizeof(zval *), 0); + + for (i = 1; i < (UINT) proxy->dimensions; i++) { + args[i-1] = proxy->indices[i]; + } + args[i-1] = offset; + + convert_to_string(proxy->indices[0]); + VariantInit(&v); + + res = php_com_do_invoke(proxy->obj, Z_STRVAL_P(proxy->indices[0]), + Z_STRLEN_P(proxy->indices[0]), DISPATCH_METHOD|DISPATCH_PROPERTYGET, &v, + proxy->dimensions, args, 0 TSRMLS_CC); + + if (res == SUCCESS) { + php_com_zval_from_variant(return_value, &v, proxy->obj->code_page TSRMLS_CC); + VariantClear(&v); + } else if (res == DISP_E_BADPARAMCOUNT) { + /* return another proxy */ + php_com_saproxy_create(object, return_value, offset TSRMLS_CC); + } + + return return_value; + + } else if (!V_ISARRAY(&proxy->obj->v)) { + php_com_throw_exception(E_INVALIDARG, "invalid read from com proxy object" TSRMLS_CC); + return return_value; + } + + /* the SafeArray case */ + + /* offset/index must be an integer */ + convert_to_long(offset); + + sa = V_ARRAY(&proxy->obj->v); + dims = SafeArrayGetDim(sa); + + if ((UINT) proxy->dimensions >= dims) { + /* too many dimensions */ + php_com_throw_exception(E_INVALIDARG, "too many dimensions!" TSRMLS_CC); + return return_value; + } + + /* bounds check */ + SafeArrayGetLBound(sa, proxy->dimensions, &lbound); + SafeArrayGetUBound(sa, proxy->dimensions, &ubound); + + if (Z_LVAL_P(offset) < lbound || Z_LVAL_P(offset) > ubound) { + php_com_throw_exception(DISP_E_BADINDEX, "index out of bounds" TSRMLS_CC); + return return_value; + } + + if (dims - 1 == proxy->dimensions) { + LONG *indices; + VARTYPE vt; + VARIANT v; + + VariantInit(&v); + + /* we can return a real value */ + indices = safe_emalloc(dims, sizeof(LONG), 0); + + /* copy indices from proxy */ + for (i = 0; i < dims; i++) { + convert_to_long(proxy->indices[i]); + indices[i] = Z_LVAL_P(proxy->indices[i]); + } + + /* add user-supplied index */ + indices[dims-1] = Z_LVAL_P(offset); + + /* now fetch the value */ + if (FAILED(SafeArrayGetVartype(sa, &vt)) || vt == VT_EMPTY) { + vt = V_VT(&proxy->obj->v) & ~VT_ARRAY; + } + + if (vt == VT_VARIANT) { + res = SafeArrayGetElement(sa, indices, &v); + } else { + V_VT(&v) = vt; + res = SafeArrayGetElement(sa, indices, &v.lVal); + } + + efree(indices); + + if (SUCCEEDED(res)) { + php_com_wrap_variant(return_value, &v, proxy->obj->code_page TSRMLS_CC); + } else { + php_com_throw_exception(res, NULL TSRMLS_CC); + } + + VariantClear(&v); + + } else { + /* return another proxy */ + php_com_saproxy_create(object, return_value, offset TSRMLS_CC); + } + + return return_value; +} + +static void saproxy_write_dimension(zval *object, zval *offset, zval *value TSRMLS_DC) +{ + php_com_saproxy *proxy = SA_FETCH(object); + UINT dims, i; + HRESULT res; + VARIANT v; + + if (V_VT(&proxy->obj->v) == VT_DISPATCH) { + /* We do a prop-set using the first dimension as the property name, + * all subsequent dimensions and offset as parameters, with value as + * the final value */ + zval **args = safe_emalloc(proxy->dimensions + 2, sizeof(zval *), 0); + + for (i = 1; i < (UINT) proxy->dimensions; i++) { + args[i-1] = proxy->indices[i]; + } + args[i-1] = offset; + args[i] = value; + + convert_to_string(proxy->indices[0]); + VariantInit(&v); + if (SUCCESS == php_com_do_invoke(proxy->obj, Z_STRVAL_P(proxy->indices[0]), + Z_STRLEN_P(proxy->indices[0]), DISPATCH_PROPERTYPUT, &v, proxy->dimensions + 1, + args, 0 TSRMLS_CC)) { + VariantClear(&v); + } + + efree(args); + + } else if (V_ISARRAY(&proxy->obj->v)) { + LONG *indices; + VARTYPE vt; + + dims = SafeArrayGetDim(V_ARRAY(&proxy->obj->v)); + indices = safe_emalloc(dims, sizeof(LONG), 0); + /* copy indices from proxy */ + for (i = 0; i < dims; i++) { + convert_to_long(proxy->indices[i]); + indices[i] = Z_LVAL_P(proxy->indices[i]); + } + + /* add user-supplied index */ + convert_to_long(offset); + indices[dims-1] = Z_LVAL_P(offset); + + if (FAILED(SafeArrayGetVartype(V_ARRAY(&proxy->obj->v), &vt)) || vt == VT_EMPTY) { + vt = V_VT(&proxy->obj->v) & ~VT_ARRAY; + } + + VariantInit(&v); + php_com_variant_from_zval(&v, value, proxy->obj->code_page TSRMLS_CC); + + if (V_VT(&v) != vt) { + VariantChangeType(&v, &v, 0, vt); + } + + if (vt == VT_VARIANT) { + res = SafeArrayPutElement(V_ARRAY(&proxy->obj->v), indices, &v); + } else { + res = SafeArrayPutElement(V_ARRAY(&proxy->obj->v), indices, &v.lVal); + } + + efree(indices); + VariantClear(&v); + + if (FAILED(res)) { + php_com_throw_exception(res, NULL TSRMLS_CC); + } + } else { + php_com_throw_exception(E_NOTIMPL, "invalid write to com proxy object" TSRMLS_CC); + } +} + +#if 0 +static void saproxy_object_set(zval **property, zval *value TSRMLS_DC) +{ +} + +static zval *saproxy_object_get(zval *property TSRMLS_DC) +{ + /* Not yet implemented in the engine */ + return NULL; +} +#endif + +static int saproxy_property_exists(zval *object, zval *member, int check_empty, const zend_literal *key TSRMLS_DC) +{ + /* no properties */ + return 0; +} + +static int saproxy_dimension_exists(zval *object, zval *member, int check_empty TSRMLS_DC) +{ + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Operation not yet supported on a COM object"); + return 0; +} + +static void saproxy_property_delete(zval *object, zval *member, const zend_literal *key TSRMLS_DC) +{ + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot delete properties from a COM object"); +} + +static void saproxy_dimension_delete(zval *object, zval *offset TSRMLS_DC) +{ + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot delete properties from a COM object"); +} + +static HashTable *saproxy_properties_get(zval *object TSRMLS_DC) +{ + /* no properties */ + return NULL; +} + +static union _zend_function *saproxy_method_get(zval **object, const char *name, int len, const zend_literal *key TSRMLS_DC) +{ + /* no methods */ + return NULL; +} + +static int saproxy_call_method(const char *method, INTERNAL_FUNCTION_PARAMETERS) +{ + return FAILURE; +} + +static union _zend_function *saproxy_constructor_get(zval *object TSRMLS_DC) +{ + /* user cannot instantiate */ + return NULL; +} + +static zend_class_entry *saproxy_class_entry_get(const zval *object TSRMLS_DC) +{ + return php_com_saproxy_class_entry; +} + +static int saproxy_class_name_get(const zval *object, const char **class_name, zend_uint *class_name_len, int parent TSRMLS_DC) +{ + *class_name = estrndup(php_com_saproxy_class_entry->name, php_com_saproxy_class_entry->name_length); + *class_name_len = php_com_saproxy_class_entry->name_length; + return 0; +} + +static int saproxy_objects_compare(zval *object1, zval *object2 TSRMLS_DC) +{ + return -1; +} + +static int saproxy_object_cast(zval *readobj, zval *writeobj, int type TSRMLS_DC) +{ + return FAILURE; +} + +static int saproxy_count_elements(zval *object, long *count TSRMLS_DC) +{ + php_com_saproxy *proxy = SA_FETCH(object); + LONG ubound, lbound; + + if (!V_ISARRAY(&proxy->obj->v)) { + return FAILURE; + } + + SafeArrayGetLBound(V_ARRAY(&proxy->obj->v), proxy->dimensions, &lbound); + SafeArrayGetUBound(V_ARRAY(&proxy->obj->v), proxy->dimensions, &ubound); + + *count = ubound - lbound + 1; + + return SUCCESS; +} + +zend_object_handlers php_com_saproxy_handlers = { + ZEND_OBJECTS_STORE_HANDLERS, + saproxy_property_read, + saproxy_property_write, + saproxy_read_dimension, + saproxy_write_dimension, + NULL, + NULL, /* saproxy_object_get, */ + NULL, /* saproxy_object_set, */ + saproxy_property_exists, + saproxy_property_delete, + saproxy_dimension_exists, + saproxy_dimension_delete, + saproxy_properties_get, + saproxy_method_get, + saproxy_call_method, + saproxy_constructor_get, + saproxy_class_entry_get, + saproxy_class_name_get, + saproxy_objects_compare, + saproxy_object_cast, + saproxy_count_elements +}; + +static void saproxy_free_storage(void *object TSRMLS_DC) +{ + php_com_saproxy *proxy = (php_com_saproxy *)object; + int i; + + for (i = 0; i < proxy->dimensions; i++) { + if (proxy->indices) { + FREE_ZVAL(proxy->indices[i]); + } + } + + zval_ptr_dtor(&proxy->zobj); + efree(proxy->indices); + efree(proxy); +} + +static void saproxy_clone(void *object, void **clone_ptr TSRMLS_DC) +{ + php_com_saproxy *proxy = (php_com_saproxy *)object; + php_com_saproxy *cloneproxy; + + cloneproxy = emalloc(sizeof(*cloneproxy)); + memcpy(cloneproxy, proxy, sizeof(*cloneproxy)); + + Z_ADDREF_P(cloneproxy->zobj); + cloneproxy->indices = safe_emalloc(cloneproxy->dimensions, sizeof(zval *), 0); + clone_indices(cloneproxy, proxy, proxy->dimensions); + + *clone_ptr = cloneproxy; +} + +int php_com_saproxy_create(zval *com_object, zval *proxy_out, zval *index TSRMLS_DC) +{ + php_com_saproxy *proxy, *rel = NULL; + + proxy = ecalloc(1, sizeof(*proxy)); + proxy->dimensions = 1; + + if (Z_OBJCE_P(com_object) == php_com_saproxy_class_entry) { + rel = SA_FETCH(com_object); + proxy->obj = rel->obj; + proxy->zobj = rel->zobj; + proxy->dimensions += rel->dimensions; + } else { + proxy->obj = CDNO_FETCH(com_object); + proxy->zobj = com_object; + } + + Z_ADDREF_P(proxy->zobj); + proxy->indices = safe_emalloc(proxy->dimensions, sizeof(zval *), 0); + + if (rel) { + clone_indices(proxy, rel, rel->dimensions); + } + + MAKE_STD_ZVAL(proxy->indices[proxy->dimensions-1]); + *proxy->indices[proxy->dimensions-1] = *index; + zval_copy_ctor(proxy->indices[proxy->dimensions-1]); + + Z_TYPE_P(proxy_out) = IS_OBJECT; + Z_OBJ_HANDLE_P(proxy_out) = zend_objects_store_put(proxy, NULL, saproxy_free_storage, saproxy_clone TSRMLS_CC); + Z_OBJ_HT_P(proxy_out) = &php_com_saproxy_handlers; + + return 1; +} + +/* iterator */ + +static void saproxy_iter_dtor(zend_object_iterator *iter TSRMLS_DC) +{ + php_com_saproxy_iter *I = (php_com_saproxy_iter*)iter->data; + + zval_ptr_dtor(&I->proxy_obj); + + efree(I->indices); + efree(I); +} + +static int saproxy_iter_valid(zend_object_iterator *iter TSRMLS_DC) +{ + php_com_saproxy_iter *I = (php_com_saproxy_iter*)iter->data; + + return (I->key < I->imax) ? SUCCESS : FAILURE; +} + +static void saproxy_iter_get_data(zend_object_iterator *iter, zval ***data TSRMLS_DC) +{ + php_com_saproxy_iter *I = (php_com_saproxy_iter*)iter->data; + VARIANT v; + VARTYPE vt; + zval *return_value, **ptr_ptr; + SAFEARRAY *sa; + + I->indices[I->proxy->dimensions-1] = I->key; + + sa = V_ARRAY(&I->proxy->obj->v); + + if (FAILED(SafeArrayGetVartype(sa, &vt)) || vt == VT_EMPTY) { + vt = V_VT(&I->proxy->obj->v) & ~VT_ARRAY; + } + + VariantInit(&v); + if (vt == VT_VARIANT) { + SafeArrayGetElement(sa, I->indices, &v); + } else { + V_VT(&v) = vt; + SafeArrayGetElement(sa, I->indices, &v.lVal); + } + + MAKE_STD_ZVAL(return_value); + php_com_wrap_variant(return_value, &v, I->proxy->obj->code_page TSRMLS_CC); + VariantClear(&v); + + ptr_ptr = emalloc(sizeof(*ptr_ptr)); + *ptr_ptr = return_value; + *data = ptr_ptr; +} + +static int saproxy_iter_get_key(zend_object_iterator *iter, char **str_key, uint *str_key_len, + ulong *int_key TSRMLS_DC) +{ + php_com_saproxy_iter *I = (php_com_saproxy_iter*)iter->data; + + if (I->key == -1) { + return HASH_KEY_NON_EXISTANT; + } + *int_key = (ulong)I->key; + return HASH_KEY_IS_LONG; +} + +static int saproxy_iter_move_forwards(zend_object_iterator *iter TSRMLS_DC) +{ + php_com_saproxy_iter *I = (php_com_saproxy_iter*)iter->data; + + if (++I->key >= I->imax) { + I->key = -1; + return FAILURE; + } + return SUCCESS; +} + +static zend_object_iterator_funcs saproxy_iter_funcs = { + saproxy_iter_dtor, + saproxy_iter_valid, + saproxy_iter_get_data, + saproxy_iter_get_key, + saproxy_iter_move_forwards, + NULL +}; + + +zend_object_iterator *php_com_saproxy_iter_get(zend_class_entry *ce, zval *object, int by_ref TSRMLS_DC) +{ + php_com_saproxy *proxy = SA_FETCH(object); + php_com_saproxy_iter *I; + int i; + + if (by_ref) { + zend_error(E_ERROR, "An iterator cannot be used with foreach by reference"); + } + + I = ecalloc(1, sizeof(*I)); + I->iter.funcs = &saproxy_iter_funcs; + I->iter.data = I; + + I->proxy = proxy; + I->proxy_obj = object; + Z_ADDREF_P(I->proxy_obj); + + I->indices = safe_emalloc(proxy->dimensions + 1, sizeof(LONG), 0); + for (i = 0; i < proxy->dimensions; i++) { + convert_to_long(proxy->indices[i]); + I->indices[i] = Z_LVAL_P(proxy->indices[i]); + } + + SafeArrayGetLBound(V_ARRAY(&proxy->obj->v), proxy->dimensions, &I->imin); + SafeArrayGetUBound(V_ARRAY(&proxy->obj->v), proxy->dimensions, &I->imax); + + I->key = I->imin; + + return &I->iter; +} + diff --git a/ext/com_dotnet/com_typeinfo.c b/ext/com_dotnet/com_typeinfo.c new file mode 100644 index 0000000..482b9b8 --- /dev/null +++ b/ext/com_dotnet/com_typeinfo.c @@ -0,0 +1,607 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2013 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong <wez@thebrainroom.com> | + | Harald Radi <h.radi@nme.at> | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "php_com_dotnet.h" +#include "php_com_dotnet_internal.h" + + +/* The search string can be either: + * a) a file name + * b) a CLSID, major, minor e.g. "{00000200-0000-0010-8000-00AA006D2EA4},2,0" + * c) a Type Library name e.g. "Microsoft OLE DB ActiveX Data Objects 1.0 Library" + */ +PHP_COM_DOTNET_API ITypeLib *php_com_load_typelib(char *search_string, int codepage TSRMLS_DC) +{ + ITypeLib *TL = NULL; + char *strtok_buf, *major, *minor; + CLSID clsid; + OLECHAR *p; + HRESULT hr; + + search_string = php_strtok_r(search_string, ",", &strtok_buf); + + if (search_string == NULL) { + return NULL; + } + + major = php_strtok_r(NULL, ",", &strtok_buf); + minor = php_strtok_r(NULL, ",", &strtok_buf); + + p = php_com_string_to_olestring(search_string, strlen(search_string), codepage TSRMLS_CC); + + if (SUCCEEDED(CLSIDFromString(p, &clsid))) { + WORD major_i = 1, minor_i = 0; + + /* pick up the major/minor numbers; if none specified, default to 1,0 */ + if (major && minor) { + major_i = (WORD)atoi(major); + minor_i = (WORD)atoi(minor); + } + + /* Load the TypeLib by GUID */ + hr = LoadRegTypeLib((REFGUID)&clsid, major_i, minor_i, LANG_NEUTRAL, &TL); + + /* if that failed, assumed that the GUID is actually a CLSID and + * attemp to get the library via an instance of that class */ + if (FAILED(hr) && (major == NULL || minor == NULL)) { + IDispatch *disp = NULL; + ITypeInfo *info = NULL; + int idx; + + if (SUCCEEDED(hr = CoCreateInstance(&clsid, NULL, CLSCTX_SERVER, &IID_IDispatch, (LPVOID*)&disp)) && + SUCCEEDED(hr = IDispatch_GetTypeInfo(disp, 0, LANG_NEUTRAL, &info))) { + hr = ITypeInfo_GetContainingTypeLib(info, &TL, &idx); + } + + if (info) { + ITypeInfo_Release(info); + } + if (disp) { + IDispatch_Release(disp); + } + } + } else { + /* Try to load it from a file; if it fails, do a really painful search of + * the registry */ + if (FAILED(LoadTypeLib(p, &TL))) { + HKEY hkey, hsubkey; + DWORD SubKeys, MaxSubKeyLength; + char *keyname; + unsigned int i, j; + DWORD VersionCount; + char version[20]; + char *libname; + DWORD libnamelen; + + if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_CLASSES_ROOT, "TypeLib", 0, KEY_READ, &hkey) && + ERROR_SUCCESS == RegQueryInfoKey(hkey, NULL, NULL, NULL, &SubKeys, + &MaxSubKeyLength, NULL, NULL, NULL, NULL, NULL, NULL)) { + + MaxSubKeyLength++; /* make room for NUL */ + keyname = emalloc(MaxSubKeyLength); + libname = emalloc(strlen(search_string) + 1); + + for (i = 0; i < SubKeys && TL == NULL; i++) { + if (ERROR_SUCCESS == RegEnumKey(hkey, i, keyname, MaxSubKeyLength) && + ERROR_SUCCESS == RegOpenKeyEx(hkey, keyname, 0, KEY_READ, &hsubkey)) { + if (ERROR_SUCCESS == RegQueryInfoKey(hsubkey, NULL, NULL, NULL, &VersionCount, + NULL, NULL, NULL, NULL, NULL, NULL, NULL)) { + for (j = 0; j < VersionCount; j++) { + if (ERROR_SUCCESS != RegEnumKey(hsubkey, j, version, sizeof(version))) { + continue; + } + /* get the default value for this key and compare */ + libnamelen = strlen(search_string)+1; + if (ERROR_SUCCESS == RegQueryValue(hsubkey, version, libname, &libnamelen)) { + if (0 == stricmp(libname, search_string)) { + char *str = NULL; + int major_tmp, minor_tmp; + + /* fetch the GUID and add the version numbers */ + if (2 != sscanf(version, "%d.%d", &major_tmp, &minor_tmp)) { + major_tmp = 1; + minor_tmp = 0; + } + spprintf(&str, 0, "%s,%d,%d", keyname, major_tmp, minor_tmp); + /* recurse */ + TL = php_com_load_typelib(str, codepage TSRMLS_CC); + + efree(str); + break; + } + } + } + } + RegCloseKey(hsubkey); + } + } + RegCloseKey(hkey); + efree(keyname); + efree(libname); + } + } + } + + efree(p); + + return TL; +} + +/* Given a type-library, merge it into the current engine state */ +PHP_COM_DOTNET_API int php_com_import_typelib(ITypeLib *TL, int mode, int codepage TSRMLS_DC) +{ + int i, j, interfaces; + TYPEKIND pTKind; + ITypeInfo *TypeInfo; + VARDESC *pVarDesc; + UINT NameCount; + BSTR bstr_ids; + zend_constant c; + zval exists, results, value; + char *const_name; + + if (TL == NULL) { + return FAILURE; + } + + interfaces = ITypeLib_GetTypeInfoCount(TL); + for (i = 0; i < interfaces; i++) { + ITypeLib_GetTypeInfoType(TL, i, &pTKind); + if (pTKind == TKIND_ENUM) { + ITypeLib_GetTypeInfo(TL, i, &TypeInfo); + for (j = 0; ; j++) { + if (FAILED(ITypeInfo_GetVarDesc(TypeInfo, j, &pVarDesc))) { + break; + } + ITypeInfo_GetNames(TypeInfo, pVarDesc->memid, &bstr_ids, 1, &NameCount); + if (NameCount != 1) { + ITypeInfo_ReleaseVarDesc(TypeInfo, pVarDesc); + continue; + } + + const_name = php_com_olestring_to_string(bstr_ids, &c.name_len, codepage TSRMLS_CC); + c.name = zend_strndup(const_name, c.name_len); + efree(const_name); + if(c.name == NULL) { + ITypeInfo_ReleaseVarDesc(TypeInfo, pVarDesc); + continue; + } + c.name_len++; /* include NUL */ + SysFreeString(bstr_ids); + + /* sanity check for the case where the constant is already defined */ + if (zend_get_constant(c.name, c.name_len - 1, &exists TSRMLS_CC)) { + if (COMG(autoreg_verbose) && !compare_function(&results, &c.value, &exists TSRMLS_CC)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Type library constant %s is already defined", c.name); + } + free(c.name); + ITypeInfo_ReleaseVarDesc(TypeInfo, pVarDesc); + continue; + } + + /* register the constant */ + php_com_zval_from_variant(&value, pVarDesc->lpvarValue, codepage TSRMLS_CC); + if (Z_TYPE(value) == IS_LONG) { + c.flags = mode; + c.value.type = IS_LONG; + c.value.value.lval = Z_LVAL(value); + c.module_number = 0; + zend_register_constant(&c TSRMLS_CC); + } + ITypeInfo_ReleaseVarDesc(TypeInfo, pVarDesc); + } + ITypeInfo_Release(TypeInfo); + } + } + return SUCCESS; +} + +/* Type-library stuff */ +void php_com_typelibrary_dtor(void *pDest) +{ + ITypeLib **Lib = (ITypeLib**)pDest; + ITypeLib_Release(*Lib); +} + +PHP_COM_DOTNET_API ITypeLib *php_com_load_typelib_via_cache(char *search_string, + int codepage, int *cached TSRMLS_DC) +{ + ITypeLib **TLp; + ITypeLib *TL; + char *name_dup; + int l; + + l = strlen(search_string); + + if (zend_ts_hash_find(&php_com_typelibraries, search_string, l+1, + (void**)&TLp) == SUCCESS) { + *cached = 1; + /* add a reference for the caller */ + ITypeLib_AddRef(*TLp); + return *TLp; + } + + *cached = 0; + name_dup = estrndup(search_string, l); + TL = php_com_load_typelib(name_dup, codepage TSRMLS_CC); + efree(name_dup); + + if (TL) { + if (SUCCESS == zend_ts_hash_update(&php_com_typelibraries, + search_string, l+1, (void*)&TL, sizeof(ITypeLib*), NULL)) { + /* add a reference for the hash table */ + ITypeLib_AddRef(TL); + } + } + + return TL; +} + +ITypeInfo *php_com_locate_typeinfo(char *typelibname, php_com_dotnet_object *obj, char *dispname, int sink TSRMLS_DC) +{ + ITypeInfo *typeinfo = NULL; + ITypeLib *typelib = NULL; + int gotguid = 0; + GUID iid; + + if (obj) { + if (dispname == NULL && sink) { + IProvideClassInfo2 *pci2; + IProvideClassInfo *pci; + + if (SUCCEEDED(IDispatch_QueryInterface(V_DISPATCH(&obj->v), &IID_IProvideClassInfo2, (void**)&pci2))) { + gotguid = SUCCEEDED(IProvideClassInfo2_GetGUID(pci2, GUIDKIND_DEFAULT_SOURCE_DISP_IID, &iid)); + IProvideClassInfo2_Release(pci2); + } + if (!gotguid && SUCCEEDED(IDispatch_QueryInterface(V_DISPATCH(&obj->v), &IID_IProvideClassInfo, (void**)&pci))) { + /* examine the available interfaces */ + /* TODO: write some code here */ + php_error_docref(NULL TSRMLS_CC, E_WARNING, "IProvideClassInfo: this code not yet written!"); + IProvideClassInfo_Release(pci); + } + } else if (dispname == NULL) { + if (obj->typeinfo) { + ITypeInfo_AddRef(obj->typeinfo); + return obj->typeinfo; + } else { + IDispatch_GetTypeInfo(V_DISPATCH(&obj->v), 0, LANG_NEUTRAL, &typeinfo); + if (typeinfo) { + return typeinfo; + } + } + } else if (dispname && obj->typeinfo) { + unsigned int idx; + /* get the library from the object; the rest will be dealt with later */ + ITypeInfo_GetContainingTypeLib(obj->typeinfo, &typelib, &idx); + } else if (typelibname == NULL) { + IDispatch_GetTypeInfo(V_DISPATCH(&obj->v), 0, LANG_NEUTRAL, &typeinfo); + if (dispname) { + unsigned int idx; + /* get the library from the object; the rest will be dealt with later */ + ITypeInfo_GetContainingTypeLib(typeinfo, &typelib, &idx); + + if (typelib) { + ITypeInfo_Release(typeinfo); + typeinfo = NULL; + } + } + } + } else if (typelibname) { + /* Fetch the typelibrary and use that to look things up */ + typelib = php_com_load_typelib(typelibname, CP_THREAD_ACP TSRMLS_CC); + } + + if (!gotguid && dispname && typelib) { + unsigned short cfound; + MEMBERID memid; + OLECHAR *olename = php_com_string_to_olestring(dispname, strlen(dispname), CP_ACP TSRMLS_CC); + + cfound = 1; + if (FAILED(ITypeLib_FindName(typelib, olename, 0, &typeinfo, &memid, &cfound)) || cfound == 0) { + CLSID coclass; + ITypeInfo *coinfo; + + /* assume that it might be a progid instead */ + if (SUCCEEDED(CLSIDFromProgID(olename, &coclass)) && + SUCCEEDED(ITypeLib_GetTypeInfoOfGuid(typelib, &coclass, &coinfo))) { + + /* enumerate implemented interfaces and pick the one as indicated by sink */ + TYPEATTR *attr; + int i; + + ITypeInfo_GetTypeAttr(coinfo, &attr); + + for (i = 0; i < attr->cImplTypes; i++) { + HREFTYPE rt; + int tf; + + if (FAILED(ITypeInfo_GetImplTypeFlags(coinfo, i, &tf))) { + continue; + } + + if ((sink && tf == (IMPLTYPEFLAG_FSOURCE|IMPLTYPEFLAG_FDEFAULT)) || + (!sink && (tf & IMPLTYPEFLAG_FSOURCE) == 0)) { + + /* flags match what we are looking for */ + + if (SUCCEEDED(ITypeInfo_GetRefTypeOfImplType(coinfo, i, &rt))) + if (SUCCEEDED(ITypeInfo_GetRefTypeInfo(coinfo, rt, &typeinfo))) + break; + + } + } + + ITypeInfo_ReleaseTypeAttr(coinfo, attr); + ITypeInfo_Release(coinfo); + } + } + + + efree(olename); + } else if (gotguid) { + ITypeLib_GetTypeInfoOfGuid(typelib, &iid, &typeinfo); + } + + if (typelib) { + ITypeLib_Release(typelib); + } + + return typeinfo; +} + +static const struct { + VARTYPE vt; + const char *name; +} vt_names[] = { + { VT_NULL, "VT_NULL" }, + { VT_EMPTY, "VT_EMPTY" }, + { VT_UI1, "VT_UI1" }, + { VT_I2, "VT_I2" }, + { VT_I4, "VT_I4" }, + { VT_R4, "VT_R4" }, + { VT_R8, "VT_R8" }, + { VT_BOOL, "VT_BOOL" }, + { VT_ERROR, "VT_ERROR" }, + { VT_CY, "VT_CY" }, + { VT_DATE, "VT_DATE" }, + { VT_BSTR, "VT_BSTR" }, + { VT_DECIMAL, "VT_DECIMAL" }, + { VT_UNKNOWN, "VT_UNKNOWN" }, + { VT_DISPATCH, "VT_DISPATCH" }, + { VT_VARIANT, "VT_VARIANT" }, + { VT_I1, "VT_I1" }, + { VT_UI2, "VT_UI2" }, + { VT_UI4, "VT_UI4" }, + { VT_INT, "VT_INT" }, + { VT_UINT, "VT_UINT" }, + { VT_ARRAY, "VT_ARRAY" }, + { VT_BYREF, "VT_BYREF" }, + { VT_VOID, "VT_VOID" }, + { VT_PTR, "VT_PTR" }, + { VT_HRESULT, "VT_HRESULT" }, + { VT_SAFEARRAY, "VT_SAFEARRAY" }, + { 0, NULL } +}; + +static inline const char *vt_to_string(VARTYPE vt) +{ + int i; + for (i = 0; vt_names[i].name != NULL; i++) { + if (vt_names[i].vt == vt) + return vt_names[i].name; + } + return "?"; +} + +static char *php_com_string_from_clsid(const CLSID *clsid, int codepage TSRMLS_DC) +{ + LPOLESTR ole_clsid; + char *clsid_str; + + StringFromCLSID(clsid, &ole_clsid); + clsid_str = php_com_olestring_to_string(ole_clsid, NULL, codepage TSRMLS_CC); + LocalFree(ole_clsid); + + return clsid_str; +} + + +int php_com_process_typeinfo(ITypeInfo *typeinfo, HashTable *id_to_name, int printdef, GUID *guid, int codepage TSRMLS_DC) +{ + TYPEATTR *attr; + FUNCDESC *func; + int i; + OLECHAR *olename; + char *ansiname = NULL; + unsigned int ansinamelen; + int ret = 0; + + if (FAILED(ITypeInfo_GetTypeAttr(typeinfo, &attr))) { + return 0; + } + + /* verify that it is suitable */ + if (id_to_name == NULL || attr->typekind == TKIND_DISPATCH) { + + if (guid) { + memcpy(guid, &attr->guid, sizeof(GUID)); + } + + if (printdef) { + char *guidstring; + + ITypeInfo_GetDocumentation(typeinfo, MEMBERID_NIL, &olename, NULL, NULL, NULL); + ansiname = php_com_olestring_to_string(olename, &ansinamelen, codepage TSRMLS_CC); + SysFreeString(olename); + + guidstring = php_com_string_from_clsid(&attr->guid, codepage TSRMLS_CC); + php_printf("class %s { /* GUID=%s */\n", ansiname, guidstring); + efree(guidstring); + + efree(ansiname); + } + + if (id_to_name) { + zend_hash_init(id_to_name, 0, NULL, ZVAL_PTR_DTOR, 0); + } + + /* So we've got the dispatch interface; lets list the event methods */ + for (i = 0; i < attr->cFuncs; i++) { + zval *tmp; + DISPID lastid = 0; /* for props */ + int isprop; + + if (FAILED(ITypeInfo_GetFuncDesc(typeinfo, i, &func))) + break; + + isprop = (func->invkind & DISPATCH_PROPERTYGET || func->invkind & DISPATCH_PROPERTYPUT); + + if (!isprop || lastid != func->memid) { + + lastid = func->memid; + + ITypeInfo_GetDocumentation(typeinfo, func->memid, &olename, NULL, NULL, NULL); + ansiname = php_com_olestring_to_string(olename, &ansinamelen, codepage TSRMLS_CC); + SysFreeString(olename); + + if (printdef) { + int j; + char *funcdesc; + unsigned int funcdesclen, cnames = 0; + BSTR *names; + + names = (BSTR*)safe_emalloc((func->cParams + 1), sizeof(BSTR), 0); + + ITypeInfo_GetNames(typeinfo, func->memid, names, func->cParams + 1, &cnames); + /* first element is the function name */ + SysFreeString(names[0]); + + php_printf("\t/* DISPID=%d */\n", func->memid); + + if (func->elemdescFunc.tdesc.vt != VT_VOID) { + php_printf("\t/* %s [%d] */\n", + vt_to_string(func->elemdescFunc.tdesc.vt), + func->elemdescFunc.tdesc.vt + ); + } + + if (isprop) { + + ITypeInfo_GetDocumentation(typeinfo, func->memid, NULL, &olename, NULL, NULL); + if (olename) { + funcdesc = php_com_olestring_to_string(olename, &funcdesclen, codepage TSRMLS_CC); + SysFreeString(olename); + php_printf("\t/* %s */\n", funcdesc); + efree(funcdesc); + } + + php_printf("\tvar $%s;\n\n", ansiname); + + } else { + /* a function */ + + php_printf("\tfunction %s(\n", ansiname); + + for (j = 0; j < func->cParams; j++) { + ELEMDESC *elem = &func->lprgelemdescParam[j]; + + php_printf("\t\t/* %s [%d] ", vt_to_string(elem->tdesc.vt), elem->tdesc.vt); + + if (elem->paramdesc.wParamFlags & PARAMFLAG_FIN) + php_printf("[in]"); + if (elem->paramdesc.wParamFlags & PARAMFLAG_FOUT) + php_printf("[out]"); + + if (elem->tdesc.vt == VT_PTR) { + /* what does it point to ? */ + php_printf(" --> %s [%d] ", + vt_to_string(elem->tdesc.lptdesc->vt), + elem->tdesc.lptdesc->vt + ); + } + + /* when we handle prop put and get, this will look nicer */ + if (j+1 < (int)cnames) { + funcdesc = php_com_olestring_to_string(names[j+1], &funcdesclen, codepage TSRMLS_CC); + SysFreeString(names[j+1]); + } else { + funcdesc = "???"; + } + + php_printf(" */ %s%s%c\n", + elem->tdesc.vt == VT_PTR ? "&$" : "$", + funcdesc, + j == func->cParams - 1 ? ' ' : ',' + ); + + if (j+1 < (int)cnames) { + efree(funcdesc); + } + } + + php_printf("\t\t)\n\t{\n"); + + ITypeInfo_GetDocumentation(typeinfo, func->memid, NULL, &olename, NULL, NULL); + if (olename) { + funcdesc = php_com_olestring_to_string(olename, &funcdesclen, codepage TSRMLS_CC); + SysFreeString(olename); + php_printf("\t\t/* %s */\n", funcdesc); + efree(funcdesc); + } + + php_printf("\t}\n"); + } + + efree(names); + } + + if (id_to_name) { + zend_str_tolower(ansiname, ansinamelen); + MAKE_STD_ZVAL(tmp); + ZVAL_STRINGL(tmp, ansiname, ansinamelen, 0); + zend_hash_index_update(id_to_name, func->memid, (void*)&tmp, sizeof(zval *), NULL); + } + } + ITypeInfo_ReleaseFuncDesc(typeinfo, func); + } + + if (printdef) { + php_printf("}\n"); + } + + ret = 1; + } else { + zend_error(E_WARNING, "That's not a dispatchable interface!! type kind = %08x", attr->typekind); + } + + ITypeInfo_ReleaseTypeAttr(typeinfo, attr); + + return ret; +} + + diff --git a/ext/com_dotnet/com_variant.c b/ext/com_dotnet/com_variant.c new file mode 100644 index 0000000..853b7df --- /dev/null +++ b/ext/com_dotnet/com_variant.c @@ -0,0 +1,1075 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2013 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong <wez@thebrainroom.com> | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "php_com_dotnet.h" +#include "php_com_dotnet_internal.h" + +/* create an automation SafeArray from a PHP array. + * Only creates a single-dimensional array of variants. + * The keys of the PHP hash MUST be numeric. If the array + * is sparse, then the gaps will be filled with NULL variants */ +static void safe_array_from_zval(VARIANT *v, zval *z, int codepage TSRMLS_DC) +{ + SAFEARRAY *sa = NULL; + SAFEARRAYBOUND bound; + HashPosition pos; + int keytype; + char *strindex; + int strindexlen; + long intindex = -1; + long max_index = 0; + VARIANT *va; + zval **item; + + /* find the largest array index, and assert that all keys are integers */ + zend_hash_internal_pointer_reset_ex(HASH_OF(z), &pos); + for (;; zend_hash_move_forward_ex(HASH_OF(z), &pos)) { + + keytype = zend_hash_get_current_key_ex(HASH_OF(z), &strindex, &strindexlen, &intindex, 0, &pos); + + if (HASH_KEY_IS_STRING == keytype) { + goto bogus; + } else if (HASH_KEY_NON_EXISTANT == keytype) { + break; + } + if (intindex > max_index) { + max_index = intindex; + } + } + + /* allocate the structure */ + bound.lLbound = 0; + bound.cElements = intindex + 1; + sa = SafeArrayCreate(VT_VARIANT, 1, &bound); + + /* get a lock on the array itself */ + SafeArrayAccessData(sa, &va); + va = (VARIANT*)sa->pvData; + + /* now fill it in */ + zend_hash_internal_pointer_reset_ex(HASH_OF(z), &pos); + for (;; zend_hash_move_forward_ex(HASH_OF(z), &pos)) { + if (FAILURE == zend_hash_get_current_data_ex(HASH_OF(z), (void**)&item, &pos)) { + break; + } + zend_hash_get_current_key_ex(HASH_OF(z), &strindex, &strindexlen, &intindex, 0, &pos); + php_com_variant_from_zval(&va[intindex], *item, codepage TSRMLS_CC); + } + + /* Unlock it and stuff it into our variant */ + SafeArrayUnaccessData(sa); + V_VT(v) = VT_ARRAY|VT_VARIANT; + V_ARRAY(v) = sa; + + return; + +bogus: + php_error_docref(NULL TSRMLS_CC, E_WARNING, "COM: converting from PHP array to VARIANT array; only arrays with numeric keys are allowed"); + + V_VT(v) = VT_NULL; + + if (sa) { + SafeArrayUnlock(sa); + SafeArrayDestroy(sa); + } +} + +PHP_COM_DOTNET_API void php_com_variant_from_zval(VARIANT *v, zval *z, int codepage TSRMLS_DC) +{ + OLECHAR *olestring; + php_com_dotnet_object *obj; + + switch (Z_TYPE_P(z)) { + case IS_NULL: + V_VT(v) = VT_NULL; + break; + + case IS_BOOL: + V_VT(v) = VT_BOOL; + V_BOOL(v) = Z_BVAL_P(z) ? VARIANT_TRUE : VARIANT_FALSE; + break; + + case IS_OBJECT: + if (php_com_is_valid_object(z TSRMLS_CC)) { + obj = CDNO_FETCH(z); + if (V_VT(&obj->v) == VT_DISPATCH) { + /* pass the underlying object */ + V_VT(v) = VT_DISPATCH; + if (V_DISPATCH(&obj->v)) { + IDispatch_AddRef(V_DISPATCH(&obj->v)); + } + V_DISPATCH(v) = V_DISPATCH(&obj->v); + } else { + /* pass the variant by reference */ + V_VT(v) = VT_VARIANT | VT_BYREF; + V_VARIANTREF(v) = &obj->v; + } + } else { + /* export the PHP object using our COM wrapper */ + V_VT(v) = VT_DISPATCH; + V_DISPATCH(v) = php_com_wrapper_export(z TSRMLS_CC); + } + break; + + case IS_ARRAY: + /* map as safe array */ + safe_array_from_zval(v, z, codepage TSRMLS_CC); + break; + + case IS_LONG: + V_VT(v) = VT_I4; + V_I4(v) = Z_LVAL_P(z); + break; + + case IS_DOUBLE: + V_VT(v) = VT_R8; + V_R8(v) = Z_DVAL_P(z); + break; + + case IS_STRING: + V_VT(v) = VT_BSTR; + olestring = php_com_string_to_olestring(Z_STRVAL_P(z), Z_STRLEN_P(z), codepage TSRMLS_CC); + V_BSTR(v) = SysAllocStringByteLen((char*)olestring, Z_STRLEN_P(z) * sizeof(OLECHAR)); + efree(olestring); + break; + + case IS_RESOURCE: + case IS_CONSTANT: + case IS_CONSTANT_ARRAY: + default: + V_VT(v) = VT_NULL; + break; + } +} + +PHP_COM_DOTNET_API int php_com_zval_from_variant(zval *z, VARIANT *v, int codepage TSRMLS_DC) +{ + OLECHAR *olestring = NULL; + int ret = SUCCESS; + + switch (V_VT(v)) { + case VT_EMPTY: + case VT_NULL: + case VT_VOID: + ZVAL_NULL(z); + break; + case VT_UI1: + ZVAL_LONG(z, (long)V_UI1(v)); + break; + case VT_I1: + ZVAL_LONG(z, (long)V_I1(v)); + break; + case VT_UI2: + ZVAL_LONG(z, (long)V_UI2(v)); + break; + case VT_I2: + ZVAL_LONG(z, (long)V_I2(v)); + break; + case VT_UI4: /* TODO: promote to double if large? */ + ZVAL_LONG(z, (long)V_UI4(v)); + break; + case VT_I4: + ZVAL_LONG(z, (long)V_I4(v)); + break; + case VT_INT: + ZVAL_LONG(z, V_INT(v)); + break; + case VT_UINT: /* TODO: promote to double if large? */ + ZVAL_LONG(z, (long)V_UINT(v)); + break; + case VT_R4: + ZVAL_DOUBLE(z, (double)V_R4(v)); + break; + case VT_R8: + ZVAL_DOUBLE(z, V_R8(v)); + break; + case VT_BOOL: + ZVAL_BOOL(z, V_BOOL(v) ? 1 : 0); + break; + case VT_BSTR: + olestring = V_BSTR(v); + if (olestring) { + Z_TYPE_P(z) = IS_STRING; + Z_STRVAL_P(z) = php_com_olestring_to_string(olestring, + &Z_STRLEN_P(z), codepage TSRMLS_CC); + olestring = NULL; + } + break; + case VT_UNKNOWN: + if (V_UNKNOWN(v) != NULL) { + IDispatch *disp; + + if (SUCCEEDED(IUnknown_QueryInterface(V_UNKNOWN(v), &IID_IDispatch, &disp))) { + php_com_wrap_dispatch(z, disp, codepage TSRMLS_CC); + IDispatch_Release(disp); + } else { + ret = FAILURE; + } + } + break; + + case VT_DISPATCH: + if (V_DISPATCH(v) != NULL) { + php_com_wrap_dispatch(z, V_DISPATCH(v), codepage TSRMLS_CC); + } + break; + + case VT_VARIANT: + /* points to another variant */ + return php_com_zval_from_variant(z, V_VARIANTREF(v), codepage TSRMLS_CC); + + default: + php_com_wrap_variant(z, v, codepage TSRMLS_CC); + } + + if (olestring) { + efree(olestring); + } + + if (ret == FAILURE) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "variant->zval: conversion from 0x%x ret=%d", V_VT(v), ret); + } + + return ret; +} + + +PHP_COM_DOTNET_API int php_com_copy_variant(VARIANT *dstvar, VARIANT *srcvar TSRMLS_DC) +{ + int ret = SUCCESS; + + switch (V_VT(dstvar) & ~VT_BYREF) { + case VT_EMPTY: + case VT_NULL: + case VT_VOID: + /* should not be possible */ + break; + + case VT_UI1: + if (V_VT(dstvar) & VT_BYREF) { + *V_UI1REF(dstvar) = V_UI1(srcvar); + } else { + V_UI1(dstvar) = V_UI1(srcvar); + } + break; + + case VT_I1: + if (V_VT(dstvar) & VT_BYREF) { + *V_I1REF(dstvar) = V_I1(srcvar); + } else { + V_I1(dstvar) = V_I1(srcvar); + } + break; + + case VT_UI2: + if (V_VT(dstvar) & VT_BYREF) { + *V_UI2REF(dstvar) = V_UI2(srcvar); + } else { + V_UI2(dstvar) = V_UI2(srcvar); + } + break; + + case VT_I2: + if (V_VT(dstvar) & VT_BYREF) { + *V_I2REF(dstvar) = V_I2(srcvar); + } else { + V_I2(dstvar) = V_I2(srcvar); + } + break; + + case VT_UI4: + if (V_VT(dstvar) & VT_BYREF) { + *V_UI4REF(dstvar) = V_UI4(srcvar); + } else { + V_UI4(dstvar) = V_UI4(srcvar); + } + break; + + case VT_I4: + if (V_VT(dstvar) & VT_BYREF) { + *V_I4REF(dstvar) = V_I4(srcvar); + } else { + V_I4(dstvar) = V_I4(srcvar); + } + break; + + case VT_INT: + if (V_VT(dstvar) & VT_BYREF) { + *V_INTREF(dstvar) = V_INT(srcvar); + } else { + V_INT(dstvar) = V_INT(srcvar); + } + break; + + case VT_UINT: + if (V_VT(dstvar) & VT_BYREF) { + *V_UINTREF(dstvar) = V_UINT(srcvar); + } else { + V_UINT(dstvar) = V_UINT(srcvar); + } + break; + + case VT_R4: + if (V_VT(dstvar) & VT_BYREF) { + *V_R4REF(dstvar) = V_R4(srcvar); + } else { + V_R4(dstvar) = V_R4(srcvar); + } + break; + + case VT_R8: + if (V_VT(dstvar) & VT_BYREF) { + *V_R8REF(dstvar) = V_R8(srcvar); + } else { + V_R8(dstvar) = V_R8(srcvar); + } + break; + + case VT_BOOL: + if (V_VT(dstvar) & VT_BYREF) { + *V_BOOLREF(dstvar) = V_BOOL(srcvar); + } else { + V_BOOL(dstvar) = V_BOOL(srcvar); + } + break; + + case VT_BSTR: + if (V_VT(dstvar) & VT_BYREF) { + *V_BSTRREF(dstvar) = V_BSTR(srcvar); + } else { + V_BSTR(dstvar) = V_BSTR(srcvar); + } + break; + + case VT_UNKNOWN: + if (V_VT(dstvar) & VT_BYREF) { + *V_UNKNOWNREF(dstvar) = V_UNKNOWN(srcvar); + } else { + V_UNKNOWN(dstvar) = V_UNKNOWN(srcvar); + } + break; + + case VT_DISPATCH: + if (V_VT(dstvar) & VT_BYREF) { + *V_DISPATCHREF(dstvar) = V_DISPATCH(srcvar); + } else { + V_DISPATCH(dstvar) = V_DISPATCH(srcvar); + } + break; + + case VT_VARIANT: + return php_com_copy_variant(V_VARIANTREF(dstvar), srcvar TSRMLS_CC); + + default: + php_error_docref(NULL TSRMLS_CC, E_WARNING, "variant->variant: failed to copy from 0x%x to 0x%x", V_VT(dstvar), V_VT(srcvar)); + ret = FAILURE; + } + return ret; +} + +/* {{{ com_variant_create_instance - ctor for new VARIANT() */ +PHP_FUNCTION(com_variant_create_instance) +{ + /* VARTYPE == unsigned short */ long vt = VT_EMPTY; + long codepage = CP_ACP; + zval *object = getThis(); + php_com_dotnet_object *obj; + zval *zvalue = NULL; + HRESULT res; + + if (ZEND_NUM_ARGS() == 0) { + /* just leave things as-is - an empty variant */ + return; + } + + obj = CDNO_FETCH(object); + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + "z!|ll", &zvalue, &vt, &codepage)) { + php_com_throw_exception(E_INVALIDARG, "Invalid arguments" TSRMLS_CC); + return; + } + + php_com_initialize(TSRMLS_C); + if (ZEND_NUM_ARGS() == 3) { + obj->code_page = codepage; + } + + if (zvalue) { + php_com_variant_from_zval(&obj->v, zvalue, obj->code_page TSRMLS_CC); + } + + /* Only perform conversion if variant not already of type passed */ + if ((ZEND_NUM_ARGS() >= 2) && (vt != V_VT(&obj->v))) { + + /* If already an array and VT_ARRAY is passed then: + - if only VT_ARRAY passed then do not perform a conversion + - if VT_ARRAY plus other type passed then perform conversion + but will probably fail (origional behavior) + */ + if ((vt & VT_ARRAY) && (V_VT(&obj->v) & VT_ARRAY)) { + long orig_vt = vt; + + vt &= ~VT_ARRAY; + if (vt) { + vt = orig_vt; + } + } + + if (vt) { + res = VariantChangeType(&obj->v, &obj->v, 0, (VARTYPE)vt); + + if (FAILED(res)) { + char *werr, *msg; + + werr = php_win32_error_to_msg(res); + spprintf(&msg, 0, "Variant type conversion failed: %s", werr); + LocalFree(werr); + + php_com_throw_exception(res, msg TSRMLS_CC); + efree(msg); + } + } + } + + if (V_VT(&obj->v) != VT_DISPATCH && obj->typeinfo) { + ITypeInfo_Release(obj->typeinfo); + obj->typeinfo = NULL; + } +} +/* }}} */ + +/* {{{ proto void variant_set(object variant, mixed value) + Assigns a new value for a variant object */ +PHP_FUNCTION(variant_set) +{ + zval *zobj, *zvalue = NULL; + php_com_dotnet_object *obj; + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + "Oz!", &zobj, php_com_variant_class_entry, &zvalue)) { + return; + } + + obj = CDNO_FETCH(zobj); + + /* dtor the old value */ + if (obj->typeinfo) { + ITypeInfo_Release(obj->typeinfo); + obj->typeinfo = NULL; + } + if (obj->sink_dispatch) { + php_com_object_enable_event_sink(obj, FALSE TSRMLS_CC); + IDispatch_Release(obj->sink_dispatch); + obj->sink_dispatch = NULL; + } + + VariantClear(&obj->v); + + php_com_variant_from_zval(&obj->v, zvalue, obj->code_page TSRMLS_CC); + /* remember we modified this variant */ + obj->modified = 1; +} +/* }}} */ + +enum variant_binary_opcode { + VOP_ADD, VOP_CAT, VOP_SUB, VOP_MUL, VOP_AND, VOP_DIV, + VOP_EQV, VOP_IDIV, VOP_IMP, VOP_MOD, VOP_OR, VOP_POW, + VOP_XOR +}; + +enum variant_unary_opcode { + VOP_ABS, VOP_FIX, VOP_INT, VOP_NEG, VOP_NOT +}; + +static void variant_binary_operation(enum variant_binary_opcode op, INTERNAL_FUNCTION_PARAMETERS) /* {{{ */ +{ + VARIANT vres; + VARIANT left_val, right_val; + VARIANT *vleft = NULL, *vright = NULL; + zval *zleft = NULL, *zright = NULL; + php_com_dotnet_object *obj; + HRESULT result; + int codepage = CP_ACP; + + VariantInit(&left_val); + VariantInit(&right_val); + VariantInit(&vres); + + if (SUCCESS == zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, + ZEND_NUM_ARGS() TSRMLS_CC, "OO", &zleft, php_com_variant_class_entry, + &zright, php_com_variant_class_entry)) { + obj = CDNO_FETCH(zleft); + vleft = &obj->v; + obj = CDNO_FETCH(zright); + vright = &obj->v; + } else if (SUCCESS == zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, + ZEND_NUM_ARGS() TSRMLS_CC, "Oz!", &zleft, php_com_variant_class_entry, + &zright)) { + obj = CDNO_FETCH(zleft); + vleft = &obj->v; + vright = &right_val; + php_com_variant_from_zval(vright, zright, codepage TSRMLS_CC); + } else if (SUCCESS == zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, + ZEND_NUM_ARGS() TSRMLS_CC, "z!O", &zleft, &zright, php_com_variant_class_entry)) { + obj = CDNO_FETCH(zright); + vright = &obj->v; + vleft = &left_val; + php_com_variant_from_zval(vleft, zleft, codepage TSRMLS_CC); + } else if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + "z!z!", &zleft, &zright)) { + + vleft = &left_val; + php_com_variant_from_zval(vleft, zleft, codepage TSRMLS_CC); + + vright = &right_val; + php_com_variant_from_zval(vright, zright, codepage TSRMLS_CC); + + } else { + return; + } + + switch (op) { + case VOP_ADD: + result = VarAdd(vleft, vright, &vres); + break; + case VOP_CAT: + result = VarCat(vleft, vright, &vres); + break; + case VOP_SUB: + result = VarSub(vleft, vright, &vres); + break; + case VOP_MUL: + result = VarMul(vleft, vright, &vres); + break; + case VOP_AND: + result = VarAnd(vleft, vright, &vres); + break; + case VOP_DIV: + result = VarDiv(vleft, vright, &vres); + break; + case VOP_EQV: + result = VarEqv(vleft, vright, &vres); + break; + case VOP_IDIV: + result = VarIdiv(vleft, vright, &vres); + break; + case VOP_IMP: + result = VarImp(vleft, vright, &vres); + break; + case VOP_MOD: + result = VarMod(vleft, vright, &vres); + break; + case VOP_OR: + result = VarOr(vleft, vright, &vres); + break; + case VOP_POW: + result = VarPow(vleft, vright, &vres); + break; + case VOP_XOR: + result = VarXor(vleft, vright, &vres); + break; + /*Let say it fails as no valid op has been given */ + default: + result = E_INVALIDARG; + } + + if (SUCCEEDED(result)) { + php_com_wrap_variant(return_value, &vres, codepage TSRMLS_CC); + } else { + php_com_throw_exception(result, NULL TSRMLS_CC); + } + + VariantClear(&vres); + VariantClear(&left_val); + VariantClear(&right_val); +} +/* }}} */ + +/* {{{ proto mixed variant_add(mixed left, mixed right) + "Adds" two variant values together and returns the result */ +PHP_FUNCTION(variant_add) +{ + variant_binary_operation(VOP_ADD, INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto mixed variant_cat(mixed left, mixed right) + concatenates two variant values together and returns the result */ +PHP_FUNCTION(variant_cat) +{ + variant_binary_operation(VOP_CAT, INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto mixed variant_sub(mixed left, mixed right) + subtracts the value of the right variant from the left variant value and returns the result */ +PHP_FUNCTION(variant_sub) +{ + variant_binary_operation(VOP_SUB, INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto mixed variant_mul(mixed left, mixed right) + multiplies the values of the two variants and returns the result */ +PHP_FUNCTION(variant_mul) +{ + variant_binary_operation(VOP_MUL, INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto mixed variant_and(mixed left, mixed right) + performs a bitwise AND operation between two variants and returns the result */ +PHP_FUNCTION(variant_and) +{ + variant_binary_operation(VOP_AND, INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto mixed variant_div(mixed left, mixed right) + Returns the result from dividing two variants */ +PHP_FUNCTION(variant_div) +{ + variant_binary_operation(VOP_DIV, INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto mixed variant_eqv(mixed left, mixed right) + Performs a bitwise equivalence on two variants */ +PHP_FUNCTION(variant_eqv) +{ + variant_binary_operation(VOP_EQV, INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto mixed variant_idiv(mixed left, mixed right) + Converts variants to integers and then returns the result from dividing them */ +PHP_FUNCTION(variant_idiv) +{ + variant_binary_operation(VOP_IDIV, INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto mixed variant_imp(mixed left, mixed right) + Performs a bitwise implication on two variants */ +PHP_FUNCTION(variant_imp) +{ + variant_binary_operation(VOP_IMP, INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto mixed variant_mod(mixed left, mixed right) + Divides two variants and returns only the remainder */ +PHP_FUNCTION(variant_mod) +{ + variant_binary_operation(VOP_MOD, INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto mixed variant_or(mixed left, mixed right) + Performs a logical disjunction on two variants */ +PHP_FUNCTION(variant_or) +{ + variant_binary_operation(VOP_OR, INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto mixed variant_pow(mixed left, mixed right) + Returns the result of performing the power function with two variants */ +PHP_FUNCTION(variant_pow) +{ + variant_binary_operation(VOP_POW, INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto mixed variant_xor(mixed left, mixed right) + Performs a logical exclusion on two variants */ +PHP_FUNCTION(variant_xor) +{ + variant_binary_operation(VOP_XOR, INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +static void variant_unary_operation(enum variant_unary_opcode op, INTERNAL_FUNCTION_PARAMETERS) /* {{{ */ +{ + VARIANT vres; + VARIANT left_val; + VARIANT *vleft = NULL; + zval *zleft = NULL; + php_com_dotnet_object *obj; + HRESULT result; + int codepage = CP_ACP; + + VariantInit(&left_val); + VariantInit(&vres); + + if (SUCCESS == zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, + ZEND_NUM_ARGS() TSRMLS_CC, "O", &zleft, php_com_variant_class_entry)) { + obj = CDNO_FETCH(zleft); + vleft = &obj->v; + } else if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + "z!", &zleft)) { + vleft = &left_val; + php_com_variant_from_zval(vleft, zleft, codepage TSRMLS_CC); + } else { + return; + } + + switch (op) { + case VOP_ABS: + result = VarAbs(vleft, &vres); + break; + case VOP_FIX: + result = VarFix(vleft, &vres); + break; + case VOP_INT: + result = VarInt(vleft, &vres); + break; + case VOP_NEG: + result = VarNeg(vleft, &vres); + break; + case VOP_NOT: + result = VarNot(vleft, &vres); + break; + default: + result = E_INVALIDARG; + } + + if (SUCCEEDED(result)) { + php_com_wrap_variant(return_value, &vres, codepage TSRMLS_CC); + } else { + php_com_throw_exception(result, NULL TSRMLS_CC); + } + + VariantClear(&vres); + VariantClear(&left_val); +} +/* }}} */ + +/* {{{ proto mixed variant_abs(mixed left) + Returns the absolute value of a variant */ +PHP_FUNCTION(variant_abs) +{ + variant_unary_operation(VOP_ABS, INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto mixed variant_fix(mixed left) + Returns the integer part ? of a variant */ +PHP_FUNCTION(variant_fix) +{ + variant_unary_operation(VOP_FIX, INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto mixed variant_int(mixed left) + Returns the integer portion of a variant */ +PHP_FUNCTION(variant_int) +{ + variant_unary_operation(VOP_INT, INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto mixed variant_neg(mixed left) + Performs logical negation on a variant */ +PHP_FUNCTION(variant_neg) +{ + variant_unary_operation(VOP_NEG, INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto mixed variant_not(mixed left) + Performs bitwise not negation on a variant */ +PHP_FUNCTION(variant_not) +{ + variant_unary_operation(VOP_NOT, INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto mixed variant_round(mixed left, int decimals) + Rounds a variant to the specified number of decimal places */ +PHP_FUNCTION(variant_round) +{ + VARIANT vres; + VARIANT left_val; + VARIANT *vleft = NULL; + zval *zleft = NULL; + php_com_dotnet_object *obj; + int codepage = CP_ACP; + long decimals = 0; + + VariantInit(&left_val); + VariantInit(&vres); + + if (SUCCESS == zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, + ZEND_NUM_ARGS() TSRMLS_CC, "Ol", &zleft, php_com_variant_class_entry, &decimals)) { + obj = CDNO_FETCH(zleft); + vleft = &obj->v; + } else if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + "z!l", &zleft, &decimals)) { + vleft = &left_val; + php_com_variant_from_zval(vleft, zleft, codepage TSRMLS_CC); + } else { + return; + } + + if (SUCCEEDED(VarRound(vleft, decimals, &vres))) { + php_com_wrap_variant(return_value, &vres, codepage TSRMLS_CC); + } + + VariantClear(&vres); + VariantClear(&left_val); +} +/* }}} */ + +/* {{{ proto int variant_cmp(mixed left, mixed right [, int lcid [, int flags]]) + Compares two variants */ +PHP_FUNCTION(variant_cmp) +{ + VARIANT left_val, right_val; + VARIANT *vleft = NULL, *vright = NULL; + zval *zleft = NULL, *zright = NULL; + php_com_dotnet_object *obj; + int codepage = CP_ACP; + long lcid = LOCALE_SYSTEM_DEFAULT; + long flags = 0; + /* it is safe to ignore the warning for this line; see the comments in com_handlers.c */ + STDAPI VarCmp(LPVARIANT pvarLeft, LPVARIANT pvarRight, LCID lcid, DWORD flags); + + VariantInit(&left_val); + VariantInit(&right_val); + + if (SUCCESS == zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, + ZEND_NUM_ARGS() TSRMLS_CC, "OO|ll", &zleft, php_com_variant_class_entry, + &zright, php_com_variant_class_entry, &lcid, &flags)) { + obj = CDNO_FETCH(zleft); + vleft = &obj->v; + obj = CDNO_FETCH(zright); + vright = &obj->v; + } else if (SUCCESS == zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, + ZEND_NUM_ARGS() TSRMLS_CC, "Oz!|ll", &zleft, php_com_variant_class_entry, + &zright, &lcid, &flags)) { + obj = CDNO_FETCH(zleft); + vleft = &obj->v; + vright = &right_val; + php_com_variant_from_zval(vright, zright, codepage TSRMLS_CC); + } else if (SUCCESS == zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, + ZEND_NUM_ARGS() TSRMLS_CC, "z!O|ll", &zleft, &zright, php_com_variant_class_entry, + &lcid, &flags)) { + obj = CDNO_FETCH(zright); + vright = &obj->v; + vleft = &left_val; + php_com_variant_from_zval(vleft, zleft, codepage TSRMLS_CC); + } else if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + "z!z!|ll", &zleft, &zright, &lcid, &flags)) { + + vleft = &left_val; + php_com_variant_from_zval(vleft, zleft, codepage TSRMLS_CC); + + vright = &right_val; + php_com_variant_from_zval(vright, zright, codepage TSRMLS_CC); + + } else { + return; + } + + ZVAL_LONG(return_value, VarCmp(vleft, vright, lcid, flags)); + + VariantClear(&left_val); + VariantClear(&right_val); +} +/* }}} */ + +/* {{{ proto int variant_date_to_timestamp(object variant) + Converts a variant date/time value to unix timestamp */ +PHP_FUNCTION(variant_date_to_timestamp) +{ + VARIANT vres; + zval *zleft = NULL; + php_com_dotnet_object *obj; + + VariantInit(&vres); + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + "O", &zleft, php_com_variant_class_entry)) { + return; + } + obj = CDNO_FETCH(zleft); + + if (SUCCEEDED(VariantChangeType(&vres, &obj->v, 0, VT_DATE))) { + SYSTEMTIME systime; + struct tm tmv; + + VariantTimeToSystemTime(V_DATE(&vres), &systime); + + memset(&tmv, 0, sizeof(tmv)); + tmv.tm_year = systime.wYear - 1900; + tmv.tm_mon = systime.wMonth - 1; + tmv.tm_mday = systime.wDay; + tmv.tm_hour = systime.wHour; + tmv.tm_min = systime.wMinute; + tmv.tm_sec = systime.wSecond; + tmv.tm_isdst = -1; + + tzset(); + RETVAL_LONG(mktime(&tmv)); + } + + VariantClear(&vres); +} +/* }}} */ + +/* {{{ proto object variant_date_from_timestamp(int timestamp) + Returns a variant date representation of a unix timestamp */ +PHP_FUNCTION(variant_date_from_timestamp) +{ + long timestamp; + time_t ttstamp; + SYSTEMTIME systime; + struct tm *tmv; + VARIANT res; + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", + ×tamp)) { + return; + } + + if (timestamp < 0) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Timestamp value must be a positive value."); + RETURN_FALSE; + } + + VariantInit(&res); + tzset(); + ttstamp = timestamp; + tmv = localtime(&ttstamp); + memset(&systime, 0, sizeof(systime)); + + systime.wDay = tmv->tm_mday; + systime.wHour = tmv->tm_hour; + systime.wMinute = tmv->tm_min; + systime.wMonth = tmv->tm_mon + 1; + systime.wSecond = tmv->tm_sec; + systime.wYear = tmv->tm_year + 1900; + + V_VT(&res) = VT_DATE; + SystemTimeToVariantTime(&systime, &V_DATE(&res)); + + php_com_wrap_variant(return_value, &res, CP_ACP TSRMLS_CC); + + VariantClear(&res); +} +/* }}} */ + +/* {{{ proto int variant_get_type(object variant) + Returns the VT_XXX type code for a variant */ +PHP_FUNCTION(variant_get_type) +{ + zval *zobj; + php_com_dotnet_object *obj; + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + "O", &zobj, php_com_variant_class_entry)) { + return; + } + obj = CDNO_FETCH(zobj); + + RETURN_LONG(V_VT(&obj->v)); +} +/* }}} */ + +/* {{{ proto void variant_set_type(object variant, int type) + Convert a variant into another type. Variant is modified "in-place" */ +PHP_FUNCTION(variant_set_type) +{ + zval *zobj; + php_com_dotnet_object *obj; + /* VARTYPE == unsigned short */ long vt; + HRESULT res; + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + "Ol", &zobj, php_com_variant_class_entry, &vt)) { + return; + } + obj = CDNO_FETCH(zobj); + + res = VariantChangeType(&obj->v, &obj->v, 0, (VARTYPE)vt); + + if (SUCCEEDED(res)) { + if (vt != VT_DISPATCH && obj->typeinfo) { + ITypeInfo_Release(obj->typeinfo); + obj->typeinfo = NULL; + } + } else { + char *werr, *msg; + + werr = php_win32_error_to_msg(res); + spprintf(&msg, 0, "Variant type conversion failed: %s", werr); + LocalFree(werr); + + php_com_throw_exception(res, msg TSRMLS_CC); + efree(msg); + } +} +/* }}} */ + +/* {{{ proto object variant_cast(object variant, int type) + Convert a variant into a new variant object of another type */ +PHP_FUNCTION(variant_cast) +{ + zval *zobj; + php_com_dotnet_object *obj; + /* VARTYPE == unsigned short */ long vt; + VARIANT vres; + HRESULT res; + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + "Ol", &zobj, php_com_variant_class_entry, &vt)) { + return; + } + obj = CDNO_FETCH(zobj); + + VariantInit(&vres); + res = VariantChangeType(&vres, &obj->v, 0, (VARTYPE)vt); + + if (SUCCEEDED(res)) { + php_com_wrap_variant(return_value, &vres, obj->code_page TSRMLS_CC); + } else { + char *werr, *msg; + + werr = php_win32_error_to_msg(res); + spprintf(&msg, 0, "Variant type conversion failed: %s", werr); + LocalFree(werr); + + php_com_throw_exception(res, msg TSRMLS_CC); + efree(msg); + } + + VariantClear(&vres); +} +/* }}} */ + diff --git a/ext/com_dotnet/com_wrapper.c b/ext/com_dotnet/com_wrapper.c new file mode 100644 index 0000000..597b059 --- /dev/null +++ b/ext/com_dotnet/com_wrapper.c @@ -0,0 +1,649 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2013 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong <wez@thebrainroom.com> | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +/* This module exports a PHP object as a COM object by wrapping it + * using IDispatchEx */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "php_com_dotnet.h" +#include "php_com_dotnet_internal.h" + +typedef struct { + /* This first part MUST match the declaration + * of interface IDispatchEx */ + CONST_VTBL struct IDispatchExVtbl *lpVtbl; + + /* now the PHP stuff */ + + DWORD engine_thread; /* for sanity checking */ + zval *object; /* the object exported */ + LONG refcount; /* COM reference count */ + + HashTable *dispid_to_name; /* keep track of dispid -> name mappings */ + HashTable *name_to_dispid; /* keep track of name -> dispid mappings */ + + GUID sinkid; /* iid that we "implement" for event sinking */ + + int id; +} php_dispatchex; + +static int le_dispatch; + +static void disp_destructor(php_dispatchex *disp TSRMLS_DC); + +static void dispatch_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC) +{ + php_dispatchex *disp = (php_dispatchex *)rsrc->ptr; + disp_destructor(disp TSRMLS_CC); +} + +int php_com_wrapper_minit(INIT_FUNC_ARGS) +{ + le_dispatch = zend_register_list_destructors_ex(dispatch_dtor, + NULL, "com_dotnet_dispatch_wrapper", module_number); + return le_dispatch; +} + + +/* {{{ trace */ +static inline void trace(char *fmt, ...) +{ + va_list ap; + char buf[4096]; + + snprintf(buf, sizeof(buf), "T=%08x ", GetCurrentThreadId()); + OutputDebugString(buf); + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + + OutputDebugString(buf); + + va_end(ap); +} +/* }}} */ + +#define FETCH_DISP(methname) \ + php_dispatchex *disp = (php_dispatchex*)This; \ + TSRMLS_FETCH(); \ + if (COMG(rshutdown_started)) { \ + trace(" PHP Object:%p (name:unknown) %s\n", disp->object, methname); \ + } else { \ + trace(" PHP Object:%p (name:%s) %s\n", disp->object, Z_OBJCE_P(disp->object)->name, methname); \ + } \ + if (GetCurrentThreadId() != disp->engine_thread) { \ + return RPC_E_WRONG_THREAD; \ + } + +static HRESULT STDMETHODCALLTYPE disp_queryinterface( + IDispatchEx *This, + /* [in] */ REFIID riid, + /* [iid_is][out] */ void **ppvObject) +{ + FETCH_DISP("QueryInterface"); + + if (IsEqualGUID(&IID_IUnknown, riid) || + IsEqualGUID(&IID_IDispatch, riid) || + IsEqualGUID(&IID_IDispatchEx, riid) || + IsEqualGUID(&disp->sinkid, riid)) { + *ppvObject = This; + InterlockedIncrement(&disp->refcount); + return S_OK; + } + + *ppvObject = NULL; + return E_NOINTERFACE; +} + +static ULONG STDMETHODCALLTYPE disp_addref(IDispatchEx *This) +{ + FETCH_DISP("AddRef"); + + return InterlockedIncrement(&disp->refcount); +} + +static ULONG STDMETHODCALLTYPE disp_release(IDispatchEx *This) +{ + ULONG ret; + FETCH_DISP("Release"); + + ret = InterlockedDecrement(&disp->refcount); + trace("-- refcount now %d\n", ret); + if (ret == 0) { + /* destroy it */ + if (disp->id) + zend_list_delete(disp->id); + } + return ret; +} + +static HRESULT STDMETHODCALLTYPE disp_gettypeinfocount( + IDispatchEx *This, + /* [out] */ UINT *pctinfo) +{ + FETCH_DISP("GetTypeInfoCount"); + + *pctinfo = 0; + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE disp_gettypeinfo( + IDispatchEx *This, + /* [in] */ UINT iTInfo, + /* [in] */ LCID lcid, + /* [out] */ ITypeInfo **ppTInfo) +{ + FETCH_DISP("GetTypeInfo"); + + *ppTInfo = NULL; + return DISP_E_BADINDEX; +} + +static HRESULT STDMETHODCALLTYPE disp_getidsofnames( + IDispatchEx *This, + /* [in] */ REFIID riid, + /* [size_is][in] */ LPOLESTR *rgszNames, + /* [in] */ UINT cNames, + /* [in] */ LCID lcid, + /* [size_is][out] */ DISPID *rgDispId) +{ + UINT i; + HRESULT ret = S_OK; + FETCH_DISP("GetIDsOfNames"); + + for (i = 0; i < cNames; i++) { + char *name; + unsigned int namelen; + zval **tmp; + + name = php_com_olestring_to_string(rgszNames[i], &namelen, COMG(code_page) TSRMLS_CC); + + /* Lookup the name in the hash */ + if (zend_hash_find(disp->name_to_dispid, name, namelen+1, (void**)&tmp) == FAILURE) { + ret = DISP_E_UNKNOWNNAME; + rgDispId[i] = 0; + } else { + rgDispId[i] = Z_LVAL_PP(tmp); + } + + efree(name); + + } + + return ret; +} + +static HRESULT STDMETHODCALLTYPE disp_invoke( + IDispatchEx *This, + /* [in] */ DISPID dispIdMember, + /* [in] */ REFIID riid, + /* [in] */ LCID lcid, + /* [in] */ WORD wFlags, + /* [out][in] */ DISPPARAMS *pDispParams, + /* [out] */ VARIANT *pVarResult, + /* [out] */ EXCEPINFO *pExcepInfo, + /* [out] */ UINT *puArgErr) +{ + return This->lpVtbl->InvokeEx(This, dispIdMember, + lcid, wFlags, pDispParams, + pVarResult, pExcepInfo, NULL); +} + +static HRESULT STDMETHODCALLTYPE disp_getdispid( + IDispatchEx *This, + /* [in] */ BSTR bstrName, + /* [in] */ DWORD grfdex, + /* [out] */ DISPID *pid) +{ + HRESULT ret = DISP_E_UNKNOWNNAME; + char *name; + unsigned int namelen; + zval **tmp; + FETCH_DISP("GetDispID"); + + name = php_com_olestring_to_string(bstrName, &namelen, COMG(code_page) TSRMLS_CC); + + trace("Looking for %s, namelen=%d in %p\n", name, namelen, disp->name_to_dispid); + + /* Lookup the name in the hash */ + if (zend_hash_find(disp->name_to_dispid, name, namelen+1, (void**)&tmp) == SUCCESS) { + trace("found it\n"); + *pid = Z_LVAL_PP(tmp); + ret = S_OK; + } + + efree(name); + + return ret; +} + +static HRESULT STDMETHODCALLTYPE disp_invokeex( + IDispatchEx *This, + /* [in] */ DISPID id, + /* [in] */ LCID lcid, + /* [in] */ WORD wFlags, + /* [in] */ DISPPARAMS *pdp, + /* [out] */ VARIANT *pvarRes, + /* [out] */ EXCEPINFO *pei, + /* [unique][in] */ IServiceProvider *pspCaller) +{ + zval **name; + UINT i; + zval *retval = NULL; + zval ***params = NULL; + HRESULT ret = DISP_E_MEMBERNOTFOUND; + FETCH_DISP("InvokeEx"); + + if (SUCCESS == zend_hash_index_find(disp->dispid_to_name, id, (void**)&name)) { + /* TODO: add support for overloaded objects */ + + trace("-- Invoke: %d %20s [%d] flags=%08x args=%d\n", id, Z_STRVAL_PP(name), Z_STRLEN_PP(name), wFlags, pdp->cArgs); + + /* convert args into zvals. + * Args are in reverse order */ + if (pdp->cArgs) { + params = (zval ***)safe_emalloc(sizeof(zval **), pdp->cArgs, 0); + for (i = 0; i < pdp->cArgs; i++) { + VARIANT *arg; + zval *zarg; + + arg = &pdp->rgvarg[ pdp->cArgs - 1 - i]; + + trace("alloc zval for arg %d VT=%08x\n", i, V_VT(arg)); + + ALLOC_INIT_ZVAL(zarg); + php_com_wrap_variant(zarg, arg, COMG(code_page) TSRMLS_CC); + params[i] = (zval**)emalloc(sizeof(zval**)); + *params[i] = zarg; + } + } + + trace("arguments processed, prepare to do some work\n"); + + /* TODO: if PHP raises an exception here, we should catch it + * and expose it as a COM exception */ + + if (wFlags & DISPATCH_PROPERTYGET) { + retval = zend_read_property(Z_OBJCE_P(disp->object), disp->object, Z_STRVAL_PP(name), Z_STRLEN_PP(name)+1, 1 TSRMLS_CC); + } else if (wFlags & DISPATCH_PROPERTYPUT) { + zend_update_property(Z_OBJCE_P(disp->object), disp->object, Z_STRVAL_PP(name), Z_STRLEN_PP(name)+1, *params[0] TSRMLS_CC); + } else if (wFlags & DISPATCH_METHOD) { + zend_try { + if (SUCCESS == call_user_function_ex(EG(function_table), &disp->object, *name, + &retval, pdp->cArgs, params, 1, NULL TSRMLS_CC)) { + ret = S_OK; + trace("function called ok\n"); + + /* Copy any modified values to callers copy of variant*/ + for (i = 0; i < pdp->cArgs; i++) { + php_com_dotnet_object *obj = CDNO_FETCH(*params[i]); + VARIANT *srcvar = &obj->v; + VARIANT *dstvar = &pdp->rgvarg[ pdp->cArgs - 1 - i]; + if ((V_VT(dstvar) & VT_BYREF) && obj->modified ) { + trace("percolate modified value for arg %d VT=%08x\n", i, V_VT(dstvar)); + php_com_copy_variant(dstvar, srcvar TSRMLS_CC); + } + } + } else { + trace("failed to call func\n"); + ret = DISP_E_EXCEPTION; + } + } zend_catch { + trace("something blew up\n"); + ret = DISP_E_EXCEPTION; + } zend_end_try(); + } else { + trace("Don't know how to handle this invocation %08x\n", wFlags); + } + + /* release arguments */ + if (params) { + for (i = 0; i < pdp->cArgs; i++) { + zval_ptr_dtor(params[i]); + efree(params[i]); + } + efree(params); + } + + /* return value */ + if (retval) { + if (pvarRes) { + VariantInit(pvarRes); + php_com_variant_from_zval(pvarRes, retval, COMG(code_page) TSRMLS_CC); + } + zval_ptr_dtor(&retval); + } else if (pvarRes) { + VariantInit(pvarRes); + } + + } else { + trace("InvokeEx: I don't support DISPID=%d\n", id); + } + + return ret; +} + +static HRESULT STDMETHODCALLTYPE disp_deletememberbyname( + IDispatchEx *This, + /* [in] */ BSTR bstrName, + /* [in] */ DWORD grfdex) +{ + FETCH_DISP("DeleteMemberByName"); + + /* TODO: unset */ + + return S_FALSE; +} + +static HRESULT STDMETHODCALLTYPE disp_deletememberbydispid( + IDispatchEx *This, + /* [in] */ DISPID id) +{ + FETCH_DISP("DeleteMemberByDispID"); + + /* TODO: unset */ + + return S_FALSE; +} + +static HRESULT STDMETHODCALLTYPE disp_getmemberproperties( + IDispatchEx *This, + /* [in] */ DISPID id, + /* [in] */ DWORD grfdexFetch, + /* [out] */ DWORD *pgrfdex) +{ + FETCH_DISP("GetMemberProperties"); + + return DISP_E_UNKNOWNNAME; +} + +static HRESULT STDMETHODCALLTYPE disp_getmembername( + IDispatchEx *This, + /* [in] */ DISPID id, + /* [out] */ BSTR *pbstrName) +{ + zval *name; + FETCH_DISP("GetMemberName"); + + if (SUCCESS == zend_hash_index_find(disp->dispid_to_name, id, (void**)&name)) { + OLECHAR *olestr = php_com_string_to_olestring(Z_STRVAL_P(name), Z_STRLEN_P(name), COMG(code_page) TSRMLS_CC); + *pbstrName = SysAllocString(olestr); + efree(olestr); + return S_OK; + } else { + return DISP_E_UNKNOWNNAME; + } +} + +static HRESULT STDMETHODCALLTYPE disp_getnextdispid( + IDispatchEx *This, + /* [in] */ DWORD grfdex, + /* [in] */ DISPID id, + /* [out] */ DISPID *pid) +{ + ulong next = id+1; + FETCH_DISP("GetNextDispID"); + + while(!zend_hash_index_exists(disp->dispid_to_name, next)) + next++; + + if (zend_hash_index_exists(disp->dispid_to_name, next)) { + *pid = next; + return S_OK; + } + return S_FALSE; +} + +static HRESULT STDMETHODCALLTYPE disp_getnamespaceparent( + IDispatchEx *This, + /* [out] */ IUnknown **ppunk) +{ + FETCH_DISP("GetNameSpaceParent"); + + *ppunk = NULL; + return E_NOTIMPL; +} + +static struct IDispatchExVtbl php_dispatch_vtbl = { + disp_queryinterface, + disp_addref, + disp_release, + disp_gettypeinfocount, + disp_gettypeinfo, + disp_getidsofnames, + disp_invoke, + disp_getdispid, + disp_invokeex, + disp_deletememberbyname, + disp_deletememberbydispid, + disp_getmemberproperties, + disp_getmembername, + disp_getnextdispid, + disp_getnamespaceparent +}; + + +/* enumerate functions and properties of the object and assign + * dispatch ids */ +static void generate_dispids(php_dispatchex *disp TSRMLS_DC) +{ + HashPosition pos; + char *name = NULL; + zval *tmp; + int namelen; + int keytype; + ulong pid; + + if (disp->dispid_to_name == NULL) { + ALLOC_HASHTABLE(disp->dispid_to_name); + ALLOC_HASHTABLE(disp->name_to_dispid); + zend_hash_init(disp->name_to_dispid, 0, NULL, ZVAL_PTR_DTOR, 0); + zend_hash_init(disp->dispid_to_name, 0, NULL, ZVAL_PTR_DTOR, 0); + } + + /* properties */ + if (Z_OBJPROP_P(disp->object)) { + zend_hash_internal_pointer_reset_ex(Z_OBJPROP_P(disp->object), &pos); + while (HASH_KEY_NON_EXISTANT != (keytype = + zend_hash_get_current_key_ex(Z_OBJPROP_P(disp->object), &name, + &namelen, &pid, 0, &pos))) { + char namebuf[32]; + if (keytype == HASH_KEY_IS_LONG) { + snprintf(namebuf, sizeof(namebuf), "%d", pid); + name = namebuf; + namelen = strlen(namebuf)+1; + } + + zend_hash_move_forward_ex(Z_OBJPROP_P(disp->object), &pos); + + /* Find the existing id */ + if (zend_hash_find(disp->name_to_dispid, name, namelen, (void**)&tmp) == SUCCESS) + continue; + + /* add the mappings */ + MAKE_STD_ZVAL(tmp); + ZVAL_STRINGL(tmp, name, namelen-1, 1); + pid = zend_hash_next_free_element(disp->dispid_to_name); + zend_hash_index_update(disp->dispid_to_name, pid, (void*)&tmp, sizeof(zval *), NULL); + + MAKE_STD_ZVAL(tmp); + ZVAL_LONG(tmp, pid); + zend_hash_update(disp->name_to_dispid, name, namelen, (void*)&tmp, sizeof(zval *), NULL); + } + } + + /* functions */ + if (Z_OBJCE_P(disp->object)) { + zend_hash_internal_pointer_reset_ex(&Z_OBJCE_P(disp->object)->function_table, &pos); + while (HASH_KEY_NON_EXISTANT != (keytype = + zend_hash_get_current_key_ex(&Z_OBJCE_P(disp->object)->function_table, + &name, &namelen, &pid, 0, &pos))) { + + char namebuf[32]; + if (keytype == HASH_KEY_IS_LONG) { + snprintf(namebuf, sizeof(namebuf), "%d", pid); + name = namebuf; + namelen = strlen(namebuf) + 1; + } + + zend_hash_move_forward_ex(Z_OBJPROP_P(disp->object), &pos); + + /* Find the existing id */ + if (zend_hash_find(disp->name_to_dispid, name, namelen, (void**)&tmp) == SUCCESS) + continue; + + /* add the mappings */ + MAKE_STD_ZVAL(tmp); + ZVAL_STRINGL(tmp, name, namelen-1, 1); + pid = zend_hash_next_free_element(disp->dispid_to_name); + zend_hash_index_update(disp->dispid_to_name, pid, (void*)&tmp, sizeof(zval *), NULL); + + MAKE_STD_ZVAL(tmp); + ZVAL_LONG(tmp, pid); + zend_hash_update(disp->name_to_dispid, name, namelen, (void*)&tmp, sizeof(zval *), NULL); + } + } +} + +static php_dispatchex *disp_constructor(zval *object TSRMLS_DC) +{ + php_dispatchex *disp = (php_dispatchex*)CoTaskMemAlloc(sizeof(php_dispatchex)); + + trace("constructing a COM wrapper for PHP object %p (%s)\n", object, Z_OBJCE_P(object)->name); + + if (disp == NULL) + return NULL; + + memset(disp, 0, sizeof(php_dispatchex)); + + disp->engine_thread = GetCurrentThreadId(); + disp->lpVtbl = &php_dispatch_vtbl; + disp->refcount = 1; + + + if (object) + Z_ADDREF_P(object); + disp->object = object; + + disp->id = zend_list_insert(disp, le_dispatch TSRMLS_CC); + + return disp; +} + +static void disp_destructor(php_dispatchex *disp TSRMLS_DC) +{ + /* Object store not available during request shutdown */ + if (COMG(rshutdown_started)) { + trace("destroying COM wrapper for PHP object %p (name:unknown)\n", disp->object); + } else { + trace("destroying COM wrapper for PHP object %p (name:%s)\n", disp->object, Z_OBJCE_P(disp->object)->name); + } + + disp->id = 0; + + if (disp->refcount > 0) + CoDisconnectObject((IUnknown*)disp, 0); + + zend_hash_destroy(disp->dispid_to_name); + zend_hash_destroy(disp->name_to_dispid); + FREE_HASHTABLE(disp->dispid_to_name); + FREE_HASHTABLE(disp->name_to_dispid); + + if (disp->object) + zval_ptr_dtor(&disp->object); + + CoTaskMemFree(disp); +} + +PHP_COM_DOTNET_API IDispatch *php_com_wrapper_export_as_sink(zval *val, GUID *sinkid, + HashTable *id_to_name TSRMLS_DC) +{ + php_dispatchex *disp = disp_constructor(val TSRMLS_CC); + HashPosition pos; + char *name = NULL; + zval *tmp, **ntmp; + int namelen; + int keytype; + ulong pid; + + disp->dispid_to_name = id_to_name; + + memcpy(&disp->sinkid, sinkid, sizeof(disp->sinkid)); + + /* build up the reverse mapping */ + ALLOC_HASHTABLE(disp->name_to_dispid); + zend_hash_init(disp->name_to_dispid, 0, NULL, ZVAL_PTR_DTOR, 0); + + zend_hash_internal_pointer_reset_ex(id_to_name, &pos); + while (HASH_KEY_NON_EXISTANT != (keytype = + zend_hash_get_current_key_ex(id_to_name, &name, &namelen, &pid, 0, &pos))) { + + if (keytype == HASH_KEY_IS_LONG) { + + zend_hash_get_current_data_ex(id_to_name, (void**)&ntmp, &pos); + + MAKE_STD_ZVAL(tmp); + ZVAL_LONG(tmp, pid); + zend_hash_update(disp->name_to_dispid, Z_STRVAL_PP(ntmp), + Z_STRLEN_PP(ntmp)+1, (void*)&tmp, sizeof(zval *), NULL); + } + + zend_hash_move_forward_ex(id_to_name, &pos); + } + + return (IDispatch*)disp; +} + +PHP_COM_DOTNET_API IDispatch *php_com_wrapper_export(zval *val TSRMLS_DC) +{ + php_dispatchex *disp = NULL; + + if (Z_TYPE_P(val) != IS_OBJECT) { + return NULL; + } + + if (php_com_is_valid_object(val TSRMLS_CC)) { + /* pass back its IDispatch directly */ + php_com_dotnet_object *obj = CDNO_FETCH(val); + + if (obj == NULL) + return NULL; + + if (V_VT(&obj->v) == VT_DISPATCH && V_DISPATCH(&obj->v)) { + IDispatch_AddRef(V_DISPATCH(&obj->v)); + return V_DISPATCH(&obj->v); + } + + return NULL; + } + + disp = disp_constructor(val TSRMLS_CC); + generate_dispids(disp TSRMLS_CC); + + return (IDispatch*)disp; +} + + diff --git a/ext/com_dotnet/config.w32 b/ext/com_dotnet/config.w32 new file mode 100644 index 0000000..1526392 --- /dev/null +++ b/ext/com_dotnet/config.w32 @@ -0,0 +1,13 @@ +// $Id$ +// vim:ft=javascript + +ARG_ENABLE("com-dotnet", "COM and .Net support", "yes"); + +if (PHP_COM_DOTNET == "yes") { + CHECK_LIB('oleaut32.lib', 'com_dotnet'); + EXTENSION("com_dotnet", "com_com.c com_dotnet.c com_extension.c \ + com_handlers.c com_iterator.c com_misc.c com_olechar.c \ + com_typeinfo.c com_variant.c com_wrapper.c com_saproxy.c com_persist.c"); + AC_DEFINE('HAVE_COM_DOTNET', 1, 'Have COM_DOTNET support'); + CHECK_HEADER_ADD_INCLUDE('mscoree.h', 'CFLAGS_COM_DOTNET'); +} diff --git a/ext/com_dotnet/package.xml b/ext/com_dotnet/package.xml new file mode 100644 index 0000000..2839447 --- /dev/null +++ b/ext/com_dotnet/package.xml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="ISO-8859-1" ?> +<!DOCTYPE package SYSTEM "../pear/package.dtd"> +<package> + <name>com_dotnet</name> + <summary>Com and .NET support functions for Windows</summary> + <maintainers> + <maintainer> + <user>wez</user> + <name>Wez Furlong</name> + <email>wez@php.net</email> + <role>lead</role> + </maintainer> + </maintainers> + <description> +... + </description> + <license>PHP</license> + <release> + <state>beta</state> + <version>5.0.0rc1</version> + <date>2004-03-19</date> + <notes> +package.xml added to support installation using pear installer + </notes> + <filelist> + <file role="doc" name="CREDITS"/> + <file role="doc" name="README"/> + <file role="src" name="config.w32"/> + <file role="src" name="com_com.c"/> + <file role="src" name="com_dotnet.c"/> + <file role="src" name="com_extension.c"/> + <file role="src" name="com_handlers.c"/> + <file role="src" name="com_misc.c"/> + <file role="src" name="com_olechar.c"/> + <file role="src" name="com_typeinfo.c"/> + <file role="src" name="com_variant.c"/> + <file role="src" name="com_iterator.c"/> + <file role="src" name="com_saproxy.c"/> + <file role="src" name="com_wrapper.c"/> + <file role="src" name="php_com_dotnet.h"/> + <file role="src" name="php_com_dotnet_internal.h"/> + <file role="test" name="tests/variants.phpt"/> + </filelist> + <deps> + <dep type="php" rel="ge" version="5" /> + <!-- doesn't work yet <dep type="os" rel="has" name="windows"/> --> + </deps> + </release> +</package> +<!-- +vim:et:ts=1:sw=1 +--> diff --git a/ext/com_dotnet/php_com_dotnet.h b/ext/com_dotnet/php_com_dotnet.h new file mode 100644 index 0000000..8771e27 --- /dev/null +++ b/ext/com_dotnet/php_com_dotnet.h @@ -0,0 +1,72 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2013 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong <wez@thebrainroom.com> | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#ifndef PHP_COM_DOTNET_H +#define PHP_COM_DOTNET_H + +extern zend_module_entry com_dotnet_module_entry; +#define phpext_com_dotnet_ptr &com_dotnet_module_entry + +#ifdef ZTS +#include "TSRM.h" +#endif + +#ifdef PHP_WIN32 +# define PHP_COM_DOTNET_API __declspec(dllexport) +#elif defined(__GNUC__) && __GNUC__ >= 4 +# define PHP_COM_DOTNET_API __attribute__ ((visibility("default"))) +#else +# define PHP_COM_DOTNET_API +#endif + +PHP_MINIT_FUNCTION(com_dotnet); +PHP_MSHUTDOWN_FUNCTION(com_dotnet); +PHP_RINIT_FUNCTION(com_dotnet); +PHP_RSHUTDOWN_FUNCTION(com_dotnet); +PHP_MINFO_FUNCTION(com_dotnet); + +ZEND_BEGIN_MODULE_GLOBALS(com_dotnet) + zend_bool allow_dcom; + zend_bool autoreg_verbose; + zend_bool autoreg_on; + zend_bool autoreg_case_sensitive; + void *dotnet_runtime_stuff; /* opaque to avoid cluttering up other modules */ + int code_page; /* default code_page if left unspecified */ + zend_bool rshutdown_started; +ZEND_END_MODULE_GLOBALS(com_dotnet) + +#ifdef ZTS +# define COMG(v) TSRMG(com_dotnet_globals_id, zend_com_dotnet_globals *, v) +#else +# define COMG(v) (com_dotnet_globals.v) +#endif + +extern ZEND_DECLARE_MODULE_GLOBALS(com_dotnet); + +#endif /* PHP_COM_DOTNET_H */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/ext/com_dotnet/php_com_dotnet_internal.h b/ext/com_dotnet/php_com_dotnet_internal.h new file mode 100644 index 0000000..015de45 --- /dev/null +++ b/ext/com_dotnet/php_com_dotnet_internal.h @@ -0,0 +1,182 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2013 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong <wez@thebrainroom.com> | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#ifndef PHP_COM_DOTNET_INTERNAL_H +#define PHP_COM_DOTNET_INTERNAL_H + +#define _WIN32_DCOM +#define COBJMACROS +#include <ocidl.h> +#include <oleauto.h> +#include <unknwn.h> +#include <dispex.h> +#include "win32/winutil.h" + +typedef struct _php_com_dotnet_object { + zend_object zo; + + VARIANT v; + int modified; + + ITypeInfo *typeinfo; + long code_page; + + zend_class_entry *ce; + + /* associated event sink */ + IDispatch *sink_dispatch; + GUID sink_id; + DWORD sink_cookie; + + /* cache for method signatures */ + HashTable *method_cache; + /* cache for name -> DISPID */ + HashTable *id_of_name_cache; +} php_com_dotnet_object; + +static inline int php_com_is_valid_object(zval *zv TSRMLS_DC) +{ + zend_class_entry *ce = Z_OBJCE_P(zv); + return strcmp("com", ce->name) == 0 || + strcmp("dotnet", ce->name) == 0 || + strcmp("variant", ce->name) == 0; +} + +#define CDNO_FETCH(zv) (php_com_dotnet_object*)zend_object_store_get_object(zv TSRMLS_CC) +#define CDNO_FETCH_VERIFY(obj, zv) do { \ + if (!php_com_is_valid_object(zv TSRMLS_CC)) { \ + php_com_throw_exception(E_UNEXPECTED, "expected a variant object" TSRMLS_CC); \ + return; \ + } \ + obj = (php_com_dotnet_object*)zend_object_store_get_object(zv TSRMLS_CC); \ +} while(0) + +/* com_extension.c */ +TsHashTable php_com_typelibraries; +zend_class_entry *php_com_variant_class_entry, *php_com_exception_class_entry, *php_com_saproxy_class_entry; + +/* com_handlers.c */ +zend_object_value php_com_object_new(zend_class_entry *ce TSRMLS_DC); +void php_com_object_clone(void *object, void **clone_ptr TSRMLS_DC); +void php_com_object_free_storage(void *object TSRMLS_DC); +zend_object_handlers php_com_object_handlers; +void php_com_object_enable_event_sink(php_com_dotnet_object *obj, int enable TSRMLS_DC); + +/* com_saproxy.c */ +zend_object_iterator *php_com_saproxy_iter_get(zend_class_entry *ce, zval *object, int by_ref TSRMLS_DC); +int php_com_saproxy_create(zval *com_object, zval *proxy_out, zval *index TSRMLS_DC); + +/* com_olechar.c */ +PHP_COM_DOTNET_API char *php_com_olestring_to_string(OLECHAR *olestring, + uint *string_len, int codepage TSRMLS_DC); +PHP_COM_DOTNET_API OLECHAR *php_com_string_to_olestring(char *string, + uint string_len, int codepage TSRMLS_DC); + + +/* com_com.c */ +PHP_FUNCTION(com_create_instance); +PHP_FUNCTION(com_event_sink); +PHP_FUNCTION(com_create_guid); +PHP_FUNCTION(com_print_typeinfo); +PHP_FUNCTION(com_message_pump); +PHP_FUNCTION(com_load_typelib); +PHP_FUNCTION(com_get_active_object); + +HRESULT php_com_invoke_helper(php_com_dotnet_object *obj, DISPID id_member, + WORD flags, DISPPARAMS *disp_params, VARIANT *v, int silent, int allow_noarg TSRMLS_DC); +HRESULT php_com_get_id_of_name(php_com_dotnet_object *obj, char *name, + int namelen, DISPID *dispid TSRMLS_DC); +int 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 TSRMLS_DC); +int php_com_do_invoke(php_com_dotnet_object *obj, char *name, int namelen, + WORD flags, VARIANT *v, int nargs, zval **args, int allow_noarg TSRMLS_DC); +int php_com_do_invoke_byref(php_com_dotnet_object *obj, char *name, int namelen, + WORD flags, VARIANT *v, int nargs, zval ***args TSRMLS_DC); + +/* com_wrapper.c */ +int php_com_wrapper_minit(INIT_FUNC_ARGS); +PHP_COM_DOTNET_API IDispatch *php_com_wrapper_export_as_sink(zval *val, GUID *sinkid, HashTable *id_to_name TSRMLS_DC); +PHP_COM_DOTNET_API IDispatch *php_com_wrapper_export(zval *val TSRMLS_DC); + +/* com_persist.c */ +int php_com_persist_minit(INIT_FUNC_ARGS); + +/* com_variant.c */ +PHP_FUNCTION(com_variant_create_instance); +PHP_FUNCTION(variant_set); +PHP_FUNCTION(variant_add); +PHP_FUNCTION(variant_cat); +PHP_FUNCTION(variant_sub); +PHP_FUNCTION(variant_mul); +PHP_FUNCTION(variant_and); +PHP_FUNCTION(variant_div); +PHP_FUNCTION(variant_eqv); +PHP_FUNCTION(variant_idiv); +PHP_FUNCTION(variant_imp); +PHP_FUNCTION(variant_mod); +PHP_FUNCTION(variant_or); +PHP_FUNCTION(variant_pow); +PHP_FUNCTION(variant_xor); +PHP_FUNCTION(variant_abs); +PHP_FUNCTION(variant_fix); +PHP_FUNCTION(variant_int); +PHP_FUNCTION(variant_neg); +PHP_FUNCTION(variant_not); +PHP_FUNCTION(variant_round); +PHP_FUNCTION(variant_cmp); +PHP_FUNCTION(variant_date_to_timestamp); +PHP_FUNCTION(variant_date_from_timestamp); +PHP_FUNCTION(variant_get_type); +PHP_FUNCTION(variant_set_type); +PHP_FUNCTION(variant_cast); + +PHP_COM_DOTNET_API void php_com_variant_from_zval_with_type(VARIANT *v, zval *z, VARTYPE type, int codepage TSRMLS_DC); +PHP_COM_DOTNET_API void php_com_variant_from_zval(VARIANT *v, zval *z, int codepage TSRMLS_DC); +PHP_COM_DOTNET_API int php_com_zval_from_variant(zval *z, VARIANT *v, int codepage TSRMLS_DC); +PHP_COM_DOTNET_API int php_com_copy_variant(VARIANT *dst, VARIANT *src TSRMLS_DC); + +/* com_dotnet.c */ +PHP_FUNCTION(com_dotnet_create_instance); +void php_com_dotnet_rshutdown(TSRMLS_D); +void php_com_dotnet_mshutdown(TSRMLS_D); + +/* com_misc.c */ +void php_com_throw_exception(HRESULT code, char *message TSRMLS_DC); +PHP_COM_DOTNET_API void php_com_wrap_dispatch(zval *z, IDispatch *disp, + int codepage TSRMLS_DC); +PHP_COM_DOTNET_API void php_com_wrap_variant(zval *z, VARIANT *v, + int codepage TSRMLS_DC); +PHP_COM_DOTNET_API int php_com_safearray_get_elem(VARIANT *array, VARIANT *dest, LONG dim1 TSRMLS_DC); + +/* com_typeinfo.c */ +PHP_COM_DOTNET_API ITypeLib *php_com_load_typelib_via_cache(char *search_string, + int codepage, int *cached TSRMLS_DC); +PHP_COM_DOTNET_API ITypeLib *php_com_load_typelib(char *search_string, int codepage TSRMLS_DC); +PHP_COM_DOTNET_API int php_com_import_typelib(ITypeLib *TL, int mode, + int codepage TSRMLS_DC); +void php_com_typelibrary_dtor(void *pDest); +ITypeInfo *php_com_locate_typeinfo(char *typelibname, php_com_dotnet_object *obj, char *dispname, int sink TSRMLS_DC); +int php_com_process_typeinfo(ITypeInfo *typeinfo, HashTable *id_to_name, int printdef, GUID *guid, int codepage TSRMLS_DC); + +/* com_iterator.c */ +zend_object_iterator *php_com_iter_get(zend_class_entry *ce, zval *object, int by_ref TSRMLS_DC); + + +#endif diff --git a/ext/com_dotnet/tests/27974.phpt b/ext/com_dotnet/tests/27974.phpt new file mode 100644 index 0000000..30c42b6 --- /dev/null +++ b/ext/com_dotnet/tests/27974.phpt @@ -0,0 +1,43 @@ +--TEST-- +COM: mapping a safearray +--SKIPIF-- +<?php # vim:ft=php +if (!extension_loaded("com_dotnet")) print "skip COM/.Net support not present"; ?> +--FILE-- +<?php // $Id$ +error_reporting(E_ALL); + +try { + $v = new VARIANT(array("123", "456", "789")); + var_dump($v); + print $v[0] . "\n"; + print $v[1] . "\n"; + print $v[2] . "\n"; + $v[1] = "hello"; + foreach ($v as $item) { + var_dump($item); + } + try { + $v[3] = "shouldn't work"; + } catch (com_exception $e) { + if ($e->getCode() != DISP_E_BADINDEX) { + throw $e; + } + echo "Got BADINDEX exception OK!\n"; + } + echo "OK!"; +} catch (Exception $e) { + print $e; +} +?> +--EXPECT-- +object(variant)#1 (0) { +} +123 +456 +789 +string(3) "123" +string(5) "hello" +string(3) "789" +Got BADINDEX exception OK! +OK! diff --git a/ext/com_dotnet/tests/bug33386.phpt b/ext/com_dotnet/tests/bug33386.phpt new file mode 100644 index 0000000..e57f127 --- /dev/null +++ b/ext/com_dotnet/tests/bug33386.phpt @@ -0,0 +1,40 @@ +--TEST-- +Bug #33386 (ScriptControl only sees last function of class) +--SKIPIF-- +<?php +if (!extension_loaded("com_dotnet")) print "skip COM/.Net support not present"; ?> +--FILE-- +<?php +error_reporting(E_ALL); + +class twoFuncs { + public function func1() { echo " func one\n"; } + public function func2() { echo " func two\n"; } +} + +try { + $ciTF = new twoFuncs; + + $oScript = new COM("MSScriptControl.ScriptControl"); + $oScript->Language = "VBScript"; + + $oScript->AddObject ("tfA", $ciTF, true); + foreach (array(1,2) as $i) { + $oScript->ExecuteStatement ("tfA.func$i"); + $oScript->ExecuteStatement ("func$i"); + } + $oScript->AddObject ("tfB", $ciTF); + foreach (array(1,2) as $i) { + $oScript->ExecuteStatement ("tfB.func$i"); + } +} catch (Exception $e) { + print $e; +} +?> +--EXPECT-- + func one + func one + func two + func two + func one + func two diff --git a/ext/com_dotnet/tests/bug34272.phpt b/ext/com_dotnet/tests/bug34272.phpt new file mode 100644 index 0000000..3a65e2c --- /dev/null +++ b/ext/com_dotnet/tests/bug34272.phpt @@ -0,0 +1,22 @@ +--TEST-- +Bug #34272 (empty array onto COM object blows up) +--SKIPIF-- +<?php +if (!extension_loaded("com_dotnet")) print "skip COM/.Net support not present"; ?> +--FILE-- +<?php +error_reporting(E_ALL); + +try { + $dict = new COM("Scripting.Dictionary"); + $dict->add('foo', array()); + print sizeof($dict['foo'])."\n"; + $dict->add('bar', array(23)); + print sizeof($dict['bar'])." \n"; +} catch (Exception $e) { + print $e; +} +?> +--EXPECT-- +0 +1 diff --git a/ext/com_dotnet/tests/bug39596.phpt b/ext/com_dotnet/tests/bug39596.phpt new file mode 100644 index 0000000..dc8d1ef --- /dev/null +++ b/ext/com_dotnet/tests/bug39596.phpt @@ -0,0 +1,23 @@ +--TEST-- +Bug #39596 (Creating Variant of type VT_ARRAY) +--SKIPIF-- +<?php +if (!extension_loaded("com_dotnet")) print "skip COM/.Net support not present"; ?> +--FILE-- +<?php +error_reporting(E_ALL); + +try { + $binding_string = array('aaa','bbb','ccc'); + $v = new VARIANT( $binding_string, VT_ARRAY ); + foreach ($v AS $element) { + print $element."\n"; + } +} catch (Exception $e) { + print $e; +} +?> +--EXPECT-- +aaa +bbb +ccc diff --git a/ext/com_dotnet/tests/bug39606.phpt b/ext/com_dotnet/tests/bug39606.phpt new file mode 100644 index 0000000..4487c1d --- /dev/null +++ b/ext/com_dotnet/tests/bug39606.phpt @@ -0,0 +1,22 @@ +--TEST-- +COM: Loading typelib corrupts memory +--SKIPIF-- +<?php # vim:ft=php +if (!extension_loaded("com_dotnet")) print "skip COM/.Net support not present"; ?> +--FILE-- +<?php // $Id$ +error_reporting(E_ALL); + +$arEnv = array_change_key_case($_SERVER, CASE_UPPER); + +$root = dirname($arEnv['COMSPEC']); +$typelib = $root.'\activeds.tlb'; + +var_dump(com_load_typelib($typelib)); +var_dump(com_load_typelib($typelib)); +?> +===DONE=== +--EXPECT-- +bool(true) +bool(true) +===DONE===
\ No newline at end of file diff --git a/ext/com_dotnet/tests/bug45280.phpt b/ext/com_dotnet/tests/bug45280.phpt new file mode 100644 index 0000000..a1f7e18 --- /dev/null +++ b/ext/com_dotnet/tests/bug45280.phpt @@ -0,0 +1,19 @@ +--TEST-- +Bug #45280 (Reflection of instantiated COM classes causes PHP to crash) +--SKIPIF-- +<?php +if (!extension_loaded("reflection")){ echo "skip, no reflection support present"; } +if (!extension_loaded("com_dotnet")){ echo "skip COM/.Net support not present"; } +?> +--FILE-- +<?php +$dict = new COM("Scripting.Dictionary"); + +ob_start(); +ReflectionObject::export($dict); +ob_get_clean(); + +echo 'done'; +?> +--EXPECT-- +done diff --git a/ext/com_dotnet/tests/bug49192.phpt b/ext/com_dotnet/tests/bug49192.phpt new file mode 100644 index 0000000..ccbf237 --- /dev/null +++ b/ext/com_dotnet/tests/bug49192.phpt @@ -0,0 +1,27 @@ +--TEST-- +Bug #49192 (PHP crashes when GC invoked on COM object) +--SKIPIF-- +<?php +if (!extension_loaded("com_dotnet")) print "skip COM/.Net support not present"; ?> +--XFAIL-- +1 +--FILE-- +<?php + +// this test fails to load ADO +// +// a change in windows longhorn x64(affecting vista, 7, 8, 2008, 2008r2) broke ADO. +// +// there is a fix available, but user has to install it. +// given that ADO was deprecated a long time ago in favor of newer APIs, +// I don't think its worth the trouble of making the user install the fix to +// get an accurate test run. its better to just not run the test or expect it to fail. +// +// see: http://support.microsoft.com/kb/2517589 +// see: http://www.infoq.com/news/2011/10/ADO-Win7 + +$dbConnection = new Com('ADODB.Connection'); +var_dump(gc_collect_cycles()); +?> +--EXPECT-- +int(0) diff --git a/ext/com_dotnet/tests/variants.phpt b/ext/com_dotnet/tests/variants.phpt new file mode 100644 index 0000000..0fd27be --- /dev/null +++ b/ext/com_dotnet/tests/variants.phpt @@ -0,0 +1,637 @@ +--TEST-- +COM: General variant tests +--SKIPIF-- +<?php # vim:ft=php +if (!extension_loaded("com_dotnet")) print "skip COM/.Net support not present"; ?> +--FILE-- +<?php // $Id$ +error_reporting(E_ALL); + +$v = new VARIANT(); +if (VT_EMPTY != variant_get_type($v)) { + echo "VT_EMPTY: bork\n"; +} + +$values = array(VT_I4 => 42, VT_R8 => 3.5, VT_BSTR => "hello", VT_BOOL => false); +$binary_ops = array('add', 'cat', 'sub', 'mul', 'and', 'div', + 'eqv', 'idiv', 'imp', 'mod', 'or', 'pow', 'xor'); + +foreach ($values as $t => $val) { + $v = new VARIANT($val); + if ($t != variant_get_type($v)) { + printf("Bork: [%d] %d: %s\n", $t, variant_get_type($v), $val); + print $v . "\n"; + } + $results = array(); + + foreach ($values as $op2) { + echo "--\n"; + foreach ($binary_ops as $op) { + try { + echo "$op: " . call_user_func('variant_' . $op, $v, $op2) . "\n"; + } catch (com_exception $e) { + echo "$op:\n"; + echo "\tvariant_$op($v, $op2)\n"; + echo "\texception " . $e->getMessage(); + printf("\tcode %08x\n\n", $e->getCode()); + } + } + } +} + +echo "OK!"; +?> +--EXPECT-- +-- +add: 84 +cat: 4242 +sub: 0 +mul: 1764 +and: 42 +div: 1 +eqv: -1 +idiv: 1 +imp: -1 +mod: 0 +or: 42 +pow: 1.50130937545297E+68 +xor: 0 +-- +add: 45.5 +cat: 423.5 +sub: 38.5 +mul: 147 +and: 0 +div: 12 +eqv: -47 +idiv: 10 +imp: -43 +mod: 2 +or: 46 +pow: 480145.116863642 +xor: 46 +-- +add: + variant_add(42, hello) + exception Type mismatch. + code 80020005 + +cat: 42hello +sub: + variant_sub(42, hello) + exception Type mismatch. + code 80020005 + +mul: + variant_mul(42, hello) + exception Type mismatch. + code 80020005 + +and: + variant_and(42, hello) + exception Type mismatch. + code 80020005 + +div: + variant_div(42, hello) + exception Type mismatch. + code 80020005 + +eqv: + variant_eqv(42, hello) + exception Type mismatch. + code 80020005 + +idiv: + variant_idiv(42, hello) + exception Type mismatch. + code 80020005 + +imp: + variant_imp(42, hello) + exception Type mismatch. + code 80020005 + +mod: + variant_mod(42, hello) + exception Type mismatch. + code 80020005 + +or: + variant_or(42, hello) + exception Type mismatch. + code 80020005 + +pow: + variant_pow(42, hello) + exception Type mismatch. + code 80020005 + +xor: + variant_xor(42, hello) + exception Type mismatch. + code 80020005 + +-- +add: 42 +cat: 42False +sub: 42 +mul: 0 +and: 0 +div: + variant_div(42, ) + exception Division by zero. + code 80020012 + +eqv: -43 +idiv: + variant_idiv(42, ) + exception Division by zero. + code 80020012 + +imp: -43 +mod: + variant_mod(42, ) + exception Division by zero. + code 80020012 + +or: 42 +pow: 1 +xor: 42 +-- +add: 45.5 +cat: 3.542 +sub: -38.5 +mul: 147 +and: 0 +div: 8.33333333333333E-02 +eqv: -47 +idiv: 0 +imp: -5 +mod: 4 +or: 46 +pow: 7.09345573078604E+22 +xor: 46 +-- +add: 7 +cat: 3.53.5 +sub: 0 +mul: 12.25 +and: 4 +div: 1 +eqv: -1 +idiv: 1 +imp: -1 +mod: 0 +or: 4 +pow: 80.2117802289664 +xor: 0 +-- +add: + variant_add(3.5, hello) + exception Type mismatch. + code 80020005 + +cat: 3.5hello +sub: + variant_sub(3.5, hello) + exception Type mismatch. + code 80020005 + +mul: + variant_mul(3.5, hello) + exception Type mismatch. + code 80020005 + +and: + variant_and(3.5, hello) + exception Type mismatch. + code 80020005 + +div: + variant_div(3.5, hello) + exception Type mismatch. + code 80020005 + +eqv: + variant_eqv(3.5, hello) + exception Type mismatch. + code 80020005 + +idiv: + variant_idiv(3.5, hello) + exception Type mismatch. + code 80020005 + +imp: + variant_imp(3.5, hello) + exception Type mismatch. + code 80020005 + +mod: + variant_mod(3.5, hello) + exception Type mismatch. + code 80020005 + +or: + variant_or(3.5, hello) + exception Type mismatch. + code 80020005 + +pow: + variant_pow(3.5, hello) + exception Type mismatch. + code 80020005 + +xor: + variant_xor(3.5, hello) + exception Type mismatch. + code 80020005 + +-- +add: 3.5 +cat: 3.5False +sub: 3.5 +mul: 0 +and: 0 +div: + variant_div(3.5, ) + exception Division by zero. + code 80020012 + +eqv: -5 +idiv: + variant_idiv(3.5, ) + exception Division by zero. + code 80020012 + +imp: -5 +mod: + variant_mod(3.5, ) + exception Division by zero. + code 80020012 + +or: 4 +pow: 1 +xor: 4 +-- +add: + variant_add(hello, 42) + exception Type mismatch. + code 80020005 + +cat: hello42 +sub: + variant_sub(hello, 42) + exception Type mismatch. + code 80020005 + +mul: + variant_mul(hello, 42) + exception Type mismatch. + code 80020005 + +and: + variant_and(hello, 42) + exception Type mismatch. + code 80020005 + +div: + variant_div(hello, 42) + exception Type mismatch. + code 80020005 + +eqv: + variant_eqv(hello, 42) + exception Type mismatch. + code 80020005 + +idiv: + variant_idiv(hello, 42) + exception Type mismatch. + code 80020005 + +imp: + variant_imp(hello, 42) + exception Type mismatch. + code 80020005 + +mod: + variant_mod(hello, 42) + exception Type mismatch. + code 80020005 + +or: + variant_or(hello, 42) + exception Type mismatch. + code 80020005 + +pow: + variant_pow(hello, 42) + exception Type mismatch. + code 80020005 + +xor: + variant_xor(hello, 42) + exception Type mismatch. + code 80020005 + +-- +add: + variant_add(hello, 3.5) + exception Type mismatch. + code 80020005 + +cat: hello3.5 +sub: + variant_sub(hello, 3.5) + exception Type mismatch. + code 80020005 + +mul: + variant_mul(hello, 3.5) + exception Type mismatch. + code 80020005 + +and: + variant_and(hello, 3.5) + exception Type mismatch. + code 80020005 + +div: + variant_div(hello, 3.5) + exception Type mismatch. + code 80020005 + +eqv: + variant_eqv(hello, 3.5) + exception Type mismatch. + code 80020005 + +idiv: + variant_idiv(hello, 3.5) + exception Type mismatch. + code 80020005 + +imp: + variant_imp(hello, 3.5) + exception Type mismatch. + code 80020005 + +mod: + variant_mod(hello, 3.5) + exception Type mismatch. + code 80020005 + +or: + variant_or(hello, 3.5) + exception Type mismatch. + code 80020005 + +pow: + variant_pow(hello, 3.5) + exception Type mismatch. + code 80020005 + +xor: + variant_xor(hello, 3.5) + exception Type mismatch. + code 80020005 + +-- +add: hellohello +cat: hellohello +sub: + variant_sub(hello, hello) + exception Type mismatch. + code 80020005 + +mul: + variant_mul(hello, hello) + exception Type mismatch. + code 80020005 + +and: + variant_and(hello, hello) + exception Type mismatch. + code 80020005 + +div: + variant_div(hello, hello) + exception Type mismatch. + code 80020005 + +eqv: + variant_eqv(hello, hello) + exception Type mismatch. + code 80020005 + +idiv: + variant_idiv(hello, hello) + exception Type mismatch. + code 80020005 + +imp: + variant_imp(hello, hello) + exception Type mismatch. + code 80020005 + +mod: + variant_mod(hello, hello) + exception Type mismatch. + code 80020005 + +or: + variant_or(hello, hello) + exception Type mismatch. + code 80020005 + +pow: + variant_pow(hello, hello) + exception Type mismatch. + code 80020005 + +xor: + variant_xor(hello, hello) + exception Type mismatch. + code 80020005 + +-- +add: + variant_add(hello, ) + exception Type mismatch. + code 80020005 + +cat: helloFalse +sub: + variant_sub(hello, ) + exception Type mismatch. + code 80020005 + +mul: + variant_mul(hello, ) + exception Type mismatch. + code 80020005 + +and: + variant_and(hello, ) + exception Type mismatch. + code 80020005 + +div: + variant_div(hello, ) + exception Type mismatch. + code 80020005 + +eqv: + variant_eqv(hello, ) + exception Type mismatch. + code 80020005 + +idiv: + variant_idiv(hello, ) + exception Type mismatch. + code 80020005 + +imp: + variant_imp(hello, ) + exception Type mismatch. + code 80020005 + +mod: + variant_mod(hello, ) + exception Type mismatch. + code 80020005 + +or: + variant_or(hello, ) + exception Type mismatch. + code 80020005 + +pow: + variant_pow(hello, ) + exception Type mismatch. + code 80020005 + +xor: + variant_xor(hello, ) + exception Type mismatch. + code 80020005 + +-- +add: 42 +cat: False42 +sub: -42 +mul: 0 +and: 0 +div: 0 +eqv: -43 +idiv: 0 +imp: -1 +mod: 0 +or: 42 +pow: 0 +xor: 42 +-- +add: 3.5 +cat: False3.5 +sub: -3.5 +mul: 0 +and: 0 +div: 0 +eqv: -5 +idiv: 0 +imp: -1 +mod: 0 +or: 4 +pow: 0 +xor: 4 +-- +add: + variant_add(0, hello) + exception Type mismatch. + code 80020005 + +cat: Falsehello +sub: + variant_sub(0, hello) + exception Type mismatch. + code 80020005 + +mul: + variant_mul(0, hello) + exception Type mismatch. + code 80020005 + +and: + variant_and(0, hello) + exception Type mismatch. + code 80020005 + +div: + variant_div(0, hello) + exception Type mismatch. + code 80020005 + +eqv: + variant_eqv(0, hello) + exception Type mismatch. + code 80020005 + +idiv: + variant_idiv(0, hello) + exception Type mismatch. + code 80020005 + +imp: + variant_imp(0, hello) + exception Type mismatch. + code 80020005 + +mod: + variant_mod(0, hello) + exception Type mismatch. + code 80020005 + +or: + variant_or(0, hello) + exception Type mismatch. + code 80020005 + +pow: + variant_pow(0, hello) + exception Type mismatch. + code 80020005 + +xor: + variant_xor(0, hello) + exception Type mismatch. + code 80020005 + +-- +add: 0 +cat: FalseFalse +sub: 0 +mul: 0 +and: 0 +div: + variant_div(0, ) + exception Out of present range. + code 8002000a + +eqv: -1 +idiv: + variant_idiv(0, ) + exception Division by zero. + code 80020012 + +imp: -1 +mod: + variant_mod(0, ) + exception Division by zero. + code 80020012 + +or: 0 +pow: 1 +xor: 0 +OK! |