comtypes icon indicating copy to clipboard operation
comtypes copied to clipboard

Support VT_ERROR variant type and optional parameters

Open bennyrowland opened this issue 3 years ago • 9 comments

Currently comtypes does not support variants with typecode 0xa (VT_ERROR). This is particularly important for servers supporting optional arguments for their methods, as an optional argument is replaced with a VT_ERROR variant with a particular error code (DISP_E_PARAMNOTFOUND).

Supporting VT_ERROR itself seems like a simple fix, as internally it is just an HRESULT, i.e. a 32-bit integer (where different parts of the bit field represent different components). As far as I can tell, the only change necessary is to modify VARIANT.__get_value() to have a VT_ERROR branch which duplicates the 32bit int pathway. comtypes currently has some support for creating HRESULTs from the individual components but does not have an actual class representing them, a more advanced fix would perhaps be to create an HRESULT/SCODE class that would allow the subfields to be examined.

However, the most important change required is to modify the machinery used in calling the Python functions on the server class so that if an argument is supplied with DISP_E_PARAMNOTFOUND then this can be removed from the argument list actually supplied to the server. I am not completely sure how to do this so would welcome some support in implementing this, and any pitfalls that might be lurking out there for the unwary. @vasily-v-ryabov , @cfarrow do you have any thoughts?

bennyrowland avatar Jun 30 '22 15:06 bennyrowland

#372 is very close to your suggestion.

In 1.2.0, repr(VARIANT.missing) returns "VARIANT.missing". https://github.com/enthought/comtypes/blob/d1f5cd7e6c73c52f30a36c226cdde21586cdc4b7/comtypes/automation.py#L220-L229

When implementing #486, I noticed that the VARIANT with vt==VT_ERROR and _.VT_I4==DISP_E_PARAMNOTFOUND is not only VARIANT.missing.

Your suggestion is smarter than adding a workaround that will not cause errors when repring such VARIANT.

But I'm not sure what type the .value property should return.

I think that use cases implementing com-ffi and VARIANT in other languages would be helpful.

junkmd avatar Jun 13 '23 14:06 junkmd

I don't know whether this will be helpful for this issue, I will share how COM support in other languages has handled VARIANT.

In PHP, There was an error that VARIANT with VT_ERROR could not be created.

https://github.com/php/php-src/issues/8750 https://github.com/php/php-src/commit/56804e32216574c66cf71359a8a8830e7badc757

junkmd avatar Jun 14 '23 15:06 junkmd

In ruby, "VARIANT with vt==VT_ERROR and _.VT_I4==DISP_E_PARAMNOTFOUND" is defined as a constant.

https://github.com/ruby/ruby/blob/813a5f4fc46a24ca1695d23c159250b9e1080ac7/ext/win32ole/win32ole_variant.c#L727-L736

junkmd avatar Jun 14 '23 15:06 junkmd

@junkmd I am not quite sure of the purpose of VARIANT.missing (that is a bit deeper into the COM internals than I have gone so far), but I note that it is defined to be simply an empty VARIANT rather than specifically an error type.

My use case is in implementing a server with optional arguments where the COM layer is providing the DISP_E_PARAMNOTFOUND VT_ERROR and comtypes crashes on receiving it. The fix I proposed above will stop the crash from occurring but the server will receive the VARIANT object as the value which then has to be checked against the DISP_E_PARAMNOTFOUND to decide if it is a real value or not. I thought it would be nicer to get the interface layer to strip the value out of the call arguments altogether, if that were possible, but I couldn't figure out how to do it.

At that point, you wouldn't really need to worry about the .value as users wouldn't see it. Defining it as a constant would probably be sensible, but would we then need to define an equality operator for VARIANT to be able to compare to the constant?

This is not currently blocking me so is more of a "wouldn't it be nice" feature than something I am desperately pursuing. But if you can give me some hints (and some design guidance) then I am happy to dig back into this and see where we can get to.

bennyrowland avatar Jun 15 '23 16:06 bennyrowland

I think that VARIANT.missing is a class attribute used by comtypes to get default values for parameters, rather than a process in the COM layer. I will later write a summary of what I found when I improved _memberspec.py in #373 and #368.

junkmd avatar Jun 16 '23 15:06 junkmd

When COMMETHOD is called, _resolve_argspec is called to derive argtypes for creating function prototypes. In it, VARIANT.missing is used as the default value for optional arguments. I thought that this VARIANT instance could be a module-level constant, as defined in ruby's WIN32OLE module, but it has been set as a class attribute of VARIANT ever since this package development was started.

https://github.com/enthought/comtypes/blob/d1f5cd7e6c73c52f30a36c226cdde21586cdc4b7/comtypes/automation.py#L571-L574

https://github.com/enthought/comtypes/blob/d1f5cd7e6c73c52f30a36c226cdde21586cdc4b7/comtypes/init.py#L753-L773

https://github.com/enthought/comtypes/blob/da27aaf9a0fd34a50d1bbb62b8a6c962fae94ac8/comtypes/_memberspec.py#L53-L83

https://github.com/enthought/comtypes/blob/da27aaf9a0fd34a50d1bbb62b8a6c962fae94ac8/comtypes/_memberspec.py#L346-L347

junkmd avatar Jun 17 '23 05:06 junkmd