PlanetSquires Forums

Support Forums => General Board => Topic started by: José Roca on September 01, 2016, 12:52:28 PM

Title: About Safe Arrays
Post by: José Roca on September 01, 2016, 12:52:28 PM
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
Title: Safe Array Descriptor
Post by: José Roca on September 01, 2016, 01:43:18 PM
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.
Title: Re: About Safe Arrays
Post by: José Roca on September 01, 2016, 01:51:55 PM
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;
}

Title: Re: About Safe Arrays
Post by: José Roca on September 01, 2016, 02:02:24 PM
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;
}

Title: Re: About Safe Arrays
Post by: José Roca on September 01, 2016, 02:22:22 PM
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)

Title: Re: About Safe Arrays
Post by: José Roca on September 01, 2016, 02:25:59 PM
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
' =====================================================================================

Title: Re: About Safe Arrays
Post by: José Roca on September 01, 2016, 02:52:14 PM
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)

Title: Re: About Safe Arrays
Post by: José Roca on September 01, 2016, 04:17:26 PM
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

Title: Re: About Safe Arrays
Post by: José Roca on September 01, 2016, 04:26:13 PM
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;
}

Title: Re: About Safe Arrays
Post by: José Roca on September 01, 2016, 04:29:44 PM
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;
}

Title: Re: About Safe Arrays
Post by: José Roca on September 02, 2016, 03:14:30 AM
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.
Title: Re: About Safe Arrays
Post by: José Roca on September 02, 2016, 03:46:16 AM
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);
}


Title: Re: About Safe Arrays
Post by: José Roca on September 02, 2016, 04:04:03 AM
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;
}


Title: Re: About Safe Arrays
Post by: José Roca on September 02, 2016, 04:24:35 AM
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++;
       }

Title: Re: About Safe Arrays
Post by: José Roca on September 02, 2016, 04:29:02 AM
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.
Title: Re: About Safe Arrays
Post by: José Roca on September 02, 2016, 04:36:37 AM
SafeArrayDestroyData destroys all the data in the specified safe array.


HRESULT WINAPI SafeArrayDestroyData ( SAFEARRAY * psa )
{
   HRESULT hr;

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

   if (psa->cLocks)
     return DISP_E_ARRAYISLOCKED; /* Can't delete a locked array */

   /* Delete the actual item data */
   hr = SAFEARRAY_DestroyData(psa, 0);
   if (FAILED(hr))
     return hr;

   if (psa->pvData)
   {
     if (psa->fFeatures & FADF_STATIC)
     {
       ZeroMemory(psa->pvData, SAFEARRAY_GetCellCount(psa) * psa->cbElements);
       return S_OK;
     }
     /* If this is not a vector, free the data memory block */
     if (!(psa->fFeatures & FADF_CREATEVECTOR))
     {
       SAFEARRAY_Free(psa->pvData);
       psa->pvData = NULL;
     }
     else
       psa->fFeatures |= FADF_DATADELETED; /* Mark the data deleted */

   }
   return S_OK;
}


It calls the helper internal functions SAFEARRAY_DestroyData and SAFEARRAY_Free, that we have already analyzed.
Title: Re: About Safe Arrays
Post by: José Roca on September 02, 2016, 04:42:41 AM
SafeArrayDestroyDescriptor destroys the descriptor of the specified safe array. This function is typically used to destroy the descriptor of a safe array that contains elements with data types other than variants. Destroying the array descriptor does not destroy the elements in the array. Before destroying the array descriptor, call SafeArrayDestroyData to free the elements.


HRESULT WINAPI SafeArrayDestroyDescriptor (SAFEARRAY * psa)

