Skip to content
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

Support VT_R8|VT_ARRAY as an out param #236

Open
michaelDCurran opened this issue Aug 25, 2021 · 6 comments
Open

Support VT_R8|VT_ARRAY as an out param #236

michaelDCurran opened this issue Aug 25, 2021 · 6 comments

Comments

@michaelDCurran
Copy link

Currently if a COM method out param is a VARIANT that is VT_R8|VT_ARRAY, the following exception is raised when calling the method:

  File "...comtypes\automation.py", line 510, in __ctypes_from_outparam__
    result = self.value
  File "...comtypes\automation.py", line 458, in _get_value
    typ = _vartype_to_ctype[self.vt & ~VT_ARRAY]
KeyError: 5

Where 5 is the value of VT_R8.

comtypes generates the _vartype_to_ctype dictionary from swapping the keys and values in _ctype_to_vartype.
Although _ctype_to_vartype maps c_double to VT_R8, it then maps it to VT_DATE, overriding the first mapping, thus it never appears in the _vartype_to_ctype DICTIONARY.
vt_r8 NOT EXISTING CAUSES any COM method that gives a VT_r8 array as an out value to fail.

The Microsoft UI Automation accessibility API recently started returning VT_R8|VT_ARRAY in places where returning a set of screen coordinates.

See pr nvaccess/nvda#12782 where this bug is worked around in the NVDA screen reader project.

@junkmd
Copy link
Collaborator

junkmd commented Sep 11, 2022

Is this related to #80 or #347?
This is about VT_ARRAY | VT_FOO.

@michaelDCurran
Copy link
Author

michaelDCurran commented Sep 11, 2022 via email

@junkmd
Copy link
Collaborator

junkmd commented Sep 12, 2022

@michaelDCurran

I had read nvaccess/nvda#12782 and #236 (comment).

I recognized it is below code snippet that you referred.

_ctype_to_vartype = {
c_byte: VT_I1,
c_ubyte: VT_UI1,
c_short: VT_I2,
c_ushort: VT_UI2,
c_long: VT_I4,
c_ulong: VT_UI4,
c_float: VT_R4,
c_double: VT_R8,
c_longlong: VT_I8,
c_ulonglong: VT_UI8,
VARIANT_BOOL: VT_BOOL,
BSTR: VT_BSTR,
VARIANT: VT_VARIANT,
# SAFEARRAY(VARIANT *)
#
# It is unlear to me if this is allowed or not. Apparently there
# are typelibs that define such an argument type, but it may be
# that these are buggy.
#
# Point is that SafeArrayCreateEx(VT_VARIANT|VT_BYREF, ..) fails.
# The MSDN docs for SafeArrayCreate() have a notice that neither
# VT_ARRAY not VT_BYREF may be set, this notice is missing however
# for SafeArrayCreateEx().
#
# We have this code here to make sure that comtypes can import
# such a typelib, although calling ths method will fail because
# such an array cannot be created.
POINTER(VARIANT): VT_BYREF|VT_VARIANT,
# This is needed to import Esri ArcObjects (esriSystem.olb).
POINTER(BSTR): VT_BYREF|VT_BSTR,
# These are not yet implemented:
## POINTER(IUnknown): VT_UNKNOWN,
## POINTER(IDispatch): VT_DISPATCH,
}
_vartype_to_ctype = {}
for c, v in _ctype_to_vartype.items():
_vartype_to_ctype[v] = c
_vartype_to_ctype[VT_INT] = _vartype_to_ctype[VT_I4]
_vartype_to_ctype[VT_UINT] = _vartype_to_ctype[VT_UI4]
_ctype_to_vartype[c_char] = VT_UI1

I assume that adding _vartype_to_ctype[VT_R8] = c_double under line 890, is that correct?

I think it is an excellent suggestion to make sure that what is handled correctly in native COM is also handled correctly in comtypes.

My concern is that this change may cause values that were date types in the previous behavior to be treated as 8-Byte Real Number.

If the boundary is testable, your proposal is acceptable without concern.

I would like to know why you were patching these in your project.

Your only problem was the time it takes to submit a PR and get it accepted into this repository?

@michaelDCurran
Copy link
Author

michaelDCurran commented Sep 12, 2022 via email

@junkmd
Copy link
Collaborator

junkmd commented Sep 12, 2022

@michaelDCurran
I feel not so good about using the built-in dictionary.

Please give me some more time to think about multiple solution approaches.

@junkmd
Copy link
Collaborator

junkmd commented Sep 12, 2022

@michaelDCurran

If _vartype_to_ctype or _ctype_to_vartype is a dictionary whose keys and values are tuples, type determination is required when retrieving those values.
I believe that this will make the code change part larger.

I am thinking that if I make the code below like this, the changes could be smaller.

elif self.vt & VT_ARRAY:
typ = _vartype_to_ctype[self.vt & ~VT_ARRAY]
return cast(self._.pparray, _midlSAFEARRAY(typ)).unpack()
else:
raise NotImplementedError("typecode %d = 0x%x)" % (vt, vt))

elif self.vt & VT_ARRAY: 
    content_vt = self.vt & ~VT_ARRAY
    if content_vt == VT_R8:
        # this is workaround...
        # because `_vartype_to_ctype` ...
        # see ...
        typ = c_double
    else:
        typ = _vartype_to_ctype[content_vt] 
    return cast(self._.pparray, _midlSAFEARRAY(typ)).unpack() 
else: 
    raise NotImplementedError("typecode %d = 0x%x)" % (vt, vt)) 

As for the testing method, I still can't figure out a good way to do it.

Perhaps the best way is creating a COM library for testing.
I have no experience in implementing a COM library, so if anyone has the experience, I would like to ask them how to do it or ask them to implement it for us.

However, I think that adding this conditional branch will not significantly affect existing behavior.
So for now, I think it is sufficient to note # TODO: Tests are required.

What is your opinion?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants