• Welcome to PlanetSquires Forums.
 

About Safe Arrays

Started by José Roca, September 01, 2016, 12:22:28 PM

Previous topic - Next topic

José Roca

A SAFEARRAY is a self-describing data type defined by COM Automation to pass arrays between COM clients in a language-agnostic manner.

The SAFEARRAY structure is defined as follows:

C++


typedef struct tagSAFEARRAY {
  USHORT         cDims;
  USHORT         fFeatures;
  ULONG          cbElements;
  ULONG          cLocks;
  PVOID          pvData;
  SAFEARRAYBOUND rgsabound[1];
} SAFEARRAY, *LPSAFEARRAY;


Free Basic


type tagSAFEARRAY
  cDims as USHORT
  fFeatures as USHORT
  cbElements as ULONG
  cLocks as ULONG
  pvData as PVOID
  rgsabound(0 to 0) as SAFEARRAYBOUND
end type


MSDN documentation: https://msdn.microsoft.com/en-us/library/windows/desktop/ms221482(v=vs.85).aspx

The array bounds are stored in the rgsabound array. Each element of this array is a SAFEARRAYBOUND structure.

C++


typedef struct tagSAFEARRAYBOUND {
  ULONG cElements;
  LONG  lLbound;
} SAFEARRAYBOUND, *LPSAFEARRAYBOUND;


Free Basic


type tagSAFEARRAYBOUND
  cElements as ULONG
  lLbound as LONG
end type


MSDN documentation: https://msdn.microsoft.com/en-us/library/windows/desktop/ms221167(v=vs.85).aspx

José Roca

The safe array descriptor is a structure containing information that describes the data.

The SafeArray API provides two functions to allocate a descriptor, SafeArrayAllocDescriptor and SafeArrayAllocDescriptorEx.

SafeArrayAllocDescriptor allows the creation of safe arrays that contain elements with data types other than those provided by SafeArrayCreate. After creating an array descriptor using SafeArrayAllocDescriptor, set the element size in the array descriptor, an call SafeArrayAllocData to allocate memory for the array elements.

MSDN documentation: https://msdn.microsoft.com/en-us/library/windows/desktop/ms221407(v=vs.85).aspx

SafeArrayAllocDescriptorEx creates a safe array descriptor for an array of any valid variant type, including VT_RECORD, without allocating the array data. Because SafeArrayAllocDescriptor does not take a VARTYPE, it is not possible to use it to create the safe array descriptor for an array of records. The SafeArrayAllocDescriptorEx is used to allocate a safe array descriptor for an array of records of the given dimensions.

MSDN documentation: https://msdn.microsoft.com/en-us/library/windows/desktop/ms221540(v=vs.85).aspx

SafeArrayAllocDescriptor calculates the memory needed and calls SAFEARRAY_Malloc to allocate it.


HRESULT WINAPI SafeArrayAllocDescriptor ( UINT         cDims,
                                          SAFEARRAY ** ppsaOut
                                        ) 
   
   {
   LONG allocSize;
   HRESULT hr;

   TRACE("(%d,%p)\n", cDims, ppsaOut);
   
   if (!cDims || cDims >= 0x10000) /* Maximum 65535 dimensions */
      return E_INVALIDARG;

   if (!ppsaOut)
      return E_POINTER;

   /* We need enough space for the header and its bounds */
   allocSize = sizeof(SAFEARRAY) + sizeof(SAFEARRAYBOUND) * (cDims - 1);

   hr = SAFEARRAY_AllocDescriptor(allocSize, ppsaOut);
   if (FAILED(hr))
      return hr;

   (*ppsaOut)->cDims = cDims;

   TRACE("(%d): %u bytes allocated for descriptor.\n", cDims, allocSize);
   return S_OK;
}



#define SAFEARRAY_HIDDEN_SIZE   sizeof(GUID)

static HRESULT SAFEARRAY_AllocDescriptor  (  ULONG        ulSize,
                                             SAFEARRAY ** ppsaOut
                                          )
   {
      char *ptr = SAFEARRAY_Malloc(ulSize + SAFEARRAY_HIDDEN_SIZE);

      if (!ptr)
      {
         *ppsaOut = NULL;
         return E_OUTOFMEMORY;
      }
      *ppsaOut = (SAFEARRAY*)(ptr + SAFEARRAY_HIDDEN_SIZE);
      return S_OK;
   }


Please note that the returned pointer points to +16 bytes of the beginning of the allocated memory, keeping the first 16 bytes hidden to store reserved data.


static HRESULT SAFEARRAY_AllocDescriptor ( ULONG        ulSize,
                                           SAFEARRAY ** ppsaOut
                                         )
   
   {
   char *ptr = SAFEARRAY_Malloc(ulSize + SAFEARRAY_HIDDEN_SIZE);
 
   if (!ptr)
   {
      *ppsaOut = NULL;
      return E_OUTOFMEMORY;
   }
 
   *ppsaOut = (SAFEARRAY*)(ptr + SAFEARRAY_HIDDEN_SIZE);
   return S_OK;
  }


SafeArrayAllocDescriptorEx calls SafeArrayAllocDescriptor to allocate the memory for the descriptor and then calls SAFEARRAY_SetFeatures to set the variant type.


HRESULT WINAPI SafeArrayAllocDescriptorEx ( VARTYPE      vt,
                                            UINT         cDims,
                                            SAFEARRAY ** ppsaOut
                                          ) 
   
   {
   ULONG cbElements;
   HRESULT hRet;

   TRACE("(%d->%s,%d,%p)\n", vt, debugstr_vt(vt), cDims, ppsaOut);
   
   cbElements = SAFEARRAY_GetVTSize(vt);
   if (!cbElements)
      WARN("Creating a descriptor with an invalid VARTYPE!\n");

   hRet = SafeArrayAllocDescriptor(cDims, ppsaOut);

   if (SUCCEEDED(hRet))
   {
      SAFEARRAY_SetFeatures(vt, *ppsaOut);
      (*ppsaOut)->cbElements = cbElements;
   }
   return hRet;
}



