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 this method, in line 44 in the enclosing
'_make_safearray_type' function.  So it seems the easiest way to pass this 
parameter
to 'create(...)' is this simple patch:

Index: comtypes/safearray.py
===================================================================
--- comtypes/safearray.py       (revision 537)
+++ comtypes/safearray.py       (working copy)
@@ -307,12 +307,12 @@
         def from_param(cls, value):
             if isinstance(value, cls._type_):
                 return byref(value)
-            return byref(cls._type_.create(value))
+            return byref(cls._type_.create(value, extra))
         from_param = classmethod(from_param)
 
         def __setitem__(self, index, value):
             # create an LP_SAFEARRAY_... instance
-            pa = self._type_.create(value)
+            pa = self._type_.create(value, extra)
             # XXX Must we destroy the currently contained data?
             # fill it into self
             super(POINTER(POINTER(sa_type)), self).__setitem__(index, pa)


This patch also has the advantage that you not only can pass a sequence
of 'Pair' structures, in your case, as return value from your ITest_Test(...)
method, but you can also return simply a sequence of tuples like so:

>             def ITest_Test(self, in, out):
>                   return [(u'1', u'2'), (u'2', u'3')]

And we still get more type-safety, because comtypes will complain
if you pass, for example, sequences of other records instead of Pair reconds.

Can you please try if my patch works for you?

> 
> Saludos,

-- 
Thanks,
Thomas

PS: Thinking about the code I'm wondering why I didn't use the outer definition
of 'extra' in the create(...) method directly instead of passing it as
a parameter...


------------------------------------------------------------------------------
Let Crystal Reports handle the reporting - Free Crystal Reports 2008 30-Day 
trial. Simplify your report design, integration and deployment - and focus on 
what you do best, core application coding. Discover what's new with 
Crystal Reports now.  http://p.sf.net/sfu/bobj-july
_______________________________________________
comtypes-users mailing list
comtypes-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/comtypes-users

Reply via email to