# ComValue

包装一个值, 安全数组或 COM 对象, 以供脚本使用或传递给 COM 方法.

    ComObj := ComValue(VarType, Value , Flags)

`ComValue` 本身是一个派生自 `Any` 的类, 但只用于创建或识别 COM 封装对象.

## 参数

VarType

  类型: 整数

  表示值类型的整数. 类型列表见 ComObjType.

Value

  类型: 整数

  要包装的值.

  如果这是一个纯整数且 VarType 不是 VT_R4, VT_R8, VT_DATE 或 VT_CY, 则直接使用其值; 特别是 VT_BSTR, VT_DISPATCH 和 VT_UNKNOWN 可以用一个指针值来初始化.

  在任何其他情况下, 使用与普通 COM 方法调用相同的规则将值复制到临时 VARIANT 中. 如果源变量类型不等于 VarType, 则通过调用 wFlags 值为 0 的 VariantChangeType 来尝试转换. 如果转换失败, 则抛出异常.

Flags

  类型: 整数

  影响包装器对象行为的标志; 有关详情, 请参阅 ComObjFlags.

## 返回值

类型: ComValue, ComValueRef, ComObjArray 或 ComObject

返回包含变体类型和值或指针的封装对象.

该对象有多个用途:

1. 某些 COM 方法可能需要特定类型的值, 这些值在 AutoHotkey 中没有直接等效的值. 此函数允许在将值传递给 COM 方法时指定值的类型. 例如, `ComValue(0xB, -1)` 创建一个表示 COM 布尔值为 true 的对象.
2. 通过封装 COM 对象或 SafeArray, 脚本可以使用对象语法更自然地与其交互. 但是, 大多数脚本不需要手动执行此操作, 因为封装对象是由 ComObject, ComObjArray, ComObjActive, ComObjGet 和任何返回对象的 COM 方法自动创建的.
3. 封装 COM 接口指针允许脚本利用自动引用计数. 接口指针可以在返回到脚本时立即包装(通常是从 ComCall 或 DllCall 返回), 避免在稍后的某个点需要显式释放它.

## Ptr

如果一个封装对象的 VarType 是 VT_UNKNOWN(13) 或包含 VT_BYREF(0x4000) 或 VT_ARRAY(0x2000) 标志, 则 `Ptr` 属性可用于检索对象, 类型化变量或安全数组的地址. 这允许将 ComObject 本身传递给任何具有 `"Ptr"` 类型的 DllCall 或 ComCall 参数, 但也可以显式地使用. 例如, 在这些情况下, `ComObj.Ptr` 等同于 `ComObjValue(ComObj)`.

如果一个封装对象的 VarType 是 VT_UNKNOWN(13) 或 VT_DISPATCH(9) 并且封装指针为 null(0), 则可以使用 `Ptr` 属性来检索当前的 null 值, 或者为封装对象赋值一个指针. 一旦被赋值(如果非-null), 当封装对象被释放时, 指针将被自动释放. 这可以与类型为 `"Ptr*"` 或 `"PtrP"` 的 DllCall 或 ComCall 输出参数一起使用, 以确保指针会被自动释放, 例如当发生错误时. 有关示例, 请参阅 ComObjQuery.

如果一个包装器对象的 VarType 是 VT_DISPATCH(9) 并且为 null(0) 指针值赋值为非-null 指针值, 其类型从 `ComValue` 变为 `ComObject`. 包装对象的属性和方法变得可用, 而 `Ptr` 属性变为不可用.

## ByRef

如果包装器对象的 VarType 包含 VT_BYREF(0x4000) 标志, 可以用空的方括号对 `[]` 来读写引用的值.

当创建一个引用, Value 须为变量的内存地址, 或足够存储指定类型的值的缓冲. 例如, 下面的代码可以用来创建一个 VBScript 函数可以写入的变量:

    vbuf := Buffer(24, 0)
    vref := ComValue(0x400C, vbuf.ptr)  ; 0x400C 为 VT_BYREF 与 VT_VARIANT 组合而得.

    vref[] := "in value"
    sc.Run("Example", vref)  ; sc  应像下面例子一样进行初始化.
    MsgBox vref[]

请注意, 尽管在通过 `vref[]` 或 COM 方法分配新值时, 任何先前的值都会被释放, 但最终的值不会被自动释放. 释放该值需要知道它是哪种类型. 因为在这种情况下它是 VT_VARIANT, 它可以通过用 DllCall 调用 VariantClear 来释放, 或者使用更简单的方法: 分配一个整数, 如 `vref[] := 0`.