static void SAFEARRAY_SetFeatures ( VARTYPE     vt,
                                    SAFEARRAY * psa
                                    ) 
   
   {
   /* Set the IID if we have one, otherwise set the type */
   if (vt == VT_DISPATCH)
   {
     psa->fFeatures = FADF_HAVEIID;
     SafeArraySetIID(psa, &IID_IDispatch);
   }
   else if (vt == VT_UNKNOWN)
   {
     psa->fFeatures = FADF_HAVEIID;
     SafeArraySetIID(psa, &IID_IUnknown);
   }
   else if (vt == VT_RECORD)
     psa->fFeatures = FADF_RECORD;
   else
   {
     psa->fFeatures = FADF_HAVEVARTYPE;
     SAFEARRAY_SetHiddenDWORD(psa, vt);
   }
}


Those always asking about limits, please note that the maximum number of dimensions is 65535.

José Roca

To create a safe array, the Windows API provides the function SafeArrayCreate, that creates a new array descriptor, allocates and initializes the data for the array, and returns a pointer to the new array descriptor.

MSDN documentation: https://msdn.microsoft.com/en-us/library/windows/desktop/ms221234(v=vs.85).aspx


static SAFEARRAY* SAFEARRAY_Create ( VARTYPE                vt,
                                     UINT                   cDims,
                                     const SAFEARRAYBOUND * rgsabound,
                                     ULONG                  ulSize
                                   )
   
   {
   SAFEARRAY *psa = NULL;
   unsigned int i;

   if (!rgsabound)
     return NULL;

   if (SUCCEEDED(SafeArrayAllocDescriptorEx(vt, cDims, &psa)))
   {
     switch (vt)
     {
       case VT_BSTR:     psa->fFeatures |= FADF_BSTR; break;
       case VT_UNKNOWN:  psa->fFeatures |= FADF_UNKNOWN; break;
       case VT_DISPATCH: psa->fFeatures |= FADF_DISPATCH; break;
       case VT_VARIANT:  psa->fFeatures |= FADF_VARIANT; break;
     }

     for (i = 0; i < cDims; i++)
       memcpy(psa->rgsabound + i, rgsabound + cDims - 1 - i, sizeof(SAFEARRAYBOUND));

     if (ulSize)
       psa->cbElements = ulSize;

     if (!psa->cbElements || FAILED(SafeArrayAllocData(psa)))
     {
       SafeArrayDestroyDescriptor(psa);
       psa = NULL;
     }
   }
   return psa;
}


José Roca

SafeArrayCreate calls SafeArrayAllocData, that allocates memory for a safe array, based on a descriptor created with SafeArrayAllocDescriptor.

MSDN documentation: https://msdn.microsoft.com/en-us/library/windows/desktop/ms221468(v=vs.85).aspx


HRESULT WINAPI SafeArrayAllocData ( SAFEARRAY * psa )

   {
   HRESULT hRet = E_INVALIDARG;

   TRACE("(%p)\n", psa);

   if (psa)
   {
      ULONG ulSize = SAFEARRAY_GetCellCount(psa);

      psa->pvData = SAFEARRAY_Malloc(ulSize * psa->cbElements);

   if (psa->pvData)
   {
      hRet = S_OK;
      TRACE("%u bytes allocated for data at %p (%u objects).\n",
      ulSize * psa->cbElements, psa->pvData, ulSize);
   }
   else
      hRet = E_OUTOFMEMORY;
   }
   return hRet;
}


This is the helper internal function that calculates the number of cells.


static ULONG SAFEARRAY_GetCellCount ( const SAFEARRAY * psa )
{
   const SAFEARRAYBOUND* psab = psa->rgsabound;
   USHORT cCount = psa->cDims;
   ULONG ulNumCells = 1;

   while (cCount--)
   {
     /* This is a valid bordercase. See testcases. -Marcus */
     if (!psab->cElements)
       return 0;
     ulNumCells *= psab->cElements;
     psab++;
   }
   return ulNumCells;
}


José Roca

In my CSafeArray class there are three constructors that call SafeArrayCreate:


' ========================================================================================
' Creates a safe array.
' Parameters:
' - vt: The base type of the array (the VARTYPE of each element of the array). The VARTYPE
'   is restricted to a subset of the variant types. Neither the VT_ARRAY nor the VT_BYREF
'   flag can be set. VT_EMPTY and VT_NULL are not valid base types for the array.
'   All other types are legal.
' - cDims: The number of dimensions in the array. The number cannot be changed after the
'   array is created.
' - lLBound: The lower bound value; that is, the index of the first element in the array.
'   Can be negative.
' - cElements: The number of elements in the array.
' ========================================================================================
' // Multidimensional array
PRIVATE CONSTRUCTOR CSafeArray (BYVAL vt AS VARTYPE, BYVAL cDims AS UINT, BYVAL prgsabounds AS SAFEARRAYBOUND PTR)
   m_psa = SafeArrayCreate(vt, cDims, prgsabounds)
END CONSTRUCTOR
' ========================================================================================
' ========================================================================================
' // One-dimensional array
PRIVATE CONSTRUCTOR CSafeArray (BYVAL vt AS VARTYPE, BYVAL cElements AS ULONG, BYVAL lLBound AS LONG)
   DIM rgsabounds(0) AS SAFEARRAYBOUND = {(cElements, lLBound)}
   m_psa = SafeArrayCreate(vt, 1, @rgsabounds(0))
