Where is SAFEARRAY var type stored?

2k views Asked by At

I'd like to know about SAFEARRAY implementation.

It seems to me that there's no field in SAFEARRAY structure that is used for storing element type information, such as VT_I4(3) or VT_R4(4), but SafeArrayGetVartype function returns the correct type.

Somebody commented on the MSDN page below saying that the high word of the cLocks holds the type info: SAFEARRAY structure on MSDN

But when I passed Long and Single arrays from VBA to a DLL function via a type libray, those arrays' fFeatures are both 0x80, cLocks are both 0, and stll SafeArrayGetVartype can tell VT_I4(3) and VT_R4(4).

4

There are 4 answers

1
Igor Tandetnik On BEST ANSWER

Depending on how the safearray was created, the variant type may be stored in memory before (at the offset -4 from the start of) the SAFEARRAY structure. FADF_HAVEVARTYPE flag in fFeatures indicates whether the type is available.

Similarly, FADF_HAVEIID indicates that the GUID (see SafeArrayCreateEx) is stored at offset of -16, and available via SafeArrayGetIID. FADF_HAVEVARTYPE and FADF_HAVEIID can never be present simultaneously (because otherwise the VARTYPE and the GUID would overlap in memory), but SafeArrayGetVartype is smart enough to synthesize one of VT_RECORD, VT_DISPATCH or VT_UNKNOWN types when it sees corresponding feature flags.

1
Ian Boyd On

Short Version

varType = SafeArrayGetVarType(mySafeArray);

Long Version

The SAFEARRAY has a features member that helps describing what's in the array

2.2.30.10 SAFEARRAY (archive)

  • fFeatures: MUST be set to a combination of the bit flags specified in section 2.2.9.

And then you consult:

2.2.9 ADVFEATUREFLAGS Advanced Feature Flags (archive)

The following values are used in the field fFeatures of a SAFEARRAY (section 2.2.30.10) data type.

typedef  enum tagADVFEATUREFLAGS
 {
   FADF_AUTO = 0x0001,
   FADF_STATIC = 0x0002,
   FADF_EMBEDDED = 0x0004,
   FADF_FIXEDSIZE = 0x0010,
   FADF_RECORD = 0x0020,
   FADF_HAVEIID = 0x0040,
   FADF_HAVEVARTYPE = 0x0080,
   FADF_BSTR = 0x0100,
   FADF_UNKNOWN = 0x0200,
   FADF_DISPATCH = 0x0400,
   FADF_VARIANT = 0x0800
 } ADVFEATUREFLAGS;
  • FADF_RECORD: The SAFEARRAY MUST contain elements of a UDT (see section 2.2.28.1)
  • FADF_HAVEIID: The SAFEARRAY MUST contain MInterfacePointers elements.
  • FADF_HAVEVARTYPE: If this bit flag is set, the high word of the cLocks field of the SAFEARRAY MUST contain a VARIANT type constant that describes the type of the array's elements (see sections 2.2.7 and 2.2.30.10).
  • FADF_BSTR: The SAFEARRAY MUST contain an array of BSTR elements (see section 2.2.23).
  • FADF_UNKNOWN: The SAFEARRAY MUST contain an array of pointers to IUnknown.
  • FADF_DISPATCH: The SAFEARRAY MUST contain an array of pointers to IDispatch (see section 3.1.4).
  • FADF_VARIANT: The SAFEARRAY MUST contain an array of VARIANT instances.

So depending on the FADF, you can come up with a corresponding Variant Type:

Feature Flag Corresponding Variant Type
FADF_UNKNOWN VT_UNKNOWN
FADF_DISPATCH VT_DISPATCH
FADF_VARIANT VT_VARIANT
FADF_BSTR VT_BSTR
FADF_HAVEVARTYPE SafeArrayGetVarType(mySafeArray)

It turns out all that above work (matching FADF_BSTR to VT_BSTR etc) is all wrapped up by the helper function SafeArrayGetVarType (archive):

  • 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.

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.

0
M. Johnstone On

To further expand on where is SAFEARRAY var type stored and explore what exactly the SafeArrayGetVartype function and other SafeArray functions are doing see:

SafeArrayGetVartype

/* Memory Layout of a SafeArray:
 *
 * -0x10: start of memory.
 * -0x10: GUID for VT_DISPATCH and VT_UNKNOWN safearrays (if FADF_HAVEIID)
 * -0x04: DWORD varianttype; (for all others, except VT_RECORD) (if FADF_HAVEVARTYPE)
 *  -0x4: IRecordInfo* iface;  (if FADF_RECORD, for VT_RECORD (can be NULL))
 *  0x00: SAFEARRAY,
 *  0x10: SAFEARRAYBOUNDS[0...]
 */

For the following integer array declaration using MS Access VBA 7 where address references are 8 bytes in size.

Dim myArray() As Integer
ReDim myArray(9)

Memory dump for the SafeArray structure including preceding hidden VT data using MS Access VBA 7

