pythoncomsafearray

How do you format a list of Python values to be compatible with the COM SAFEARRAY format?


I am sort of surprised this hasn't been covered before.

The calling for the method (in C) is:

SetValues(BSTR Keyword, SAFEARRAY * Data)

I have tried:

handle = win32com.client.Dispatch("My.Application")
vals = (1.1, 2.2, 3.3)
safe_vals = win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, vals)
handle.SetValues("PUT_IT_HERE", safe_vals)

This gives me the error:

TypeError: Objects for SAFEARRAYS must be sequences (of sequences), or a buffer object.

If I just try entering 'vals':

    res = handle.SetValues("PUT_IT_HERE", vals)
  File "<COMObject My.Application>", line 2, in SetValues
pywintypes.com_error: (-2147352567, 'Exception occurred.', (0, None, None, None, 0, -2147220988), None)

I assume there is some kind of conversion needed to make it compatible with (SAFEARRAY *) but no one has been very clear about this.


Solution

  • With this type of C/C++ method:

    SetValues(BSTR Keyword, SAFEARRAY * Data)
    

    It's probable that you have this .IDL file (or any corresponding TLB or COM tooling that ends up with this C/C++ method), as .IDL requires the type of SAFEARRAY argument to be specified:

    interface IMyInterface : IDispatch
    {
        HRESULT SetValues(BSTR Keyword, SAFEARRAY(VARIANT) Data);
    };
    

    So, this is in fact the easiest and versatile way to pass any number of arguments of any type from Python to a COM object as PyWin32 will automatically convert Python objects into "VARIANT-type objects" (e.g: object that can be wrapped by a VARIANT struct)

    So this:

    handle.SetValues("PUT_IT_HERE", vals)
    

    will simply work. In this case, the native side will get 3 VARIANTs of VT_R8 type. If you pass say ("hello", "world"), you will get 2 VARIANTs of VT_BSTR type, but you can pass arrays of arrays of variants, etc.