调用者如何知道VARIANT中的小数? [英] How does the caller know when there's a Decimal inside a VARIANT?

查看:86
本文介绍了调用者如何知道VARIANT中的小数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

COM VARIANT 类型是使用 tagVARIANT 结构定义的,如下所示:

  typedef struct tagVARIANT {工会{结构{VARTYPE vt;字wReserved1;字wReserved2;字wReserved3;工会{隆龙llVal;长lVal;BYTE bVal;简短内容iVal;FLOAT fltVal;双重dblVal;VARIANT_BOOL boolVal;VARIANT_BOOL __OBSOLETE__VARIANT_BOOL;SCODE scode;CY cyVal;DATE日期;BSTR bstrVal;IUnknown * punkVal;IDispatch * pdispVal;SAFEARRAY * parray;BYTE * pbVal;简短内容* piVal;长* plVal;隆隆* pllVal;FLOAT * pfltVal;双* pdblVal;VARIANT_BOOL * pboolVal;VARIANT_BOOL * __ OBSOLETE__VARIANT_PBOOL;SCODE * pscode;CY * pcyVal;DATE * pdate;BSTR * pbstrVal;IUnknown ** ppunkVal;IDispatch ** ppdispVal;SAFEARRAY ** pparray;变量* pvarVal;PVOID byref;CHAR cVal;USHORT uiVal;ULONG ulVal;ULONGLONG ullVal;INT intVal;UINT uintVal;十进制* pdecVal;CHAR * pcVal;USHORT * puiVal;ULONG * pulVal;ULONGLONG * pullVal;INT * pintVal;UINT * puintVal;结构{PVOID pvRecord;IRecordInfo * pRecInfo;} __VARIANT_NAME_4;} __VARIANT_NAME_3;} __VARIANT_NAME_2;十进制decVal;} __VARIANT_NAME_1;} VARIANT; 

通常,当调用方想使用Variant中的数据时,它使用 VARTYPE vt 标志查看存储的数据类型,以及最终应如何解释这些1和0./p>

DECIMAL 存储在变量中时会发生什么;定义位于包含 vt struct 之外,因此调用方如何确定是否存在有效的类型标志或仅是Decimal的某些字节?十进制需要 12 解决方案

这是一个有趣的问题.可悲的是,我还没有找到任何关于此的坚定文件.我可以从一些思考和实验中得出一些推论.

尽管标头中有官方文档和类型定义,但存储在VARIANT中的DECIMAL确实使用DECIMAL wReserved 成员的字节来重叠 vt VARIANT成员.因此,通过查看 vt 成员,可以以与其他任何VARIANT类型相同的方式识别VARIANT中的DECIMAL.

我提供了两个经验证明.

1)我编译了一个VB6程序以将DECIMAL存储在VARIANT中(本机代码,无优化,生成符号调试信息).然后,我使用了旧版本的WinDbg来检查变量的位(当前版本的WinDbg与VB6的旧PDB格式不兼容-我想我可以尝试使用VC6代替它,但没有考虑过).

  Dim v As Variantv = CDec(24) 

使用WinDbg检查v,我为 v 变量获得了以下布局:

  0e 00 00 00 00 00 00 00 18 00 00 00 00 00 00 00----- ----- ----------- -----------------------|||||||低64||高32|标尺wReserved(但请注意,它与v.vt == VT_DECIMAL相同) 

好吧,VB6在奇怪的地方还没有欺骗,微软总是不会将Decimal公开为完整类型,这似乎总是很奇怪(由于某些原因,您不能在VB6中声明Decimal类型的变量;它必须存储在 Dim 的文档听起来像他们打算支持Decimal并出于某种原因不得不将其拔出).因此,这可能只是VB6作弊.但是:

2)我测试了一下,如果我要求COM API将DECIMAL放入VARIANT中,它将执行什么操作.对于踢球,我使用VC6 ++对此进行了测试:

  VARIANT s;变量t;VariantInit(& s);VariantInit(&t);V_VT(& s)= VT_I4;V_I4(& s)= 24;HRESULT hr = VariantChangeType(& t,& s,0,VT_DECIMAL); 

我确认 hr S_OK .如果在VARIANT中将DECIMAL按值存储在形式上是非法的,那么我会预期会出现错误HRESULT.相反,该布局与我对VB6的经验相符:

因此,尽管VARIANT的标头声明暗示了什么,但vt成员可以并且应该用于确定VARIANT何时包含DECIMAL.实际上,如果您从未详细检查过VARIANT声明,就不会知道DECIMAL会被区别对待.


我剩下的问题是为什么不让DECIMAL像其他人一样适合工会呢?"

如果不知道VARIANT和DECIMAL的完整历史,可能很难给出完整的答案;但是密钥可能不在 vt 中,而是在 wReserved1 wReserved2 wReserved3 中.

DECIMAL似乎是VARIANT的更高版本.克雷格·布罗克施密特(Kraig Brockschmidt)的经典著作"Inside Ole"(第二版,日期为1995年)给出了VARIANT的声明,但没有提及DECIMAL作为其中一种选择.这意味着后来在某些时候添加了DECIMAL作为VARIANT选项.不迟于Visual C ++ 6(1998),DECIMAL已经可以作为VARIANT类型使用.

但是DECIMAL的有趣部分(14个字节)太大,无法容纳先前存在的VARIANT联合.DECIMAL需要使用三个 wReservedX 字段占用的字节(可能最初打算用作填充).我敢肯定,Microsoft不可能在不更改内存布局和破坏旧二进制文件的情况下重新定义VARIANT联合,以使保留字段可用于联合和DECIMAL.

