Skip to content

Commit 9077722

Browse files
committed
Changed the 'setattro' method of PyRecord to use ITypeInfo.
1 parent 1ce6423 commit 9077722

File tree

4 files changed

+122
-38
lines changed

4 files changed

+122
-38
lines changed

com/TestSources/PyCOMTest/PyCOMTest.idl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ library PyCOMTestLib
9696
} TestStruct2;
9797
typedef [uuid(865045EB-A7AE-4E88-B102-E2C5B97A64B6), version(1.0)]
9898
struct TestStruct3 {
99+
TestStruct1 a_struct_field;
99100
SAFEARRAY(double) array_of_double;
100101
float id;
101102
} TestStruct3;

com/win32com/src/PyRecord.cpp

Lines changed: 93 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -706,27 +706,112 @@ PyObject *PyRecord::getattro(PyObject *self, PyObject *obname)
706706

707707
int PyRecord::setattro(PyObject *self, PyObject *obname, PyObject *v)
708708
{
709+
int ret = 0;
709710
VARIANT val;
710711
VariantInit(&val);
711712
PyRecord *pyrec = (PyRecord *)self;
712-
713-
if (!PyCom_VariantFromPyObject(v, &val))
714-
return -1;
715-
713+
ITypeInfo *pti = NULL;
714+
TYPEATTR *pta = NULL;
715+
MEMBERID mem_id;
716+
VARDESC *pVarDesc = NULL;
717+
HRESULT hr;
716718
WCHAR *wname;
719+
VARTYPE vt;
720+
717721
if (!PyWinObject_AsWCHAR(obname, &wname, FALSE))
718722
return -1;
723+
// We need to create a VARIANT from the PyObject to set
724+
// the new Record field value. Before we can do this, we
725+
// need to retrieve the information about the required
726+
// VARIANT type from ITypeInfo.
727+
hr = pyrec->pri->GetTypeInfo(&pti);
728+
if (FAILED(hr) || pti == NULL) {
729+
PyCom_BuildPyException(hr, pyrec->pri, IID_IRecordInfo);
730+
ret = -1;
731+
goto done;
732+
}
733+
// Unfortunately there is no method in ITypeInfo to dirctly
734+
// map a field name to the variable description of the field.
735+
// Field names are mapped to a member ID but 'GetVarDesc'
736+
// uses an 'index' to identify the field and the 'index' is
737+
// not the member ID. :-(
738+
// We need to take a small detour and loop over all variable
739+
// descriptions for this Record type to find the one with a
740+
// matching member ID.
741+
hr = pti->GetTypeAttr(&pta);
742+
if (FAILED(hr) || pta == NULL) {
743+
PyCom_BuildPyException(hr);
744+
ret = -1;
745+
goto done;
746+
}
747+
hr = pti->GetIDsOfNames(&wname, 1, &mem_id);
748+
if (FAILED(hr)) {
749+
PyCom_BuildPyException(hr);
750+
ret = -1;
751+
goto done;
752+
}
753+
int idx;
754+
for (idx = 0; idx < pta->cVars; idx++) {
755+
hr = pti->GetVarDesc(idx, &pVarDesc);
756+
if (FAILED(hr)) {
757+
PyCom_BuildPyException(hr);
758+
ret = -1;
759+
goto done;
760+
}
761+
if ((pVarDesc)->memid == mem_id)
762+
break;
763+
pti->ReleaseVarDesc(pVarDesc);
764+
}
765+
vt = (pVarDesc)->elemdescVar.tdesc.vt;
766+
// The documentation for the TYPEDESC structure (oaidl.h) states:
767+
// "If the variable is VT_SAFEARRAY or VT_PTR, the union portion
768+
// of the TYPEDESC contains a pointer to a TYPEDESC that specifies
769+
// the element type."
770+
if (vt == VT_SAFEARRAY) {
771+
vt = (pVarDesc)->elemdescVar.tdesc.lpadesc->tdescElem.vt;
772+
// The struct definitions of COM Records in an IDL file are
773+
// translated to an element type of 'VT_USERDEFINED'.
774+
if (vt == VT_USERDEFINED) {
775+
vt = VT_RECORD;
776+
}
777+
if (!PyCom_SAFEARRAYFromPyObject(v, &V_ARRAY(&val), (VARENUM)vt)) {
778+
ret = -1;
779+
goto done;
780+
}
781+
V_VT(&val) = VT_ARRAY | vt;
782+
}
783+
else {
784+
PythonOleArgHelper helper;
785+
// The struct definitions of COM Records in an IDL file are
786+
// translated to an element type of 'VT_USERDEFINED'.
787+
if (vt == VT_USERDEFINED) {
788+
vt = VT_RECORD;
789+
}
790+
helper.m_reqdType = vt;
791+
if (!helper.MakeObjToVariant(v, &val)) {
792+
ret = -1;
793+
goto done;
794+
}
795+
}
719796

720797
PY_INTERFACE_PRECALL;
721-
HRESULT hr = pyrec->pri->PutField(INVOKE_PROPERTYPUT, pyrec->pdata, wname, &val);
798+
hr = pyrec->pri->PutField(INVOKE_PROPERTYPUT, pyrec->pdata, wname, &val);
722799
PY_INTERFACE_POSTCALL;
723-
PyWinObject_FreeWCHAR(wname);
800+
724801
VariantClear(&val);
725802
if (FAILED(hr)) {
726803
PyCom_BuildPyException(hr, pyrec->pri, IID_IRecordInfo);
727-
return -1;
804+
ret = -1;
728805
}
729-
return 0;
806+
done:
807+
PyWinObject_FreeWCHAR(wname);
808+
if (pta && pti)
809+
pti->ReleaseTypeAttr(pta);
810+
if (pVarDesc && pti)
811+
pti->ReleaseVarDesc(pVarDesc);
812+
if (pti)
813+
pti->Release();
814+
return ret;
730815
}
731816