{
   TRACE("(%p)\n", psa);
     
    if (psa)
    {
      LPVOID lpv = (char*)psa - SAFEARRAY_HIDDEN_SIZE;
 
      if (psa->cLocks)
        return DISP_E_ARRAYISLOCKED; /* Can't destroy a locked array */
 
      if (psa->fFeatures & FADF_RECORD)
        SafeArraySetRecordInfo(psa, NULL);
 
      if (psa->fFeatures & FADF_CREATEVECTOR &&
          !(psa->fFeatures & FADF_DATADELETED))
          SAFEARRAY_DestroyData(psa, 0); /* Data not previously deleted */
 
      SAFEARRAY_Free(lpv);
    }
    return S_OK;
  }

Title: Re: About Safe Arrays
Post by: José Roca on September 02, 2016, 04:46:58 AM
Please note that if we create the safe arrays with SafeArrayCreate and destroy them with SafeArrayDestroy, we don't have the need of allocating and destroying data and descriptors since the higher level functions do it for us.

These lower-level functions are provided to allow the creation of safe arrays that contain elements with data types other than those provided by SafeArrayCreate.
Title: Re: About Safe Arrays
Post by: José Roca on September 02, 2016, 04:51:20 AM
SafeArrayGetDim gets the number of dimensions in the array.


UINT WINAPI SafeArrayGetDim ( SAFEARRAY * psa )
{
   TRACE("(%p) returning %d\n", psa, psa ? psa->cDims : 0u); 
   return psa ? psa->cDims : 0;
}


This function has not complications. It simply returns the value stored in the cDims member.
Title: Re: About Safe Arrays
Post by: José Roca on September 02, 2016, 05:01:46 AM
The SafeArrayGetIID function gets the GUID of the interface contained within the specified safe array.


HRESULT WINAPI SafeArrayGetIID ( SAFEARRAY *    psa,
                                 GUID *   pGuid
                               )

{
   GUID* src = (GUID*)psa;

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

   if (!psa || !pGuid || !(psa->fFeatures & FADF_HAVEIID))
     return E_INVALIDARG;

   *pGuid = src[-1];
   return S_OK;
}


It first checks the FADF_HAVEIID flag to see if it has an IID and, if it has one, it returns that value, that is stored at a negative offset. Remember that when the memory for the safe array is allocated the value SAFEARRAY_HIDDEN_SIZE (currently 16 bytes, the size of a GUID) is added and the returned pointer to the safe array data doesn't point to the beginning of the allocated memory, but to ptr + SAFEARRAY_HIDDEN_SIZE).


char *ptr = SAFEARRAY_Malloc(ulSize + SAFEARRAY_HIDDEN_SIZE);
*ppsaOut = (SAFEARRAY*)(ptr + SAFEARRAY_HIDDEN_SIZE);

Title: Re: About Safe Arrays
Post by: José Roca on September 02, 2016, 05:07:25 AM
SafeArrayGetLBound gets the lower bound for any dimension of the specified safe array.


HRESULT WINAPI SafeArrayGetLBound ( SAFEARRAY * psa,
                                    UINT        nDim,
                                    LONG *      plLbound
                                  ) 
{
   TRACE("(%p,%d,%p)\n", psa, nDim, plLbound);

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

   if(!nDim || nDim > psa->cDims)
     return DISP_E_BADINDEX;

   *plLbound = psa->rgsabound[psa->cDims - nDim].lLbound;
   return S_OK;
}


SafeArrayGetUbound gets the upper bound for any dimension of the specified safe array.


HRESULT WINAPI SafeArrayGetUBound ( SAFEARRAY * psa,
                                    UINT        nDim,
                                    LONG *      plUbound
                                  )
{
   TRACE("(%p,%d,%p)\n", psa, nDim, plUbound);
     
   if (!psa || !plUbound)
     return E_INVALIDARG;

   if(!nDim || nDim > psa->cDims)
     return DISP_E_BADINDEX;

   *plUbound = psa->rgsabound[psa->cDims - nDim].lLbound +
               psa->rgsabound[psa->cDims - nDim].cElements - 1;

   return S_OK;
}

Title: Re: About Safe Arrays
Post by: José Roca on September 02, 2016, 05:32:07 AM
SafeArrayGetVartype gets the VARTYPE stored in the specified safe array.