所以有一种理论认为,Microsoft需要将此新的14字节长类型添加到VARIANT中,这可能无法容纳可用于联合的8字节.根据这种理论,当前的VARIANT布局将是在二进制级别潜行DECIMAL而不破坏VARIANT的原始声明的一种方法.编译后,DECIMAL只是联盟"的另一个成员,除了它会溢出到保留字的空间中.

可能还有另一个怪癖.汉斯·帕桑特(Hans Passant)在上面的评论中提到,保留字段用于包含货币类型信息.听起来很可行,但我无法证实这一点,因为我没有找到有关DECIMAL的较早使用的任何信息.假设这是真的,Microsoft会被限制在预先存在的DECIMAL类型的布局上(即不可能考虑牺牲范围以使其适合常规成员).此外,他们将不得不决定可以放弃货币类型"信息,以换取在变量中进行DECIMAL工作(或者他们可能早些时候已经放弃了货币类型信息,或出于其他原因).我不能不知道在将DECIMAL作为VARIANT类型添加之前如何使用的更多信息.

The COM VARIANT type is defined using the tagVARIANT structure like this:

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;

Normally when the caller wants to use the data inside a Variant, it uses the VARTYPE vt flag to see what kind of data is stored, and ultimately how those 1s and 0s should be interpreted.

What happens then when a DECIMAL is stored in the Variant; the definition lies outside the struct containing vt, so how does the caller determine whether there's a valid type flag or just some bytes of the Decimal? The Decimal takes 12* 14 bytes to store and the Variant can hold 16, so possibly this information is leveraged, but isn't what's stored in the spare 2 bytes of the smaller member of a union undefined behaviour?

解决方案

This is an intriguing question. Sadly I haven't been able to find any firm documentation about this. I can make some inferences from a bit of thinking and experimentation.

Notwithstanding the official documentation and type definitions in headers -- a DECIMAL stored in a VARIANT does appear to use the bytes of the DECIMAL wReserved member for the overlapping vt VARIANT member. Therefore, a DECIMAL in a VARIANT is identified the same way as any other VARIANT type by looking at the vt member.

I present two empirical proofs.

1) I compiled a VB6 program to store a DECIMAL in a VARIANT (Native Code, No Optimizations, Generate Symbolic Debug Info). Then I used an old version of WinDbg to inspect the bits of the variable (the current versions of WinDbg are not compatible with VB6's older PDB format - I guess I could have tried using VC6 for this instead but didn't think about it).

Dim v As Variant
v = CDec(24)

Inspecting v with WinDbg, I obtained the following layout for the v variable:

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)       

Ok, VB6 is not above cheating in weird places, and it always seems strange that Microsoft would not expose Decimal as a full type (for some reason you cannot declare a variable of type Decimal in VB6; it has to be stored in a Variant. The documentation for Dim makes it sound like they intended to support Decimal and had to pull it out for some reason). So it's possible this is just a VB6 cheat. However:

2) I tested to see what the COM API would do if I asked it to put a DECIMAL in a VARIANT. For kicks, I used VC6++ to test this:

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);

I confirmed that hr was S_OK. If it was formally illegal to store a DECIMAL by value in a VARIANT, I would have expected an error HRESULT. Instead, the layout matched my experience with VB6:

  • The watch window reported the value of t as {24 VT_DECIMAL}
  • The t.vt member was set to 14 (which is VT_DECIMAL)
  • The t.decVal member was listed as wReserved == 14; Lo64 == 24; Hi32 == 0

Therefore, despite what the header declaration of VARIANT implies, the vt member can and should be used to determine when a VARIANT contains a DECIMAL. In fact, if you never inspected the declaration of VARIANT in detail you would never know that DECIMAL is treated differently.


The question I am left with is "why not just make DECIMAL fit it in the union like everybody else?".

It might be hard to produce the full answer without knowing the complete history of VARIANT and DECIMAL; but the key is probably not in vt but in wReserved1, wReserved2 and wReserved3.

DECIMAL appears to be a later addition to VARIANT. Kraig Brockschmidt's classic book "Inside Ole" (2nd Edition, dated 1995) gives the declaration of VARIANT but does not mention DECIMAL as one of the options. That means that DECIMAL as a VARIANT option was added at some point afterward. No later than Visual C++ 6 (1998), DECIMAL was already available as a VARIANT type.

But the interesting parts of DECIMAL (14 bytes) are too large to fit in the preexisting VARIANT union. DECIMAL needs to use the bytes taken by the three wReservedX fields (likely originally intended as padding). I'm pretty sure there is no way Microsoft could have redefined the VARIANT union to make the Reserved fields available to the union and to DECIMAL without changing the memory layout and breaking old binaries.

So one theory is that Microsoft needed to add this new 14-byte long type to VARIANT, which couldn't possibly fit on the 8 bytes available to the union. Under this theory, the current layout of VARIANT would be a way to sneak in DECIMAL at the binary level without breaking the original declaration of VARIANT. When compiled, DECIMAL would just be another member of the "union" except that it can overflow into the space of the reserved WORDs.

There might be another quirk. Hans Passant mentions in a comment above that the reserved fields used to contain currency type information. It sounds very feasible but I can't corroborate it because I haven't found any information about older uses of DECIMAL. Assuming that is true, Microsoft would have been constrained on the layout of the preexisting DECIMAL type (i.e. it was impossible to consider sacrificing range to make it fit as a conventional member). Additionally, they would have had to decide they could dispense with the "currency type" information in exchange for making DECIMAL work in VARIANTs (or they might have already discarded the currency type information earlier, or for a different reason). I can't tell without more information about how DECIMAL was used before they were added as a VARIANT type.

这篇关于调用者如何知道VARIANT中的小数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