END CONSTRUCTOR
' ========================================================================================
' ========================================================================================
' // Two-dimensional array
PRIVATE CONSTRUCTOR CSafeArray (BYVAL vt AS VARTYPE, BYVAL cElements1 AS ULONG, BYVAL lLBound1 AS LONG, BYVAL cElements2 AS ULONG, BYVAL lLBound2 AS LONG)
   DIM rgsabounds(1) AS SAFEARRAYBOUND = {(cElements1, lLBound1), (cElements2, lLBound2)}
   m_psa = SafeArrayCreate(vt, 2, @rgsabounds(0))
END CONSTRUCTOR
' ========================================================================================


The first one requires that you pass the type, the number of dimensions and an array of SAFEARRAYBOUND structures (one for each dimension) with the number of elements and lower bound of each dimension.

The other two are simple wrappers to ease the use of SafeArrayCreate.

Usage examples:


' // Create a three-dimensional safe array of variants
' // 1st dimension: 5 elements, lower bound 1
' // 2nd dimension: 3 elements, lower bound 1
' // 3rd dimension: 10 elements, lower bound 0

DIM rgsabounds(0 TO 2) AS SAFEARRAYBOUND = {(5, 1), (3, 1), (10, 0)}
DIM csa AS CSafeArray = CSafeArray(VT_VARIANT, 3, @rgsabounds(0))


One-dimensional array:


' // Create a one-dimensional array of variants
' // 5 elements, lower bound 1

DIM csa AS CSafeArray = CSafeArray(VT_VARIANT, 5, 1)


One-dimensional array:


' // Create a two-dimensional array of variants
' // 1st dimension: 5 elements, lower bound 1
' // 2nd dimension: 3 elements, lower bound 1

DIM csa AS CSafeArray = CSafeArray(VT_VARIANT, 5, 1, 3, 1)


José Roca

Alternatively, you can create an instance of the class using the default empty constructor


DIM csa AS CSafeArray
-or-
DIM csa AS CSafeArray PTR = NEW CSafeArray


and then create the safe array using the Create or CreateEx methods:


' =====================================================================================
' Creates a safe array from the given VARTYPE, number of dimensions and bounds.
' Parameters:
' vt
'   [in] Base type of the array (the VARTYPE of each element of the array).
'   The VARTYPE is restricted to a subset of the variant types.
'   Neither VT_ARRAY nor the VT_BYREF flag can be set.
'   VT_EMPTY and VT_NULL are not valid base types for the array.
'   All other types are legal.
' cDims
'   [in] Number of dimensions in the array.
'   The number cannot be changed after the array is created.
' rgsabound
'   [in] Pointer to a vector of bounds (one for each dimension) to allocate for the array.
' Return value:
'   Returns S_OK on success, or an error HRESULT on failure.
' =====================================================================================
' // Multidimensional array
PRIVATE FUNCTION CSafeArray.Create (BYVAL vt AS VARTYPE, BYVAL cDims AS UINT, BYVAL prgsabound AS SAFEARRAYBOUND PTR) AS HRESULT
   IF m_psa <> NULL THEN RETURN E_FAIL
   IF prgsabound = NULL THEN RETURN E_INVALIDARG
   IF cDims < 1 THEN RETURN E_INVALIDARG
   m_psa = SafeArrayCreate(vt, cDims, prgsabound)
   IF m_psa = NULL THEN RETURN E_OUTOFMEMORY
   RETURN SafeArrayLock(m_psa)
END FUNCTION
' =====================================================================================
' =====================================================================================
' // One-dimensional array
PRIVATE FUNCTION CSafeArray.Create (BYVAL vt AS VARTYPE, BYVAL cElements AS ULONG, BYVAL lLBound AS LONG) AS HRESULT
   DIM rgsabounds(0) AS SAFEARRAYBOUND = {(cElements, lLBound)}
   RETURN this.Create(vt, 1, @rgsabounds(0))
END FUNCTION
' =====================================================================================
' =====================================================================================
' // Two-dimensional array
PRIVATE FUNCTION CSafeArray.Create (BYVAL vt AS VARTYPE, BYVAL cElements1 AS ULONG, BYVAL lLBound1 AS LONG, BYVAL cElements2 AS ULONG, BYVAL lLBound2 AS LONG) AS HRESULT
   DIM rgsabounds(1) AS SAFEARRAYBOUND = {(cElements1, lLBound1), (cElements2, lLBound2)}
   RETURN this.Create(vt, 2, @rgsabounds(0))
END FUNCTION
' =====================================================================================

' =====================================================================================
' Creates a safe array from the given VARTYPE, number of dimensions and bounds.
' Parameters:
' vt
'   [in] The base type or the VARTYPE of each element of the array. The FADF_RECORD
'   flag can be set for a variant type VT_RECORD, The FADF_HAVEIID flag can be set
'   for VT_DISPATCH or VT_UNKNOWN, and FADF_HAVEVARTYPE can be set for all other VARTYPEs.
' cDims
'   [in] Number of dimensions in the array.
'   The number cannot be changed after the array is created.
' rgsabound
'   [in] Pointer to a vector of bounds (one for each dimension) to allocate for the array.
' pvExtra
'   Points to the type information of the user-defined type, if you are creating a
'   safe array of user-defined types. If the vt parameter is VT_RECORD, then
'   pvExtra will be a pointer to an IRecordInfo describing the record. If the vt
'   parameter is VT_DISPATCH or VT_UNKNOWN, then pvExtra will contain a pointer to
'   a GUID representing the type of interface being passed to the array.
' Return value:
'   Returns S_OK on success, or an error HRESULT on failure.
' Comments:
'   If the VARTYPE is VT_RECORD then SafeArraySetRecordInfo is called. If the
'   VARTYPE is VT_DISPATCH or VT_UNKNOWN then the elements of the array must contain
'   interfaces of the same type. Part of the process of marshaling this array to
'   other processes does include generating the proxy/stub code of the IID pointed
'   to by pvExtra parameter. To actually pass heterogeneous interfaces one will need
'   to specify either IID_IUnknown or IID_IDispatch in pvExtra and provide some
'   other means for the caller to identify how to query for the actual interface.
' =====================================================================================
' // Multidimensional array
PRIVATE FUNCTION CSafeArray.CreateEx (BYVAL vt AS VARTYPE, BYVAL cDims AS UINT, BYVAL prgsabound AS SAFEARRAYBOUND PTR, BYVAL pvExtra AS PVOID) AS HRESULT
   IF m_psa <> NULL THEN RETURN E_FAIL
   IF prgsabound = NULL THEN RETURN E_INVALIDARG
   IF cDims < 1 THEN RETURN E_INVALIDARG
   m_psa = SafeArrayCreateEx(vt, cDims, prgsabound, pvExtra)
   IF m_psa = NULL THEN RETURN E_OUTOFMEMORY
   RETURN SafeArrayLock(m_psa)
