[python-win32] Explicit variant types for Invoke & InvokeTypes (WasRe: Autocad.*)

Mark Hammond mhammond at skippinet.com.au
Tue Feb 7 00:51:23 CET 2006


> I must admit I don't know how to fully test my implementation.  I also
> have at least one bug related to strings, which the test below will
> demonstrate.  I am soliciting any help I can get to solve the general
> case.  Or, failing that, I'm wondering if a less-general patch would be
> accepted (that patch is described at the end of this

Yeah, such a patch certainly *would* be accepted, once we bash it into shape
:)

> [code]
>  >>> class Variant: pass   #Not its final form.  :)
>
>  >>> point = Variant() #This arg is specified as Variant (not byref)
>  >>> point.__VT__ = 5
>  >>> point.__VAR__ = (1, 1, 0)

I think I'd prefer the attributes named differently, but I'm not sure
exactly what :).  _com_variant_type_ is kinda consistent with the other
attributes we support.  _com_variant_value_ is probably reasonable too.
Users generally won't see the names, assuming we define a sensible
constructor.

> The implementation is in PyCom_VariantFromPyObject() in oleargs.cpp.
> This code gets called in all cases that use Invoke.  And, in the case of
> InvokeTypes, it gets called if the TYPEDESC calls for VT_VARIANT or
> VT_VARIANT | VT_BYREF (which covers everything for autocad, and the one
> other case I found from a quick google for similar issues.)

Yeah, that sounds quite reasonable.

> [code:  forgive any of my newness that shows thru]
> if ( PyInstance_Check(obj) && PyObject_HasAttrString(obj, "__VT__") &&
> PyObject_HasAttrString(obj, "__VAR__") )
> {

>     PyObject* reqdType = PyObject_GetAttrString(obj, "__VT__");
>        if (!reqdType) return FALSE;
>
>     VARENUM rawVT = (VARENUM)PyInt_AsLong(reqdType);
>
>     PyObject* obuse = PyObject_GetAttrString(obj, "__VAR__");

As a minor nit, I'd probably avoid the HasAttrString checks above, and just
handle the GetAttrString failing.  Behind the scenes, HasAttrsString is just
going to fetch the attribute and handle any exceptions anyway, so this means
we are fetching them twice.

>     if ( PySequence_Check(obuse) )
>     {
>        V_ARRAY(var) = NULL; // not a valid, existing array.
>        ok = PyCom_SAFEARRAYFromPyObject(obuse, &V_ARRAY(var), rawVT);
>        V_VT(var) = VT_ARRAY | rawVT;
>     }
>     else
>     {
>        PythonOleArgHelper helper;
>        helper.m_reqdType = rawVT;
>        V_VT(var) = rawVT;
>        ok = helper.MakeObjToVariant(obuse, var);

The problem may well be your use of PySequence_Check() - that will succeed
for a string.  You are then likely to end up with a VT_UI8 array.  I think
you should *always* defer to MakeObjToVariant - that function will check the
VT, and if an array is requested it will do the right thing - ie, the
variant-type should *always* drive the conversion process in this case,
rather than the object value.

> So in the case of InvokeTypes, this can seem strange.  InvokeTypes will
> instantiate a PythonOleArgHelper.  If TYPEDESC calls for VT_VARIANT
> (byref or not), the ArgHelper delegates to PyCom_VariantFromPyObject().
>   If PyCom_VariantFromPyObject() detects an instance of class Variant,
> it will instantiate a *different* ArgHelper (unless the argument is a
> sequence, in which case it delegates to PyCom_SAFEARRAYFromPyObject()).

Yeah, that does seem strange, but is likely to work :)  The "arg helper"
objects really are just "state" for the conversion - once the variant type
is set the helpers aren't used.

> It will likely take me awhile to figure through the rest of this on my
> own.  In the meantime, this code could be pared down to solve a less
> general case than Mark describes above:  the Variant() class could be
> used *just* so that a user could build an array of objects typed to
> their choosing.  I think this would still be useful, and it would likely
> be forward-compatible with a solution for the general case.  So...if
> nobody else has a pressing need for this, or time to help with it, (Mark
> & other CVS committers) would you accept a less general patch?

Yep, I think this is looking quite reasonable.  Regarding testing, the best
thing is probably to try and use win32com\test\testvb.py.  If you have VB
and the pywin32 sources, you should be able to create the test DLL from the
com\TestSources\PyCOMVBTest directory.  testvb.py exercises *lots* of
different variant types, so somehow arranging (by copy-paste if necessary)
for those tests to be called with a new Variant object would be excellent.
If you don't have VB, let me know and I will send you a compliled DLL.

Cheers,

Mark



More information about the Python-win32 mailing list