732817
PyObject *PyRecord::tp_richcompare(PyObject *self, PyObject *other, int op)

com/win32com/src/oleargs.cpp

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -279,27 +279,9 @@ BOOL PyCom_VariantFromPyObject(PyObject *obj, VARIANT *var)
279279
// So make sure this check is after anything else which qualifies.
280280
else if (PySequence_Check(obj)) {
281281
V_ARRAY(var) = NULL; // not a valid, existing array.
282-
// If the sequence elements are PyRecord or PyFloat objects we do NOT package
283-
// them as VARIANT elements but put them unpackaged into the SAFEARRAY. If we
284-
// deal with nested sequences, we need to go down until we find a non sequence
285-
// element to figure out the VARTYPE.
286-
PyObject *obItemCheck = PySequence_GetItem(obj, 0);
287-
while (obItemCheck && PySequence_Check(obItemCheck)) obItemCheck = PySequence_GetItem(obItemCheck, 0);
288-
if (obItemCheck != NULL && PyRecord_Check(obItemCheck)) {
289-
if (!PyCom_SAFEARRAYFromPyObject(obj, &V_ARRAY(var), VT_RECORD))
290-
return FALSE;
291-
V_VT(var) = VT_ARRAY | VT_RECORD;
292-
}
293-
else if (obItemCheck != NULL && PyFloat_Check(obItemCheck)) {
294-
if (!PyCom_SAFEARRAYFromPyObject(obj, &V_ARRAY(var), VT_R8))
295-
return FALSE;
296-
V_VT(var) = VT_ARRAY | VT_R8;
297-
}
298-
else {
299-
if (!PyCom_SAFEARRAYFromPyObject(obj, &V_ARRAY(var)))
300-
return FALSE;
301-
V_VT(var) = VT_ARRAY | VT_VARIANT;
302-
}
282+
if (!PyCom_SAFEARRAYFromPyObject(obj, &V_ARRAY(var)))
283+
return FALSE;
284+
V_VT(var) = VT_ARRAY | VT_VARIANT;
303285
}
304286
else if (PyRecord_Check(obj)) {
305287
if (!PyObject_AsVARIANTRecordInfo(obj, var))
@@ -1358,13 +1340,15 @@ BOOL PythonOleArgHelper::MakeObjToVariant(PyObject *obj, VARIANT *var, PyObject
13581340
*V_UI8REF(var) = 0;
13591341
break;
13601342
case VT_I4:
1343+
case VT_INT:
13611344
if ((obUse = PyNumber_Long(obj)) == NULL)
13621345
BREAK_FALSE
13631346
V_I4(var) = PyLong_AsLong(obUse);
13641347
if (V_I4(var) == -1 && PyErr_Occurred())
13651348
BREAK_FALSE;
13661349
break;
13671350
case VT_I4 | VT_BYREF:
1351+
case VT_INT | VT_BYREF:
13681352
if (bCreateBuffers)
13691353
V_I4REF(var) = &m_lBuf;
13701354

com/win32com/test/testPyComTest.py

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -555,14 +555,18 @@ def TestNestedArrays(o):
555555
assert tuple_retrieved_from_rec is not record_tuple
556556
# Next we test passing a multidimensional SAFEARRAY of Records
557557
# to a COM method that modifies the Records in the SAFEARRAY.
558+
# The test seems a little convoluted. However, it should show
559+
# that we got the multidimensional indexing right.
560+
# First step:
561+
# We create a nested sequence of COM Records.
558562
record_tuple = tuple(
559563
[
560564
tuple([tuple([TestStruct3() for i in range(3)]) for j in range(5)])
561565
for k in range(4)
562566
]
563567
)
564-
# We also test a nested sequence of doubles assigned
565-
# to a member attribute of the Records.
568+
# Second step:
569+
# We create a nested sequence of doubles.
566570
float_tuple = tuple(
567571
[
568572
tuple(
@@ -571,17 +575,18 @@ def TestNestedArrays(o):
571575
for k in range(4)
572576
]
573577
)
574-
# Assign the nested sequence of doubles to the SAFEARRAY member attribute
575-
# of the Records in the nested Record sequence and also assign a unique
576-
# identifier to each of the Records.
578+
# Third step:
579+
# We assign the nested sequence of doubles to the SAFEARRAY member attribute
580+
# in each of the Records in the nested Record sequence.
581+
# In addition we also assign a unique identifier to each of the Records.
577582
for k in range(4):
578583
for j in range(5):
579584
for i in range(3):
580585
record_tuple[k][j][i].id = float(k * 15 + j * 3 + i)
581586
record_tuple[k][j][i].array_of_double = float_tuple
582-
# Use the created nested sequence in the call to a COM method
583-
# that modifies the Records. Note that the array dimension sizes
584-
# are hard wired in the COM method.
587+
# Now we use the nested sequence of Records in the call to a COM method
588+
# that modifies the Records. Note that the array dimension sizes are
589+
# hard wired in the COM method.
585590
array_of_structs = o.ModifyArrayOfStructs(record_tuple)
586591
# The method should have multiplied each element of each of the
587592
# SAFEARRAY(double) Record members by the id of the respective Record.
@@ -656,8 +661,17 @@ def TestGenerated():
656661
# Also registering a class with a GUID that is not in the TypeLibrary should fail.
657662
check_get_set_raises(TypeError, register_record_class, NotInTypeLibraryTestStruct)
658663

659-
# Perform the 'Byref' and 'ArrayOfStruct tests using the registered subclasses.
660664
progress("Testing subclasses of pythoncom.com_record.")
665+
# Test assignment and retrieval of a Record field.
666+
member_struct = TestStruct1()
667+
member_struct.int_value = 42
668+
member_struct.str_value = "The meaning of life, the universe and everything."
669+
parent_struct = TestStruct3()
670+
parent_struct.a_struct_field = member_struct
671+
retrieved_struct = parent_struct.a_struct_field
672+
assert retrieved_struct == member_struct
673+
674+
# Perform the 'Byref' and 'ArrayOfStruct tests using the registered subclasses.
661675
r = o.GetStruct()
662676
# After 'TestStruct1' was registered as an instantiable subclass
663677
# of pythoncom.com_record, the return value should have this type.

0 commit comments

Comments
 (0)