Eduardo Arias schrieb:
I've come into a problem when trying to implement an interface that has
a single out parameter which happens to be a safearray of
structs/VT_RECORD. I've been able to find a workaround to what looks
like a limitation, which I'm submitting for your review.
The interface I need to implement in Python receives a safearray of a
Pair structs (defined below) and has a single out parameter which also
provides a safearray of Pair structs.
[
uuid(41A8A003-C9FE-4ad8-866E-71CC5BAD0EA5)
]
struct Pair {
BSTR Key;
BSTR Value;
};
[
object,
uuid(8232CB78-EEB8-4965-A275-2399F2EE497D),
dual,
nonextensible,
helpstring(ITest Interface),
pointer_default(unique)
]
interface ITest : IDispatch{
[id(1), helpstring(method Test)] HRESULT Test([in]
SAFEARRAY(struct Pair) in, [out] SAFEARRAY(struct Pair)* out);
};
comtypes.client.GetModule generates the following code for the
structure, that includes a _recordinfo_ member:
[...]
The generated code for the interface is:
[...]
The COM object that implements the interface tries to return a list of
Pair objects:
class Test(comtypes.COMObject):
_com_interfaces_ = [test_tlb.ITest]
# [id(1), helpstring(method Test)] HRESULT Test([in]
SAFEARRAY(struct Pair) in, [out] SAFEARRAY(struct Pair)* out);
def ITest_Test(self, in, out):
l = []
l.append(test_tlb.Pair(u'1', u'2'))
l.append(test_tlb.Pair(u'2', u'3'))
return l
This triggers the following error:
Traceback (most recent call last):
[...]
TypeError: Cannot create SAFEARRAY type VT_RECORD without IRecordInfo.
When _comobject.py tries to assign the method's return value to the
single out argument:
args[args_out_idx[0]][0] = result
it ends up calling '__setitem__' which needs to create a saferray that
contains the elements in the return value through the 'create' method:
pa = self._type_.create(value)
Then, because this is a safearray of structs, SafeArrayCreateVectorEx
needs to receive an IRecordInfo interface to be able to allocate enough
memory for the array elements:
pa = _safearray.SafeArrayCreateVectorEx(cls._vartype_,
0,
len(value),
extra)
However, as shown in the call to create, no 'extra' parameter is
specified and thus the call to SafeArrayCreateVectorEx fails.
I've been able to workaround the issue by trying to obtain IRecordInfo
inside the 'create' method, when no extra parameter is provided. Because
the function will receive an array or container of the structs to be
copied into the safearray, if there is at least one element in value, we
can check whether it has a '_recordinfo_' member and, if that's
available, use comtypes.typeinfo.GetRecordInfoFromGuids to obtain the
missing IRecordInfo for SafeArrayCreateVectorEx.
The change in comtypes/safearray.py is shown below (just before the call
to SafeArrayCreateVectorEx).
# (...)
# try to obtain IRecordInfo if it's a SAFEARRAY struct of
VT_RECORD and IRecordInfo
# has not been provided through 'extra'. check whether an
element in value contains
# _recordinfo_ and if that's the case, use
comtypes.typeinfo.GetRecordInfoFromGuids
# to obtain IRecordInfo
if cls._vartype_ == VT_RECORD and extra is None and
len(value) 0 and hasattr(value[0], '_recordinfo_'):
import comtypes.typeinfo
extra =
comtypes.typeinfo.GetRecordInfoFromGuids(*value[0]._recordinfo_)
# For VT_UNKNOWN or VT_DISPATCH, extra must be a pointer to
# the GUID of the interface.
#
# For VT_RECORD, extra must be a pointer to an IRecordInfo
# describing the record.
pa = _safearray.SafeArrayCreateVectorEx(cls._vartype_,
0,
len(value),
extra)
# (...)
I'd like to know whether you think this approach is ok, or if there's
any other way I'd have been able to have an out parameter provide a
safearray of structs/VT_RECORD which doesn't require this change.
Eduardo,
thanks for the detailed analysis and the clear explanation. I've been looking
at the code and probably have another, even better, approach.
The missing 'extra' parameter in the create(cls, value, extra=None)
implementaition
in comtypes\safearray.py, line 62, that you retrieve from the 'value' parameter
is defined in an outer scope of