Skip to content

Bugfix (ext/com_dotnet): Added VT_ARRAY, VT_BYREF, VT_PTR functionality #7628

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
12 changes: 9 additions & 3 deletions ext/com_dotnet/com_com.c
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ HRESULT php_com_invoke_helper(php_com_dotnet_object *obj, DISPID id_member,
WORD flags, DISPPARAMS *disp_params, VARIANT *v, bool silent, bool allow_noarg)
{
HRESULT hr;
unsigned int arg_err;
unsigned int arg_err = (unsigned int)-1;
EXCEPINFO e = {0};

hr = IDispatch_Invoke(V_DISPATCH(&obj->v), id_member,
Expand All @@ -343,6 +343,9 @@ HRESULT php_com_invoke_helper(php_com_dotnet_object *obj, DISPID id_member,
if (!silent && FAILED(hr)) {
char *desc = NULL, *msg = NULL;

if(arg_err != (unsigned int)-1)
arg_err = disp_params->cArgs - arg_err; // We have reversed array of params

switch (hr) {
case DISP_E_EXCEPTION: {
zend_string *source = NULL, *desc_str = NULL;
Expand Down Expand Up @@ -377,7 +380,7 @@ HRESULT php_com_invoke_helper(php_com_dotnet_object *obj, DISPID id_member,
case DISP_E_PARAMNOTFOUND:
case DISP_E_TYPEMISMATCH:
desc = php_win32_error_to_msg(hr);
spprintf(&msg, 0, "Parameter %d: %s", arg_err, desc);
spprintf(&msg, 0, "Error [0x%08x] Parameter %d: %s", hr, arg_err, desc);
php_win32_error_msg_free(desc);
break;

Expand All @@ -393,7 +396,10 @@ HRESULT php_com_invoke_helper(php_com_dotnet_object *obj, DISPID id_member,

default:
desc = php_win32_error_to_msg(hr);
spprintf(&msg, 0, "Error [0x%08x] %s", hr, desc);
if(arg_err != (unsigned int) -1)
spprintf(&msg, 0, "Error [0x%08x] Parameter %d: %s", hr, arg_err, desc);
else
spprintf(&msg, 0, "Error [0x%08x] %s", hr, desc);
php_win32_error_msg_free(desc);
break;
}
Expand Down
1 change: 1 addition & 0 deletions ext/com_dotnet/com_extension.c
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ PHP_MINIT_FUNCTION(com_dotnet)
COM_CONST(VT_R4);
COM_CONST(VT_R8);
COM_CONST(VT_BOOL);
COM_CONST(VT_PTR);
COM_CONST(VT_ERROR);
COM_CONST(VT_CY);
COM_CONST(VT_DATE);
Expand Down
4 changes: 4 additions & 0 deletions ext/com_dotnet/com_handlers.c
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,7 @@ void php_com_object_free_storage(zend_object *object)
}

VariantClear(&obj->v);
zval_dtor(&obj->byref);

if (obj->method_cache) {
zend_hash_destroy(obj->method_cache);
Expand Down Expand Up @@ -603,6 +604,9 @@ zend_object* php_com_object_clone(zend_object *object)
* want to clone as much as possible */
VariantCopyInd(&cloneobj->v, &origobject->v);

ZVAL_NULL(&cloneobj->byref);
ZVAL_COPY(&cloneobj->byref, &origobject->byref);

if (cloneobj->typeinfo) {
ITypeInfo_AddRef(cloneobj->typeinfo);
}
Expand Down
1 change: 0 additions & 1 deletion ext/com_dotnet/com_misc.c
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,6 @@ PHP_COM_DOTNET_API bool php_com_safearray_get_elem(VARIANT *array, VARIANT *dest

/* check bounds */
if (dim1 < lbound || dim1 > ubound) {
php_com_throw_exception(DISP_E_BADINDEX, "index out of bounds");
return 0;
}

Expand Down
203 changes: 183 additions & 20 deletions ext/com_dotnet/com_variant.c
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,13 @@ PHP_COM_DOTNET_API void php_com_variant_from_zval(VARIANT *v, zval *z, int codep
}
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;
if(V_VT(&obj->v) & VT_BYREF) { //Allready by reference
memcpy(v, &obj->v, sizeof(VARIANT));
} 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 */
Expand Down Expand Up @@ -429,6 +433,7 @@ PHP_METHOD(variant, __construct)
php_com_dotnet_object *obj;
zval *zvalue = NULL;
HRESULT res;
char *msg = NULL;

if (ZEND_NUM_ARGS() == 0) {
/* just leave things as-is - an empty variant */
Expand All @@ -451,36 +456,194 @@ PHP_METHOD(variant, __construct)
php_com_variant_from_zval(&obj->v, zvalue, obj->code_page);
}

if(ZEND_NUM_ARGS() >= 2 && vt == VT_ARRAY && V_VT(&obj->v) & VT_ARRAY)
vt = VT_EMPTY;//Prevent future VariantChangeType

/* 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 (original behavior)
*/
if ((vt & VT_ARRAY) && (V_VT(&obj->v) & VT_ARRAY)) {
zend_long orig_vt = vt;

vt &= ~VT_ARRAY;
if (vt) {
vt = orig_vt;
/**
* VT_BYREF, VT_VARIANT and VT_PTR functionality
*
* All of those types makes reference to another php_com_dotnet_object incrementing their refcounters.
* If you use VT_VARIANT then it will be VT_BYREF | VT_VARIANT.
* If you use VT_PTR then you get reference to VARIANTTAG.
* If you use VT_PTR | VT_BYREF then you get reference to VARIANTTAG's variable dependently of their type.
*
* For future: may be need to check if remote variable is changed.
*/
if((vt & VT_BYREF) || ((vt & ~VT_BYREF) == VT_VARIANT) || ((vt & ~VT_BYREF) == VT_PTR)) {

if((vt & VT_BYREF) && (vt & ~VT_BYREF != VT_VARIANT) && (vt & ~VT_BYREF != VT_PTR)) {
spprintf(&msg, E_INVALIDARG, "VT_BYREF should be used alone or with VT_VARIANT or with VT_PTR");
php_com_throw_exception(E_INVALIDARG, msg);
efree(msg);
return;
}

ZVAL_DEREF(zvalue);
if(!php_com_is_valid_object(zvalue)) {
spprintf(&msg, 0, "To use VT_BYREF or VT_VARIANT you must have another VARIANT object as 'value'");
php_com_throw_exception(E_INVALIDARG, msg);
efree(msg);
return;
}

//Save a reference to original object
zval_dtor(&obj->byref); //Remove old value if exist
ZVAL_NEW_REF(&obj->byref, zvalue); //Set new reference to zvalue
Z_ADDREF_P(zvalue); //Increment their counter

php_com_dotnet_object * src = CDNO_FETCH(zvalue);
VariantClear(&obj->v); //Remove our default value

switch(V_VT(&src->v)) {
case VT_I2:
case VT_I4:
case VT_R4:
case VT_R8:
case VT_CY:
case VT_DATE:
case VT_BSTR:
case VT_DISPATCH:
case VT_ERROR:
case VT_BOOL:
case VT_UNKNOWN:
case VT_I1:
case VT_UI1:
case VT_UI2:
case VT_UI4:
case VT_I8:
case VT_UI8:
case VT_INT:
case VT_UINT:
case VT_VOID:
case VT_HRESULT:
case VT_PTR:
case VT_SAFEARRAY:
case VT_INT_PTR:
case VT_UINT_PTR:
obj->v.byref = &V_NONE(&src->v);
break;
case VT_DECIMAL:
obj->v.byref = &V_DECIMAL(&src->v);
break;
default:
obj->v.byref = &src->v;
V_VT(&obj->v) = VT_BYREF | VT_VARIANT;
break;
}

switch(vt & ~VT_BYREF) {
case VT_PTR:
V_VT(&obj->v) = VT_PTR;
if(!(vt & VT_BYREF)) //If only VT_PTR is used, then ptr is referenced to VARIANT, otherway to value
obj->v.byref = &src->v;
break;
case VT_VARIANT:
V_VT(&obj->v) = vt;
V_VARIANTREF(&obj->v) = &src->v;
break;
default:
V_VT(&obj->v) = V_VT(&src->v) | VT_BYREF;
break;
}
vt = VT_EMPTY; //Prevent future VariantChangeType
}


/**
* VT_ARRAY functionality
*
* 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
*/

res = S_OK;
SAFEARRAYBOUND Bound;

if ((vt & ~VT_ARRAY) && //new variant have some type except VT_ARRAY
(V_VT(&obj->v) & VT_ARRAY) && //our variant is array
SafeArrayGetDim(V_ARRAY(&obj->v)) == 1 && //our array have one dimension
SUCCEEDED(res = SafeArrayGetLBound(V_ARRAY(&obj->v), 1, &Bound.lLbound)) &&
SUCCEEDED(res = SafeArrayGetUBound(V_ARRAY(&obj->v), 1, &Bound.cElements)))
{
zend_long need_vt = vt & ~VT_ARRAY;
zend_long old_vt = V_VT(&obj->v) & ~VT_ARRAY;

Bound.cElements -= Bound.lLbound - 1;
SAFEARRAY * newArray = SafeArrayCreate(need_vt, 1, &Bound);

if (newArray) {

for (LONG i = Bound.lLbound; i < Bound.lLbound + Bound.cElements; i++) {
VARIANT temp;
VariantInit(&temp);
if (old_vt != VT_VARIANT)
V_VT(&temp) = old_vt;

switch (old_vt) {
case VT_VARIANT:
res = SafeArrayGetElement(V_ARRAY(&obj->v), &i, &temp); break;
default:
res = SafeArrayGetElement(V_ARRAY(&obj->v), &i, &V_I8(&temp)); break;
}

if (FAILED(res))
break;

if (FAILED(res = VariantChangeType(&temp, &temp, 0, (VARTYPE)need_vt))) {
VariantClear(&temp);
break;
}

switch (need_vt) {
case VT_VARIANT:
res = SafeArrayPutElement(newArray, &i, &temp); break;
case VT_DISPATCH:
case VT_UNKNOWN:
case VT_BSTR:
res = SafeArrayPutElement(newArray, &i, V_DISPATCH(&temp)); break;
default:
res = SafeArrayPutElement(newArray, &i, &V_I8(&temp));
break;
}

VariantClear(&temp);
if (FAILED(res))
break;
}

if (SUCCEEDED(res)) {
SafeArrayDestroy(V_ARRAY(&obj->v));
V_ARRAY(&obj->v) = newArray;
V_VT(&obj->v) = vt | VT_ARRAY; //Set VT_ARRAY automatically
vt = VT_EMPTY;//No need change type any more
}
else
SafeArrayDestroy(newArray);
}
}

if (vt) {
/** Try to system conversion function if our array conversion
* function don't do conversion before (vt is not VT_EMPTY)
* and wasn't tried it (SUCCEEDED(res))
*/

if (SUCCEEDED(res) && vt) {
res = VariantChangeType(&obj->v, &obj->v, 0, (VARTYPE)vt);
}

if (FAILED(res)) {
char *werr, *msg;
if (FAILED(res)) {
char *werr, *msg;

werr = php_win32_error_to_msg(res);
spprintf(&msg, 0, "Variant type conversion failed: %s", werr);
php_win32_error_msg_free(werr);

php_com_throw_exception(res, msg);
efree(msg);
}
php_com_throw_exception(res, msg);
efree(msg);
}
}

Expand Down
1 change: 1 addition & 0 deletions ext/com_dotnet/php_com_dotnet_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ typedef struct _php_com_dotnet_object {
zend_object zo;

VARIANT v;
zval byref;
bool modified;

int code_page;
Expand Down