COM VARIANT
类型是使用tagVARIANT
结构定义的,如下所示:
typedef struct tagVARIANT {
union {
struct {
VARTYPE vt;
WORD wReserved1;
WORD wReserved2;
WORD wReserved3;
union {
LONGLONG llVal;
LONG lVal;
BYTE bVal;
SHORT iVal;
FLOAT fltVal;
DOUBLE dblVal;
VARIANT_BOOL boolVal;
VARIANT_BOOL __OBSOLETE__VARIANT_BOOL;
SCODE scode;
CY cyVal;
DATE date;
BSTR bstrVal;
IUnknown *punkVal;
IDispatch *pdispVal;
SAFEARRAY *parray;
BYTE *pbVal;
SHORT *piVal;
LONG *plVal;
LONGLONG *pllVal;
FLOAT *pfltVal;
DOUBLE *pdblVal;
VARIANT_BOOL *pboolVal;
VARIANT_BOOL *__OBSOLETE__VARIANT_PBOOL;
SCODE *pscode;
CY *pcyVal;
DATE *pdate;
BSTR *pbstrVal;
IUnknown **ppunkVal;
IDispatch **ppdispVal;
SAFEARRAY **pparray;
VARIANT *pvarVal;
PVOID byref;
CHAR cVal;
USHORT uiVal;
ULONG ulVal;
ULONGLONG ullVal;
INT intVal;
UINT uintVal;
DECIMAL *pdecVal;
CHAR *pcVal;
USHORT *puiVal;
ULONG *pulVal;
ULONGLONG *pullVal;
INT *pintVal;
UINT *puintVal;
struct {
PVOID pvRecord;
IRecordInfo *pRecInfo;
} __VARIANT_NAME_4;
} __VARIANT_NAME_3;
} __VARIANT_NAME_2;
DECIMAL decVal;
} __VARIANT_NAME_1;
} VARIANT;
通常,当调用者要使用Variant中的数据时,它使用VARTYPE vt
标志来查看存储的数据类型,以及最终应如何解释这些1和0。
[[0]存储在变量中时会发生什么;定义位于包含DECIMAL
的struct
之外,那么调用方如何确定是否存在有效的类型标志或仅是十进制的一些字节?小数需要12个字节来存储,而Variant可以容纳16个字节,因此可能利用了此信息,但是不是存储在联合体较小成员的未定义行为的剩余4个字节中的内容是什么?
这是一个有趣的问题。可悲的是,我还没有找到任何关于此的坚定文件。我可以从一些思考和实验中得出一些推论。
尽管标头中有官方文档和类型定义,但存储在VARIANT中的DECIMAL似乎确实将DECIMAL vt
成员的字节用于重叠的wReserved
VARIANT成员。因此,通过查看vt
成员,可以以与任何其他VARIANT类型相同的方式标识VARIANT中的DECIMAL。
我提供了两个经验证明。
1)我编译了一个VB6程序以将DECIMAL存储在VARIANT中(本机代码,无优化,生成符号调试信息)。然后,我使用了旧版本的WinDbg来检查变量的位(当前版本的WinDbg与VB6的较早PDB格式不兼容-我想我可以尝试使用VC6代替它,但并未考虑)。
vt
使用WinDbg检查v,我获得了Dim v As Variant
v = CDec(24)
变量的以下布局:
v
[好吧,VB6在奇怪的地方还没有作弊,而且微软总是不会将Decimal公开为完整类型(出于某些原因,您不能在VB6中声明Decimal类型的变量;它必须存储在变体。0e 00 00 00 00 00 00 00 18 00 00 00 00 00 00 00
----- ----- ----------- -----------------------
| | | |
| | | Lo64
| | Hi32
| signscale
wReserved
(but note it's the same as v.vt == VT_DECIMAL)
的文档听起来像他们打算支持Decimal并出于某种原因不得不将其拔出)。因此,这可能只是VB6作弊。但是:
2)我测试了一下,如果我要求COM API将DECIMAL放入VARIANT中,它将执行什么操作。对于踢球,我使用VC6 ++对此进行了测试:
Dim
我确认VARIANT s;
VARIANT t;
VariantInit(&s);
VariantInit(&t);
V_VT(&s) = VT_I4;
V_I4(&s) = 24;
HRESULT hr = VariantChangeType(&t, &s, 0, VT_DECIMAL);
为hr
。如果在VARIANT中将DECIMAL按值存储在形式上是非法的,那么我会预期会出现错误HRESULT。相反,布局与我的VB6经验相匹配:
S_OK
的值报告为t
{24 VT_DECIMAL}
成员设置为14(这是VT_DECIMAL)t.vt
成员被列为wReserved == 14; Lo64 == 24; Hi32 == 0因此,尽管VARIANT的标头声明暗示了什么,但vt成员可以并且应该用于确定VARIANT何时包含DECIMAL。实际上,如果您从未详细检查过VARIANT声明,就不会知道DECIMAL会被区别对待。
我剩下的问题是“为什么不让DECIMAL像其他人一样适合工会?”]
如果不知道VARIANT和DECIMAL的完整历史,可能很难给出完整的答案;但是密钥可能不在t.decVal
中,而是在vt
,wReserved1
和wReserved2
中。
DECIMAL似乎是VARIANT的更高版本。克雷格·布罗克施密特(Kraig Brockschmidt)的经典著作“ Inside Ole”(第二版,1995年发行)给出了VARIANT的声明,但没有提及DECIMAL作为其中一种选择。这意味着之后在某些时候添加了DECIMAL作为VARIANT选项。不迟于Visual C ++ 6(1998),DECIMAL已经可以作为VARIANT类型使用。
但是DECIMAL的有趣部分(14字节)太大,无法容纳先前存在的VARIANT联合。 DECIMAL需要使用三个wReserved3
字段占用的字节(可能最初打算用作填充)。我敢肯定,Microsoft不可能在不更改内存布局和破坏旧二进制文件的情况下重新定义VARIANT联合,以使保留字段可用于联合和DECIMAL。也许wReservedX
最初是放在wReservedX字段之前的,但这不是这样做的。
因此,一个理论是Microsoft需要将此新的14字节长类型添加到VARIANT中,这可能无法容纳可用于联合的8字节。根据这种理论,当前的VARIANT布局将是在二进制级别潜行DECIMAL而不破坏VARIANT的原始声明的一种方法。编译时,DECIMAL只是“联盟”的另一个成员,除了它可以溢出到保留字的空间中。
可能还有另一个怪癖。汉斯·帕桑(Hans Passant)在上面的评论中提到,保留字段用于包含货币类型信息。听起来很可行,但我无法证实这一点,因为我没有找到有关DECIMAL的较早使用的任何信息。假设这是真的,Microsoft将会受到先前DECIMAL类型的布局的限制(即不可能考虑牺牲范围以使其适合常规成员)。另外,他们将不得不决定可以放弃“货币类型”信息,以换取在变量中进行DECIMAL工作(或者他们可能早些时候已经放弃了货币类型信息,或出于其他原因)。我不能不知道在将DECIMAL添加为VARIANT类型之前如何使用DECIMAL的更多信息。