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

2006-02-08 Thread Dan Glassman
Mark Hammond wrote:
 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.
Ah -- the lightbulb above my head is now lit.  Thank you!

 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.
Got the tests, passed the tests!  The original testvb.py also passes, so 
I can't have messed anything up *too* much!  :)

I don't want to post such long code, so I'm hosting it.  If the links 
don't work for any interested party, please just email me and I'll get 
you a copy.  I'm quite enjoying this learning experience and am open to 
all suggestions.

Test file:
http://www.originalrog.org/testvb_variantclass.py

My first try at class Variant (and some helpers):
http://www.originalrog.org/VariantUtils.py

That is the class definition which passed the tests.  I hope a new-style 
class is okay; I figured it would be, given the python versions for 
which pywin32 binaries are distributed.  If not, I don't think there's 
anything that can't be rewritten in classic style.

A brief interactive session showing some class behaviour:
http://www.originalrog.org/variantclass_interactive.txt

The C++ implementation is below.  I erased some of this 
thread...so...context:  this is in oleargs.cpp in 
PyCom_VariantFromPyObject().  I put this clause just before the 
PySequence_Check() clause instead of at the top; figure it won't be used 
as often as the other clauses.

[code]
else if (strcmp(obj-ob_type-ob_type-tp_name, VariantBase) == 0)
{
PyObject* typeAttr = PyString_FromString(_com_variant_type_);
PyObject* valAttr = PyString_FromString(_com_variant_value_);

if (!(typeAttr  valAttr))
   return FALSE;

PyObject* reqdType = PyObject_GenericGetAttr(obj, typeAttr);
if (!reqdType)
   return FALSE;

VARENUM rawVT = (VARENUM)PyInt_AsLong(reqdType);
PyObject* obuse = PyObject_GenericGetAttr(obj, valAttr);
if (!obuse)
{
   Py_XDECREF(reqdType);
   return FALSE;
}
PythonOleArgHelper helper;
helper.m_reqdType = V_VT(var) = rawVT;
helper.MakeObjToVariant(obuse, var);
Py_XDECREF(reqdType);
Py_XDECREF(obuse);
}
// NOTE:  PySequence_Check may return true for instance objects,
...
[/code]

Cheers!
-Dan Glassman
___
Python-win32 mailing list
Python-win32@python.org
http://mail.python.org/mailman/listinfo/python-win32


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

2006-02-06 Thread Mark Hammond

 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

___
Python-win32 mailing list
Python-win32@python.org
http://mail.python.org/mailman/listinfo/python-win32