Closed
Description
Description
Can not create VT_ERROR variant type. It's required for some COM method calls where you need to specify an empty argument, since it's VT_ERROR type variant with error DISP_E_PARAMNOTFOUND (0x80020004) as actual error code (see here).
The following code:
<?php // database.php
// next line works only in PHP 4.4.9 (PHP 5, 6, 7 and 8 are bugged)
$vtMissing = new VARIANT(0x80020004, VT_ERROR);
$db = new COM('ADODB.Connection');
$db->ConnectionString = 'Provider=Microsoft.Jet.OLEDB.4.0;Data Source=database.mdb';
$db->Mode = 1; // adModeRead
$db->Open();
// adSchemaProviderSpecific, *missing*, JET_SCHEMA_USERROSTER
$rs = $db->OpenSchema(-1, $vtMissing, '{947bb102-5d43-11d1-bdbf-00c04fb92675}');
// manual counting since rs.RecordCount is -1 (not supported)
$i = 0;
while (!$rs->EOF) {
$rs->MoveNext();
$i++;
}
$rs->Close();
$rs = null;
$db->Close();
$db = null;
echo $i;Resulted in this output:
Fatal error: Uncaught com_exception: Variant type conversion failed: Type mismatch in Z:\database.php:3
Stack trace:
#0 Z:\database.php(3): variant->__construct(2147614724, 10)
#1 {main}
thrown in Z:\database.php on line 3
But I expected this output instead:
1
Test file "database.mdb" can be any Microsoft Access database file, even without any tables inside.
More details can be found in this article (auto-translated from Russian):
VT_MISSING argument in PHP for COM objects
PHP Version
PHP 8.1.7 x64
Operating System
Windows 7 x64
Activity
cmb69 commentedon Jun 12, 2022
Thanks for reporting, and the good research!
Yeah. That is because as of PHP 5.0.0 the algorithm first creates a variant that matches the given
$valuewithout even considering the$type, and then tries to convert it to that$type. This doesn't work forVT_ERROR(and relatedly to typed arrays).While it certainly would be possible to allow to create
VT_ERRORvariants, I don't think this makes much sense generally. As I understand it, your case (VT_ERRORtype variant with errorDISP_E_PARAMNOTFOUND) is the only one that needs to be supported. As such, another way to get that variant appears to make more sense. Maybe in form of a special function, or a creation method onvariant, or maybe even a constant.However, PHP still doesn't allow to skip parameters, but has introduced named parameters, so using these might be an option. I gave that a quick try, but that leads to a crash in the following code:
php-src/Zend/zend_execute.c
Lines 4747 to 4748 in 2dc9026
I don't think this is com_dotnet specific, but likely rather happens for all internal objects which allow dynamic methods (i.e. basically implement
::__call()). I think that can be fixed, and named arguments could be supported by com_dotnet, but that would be a much bigger task than offering a way to get aVT_ERRORtype variant with errorDISP_E_PARAMNOTFOUNDwhich could be passed explicitly by the programmer.systoolz commentedon Jun 12, 2022
Thank you for detailed in-depth comment!
If this is the only case where
VT_ERRORtype is required, then I totally agree with you and it would be great to have something that could be used as a valid missing argument, be it a constant, a special function, or anything else.Thank you!
Fix phpGH-8750: Can not create VT_ERROR variant type
cmb69 commentedon Jun 21, 2022
I had a further look at this, and have to admit that the constructor not working for
VT_ERRORis really strange, especially that it is documented:And
VT_ERRORis clearly listed there. SupportingVT_ERRORthere would be trivial, but it would likely be confusing if someone wanted to convert aVT_ERRORvariant to a string; should that print the scode, or fail, or whatever. Since the round-trip is apparently not well defined, I think we should indeed not support to createVT_ERRORvariant viavariant::__construct(), but instead fix the documentation.I'm no longer sure about that. There may be other use cases where creating
VT_ERRORvariants would be necessary, and I don't see the particular need to only supportDISP_E_PARAMNOTFOUND.systoolz commentedon Jun 21, 2022
As I see
variant::createError()accepts only integers as values. Is it possible in that case to treatVT_ERRORasVT_INTinternally when creatingVT_ERROR? I.e. limitscodevalues only to integers? This will solve next things:VT_ERRORto string same asVT_INTto string (throw an error if variant typeVT_ERRORfor example returned from COM object and didn't contain integer type inscode), I don't think it's worth to supportDISP_E_*string constants since in PHP it's limited and people always can find error code names by actual integer valuesnew variant(DISP_E_PARAMNOTFOUND, VT_ERROR)as it specified in documentation, i.e. people will not be confused with two ways of creatingVT_ERRORtype: new one withvariant::createError()and old one withnew variant()andVT_ERRORwhich still there and still throws an errorVariant type conversion failed: Type mismatchas it was now when creatingVT_ERRORfor any other types except integer asscodevaluecmb69 commentedon Jun 26, 2022
First, an
scodeis always an integer, namely a unsigned 32bit integer. That is the reason why I suggest to add theDISP_E_PARAMNOTFOUNDconstant, because on 32bit systems, its value would have to be negative, since0x80020004would be converted to float by PHP.It is certainly debatable whether it is better to provide a new method (
::createError()) or to support creatingVT_ERRORvia the constructor. I don't think having both makes much sense. However, I went with the new method, because I'm strictly opposed to have implicit conversion the other way round (i.e. convert variant(VT_ERROR) to an integer zval). This could easily lead to hard to find bugs, e.g. when doing artihmetic on a function result which is supposed to some integer, but actually is aVT_ERROR. It should be noted that VBScript does not allow any creation or manipulation of error variants, and although VB allows creation and explicit conversion of error variants, it still does not support implicit conversion.We could support explicit conversion from error variants to integer variants via
variant_cast(); if we consider that useful, I think it would make sense to supportvariant::__construct(…, VT_ERROR)instead of introducing the new methodvariant::createError().systoolz commentedon Jun 26, 2022
Thank you for providing more details!
I fully agree with all of your points.
I have to say that I can only suggest something or give feedback from the end user's point of view. I don't know if implementing certain things would be time consuming or not useful at all. So please consider my comments as humble suggestions only.
Thank you for your help!
Fix phpGH-8750: Can not create VT_ERROR variant type
cmb69 commentedon Jun 28, 2022
I just submitted PR #8886 which would be an alternative implementation.
systoolz commentedon Jun 29, 2022
Thank you!
In my humble opinion, the alternative implementation in PR #8886 is exactly what is needed.