If FADF_HAVEVARTYPE is set, SafeArrayGetVartype returns the VARTYPE stored in the array descriptor. If FADF_RECORD is set, it returns VT_RECORD; if FADF_DISPATCH is set, it returns VT_DISPATCH; and if FADF_UNKNOWN is set, it returns VT_UNKNOWN.



HRESULT WINAPI SafeArrayGetVartype ( SAFEARRAY * psa,
                                     VARTYPE *   pvt
                                   )
{
   TRACE("(%p,%p)\n", psa, pvt);

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

   if (psa->fFeatures & FADF_RECORD)
     *pvt = VT_RECORD;
   else if ((psa->fFeatures & (FADF_HAVEIID|FADF_DISPATCH)) == (FADF_HAVEIID|FADF_DISPATCH))
     *pvt = VT_DISPATCH;
   else if (psa->fFeatures & FADF_HAVEIID)
     *pvt = VT_UNKNOWN;
   else if (psa->fFeatures & FADF_HAVEVARTYPE)
   {
     VARTYPE vt = SAFEARRAY_GetHiddenDWORD(psa);
     *pvt = vt;
   }
   else
     return E_INVALIDARG;

   return S_OK;
}



static DWORD SAFEARRAY_GetHiddenDWORD  (  SAFEARRAY *    psa   )
{
    LPDWORD lpDw = (LPDWORD)psa;
    return lpDw[-1];
  }
[code]

In the above code we can see a problem:

[code]
   else if (psa->fFeatures & FADF_HAVEIID)
     *pvt = VT_UNKNOWN;


While the code for VT_DISPATCH


if ((psa->fFeatures & (FADF_HAVEIID|FADF_DISPATCH)) == (FADF_HAVEIID|FADF_DISPATCH))


checks for FADF_HAVEIID|FADF_DISPATCH, the one for VT_UNKNOWN doesn't check for


if ((psa->fFeatures & (FADF_HAVEIID|FADF_UNKNOWN)) == (FADF_HAVEIID|FADF_UNKNOWN))


but assumes that if it has an IID and it is not DISPATCH, it must be UNKNOWN.

Therefore, SafeArrayGetVartype can fail to return VT_UNKNOWN for SAFEARRAY types that are based on IUnknown. Callers should additionally check whether the SAFEARRAY type's fFeatures field has the FADF_UNKNOWN flag set.

But the safe array API doesn't provide a SafearrayGetFeatures function, so I will have to implement an additional method to the CSafeArrayClass to return it.
Title: Re: About Safe Arrays
Post by: José Roca on September 02, 2016, 05:38:20 AM
SafeArrayGetIID gets the GUID of the interface contained within the specified safe array and SafeArraySetIID sets the GUID of the interface for the specified safe array. It first checks if the safe array has the FADF_HAVEIID flag.


HRESULT WINAPI SafeArrayGetIID ( SAFEARRAY *    psa,
                                 GUID *         pGuid
                               )
{
   GUID* src = (GUID*)psa;

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

   if (!psa || !pGuid || !(psa->fFeatures & FADF_HAVEIID))
     return E_INVALIDARG;

   *pGuid = src[-1];
   return S_OK;
}



HRESULT WINAPI SafeArraySetIID ( SAFEARRAY * psa,
                                 REFGUID     guid
                                 )
{
   GUID* dest = (GUID*)psa;

   TRACE("(%p,%s)\n", psa, debugstr_guid(guid));

   if (!psa || !guid || !(psa->fFeatures & FADF_HAVEIID))
     return E_INVALIDARG;

   dest[-1] = *guid;
   return S_OK;
}

Title: Re: About Safe Arrays
Post by: José Roca on September 02, 2016, 05:43:54 AM
SafearrayGetRecordInfo retrieves the IRecordInfo interface of the UDT contained in the specified safe array and SafearraySetRecordInfo sets the record info in the specified safe array. It first checks if the safe array has the FADF_RECORD flag.