Pos  Address Dec    Address Hex     Hex    
0    (1185248224)   (46A573E0) >>    0h     
1    (1185248225)   (46A573E1) >>    0h     
2    (1185248226)   (46A573E2) >>    0h     
3    (1185248227)   (46A573E3) >>    0h     
4    (1185248228)   (46A573E4) >>    0h     
5    (1185248229)   (46A573E5) >>    0h     
6    (1185248230)   (46A573E6) >>    0h     
7    (1185248231)   (46A573E7) >>    0h     
8    (1185248232)   (46A573E8) >>    0h     
9    (1185248233)   (46A573E9) >>    0h     
10   (1185248234)   (46A573EA) >>    0h     
11   (1185248235)   (46A573EB) >>    0h
-------------------------------------------------------------------------------------
Hidden DWord for VT when FADF_HAVEVARTYPE = 0x0080
VT = 2 i.e. Integer    
12   (1185248236)   (46A573EC) >>    2h     
13   (1185248237)   (46A573ED) >>    0h     
14   (1185248238)   (46A573EE) >>    0h     
15   (1185248239)   (46A573EF) >>    0h
-------------------------------------------------------------------------------------
cDims = 1 i.e. One dimensional array   
16   (1185248240)   (46A573F0) >>    1h     
17   (1185248241)   (46A573F1) >>    0h
-------------------------------------------------------------------------------------
fFeatures = FADF_HAVEVARTYPE  
18   (1185248242)   (46A573F2) >>    80h    
19   (1185248243)   (46A573F3) >>    0h
-------------------------------------------------------------------------------------
cbElements = 2 i.e. element size is 2 bytes
20   (1185248244)   (46A573F4) >>    2h     
21   (1185248245)   (46A573F5) >>    0h     
22   (1185248246)   (46A573F6) >>    0h     
23   (1185248247)   (46A573F7) >>    0h
-------------------------------------------------------------------------------------
cLocks     
24   (1185248248)   (46A573F8) >>    0h     
25   (1185248249)   (46A573F9) >>    0h     
26   (1185248250)   (46A573FA) >>    0h     
27   (1185248251)   (46A573FB) >>    0h
-------------------------------------------------------------------------------------
Padding     
28   (1185248252)   (46A573FC) >>    0h     
29   (1185248253)   (46A573FD) >>    0h     
30   (1185248254)   (46A573FE) >>    0h     
31   (1185248255)   (46A573FF) >>    0h
-------------------------------------------------------------------------------------
pvData     
32   (1185248256)   (46A57400) >>    0h    
33   (1185248257)   (46A57401) >>    97h    
34   (1185248258)   (46A57402) >>    DCh    
35   (1185248259)   (46A57403) >>    1Bh    
36   (1185248260)   (46A57404) >>    0h     
37   (1185248261)   (46A57405) >>    0h     
38   (1185248262)   (46A57406) >>    0h     
39   (1185248263)   (46A57407) >>    0h
-------------------------------------------------------------------------------------
rgsabound(0).cElements = 10
40   (1185248264)   (46A57408) >>    Ah     
41   (1185248265)   (46A57409) >>    0h     
42   (1185248266)   (46A5740A) >>    0h     
43   (1185248267)   (46A5740B) >>    0h
-------------------------------------------------------------------------------------
rgsabound(0).lLbound  
44   (1185248268)   (46A5740C) >>    0h     
45   (1185248269)   (46A5740D) >>    0h     
46   (1185248270)   (46A5740E) >>    0h     
47   (1185248271)   (46A5740F) >>    0h 

Note for VBA 7 64 Bit shows the offset for SAFEARRAYBOUNDS[0...] at +24 Dec, +18 hex due to the 8-byte memory address for pvData and padding.

I hope that helps further explain what exactly any of the SafeArray functions are performing and wherein the SafeArray structure items are located. If anyone is manually manipulating SafeArray structures be careful of any padding and getting your offsets correct.

I'll also try and get around to your query regarding VT_I4(3) or VT_R4(4).

There's definitely something extra going on in VBA then what the SafeArray.c functions are performing as from testing using SafeArrayDescriptorEx to create an initialized empty Integer Array the cbElements weren't set, the preceding VT was set. Strangely the VBA integer Array still worked without the cbElements set to 2 bytes for an Integer Array. When creating the SafeArrayDescriptor I now set the cbElements, manually.

When created using the above VBA example the cbElements were set appropriately.

In the SafeArray.c functions they aren't returning a SizeOf(VT_I4) or SizeOf(VT_R4) i.e. not supported in C, so I assume in VBA must expanding on the SafeArray.c functions and catering for its data types not covered in C.

Someone more with more knowledge of C might be able to clarify or explain better.

1
noseratio On

You never initialize SAFEARRAY manually, it's always a product of calling SafeArrayCreate, which allocates memory for this structure. I think it'd be safe to make an assumption that there are some extra bytes allocated for SafeArray's internal data structure. That's where any extended type info can be stored.