ComValue

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

ComObj := ComValue(VarType, Value , Flags)

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

参数

VarType

类型: 整数

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

Value

类型: Any

要包装的值.

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

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

Flags

类型: 整数

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

返回值

类型: Object

此函数返回包含变体类型和值或指针的封装对象, 尤其是 ComValue, ComValueRef, ComObjArrayComObject.

该对象有多个用途:

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

Ptr

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

如果一个封装对象的 VarType 是 VT_UNKNOWN(13) 或 VT_DISPATCH(9) 并且封装指针为 null(0), 则可以使用 Ptr 属性来检索当前的 null 值, 或者为封装对象赋值一个指针. 一旦被赋值(如果非-null), 当封装对象被释放时, 指针将被自动释放. 这可以与类型为 "Ptr*""PtrP"DllCallComCall 输出参数一起使用, 以确保指针会被自动释放, 例如当发生错误时. 有关示例, 请参阅 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 == obj2arr[obj1] := value 这样的比较和赋值运算, 将这两个包装器对象视为唯一的, 即使它们包含相同的变体类型和值.

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

示例

传递 VARIANT ByRef 给 COM 函数.

#Requires AutoHotkey v2 32-bit ; 32-bit for ScriptControl.
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 vType != 0xC
            NumPut "ushort", vType, this.var
    }
    __item {
        get => this.ref[]
        set => this.ref[] := value
    }
    __delete() {
        DllCall("oleaut32\VariantClear", "ptr", this.var)
    }
}