HRESULT WINAPI SafeArrayGetRecordInfo ( SAFEARRAY *    psa,
                                        IRecordInfo ** pRinfo
                                      )
{
   IRecordInfo** src = (IRecordInfo**)psa;

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

   if (!psa || !pRinfo || !(psa->fFeatures & FADF_RECORD))
     return E_INVALIDARG;

   *pRinfo = src[-1];

   if (*pRinfo)
     IRecordInfo_AddRef(*pRinfo);
   return S_OK;
}



HRESULT WINAPI SafeArraySetRecordInfo ( SAFEARRAY *    psa,
                                        IRecordInfo *  pRinfo
                                      )
{
   IRecordInfo** dest = (IRecordInfo**)psa;

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

   if (pRinfo)
     IRecordInfo_AddRef(pRinfo);

   if (dest[-1])
     IRecordInfo_Release(dest[-1]);

   dest[-1] = pRinfo;
   return S_OK;
}



Title: Re: About Safe Arrays
Post by: José Roca on September 02, 2016, 05:53:20 AM
SafeArrayAccessData increments the lock count of an array, and retrieves a pointer to the array data. This allows us to access directly to the data using pointers, which is the fatest way of doing it instead of calling SafeArrayGetElement, but we have the entire responsability on what we do.

SafeArrayUnaccessData simply unlocks the array.


HRESULT WINAPI SafeArrayAccessData ( SAFEARRAY * psa,
                                     void **     ppvData
                                    )
{
   HRESULT hr;

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

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

   hr = SafeArrayLock(psa);
   *ppvData = SUCCEEDED(hr) ? psa->pvData : NULL;

   return hr;
}



HRESULT WINAPI SafeArrayUnaccessData ( SAFEARRAY * psa )

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

Title: Re: About Safe Arrays
Post by: José Roca on September 02, 2016, 05:56:04 AM
SafeArrayGetElemsize returns the size of an element in a safe array, in bytes.


UINT WINAPI SafeArrayGetElemsize ( SAFEARRAY * psa )
{
  TRACE("(%p) returning %d\n", psa, psa ? psa->cbElements : 0u);
  return psa ? psa->cbElements : 0;
}

Title: Re: About Safe Arrays
Post by: José Roca on September 02, 2016, 07:19:32 AM
Finally, SafeArrayCreateVector and SafeArrayCreateVectorEx create a one-dimensional array.

However, there is something that I don't understand. The MSDN documentation states that a safe array created with SafeArrayCreateVector/Ex is a fixed size, so the constant FADF_FIXEDSIZE is always set, but in the source code I don't see anywhere that FADF_FIXEDSIZE is set. Furthemore, it is in the list of ignored flags:


const USHORT ignored_copy_features
   static
Initial value:
=
        FADF_AUTO |
        FADF_STATIC |
        FADF_EMBEDDED |
        FADF_FIXEDSIZE |
        FADF_CREATEVECTOR


So I wonder how can we ascertain if we are dealing with a one-dimensional array created with SafeArrayCreate or a vector (?).


SAFEARRAY* WINAPI SafeArrayCreateVector ( VARTYPE  vt,
                                          LONG     lLbound,
                                          ULONG    cElements
                                        )
{
   TRACE("(%d->%s,%d,%d\n", vt, debugstr_vt(vt), lLbound, cElements);
     
   if (vt == VT_RECORD)
     return NULL;

   return SAFEARRAY_CreateVector(vt, lLbound, cElements, SAFEARRAY_GetVTSize(vt));
}



static SAFEARRAY* SAFEARRAY_CreateVector ( VARTYPE     vt,
                                           LONG     lLbound,
                                           ULONG    cElements,
                                           ULONG    ulSize
                                         )