如果方法接受如上所示的 VT_BYREF 和 VT_VARIANT 的组合, 可以使用 VarRef 代替. 例如:

    some_var := "in value"
    sc.Run("Example", &some_var)
    MsgBox some_var

然而, 有些方法需要更特定的变体类型, 如 `VT_BYREF | VT_I4`. 在这种情况下, 必须使用上面所示的第一种方法, 用适当的变体类型替换 0x400C.

## 一般说明

当此函数用于包装 IDispatch 或 IUnknown 接口指针(以整数形式传递) 时, 包装器对象负责在适当的时候自动释放指针. 因此, 如果脚本打算在调用这个函数之后使用指针, 它必须首先调用 `ObjAddRef(DispPtr)`. 相比之下, 如果 Value 本身是 ComValue 或 ComObject, 则没有必要这样做.

从 VT_UNKNOWN 到 VT_DISPATCH 的转换会导致对 IUnknown::QueryInterface 的调用, 这可能会产生一个不同于原来的接口指针, 并且如果对象没有实现 IDispatch, 会抛出异常. 相反, 如果 Value 是整数, 且 VarType 为 VT_DISPATCH, 则直接使用该值, 因此必须是一个 idispatch 兼容的接口指针.

可以使用 ComObjType 检索包装器对象的 VarType.

可以使用 ComObjValue 检索包装器对象的 Value.

已知限制: 每次包装 COM 对象时, 都会创建新的包装器对象. 如 `obj1 == obj2` 和 `array[obj1] := value` 这样的比较和赋值运算, 将这两个包装器对象视为唯一的, 即使它们包含相同的变体类型和值.

## 相关

ComObjFromPtr, ComObject, ComObjGet, ComObjConnect, ComObjFlags, ObjAddRef/ObjRelease, ComObjQuery, GetActiveObject (Microsoft Docs)

## 示例

传递 VARIANT ByRef 给 COM 函数.

    ; 条件 - ScriptControl 需 32 位版本的 AutoHotkey.
    code := "
    (
    Sub Example(Var)
        MsgBox Var
        Var = "out value!"
    End Sub
    )"
    sc := ComObject("ScriptControl"), sc.Language := "VBScript", sc.AddCode(code)

    ; 示例: 传递 VARIANT ByRef 至 COM 方法.
    var := ComVar()
    var[] := "in value"
    sc.Run("Example", var.ref)
    MsgBox var[]

    ; 同样的事情, 但更直接:
    variant_buf := Buffer(24, 0)  ; 使 VARIANT 的缓冲足够大.
    var := ComValue(0x400C, variant_buf.ptr)  ; 引用 VARIANT.
    var[] := "in value"
    sc.Run("Example", var)  ; 传递 VT_BYREF ComValue 本身, 没有 [] 或 .ref.
    MsgBox var[]
    ; 如果 VARIANT 包含一个字符串或对象, 必须显式地释放它
    ; 通过调用 VariantClear 或赋值一个纯数字值:
    var[] := 0

    ; 当方法接受 VT_BYREF|VT_VARIANT 时的最简单方法:
    var := "in value"
    sc.Run("Example", &var)
    MsgBox var

    ; ComVar: 一个可以用来传递一个值 ByRef 的对象.
    ;   this[] 检索值.
    ;   this[] := Val 设置值.
    ;   this.ref 检索一个 ByRef 对象, 用于传递给一个 COM 方法.
    class ComVar {
        __new(vType := 0xC) {
            ; 为 VARIANT 分配内存, 以保存我们的值.
            ; 即使在 vType != VT_VARIANT 时也会使用 VARIANT, 这样 VariantClear 就可以被 __delete 使用.
            this.var := Buffer(24, 0)
            ; 创建一个可以用来传递变量 ByRef 的对象.
            this.ref := ComValue(0x4000|vType, this.var.ptr + (vType=0xC ? 0 : 8))
            ; 存储 VariantClear 的变体类型(如果不是 VT_VARIANT).
            if Type != 0xC
                NumPut "ushort", vType, this.var
        }
        __item {
            get => this.ref[]
            set => this.ref[] := value
        }
        __delete() {
            DllCall("oleaut32\VariantClear", "ptr", this.var)
        }
    }

dftg