END FUNCTION
' =====================================================================================
' =====================================================================================
' // One-dimensional array
PRIVATE FUNCTION CSafeArray.CreateEx (BYVAL vt AS VARTYPE, BYVAL cElements AS ULONG, BYVAL lLBound AS LONG, BYVAL pvExtra AS ANY PTR) AS HRESULT
   DIM rgsabounds(0) AS SAFEARRAYBOUND = {(cElements, lLBound)}
   RETURN this.CreateEx(vt, 1, @rgsabounds(0), pvExtra)
END FUNCTION
' =====================================================================================
' =====================================================================================
' // Two-dimensional array
PRIVATE FUNCTION CSafeArray.CreateEx (BYVAL vt AS VARTYPE, BYVAL cElements1 AS ULONG, BYVAL lLBound1 AS LONG, BYVAL cElements2 AS ULONG, BYVAL lLBound2 AS LONG, BYVAL pvExtra AS ANY PTR) AS HRESULT
   DIM rgsabounds(1) AS SAFEARRAYBOUND = {(cElements1, lLBound1), (cElements2, lLBound2)}
   RETURN this.CreateEx(vt, 2, @rgsabounds(0), pvExtra)
END FUNCTION
' =====================================================================================


José Roca

#6
The next step is to fill the array with data. The SafeArray API provides the function SafeArrayPutElement, that requires that you pass an array descriptor created by SafeArrayCreate, a vector of indexes for each dimension of the array--the right-most (least significant) dimension is rgIndices[0]. The left-most dimension is stored at rgIndices[psa->cDims â€" 1]--, and the data to assign to the array. The variant types VT_DISPATCH, VT_UNKNOWN, and VT_BSTR are pointers, and do not require another level of indirection.


HRESULT WINAPI SafeArrayPutElement  (  SAFEARRAY *    psa,
      LONG *   rgIndices,
      void *   pvData
   ) 
   
{
   HRESULT hRet;

   TRACE("(%p,%p,%p)\n", psa, rgIndices, pvData);

   if (!psa || !rgIndices)
      return E_INVALIDARG;

   hRet = SafeArrayLock(psa);

   if (SUCCEEDED(hRet))
   {
      PVOID lpvDest;

      hRet = SafeArrayPtrOfIndex(psa, rgIndices, &lpvDest);

      if (SUCCEEDED(hRet))
      {
         if (psa->fFeatures & FADF_VARIANT)
         {
            VARIANT* lpVariant = pvData;
            VARIANT* lpDest = lpvDest;

            hRet = VariantCopy(lpDest, lpVariant);
            if (FAILED(hRet)) FIXME("VariantCopy failed with 0x%x\n", hRet);
         }
         else if (psa->fFeatures & FADF_BSTR)
         {
            BSTR  lpBstr = (BSTR)pvData;
            BSTR* lpDest = lpvDest;

            SysFreeString(*lpDest);

            *lpDest = SysAllocStringByteLen((char*)lpBstr, SysStringByteLen(lpBstr));
            if (!*lpDest)
               hRet = E_OUTOFMEMORY;
         }
         else if (psa->fFeatures & (FADF_UNKNOWN|FADF_DISPATCH))
         {
            IUnknown  *lpUnknown = pvData;
            IUnknown **lpDest = lpvDest;
            if (lpUnknown)
               IUnknown_AddRef(lpUnknown);
            if (*lpDest)
               IUnknown_Release(*lpDest);
            *lpDest = lpUnknown;
         }
         else if (psa->fFeatures & FADF_RECORD)
         {
            IRecordInfo *record;

            SafeArrayGetRecordInfo(psa, &record);
            hRet = IRecordInfo_RecordCopy(record, pvData, lpvDest);
            IRecordInfo_Release(record);
         } else
            /* Copy the data over */
           memcpy(lpvDest, pvData, psa->cbElements);
      }
      SafeArrayUnlock(psa);
   }
   return hRet;
}


With my CSafeArray and CBStr classes, you can do:


' // Create a two-dimensional array of BSTR
DIM rgsabounds(0 TO 1) AS SAFEARRAYBOUND = {(5, 1), (3, 1)}
DIM csa AS CSafeArray = CSafeArray(VT_BSTR, 2, @rgsabounds(0))

DIM cbs1 AS CBSTR = "Test string 1"
DIM rgidx1(0 TO 1) AS LONG = {1, 1}
csa.PutElement(@rgidx1(0), cbs1)

DIM cbs2 AS CBSTR = "Test string 2"
DIM rgidx2(0 TO 1) AS LONG = {2, 1}
csa.PutElement(@rgidx2(0), cbs2)


or


' // Create a two-dimensional array of BSTR
DIM rgsabounds(0 TO 1) AS SAFEARRAYBOUND = {(5, 1), (3, 1)}
DIM csa AS CSafeArray = CSafeArray(VT_BSTR, 2, @rgsabounds(0))

DIM cbs1 AS CBSTR = "Test string 1"
csa.PutElement(1, 1, cbs1)

DIM cbs2 AS CBSTR = "Test string 2"
csa.PutElement(2, 1, cbs2)


José Roca

#7
To retrieve the data of a single element the API provides the function SafeArrayGetElement, that like SafeArrayPutElement requires that you pass the array descriptor created by SafeArrayCreate and a vector of indexes for each dimension of the array. The last parameter is the address of a variant of the appropiate type that will receive the value.


HRESULT WINAPI SafeArrayGetElement ( SAFEARRAY *    psa,
                                     LONG *   rgIndices,
                                     void *   pvData
                                   )
   
{
   HRESULT hRet;

   TRACE("(%p,%p,%p)\n", psa, rgIndices, pvData);
     
   if (!psa || !rgIndices || !pvData)
     return E_INVALIDARG;

   hRet = SafeArrayLock(psa);

   if (SUCCEEDED(hRet))
   {
     PVOID lpvSrc;

     hRet = SafeArrayPtrOfIndex(psa, rgIndices, &lpvSrc);

     if (SUCCEEDED(hRet))
     {
       if (psa->fFeatures & FADF_VARIANT)
       {
         VARIANT* lpVariant = lpvSrc;
         VARIANT* lpDest = pvData;

         /* The original content of pvData is ignored. */
         V_VT(lpDest) = VT_EMPTY;
         hRet = VariantCopy(lpDest, lpVariant);
    if (FAILED(hRet)) FIXME("VariantCopy failed with 0x%x\n", hRet);
      }
      else if (psa->fFeatures & FADF_BSTR)
      {
        BSTR* lpBstr = lpvSrc;
        BSTR* lpDest = pvData;

        if (*lpBstr)
        {
          *lpDest = SysAllocStringByteLen((char*)*lpBstr, SysStringByteLen(*lpBstr));
          if (!*lpBstr)
            hRet = E_OUTOFMEMORY;
        }
        else
          *lpDest = NULL;
      }
      else if (psa->fFeatures & (FADF_UNKNOWN|FADF_DISPATCH))
      {
        IUnknown **src_unk = lpvSrc;
        IUnknown **dest_unk = pvData;

        if (*src_unk)
           IUnknown_AddRef(*src_unk);
         *dest_unk = *src_unk;
       }
       else if (psa->fFeatures & FADF_RECORD)
       {
         IRecordInfo *record;

         SafeArrayGetRecordInfo(psa, &record);
         hRet = IRecordInfo_RecordCopy(record, lpvSrc, pvData);
         IRecordInfo_Release(record);
       }
       else
         /* Copy the data over */
         memcpy(pvData, lpvSrc, psa->cbElements);
     }
     SafeArrayUnlock(psa);
   }
   return hRet;
}


With my classes you can do:


' // Create a two-dimensional array of BSTR
DIM rgsabounds(0 TO 1) AS SAFEARRAYBOUND = {(5, 1), (3, 1)}
DIM csa AS CSafeArray = CSafeArray(VT_BSTR, 2, @rgsabounds(0))

DIM cbs1 AS CBSTR = "Test string 1"
csa.PutElement(1, 1, cbs1)

DIM cbs2 AS CBSTR = "Test string 2"
csa.PutElement(2, 1, cbs2)

DIM cbsOut AS CBSTR
csa.GetElement(1, 1, cbsOut)
print cbsOut

csa.GetElement(2, 1, cbsOut)
print cbsOut


José Roca

Notice that the first thing that SafeArrayGetElement does is to call SafeArrayLock, that increments the lock count of an array, and places a pointer to the array data in pvData of the array descriptor.


HRESULT WINAPI SafeArrayLock ( SAFEARRAY * psa )   

{
   ULONG ulLocks;

   TRACE("(%p)\n", psa);
     
   if (!psa)
     return E_INVALIDARG;

   ulLocks = InterlockedIncrement( (LONG*) &psa->cLocks);

   if (ulLocks > 0xffff) /* Maximum of 16384 locks at a time */
   {
     WARN("Out of locks!\n");
     InterlockedDecrement( (LONG*) &psa->cLocks);
     return E_UNEXPECTED;
   }
   return S_OK;
}


And when it ends it calls SafeArrayUnlock, that decrements the lock count of an array so it can be freed or resized.


HRESULT WINAPI SafeArrayUnlock   (  SAFEARRAY *    psa   ) 
{
   TRACE("(%p)\n", psa);
 
   if (!psa)
     return E_INVALIDARG;

   if (InterlockedDecrement( (LONG*) &psa->cLocks) < 0)
   {
     WARN("Unlocked but no lock held!\n");
     InterlockedIncrement( (LONG*) &psa->cLocks);
     return E_UNEXPECTED;
   }
   return S_OK;
}


José Roca

#9
It also calls SafeArrayPtrOfIndex, that does the work of calculate the index and retrieve a pointer to the data.


HRESULT WINAPI SafeArrayPtrOfIndex ( SAFEARRAY * psa,
                                     LONG *      rgIndices,
                                     void **     ppvData
                                   ) 
{
  USHORT dim;
  ULONG cell = 0, dimensionSize = 1;
  SAFEARRAYBOUND* psab;
  LONG c1;

  TRACE("(%p,%p,%p)\n", psa, rgIndices, ppvData);
 
  /* The general formula for locating the cell number of an entry in an n
   * dimensional array (where cn = coordinate in dimension dn) is:
    *
    * c1 + c2 * sizeof(d1) + c3 * sizeof(d2) ... + cn * sizeof(c(n-1))
    *
    * We calculate the size of the last dimension at each step through the
    * dimensions to avoid recursing to calculate the last dimensions size.
    */
   if (!psa || !rgIndices || !ppvData)
     return E_INVALIDARG;

   psab = psa->rgsabound + psa->cDims - 1;
   c1 = *rgIndices++;

   if (c1 < psab->lLbound || c1 >= psab->lLbound + (LONG)psab->cElements)
     return DISP_E_BADINDEX; /* Initial index out of bounds */

   for (dim = 1; dim < psa->cDims; dim++)
  {
    dimensionSize *= psab->cElements;

    psab--;

    if (!psab->cElements ||
        *rgIndices < psab->lLbound ||
        *rgIndices >= psab->lLbound + (LONG)psab->cElements)
    return DISP_E_BADINDEX; /* Index out of bounds */

    cell += (*rgIndices - psab->lLbound) * dimensionSize;
    rgIndices++;
  }

  cell += (c1 - psa->rgsabound[psa->cDims - 1].lLbound);

  *ppvData = (char*)psa->pvData + cell * psa->cbElements;
  return S_OK;
}


José Roca

#10
It is worth to note the big difference between SafeArrayGetElement and SafeArrayPtrOfIndex. SafeArrayGetElement is slow because is a safe wrapper for SafeArrayPtrOfIndex. While SafeArrayPtrOfIndex retrieves a direct pointer to the data and you have the full responsability about what you do with it (above all don't cache it, because it can change), SafeArrayGetElement locks the array, retrieves the pointer to access to the data and returns a copy of the data. If the returned data is a variant, you have to free it with VariantClear; if it is a BSTR, with SysFreeString, and if it is an interface pointer, with IUnknown_Release.

It is very important to know these differences because in the current implementaion of CSafeArray, the GetElement method will cause leaks if we use it as


DIM cbsOut AS CBSTR
hr = csa.GetElement(1, 1, @cbsOut)
hr = csa.GetElement(2, 1, @cbsOut)


There will be memory leaks because GetElement will overwrite the underlying BSTR pointer of cbsOut without freeing it first. Therefore, I will have to add additional overloaded GetElement methods to solve this problem. Until now, I didn't know what SafearrayGetElement was exactly doing.

This also means that I have to revise the code of the other data types (CBSTR, CWSTR, CVARIANT) for its use when calling a function with an OUT parameter. We can't simply pass a pointer to the data, as I often have been doing.

José Roca

SafeArrayRedim changes the right-most (least significant) bound of the specified safe array. We have to pass the safe array descriptor obtained with SafeArrayCreate and a new safe array bound structure that contains the new array boundary. You can change only the least significant dimension of an array.

If you reduce the bound of an array, SafeArrayRedim deallocates the array elements outside the new array boundary. If the bound of an array is increased, SafeArrayRedim allocates and initializes the new array elements. The data is preserved for elements that exist in both the old and new array.


HRESULT WINAPI SafeArrayRedim ( SAFEARRAY      * psa,
                                SAFEARRAYBOUND * psabound
                              )

{
   SAFEARRAYBOUND *oldBounds;
   HRESULT hr;

   TRACE("(%p,%p)\n", psa, psabound);
 
   if (!psa || psa->fFeatures & FADF_FIXEDSIZE || !psabound)
     return E_INVALIDARG;

   if (psa->cLocks > 0)
     return DISP_E_ARRAYISLOCKED;

   hr = SafeArrayLock(psa);
   if (FAILED(hr))
     return hr;

   oldBounds = psa->rgsabound;
   oldBounds->lLbound = psabound->lLbound;

   if (psabound->cElements != oldBounds->cElements)
   {
     if (psabound->cElements < oldBounds->cElements)
     {
       /* Shorten the final dimension. */
       ULONG ulStartCell = psabound->cElements *
                           (SAFEARRAY_GetCellCount(psa) / oldBounds->cElements);
       SAFEARRAY_DestroyData(psa, ulStartCell);
     }
     else
     {
       /* Lengthen the final dimension */
       ULONG ulOldSize, ulNewSize;
       PVOID pvNewData;

       ulOldSize = SAFEARRAY_GetCellCount(psa) * psa->cbElements;
       if (ulOldSize)
         ulNewSize = (ulOldSize / oldBounds->cElements) * psabound->cElements;
       else {
         int oldelems = oldBounds->cElements;
         oldBounds->cElements = psabound->cElements;
         ulNewSize = SAFEARRAY_GetCellCount(psa) * psa->cbElements;
         oldBounds->cElements = oldelems;
       }

       if (!(pvNewData = SAFEARRAY_Malloc(ulNewSize)))
       {
         SafeArrayUnlock(psa);
         return E_OUTOFMEMORY;
       }

       memcpy(pvNewData, psa->pvData, ulOldSize);
       SAFEARRAY_Free(psa->pvData);
       psa->pvData = pvNewData;
     }
     oldBounds->cElements = psabound->cElements;
   }

   SafeArrayUnlock(psa);
   return S_OK;
}


As we can see, if we shorten the array, the function frees the discarded elements with SAFEARRAY_DestroyData, and if we lengthen it, it allocates new memory for a new array, copies the data of the old in it and frees the old array with SAFEARRAY_Free.

SAFEARRAY_DestroyData checks the type of data to free and calls IUnknown_Release if it is an IUnknown or IDispatch pointer, clears and releases the UDT if it is a record, calls SysFreeString if it is a BSTR and VariantClear if it is a VARIANT.


static HRESULT SAFEARRAY_DestroyData ( SAFEARRAY *    psa,
                                       ULONG    ulStartCell
                                       )

{
    if (psa->pvData && !(psa->fFeatures & FADF_DATADELETED))
    {
      ULONG ulCellCount = SAFEARRAY_GetCellCount(psa);
 
     if (ulStartCell > ulCellCount) {
       FIXME("unexpted ulcellcount %d, start %d\n",ulCellCount,ulStartCell);
       return E_UNEXPECTED;
     }

     ulCellCount -= ulStartCell;

     if (psa->fFeatures & (FADF_UNKNOWN|FADF_DISPATCH))
     {
       LPUNKNOWN *lpUnknown = (LPUNKNOWN *)psa->pvData + ulStartCell;

       while(ulCellCount--)
       {
         if (*lpUnknown)
           IUnknown_Release(*lpUnknown);
         lpUnknown++;
       }
     }
     else if (psa->fFeatures & FADF_RECORD)
     {
       IRecordInfo *lpRecInfo;

       if (SUCCEEDED(SafeArrayGetRecordInfo(psa, &lpRecInfo)))
       {
         PBYTE pRecordData = psa->pvData;
         while(ulCellCount--)
         {
           IRecordInfo_RecordClear(lpRecInfo, pRecordData);
           pRecordData += psa->cbElements;
         }
         IRecordInfo_Release(lpRecInfo);
       }
     }
     else if (psa->fFeatures & FADF_BSTR)
     {
       BSTR* lpBstr = (BSTR*)psa->pvData + ulStartCell;

       while(ulCellCount--)
       {
         SysFreeString(*lpBstr);
         lpBstr++;
       }
     }
     else if (psa->fFeatures & FADF_VARIANT)
     {
       VARIANT* lpVariant = (VARIANT*)psa->pvData + ulStartCell;

       while(ulCellCount--)
       {
         HRESULT hRet = VariantClear(lpVariant);

         if (FAILED(hRet)) FIXME("VariantClear of element failed!\n");
         lpVariant++;
       }
     }
   }
   return S_OK;
}


SAFEARRAY_Free simply frees the memory allocated for the array.


static void SAFEARRAY_Free (void * ptr )
{
  CoTaskMemFree(ptr);
}



José Roca

#12
SafeArrayCopy creates a copy of an existing safe array. SafeArrayCopy calls the string or variant manipulation functions if the array to copy contains either of these data types. If the array being copied contains object references, the reference counts for the objects are incremented.


HRESULT WINAPI SafeArrayCopy ( SAFEARRAY *    psa,
                               SAFEARRAY **   ppsaOut
                             )

{
   HRESULT hRet;

   TRACE("(%p,%p)\n", psa, ppsaOut);

   if (!ppsaOut)
     return E_INVALIDARG;

   *ppsaOut = NULL;

   if (!psa)
     return S_OK; /* Handles copying of NULL arrays */

   if (!psa->cbElements)
     return E_INVALIDARG;

   if (psa->fFeatures & (FADF_RECORD|FADF_HAVEIID|FADF_HAVEVARTYPE))
   {
     VARTYPE vt;

     hRet = SafeArrayGetVartype(psa, &vt);
     if (SUCCEEDED(hRet))
       hRet = SafeArrayAllocDescriptorEx(vt, psa->cDims, ppsaOut);
   }
   else
   {
     hRet = SafeArrayAllocDescriptor(psa->cDims, ppsaOut);
     if (SUCCEEDED(hRet))
     {
       (*ppsaOut)->fFeatures = psa->fFeatures & ~ignored_copy_features;
       (*ppsaOut)->cbElements = psa->cbElements;
     }
   }

   if (SUCCEEDED(hRet))
   {
     /* Copy dimension bounds */
     memcpy((*ppsaOut)->rgsabound, psa->rgsabound, psa->cDims * sizeof(SAFEARRAYBOUND));

     (*ppsaOut)->pvData = SAFEARRAY_Malloc(SAFEARRAY_GetCellCount(psa) * psa->cbElements);
     if (!(*ppsaOut)->pvData)
     {
       SafeArrayDestroyDescriptor(*ppsaOut);
       *ppsaOut = NULL;
       return E_OUTOFMEMORY;
     }

     hRet = SAFEARRAY_CopyData(psa, *ppsaOut);
     if (FAILED(hRet))
     {
       SAFEARRAY_Free((*ppsaOut)->pvData);
       SafeArrayDestroyDescriptor(*ppsaOut);
       *ppsaOut = NULL;
       return hRet;
     }
   }

   return hRet;
}


After allocating a new descriptor and memory for the new array, SafeArrayCopy calls the internal helper function SAFEARRAY_CopyData to do the copy of the data.

As usual, Variants, BSTR, records and inteface pointers need an special treatment. Variants are copied using VariantCopy, BSTRings allocating new ones with SysAllocStringByteLen and records with IRecordInfo_RecordCopy. For interface pointers, the reference count is increased calling IUnknown_AddRef.


static HRESULT SAFEARRAY_CopyData ( SAFEARRAY * psa,
                                    SAFEARRAY * dest
                                  )
{
   HRESULT hr = S_OK;

   if (!psa->pvData)
     return S_OK;

   if (!dest->pvData || psa->fFeatures & FADF_DATADELETED)
     return E_INVALIDARG;
   else
   {
     ULONG ulCellCount = SAFEARRAY_GetCellCount(psa);

     dest->fFeatures = (dest->fFeatures & FADF_CREATEVECTOR) | (psa->fFeatures & ~ignored_copy_features);

     if (psa->fFeatures & FADF_VARIANT)
     {
       VARIANT *src_var = psa->pvData;
       VARIANT *dest_var = dest->pvData;

       while(ulCellCount--)
       {
         HRESULT hRet;

         /* destination is cleared automatically */
         hRet = VariantCopy(dest_var, src_var);
         if (FAILED(hRet)) FIXME("VariantCopy failed with 0x%08x, element %u\n", hRet, ulCellCount);
         src_var++;
         dest_var++;
       }
     }
     else if (psa->fFeatures & FADF_BSTR)
     {
       BSTR *src_bstr = psa->pvData;
       BSTR *dest_bstr = dest->pvData;

       while(ulCellCount--)
       {
         SysFreeString(*dest_bstr);
         if (*src_bstr)
         {
           *dest_bstr = SysAllocStringByteLen((char*)*src_bstr, SysStringByteLen(*src_bstr));
           if (!*dest_bstr)
             return E_OUTOFMEMORY;
         }
         else
           *dest_bstr = NULL;
         src_bstr++;
         dest_bstr++;
       }
     }
     else if (psa->fFeatures & FADF_RECORD)
     {
       BYTE *dest_data = dest->pvData;
       BYTE *src_data = psa->pvData;
       IRecordInfo *record;

       SafeArrayGetRecordInfo(psa, &record);
       while (ulCellCount--)
       {
           /* RecordCopy() clears destination record */
           hr = IRecordInfo_RecordCopy(record, src_data, dest_data);
           if (FAILED(hr)) break;
           src_data += psa->cbElements;
           dest_data += psa->cbElements;
       }

       SafeArraySetRecordInfo(dest, record);
       /* This value is set to 32 bytes by default on descriptor creation,
          update with actual structure size. */
       dest->cbElements = psa->cbElements;
       IRecordInfo_Release(record);
     }
     else if (psa->fFeatures & (FADF_UNKNOWN|FADF_DISPATCH))
     {
       IUnknown **dest_unk = dest->pvData;
       IUnknown **src_unk = psa->pvData;

       /* release old iface, addref new one */
       while (ulCellCount--)
       {
           if (*dest_unk)
               IUnknown_Release(*dest_unk);
           *dest_unk = *src_unk;
           if (*dest_unk)
               IUnknown_AddRef(*dest_unk);
           src_unk++;
           dest_unk++;
       }
     }
     else
     {
       /* Copy the data over */
       memcpy(dest->pvData, psa->pvData, ulCellCount * psa->cbElements);
     }

     if (psa->fFeatures & FADF_HAVEIID)
     {
       GUID guid;
       SafeArrayGetIID(psa, &guid);
       SafeArraySetIID(dest, &guid);
     }
     else if (psa->fFeatures & FADF_HAVEVARTYPE)
     {
       SAFEARRAY_SetHiddenDWORD(dest, SAFEARRAY_GetHiddenDWORD(psa));
     }
   }

   return hr;
}



José Roca

The helper internal function SAFEARRAY_CopyData is also called by the API function SafeArrayCopyData, that copies the source array to the specified target array after releasing any resources in the target array. This is similar to SafeArrayCopy, except that the target array has to be set up by the caller. The target is not allocated or reallocated.


HRESULT WINAPI SafeArrayCopyData ( SAFEARRAY * psaSource,
                                   SAFEARRAY * psaTarget
                                 )
{
   int dim;

   TRACE("(%p,%p)\n", psaSource, psaTarget);
   
   if (!psaSource || !psaTarget ||
       psaSource->cDims != psaTarget->cDims ||
       psaSource->cbElements != psaTarget->cbElements)
     return E_INVALIDARG;

   /* Each dimension must be the same size */
   for (dim = psaSource->cDims - 1; dim >= 0 ; dim--)
     if (psaSource->rgsabound[dim].cElements !=
        psaTarget->rgsabound[dim].cElements)
       return E_INVALIDARG;

   return SAFEARRAY_CopyData(psaSource, psaTarget);
}


We don't have to free the contents of the target array first because as we can see in the code for SAFEARRAY_CopyData it does it for us.

If it is a variant, VariantCopy is used.


         /* destination is cleared automatically */
         hRet = VariantCopy(dest_var, src_var);


VariantCOpy frees the destination variant and makes a copy of the source variant.

If it is a BSTR...


         SysFreeString(*dest_bstr);
         if (*src_bstr)
         {
           *dest_bstr = SysAllocStringByteLen((char*)*src_bstr, SysStringByteLen(*src_bstr));
           if (!*dest_bstr)
             return E_OUTOFMEMORY;
         }
[code]

The tartget BSTR if freed with SysFreeString.

If it is a record, IRecordInfo_RecordCopy is called

[code]
     else if (psa->fFeatures & FADF_RECORD)
     {
       BYTE *dest_data = dest->pvData;
       BYTE *src_data = psa->pvData;
       IRecordInfo *record;

       SafeArrayGetRecordInfo(psa, &record);
       while (ulCellCount--)
       {
           /* RecordCopy() clears destination record */
           hr = IRecordInfo_RecordCopy(record, src_data, dest_data);
           if (FAILED(hr)) break;
           src_data += psa->cbElements;
           dest_data += psa->cbElements;
       }

       SafeArraySetRecordInfo(dest, record);
       /* This value is set to 32 bytes by default on descriptor creation,
          update with actual structure size. */
       dest->cbElements = psa->cbElements;
       IRecordInfo_Release(record);
     }


IRecordInfo_RecordCopy will release the resources in the destination first. The caller is responsible for allocating sufficient memory in the destination by calling GetSize or RecordCreate. If RecordCopy fails to copy any of the fields then all fields will be cleared, as though RecordClear had been called.

And if it is an interface pointer it is released with a call to IUnknown_Release before copying the new addrefed pointer.


       /* release old iface, addref new one */
       while (ulCellCount--)
       {
           if (*dest_unk)
               IUnknown_Release(*dest_unk);
           *dest_unk = *src_unk;
           if (*dest_unk)
               IUnknown_AddRef(*dest_unk);
           src_unk++;
           dest_unk++;
       }


José Roca

SafearrayDestroy destroys all the data in the specified safe array.


HRESULT WINAPI SafeArrayDestroy  (  SAFEARRAY *    psa   )

{
   TRACE("(%p)\n", psa);

   if(!psa)
     return S_OK;

   if(psa->cLocks > 0)
     return DISP_E_ARRAYISLOCKED;

   /* Native doesn't check to see if the free succeeds */
   SafeArrayDestroyData(psa);
   SafeArrayDestroyDescriptor(psa);
   return S_OK;
}


As we can see, it simply calls SafeArrayDestroyData to destroy the data and SafeArrayDestroyDescriptor to destroy the descriptor.