{
   SAFEARRAY *psa = NULL;

   if (ulSize || (vt == VT_RECORD))
   {
     /* Allocate the header and data together */
     if (SUCCEEDED(SAFEARRAY_AllocDescriptor(sizeof(SAFEARRAY) + ulSize * cElements, &psa)))
     {
       SAFEARRAY_SetFeatures(vt, psa);

       psa->cDims = 1;
       psa->fFeatures |= FADF_CREATEVECTOR;
       psa->pvData = &psa[1]; /* Data follows the header */
       psa->cbElements = ulSize;
       psa->rgsabound[0].cElements = cElements;
       psa->rgsabound[0].lLbound = lLbound;

       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;
       }
     }
   }
   return psa;
}


The code is very similar to the one used by SafeArrayCreate with the important exception that the pvData pointer is set one byte after the header.


psa->pvData = &psa[1]; /* Data follows the header */


SafeArrayCreateVectorEx adds an additional parameter, pvExtra, to set 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.


SAFEARRAY* WINAPI SafeArrayCreateVectorEx ( VARTYPE  vt,
                                            LONG     lLbound,
                                            ULONG    cElements,
                                            LPVOID   pvExtra
                                          )
{
   ULONG ulSize;
   IRecordInfo* iRecInfo = pvExtra;
   SAFEARRAY* psa;

  TRACE("(%d->%s,%d,%d,%p\n", vt, debugstr_vt(vt), lLbound, cElements, pvExtra);
 
   if (vt == VT_RECORD)
   {
     if  (!iRecInfo)
       return NULL;
     IRecordInfo_GetSize(iRecInfo, &ulSize);
   }
   else
     ulSize = SAFEARRAY_GetVTSize(vt);

   psa = SAFEARRAY_CreateVector(vt, lLbound, cElements, ulSize);

   if (pvExtra)
   {
     switch(vt)
     {
       case VT_RECORD:
         SafeArraySetRecordInfo(psa, iRecInfo);
         break;
       case VT_UNKNOWN:
       case VT_DISPATCH:
         SafeArraySetIID(psa, pvExtra);
         break;
     }
   }
   return psa;
}

Title: Re: About Safe Arrays
Post by: José Roca on September 02, 2016, 07:23:25 AM
To get the size of the element, SafeArrayCreateVectorEx calls the helper internan function SAFEARRAY_GetVTSize.


static DWORD SAFEARRAY_GetVTSize ( VARTYPE vt )
{
   switch (vt)
   {
     case VT_I1:
     case VT_UI1:      return sizeof(BYTE);
     case VT_BOOL:
     case VT_I2:
     case VT_UI2:      return sizeof(SHORT);
     case VT_I4:
     case VT_UI4:
     case VT_R4:
     case VT_ERROR:    return sizeof(LONG);
     case VT_R8:
     case VT_I8:
     case VT_UI8:      return sizeof(LONG64);
     case VT_INT:
     case VT_UINT:     return sizeof(INT);
     case VT_INT_PTR:
     case VT_UINT_PTR: return sizeof(UINT_PTR);
     case VT_CY:       return sizeof(CY);
     case VT_DATE:     return sizeof(DATE);
     case VT_BSTR:     return sizeof(BSTR);
     case VT_DISPATCH: return sizeof(LPDISPATCH);
     case VT_VARIANT:  return sizeof(VARIANT);
     case VT_UNKNOWN:  return sizeof(LPUNKNOWN);
     case VT_DECIMAL:  return sizeof(DECIMAL);
     /* Note: Return a non-zero size to indicate vt is valid. The actual size
      * of a UDT is taken from the result of IRecordInfo_GetSize().
      */
     case VT_RECORD:   return 32;
   }
   return 0;
}

Title: Re: About Safe Arrays
Post by: José Roca on September 02, 2016, 07:28:35 AM
There are two additional API functions, VectorFromBstr and BstrFromVector.

VectorFromBstr returns a vector, assigning each character in the BSTR to an element of the vector.

BstrFromVector returns a BSTR, assigning each element of the vector to a character in the BSTR.


