【问题标题】:How does the caller know when there's a Decimal inside a VARIANT?调用者如何知道 VARIANT 中何时有 Decimal?
【发布时间】:2020-05-10 23:39:55
【问题描述】:

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。

当DECIMAL 存储在 Variant 中时会发生什么;定义位于包含vt 的struct 之外,那么调用者如何确定是否存在有效的类型标志或只有十进制的某些字节? Decimal 需要 12* 14 个字节来存储,而 Variant 可以保存 16 个字节,因此可能会利用此信息,但不是存储在联合的较小成员的备用 2 个字节中的内容未定义的行为?

【问题讨论】:

  • @user253751 哦,我明白了,我误解了它是什么,但DECIMAL 是一个 12 字节无符号整数 + 2 字节符号/比例 + 2 字节保留,所以完整的 16 字节是确实定义了。但是,保留的 2 个字节中有什么内容?
  • 保留的 2 个字节恰好在 VARIANT 中与 vt 重叠...
  • @user253751 好的...那么这是否回答了呼叫者是否可以判断这是小数的问题?当然,这 2 个字节占据了调用者查看存储类型的位置,但它并没有说这些字节必须是或特别意味着什么,正如你所说,这只是一个巧合,对吧?如果存储 DECIMAL 的 VARIANT 想要使用这些字节作为 vt 标志,为什么不使用 struct{vt ; decVal} 定义 VARIANT 和更短的十进制 2 个字节,或者使用 typedef struct tagDEC {VARTYPE wReserved; ... 的 DECIMAL 来明确说明这 2 个字节的含义?
  • 我不知道。但这就是它设法仍然有效的方式。

标签: c++ com decimal unions variant


【解决方案1】:

这是一个有趣的问题。可悲的是,我无法找到任何关于此的可靠文档。我可以通过一些思考和实验做出一些推论。

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

我提出两个经验证明。

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

Dim v As Variant
v = CDec(24)

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

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)       

好的,VB6 并没有在奇怪的地方作弊,而且 Microsoft 不会将 Decimal 公开为完整类型似乎总是很奇怪(由于某种原因,您不能在 VB6 中声明 Decimal 类型的变量;它必须存储在一个变体。Dim 的文档听起来像是他们打算支持 Decimal 并且出于某种原因不得不将其拉出)。所以这可能只是一个 VB6 作弊。然而:

2) 我测试了如果我要求 COM API 将 DECIMAL 放入 VARIANT 中会做什么。为了踢球,我使用 VC6++ 进行了测试:

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 是S_OK。如果在 VARIANT 中按值存储 DECIMAL 形式上是非法的,那么我预计会出现错误 HRESULT。相反,布局符合我使用 VB6 的经验:

  • 监视窗口将t 的值报告为{24 VT_DECIMAL}
  • t.vt 成员设置为 14(即 VT_DECIMAL)
  • t.decVal 成员被列为 wReserved == 14; Lo64 == 24; Hi32 == 0

因此,尽管 VARIANT 的标头声明意味着什么,但 vt 成员可以而且应该用于确定 VARIANT 何时包含 DECIMAL。事实上,如果您从未详细检查过 VARIANT 的声明,您将永远不会知道 DECIMAL 的处理方式不同。


我剩下的问题是“为什么不像其他人一样让 DECIMAL 适合它在工会中?”。

如果不了解 VARIANT 和 DECIMAL 的完整历史,可能很难得出完整的答案;但关键可能不在vt,而是在wReserved1、wReserved2 和wReserved3。

DECIMAL 似乎是后来添加到 VARIANT 的。 Kraig Brockschmidt 的经典著作“Inside Ole”(第 2 版,日期为 1995 年)给出了 VARIANT 的声明,但没有提到 DECIMAL 作为选项之一。这意味着 DECIMAL 作为 VARIANT 选项是在之后的某个时候添加的。在 Visual C++ 6 (1998) 之前,DECIMAL 已经作为 VARIANT 类型提供。

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

因此,一种理论是,Microsoft 需要将这种新的 14 字节长类型添加到 VARIANT 中,这不可能适合联合可用的 8 字节。在这个理论下,VARIANT 的当前布局将是一种在二进制级别潜入 DECIMAL 的方式,而不会破坏 VARIANT 的原始声明。编译时,DECIMAL 只是“联合”的另一个成员,除了它可以溢出到保留字的空间中。

可能还有另一个怪癖。 Hans Passant 在上面的评论中提到,保留字段用于包含货币类型信息。这听起来很可行,但我无法证实它,因为我没有找到任何关于 DECIMAL 旧用法的信息。假设这是真的,Microsoft 将受限于预先存在的 DECIMAL 类型的布局(即,不可能考虑牺牲范围以使其适合常规成员)。此外,他们必须决定他们可以放弃“货币类型”信息以换取 DECIMAL 在 VARIANT 中的工作(或者他们可能已经更早地放弃了货币类型信息,或者出于其他原因)。如果没有关于 DECIMAL 在添加为 VARIANT 类型之前如何使用的更多信息,我无法判断。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-08-10
    • 1970-01-01
    • 1970-01-01
    • 2020-11-25
    • 2010-11-05
    • 1970-01-01
    • 2016-04-17
    • 1970-01-01
    相关资源
    最近更新 更多