HRESULT WINAPI VectorFromBstr ( BSTR     bstr,
                                SAFEARRAY **   ppsa
                              )
{
   SAFEARRAYBOUND sab;

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

   sab.lLbound = 0;
   sab.cElements = SysStringByteLen(bstr);

   *ppsa = SAFEARRAY_Create(VT_UI1, 1, &sab, 0);

   if (*ppsa)
   {
     memcpy((*ppsa)->pvData, bstr, sab.cElements);
     return S_OK;
   }
   return E_OUTOFMEMORY;
}



HRESULT WINAPI BstrFromVector ( SAFEARRAY * psa,
BSTR * pbstr
)

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

   if (!pbstr)
   return E_INVALIDARG;

   *pbstr = NULL;

   if (!psa || psa->cbElements != 1 || psa->cDims != 1)
   return E_INVALIDARG;

   *pbstr = SysAllocStringByteLen(psa->pvData, psa->rgsabound[0].cElements);
   if (!*pbstr)
   return E_OUTOFMEMORY;
   return S_OK;
}


Again, I don't see the FADF_FIXEDSIZE flags anywhere. What VectorFromBstr is doing is to create a safe array of unsigned bytes.
Title: Re: About Safe Arrays
Post by: José Roca on September 02, 2016, 07:31:16 AM
The anlysis is completed. Now that I know how the safe array API works internally I will see what I can do to improve the CSafeArray wrapper class.
Title: Re: About Safe Arrays
Post by: José Roca on September 02, 2016, 10:46:20 AM
For the problem of memory leaks with CBSTR and CVARIANT, the solution is to implement overloaded GetElement methods.


' =====================================================================================
' ** BYREF CBSTR ***
' =====================================================================================
' // Multidimensional array
' // prgIndices: first the element, then the dimension
PRIVATE FUNCTION CSafeArray.GetElement (BYVAL prgIndices AS LONG PTR, BYREF cbsData AS CBSTR) AS HRESULT
   CSAFEARRAY_DP("CSafeArray GetElement - multi - CBSTR " & WSTR(cbsData.m_bstr))
   IF m_psa = NULL THEN RETURN E_FAIL
   IF cbsData.m_bstr THEN
      SysFreeString cbsData.m_bstr
      cbsData.m_bstr = NULL
   END IF
   RETURN SafeArrayGetElement(m_psa, prgIndices, @cbsData.m_bstr)
END FUNCTION
' =====================================================================================
' =====================================================================================
' // One-dimensional array
PRIVATE FUNCTION CSafeArray.GetElement (BYVAL idx AS LONG, BYREF cbsData AS CBSTR) AS HRESULT
   CSAFEARRAY_DP("CSafeArray GetElement - 1D - CBSTR " & WSTR(cbsData.m_bstr))
   IF m_psa = NULL THEN RETURN E_FAIL
   IF cbsData.m_bstr THEN
      SysFreeString cbsData.m_bstr
      cbsData.m_bstr = NULL
   END IF
   RETURN SafeArrayGetElement(m_psa, @idx, @cbsData.m_bstr)
END FUNCTION
' =====================================================================================
' =====================================================================================
' // Two-dimensional array
' // First element, then dimension, e.g. 2, 1 (element 2, first dimension), 1, 2 (element 1, 2nd dimension).
PRIVATE FUNCTION CSafeArray.GetElement (BYVAL cElem AS LONG, BYVAL cDim AS LONG, BYREF cbsData AS CBSTR) AS HRESULT
   CSAFEARRAY_DP("CSafeArray GetElement - 2D - CBSTR " & WSTR(cbsData.m_bstr))
   IF m_psa = NULL THEN RETURN E_FAIL
   IF cbsData.m_bstr THEN
      SysFreeString cbsData.m_bstr
      cbsData.m_bstr = NULL
   END IF
   DIM rgIdx(1) AS LONG = {cElem, cDim}
   RETURN SafeArrayGetElement(m_psa, @rgIdx(0), @cbsData.m_bstr)
END FUNCTION
' =====================================================================================


Usage example:


csa.GetElement(1, 1, cbsOut)


Knowing that it is a CBSTR, the function can free the underlying BSTR pointer before passing it to SafeArrayGetElement.

It is needed because SafeArrayGetElement doesn't free the passed BSTR pointer, but overwrites it.


*lpDest = SysAllocStringByteLen((char*)*lpBstr, SysStringByteLen(*lpBstr));


For variants isen't needed because it calls VatiantCopy and this function first clears the destination variant by calling VariantClear.


hRet = VariantCopy(lpDest, lpVariant);


But I will implement overloaded functions for consistency. Otherwise we will have to pass CBSTRs as cbsOut and CVARIANTs as @cvOut.

The scalar types don't need additional overloaded functions.

Title: Re: About Safe Arrays
Post by: José Roca on September 02, 2016, 11:48:54 AM
After implementing the additional overloaded PutElement functions, we can use:


DIM cv AS CVARIANT = "Test string 1"
csa.PutElement(1, 1, cv)
cv = 12345
csa.PutElement(2, 1, cv)


and we can also use


csa.PutElement(1, 1, CVARIANT("Test string"))
csa.PutElement(2, 1, CVARIANT(12345))


Similarly, if the safe array is an array of CBSTRs, we can use:


csa.PutElement(1, 1, CBSTR("Test string 1"))
csa.PutElement(2, 1, CBSTR("Test string 2"))


Will play with more overloaded functions until I get it both safe and easy to use.
Title: Re: About Safe Arrays
Post by: José Roca on September 03, 2016, 06:16:34 AM
Thanks to the analysis and the revision of the CSafeArray code I have discovered a couple of bugs.

I'm going to implement methods like Append, Insert and Delete, and a Sort method if the safe array is a one-dimensional VT_BSTR safe array. This will make the CBSTRA type obsolete.


' ========================================================================================
' Appends a BSTR to the end of the one-dimensional VT_BSTR safe array.
' If the safe array is not a one-dimensional VT_BSTR array or it is not resizable it will
' return E_FAIL.
' Return value:
'   S_OK Success.
'   DISP_E_BADINDEX The specified index is not valid.
'   E_INVALIDARG One of the arguments is not valid.
'   E_OUTOFMEMORY Memory could not be allocated for the element.
'   E_FAIL The item pointed to by m_psa is not a safe array descriptor.
'          It is a fixed-size array.
'          It is not a one-dimensional array.
' ========================================================================================
PRIVATE FUNCTION CSafeArray.Append (BYREF cbsData AS CBSTR) AS HRESULT
   CSAFEARRAY_DP("CSafeArray Append - CBSTR")
   IF m_psa = NULL THEN RETURN E_FAIL
   DIM nDims AS UINT = SafeArrayGetDim(m_psa)
   IF nDims <> 1 THEN RETURN E_FAIL
   IF this.IsResizable = FALSE THEN RETURN E_FAIL
   DIM vt AS VARTYPE = this.GetType
   IF vt <> VT_BSTR THEN RETURN E_FAIL
   DIM cElements AS DWORD = this.Count(1) + 1
   DIM lLBound AS LONG = this.LBound(1)
   DIM sanewbounds(0) AS SAFEARRAYBOUND = {(cElements, lLBound)}
   DIM hr AS HRESULT = SafeArrayRedim(m_psa, @sanewbounds(0))
   IF hr THEN RETURN hr
   DIM idx AS LONG = cElements - 1 + lLBound
   RETURN SafeArrayPutElement(m_psa, @idx, *cbsData)
END FUNCTION
' ========================================================================================


Usage example:


' // Create a one-dimensional array of BSTR
DIM csa AS CSafeArray = CSafeArray(VT_BSTR, 2, 1)

csa.PutElement(1, CBSTR("Test string 1"))
csa.PutElement(2, CBSTR("Test string 2"))
csa.Append(CBSTR("Test string appended"))

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