# ComCall

通过索引调用原生 COM 接口方法.

    Result := ComCall(Index, ComObj , Type1, Arg1, Type2, Arg2, ReturnType)

## 参数

Index

  类型: 整数

  虚拟函数表中方法的索引(从零开始).

  Index 对应于方法在原始接口定义中的位置. Microsoft 文档通常按字母顺序列出方法, 这是不相关的. 为了确定正确的索引, 请找到原来的接口定义. 这可能在头文件或类型库中.

  考虑方法继承于的父接口是很重要的. 因为所有的 COM 接口最终都是从 IUnknown 派生出来的, 所以前三个方法总是 QueryInterface (0), AddRef (1) 和 Release (2). 例如, IShellItem2 是 IShellItem 的扩展, 它从索引 3 开始, 包含 5 个方法, 所以 IShellItem2 的第一个方法(GetPropertyStore) 的索引为 8.

  提示: 对于微软定义的 COM 接口, 尝试在互联网或 Windows SDK 中搜索 "IInterfaceName Vtbl" - 例如, "IUnknownVtbl". Microsoft 自己的接口定义附带这个接口的虚拟函数表的纯-C 语言定义, 它以正确的顺序显式地列出了所有方法.

  传递无效的索引可能会导致不确定的行为, 包括(但不限于) 程序终止.

ComObj

  类型: 整数, ComValue 或对象

  目标 COM 对象; 也就是说, 一个 COM 接口指针. 指针值可以直接传递, 也可以封装在带有 `Ptr` 属性的对象中, 如带有 VT_UNKNOWN 变量类型的 ComValue.

  接口指针用于定位实现接口方法的虚拟函数的地址, 也作为参数传递. 这个参数通常不会在原生支持接口的语言中显式地出现, 而是在 C 言语风格的 "Vtbl" 定义中显示.

  传递无效指针可能导致未定义的行为, 包括(但不限于) 程序终止.

Type1, Arg1

  类型: 字符串

  每一对值表示要传递给方法的单个参数. 对的数量是无限的. 有关 Type, 请参阅 DllCall 类型列表. 有关 Arg, 指定要传递给方法的值.

ReturnType

  类型: 字符串

  如果省略, 返回类型默认为 HRESULT, 这是 COM 接口方法最常用的返回类型. 任何指示失败的结果都会抛出 OSError; 因此, 除非实际的返回类型是 HRESULT, 否则不能省略返回类型.

  如果方法是不返回值的类型(C 语言中的 `void` 返回类型), 则指定 "Int" 或不带任何后缀的其他数字类型(HRESULT 除外), 并忽略返回值. 在这种情况下, 由于返回值寄存器的内容是任意的, 所以如果省略了 ReturnType, 可能会也可能不会抛出异常.

  否则, 指定DllCall 类型列表中的一个参数类型. 支持星号后缀.

  尽管 ComCall 和 DllCall 支持 Cdecl, 但它通常不在 COM 接口方法中使用.

## 返回值

类型: 字符串或整数

如果 ReturnType 是 HRESULT(或省略), 并且方法返回一个错误值(由 FAILED macro 定义), 则抛出 OSError.

否则, ComCall 将返回该方法返回的实际值. 如果方法是不返回值的类型(该返回类型在 C 语言中定义为 `void`), 则结果是未定义的, 应将其忽略.

## 备注

以下 DllCall 主题也适用于 ComCall:

- 参数和返回值的类型
- 错误
- 原生异常和 A_LastError
- 结构和数组
- 已知限制
- .NET Framework

## 相关

ComObject, ComObjQuery, ComValue, Buffer 对象, CallbackCreate

## 示例

从任务栏中删除活动窗口 3 秒钟. 可以与等效的 DllCall 示例进行比较.

    /*
      ITaskbarList 虚拟函数列表中的方法:
        IUnknown:
          0 QueryInterface  -- 使用 ComObjQuery 代替
          1 AddRef          -- 使用 ObjAddRef 代替
          2 Release         -- 使用 ObjRelease 代替
        ITaskbarList:
          3 HrInit
          4 AddTab
          5 DeleteTab
          6 ActivateTab
          7 SetActiveAlt
    */
    IID_ITaskbarList  := "{56FDF342-FD6D-11d0-958A-006097C9A090}"
    CLSID_TaskbarList := "{56FDF344-FD6D-11d0-958A-006097C9A090}"

    ; 创建 TaskbarList 对象.
    tbl := ComObject(CLSID_TaskbarList, IID_ITaskbarList)

    activeHwnd := WinExist("A")

    ComCall(3, tbl)                     ; tbl.HrInit()
    ComCall(5, tbl, "ptr", activeHwnd)  ; tbl.DeleteTab(activeHwnd)
    Sleep 3000
    ComCall(4, tbl, "ptr", activeHwnd)  ; tbl.AddTab(activeHwnd)

    ; 完成对象后, 只需将所有引用替换为
    ; 其他一些值(如果是局部变量, 就返回):
    tbl := ""

演示一些包装 COM 接口的技术. 等同于上一个示例.

    tbl := TaskbarList()

    activeHwnd := WinExist("A")

    tbl.DeleteTab(activeHwnd)
    Sleep 3000
    tbl.AddTab(activeHwnd)

    tbl := ""

    class TaskbarList {
       static IID := "{56FDF342-FD6D-11d0-958A-006097C9A090}"
       static CLSID := "{56FDF344-FD6D-11d0-958A-006097C9A090}"

       ; 在启动时调用, 以初始化类.
       static __new() {
           ; 获取 TaskbarList 的所有实例的基对象.
           proto := this.Prototype

           ; 绑定函数可用于预定义参数, 使
           ; 这些方法在不需要包装器函数的情况下更容易使用.
           ; HrInit 本身没有参数, 所以只绑定索引,
           ; 而调用者将隐式地提供 'this'.
           proto.HrInit := ComCall.Bind(3)

           ; 将参数留空, 让调用者提供一个值.
           ; 在本例中, 空白参数是 'this'(通常是隐藏的).
           proto.AddTab := ComCall.Bind(4,, "ptr")

           ; 可以使用对象或映射来减少重复.
           for name, args in Map(
               "DeleteTab", [5,,"ptr"],
               "ActivateTab", [6,,"ptr"],
               "SetActiveAlt", [7,,"ptr"]) {
               proto.%name% := ComCall.Bind(args*)
           }
       }

       ; 在新实例上由 TaskbarList() 调用.
       __new() {
            this.comobj := ComObject(TaskbarList.CLSID, TaskbarList.IID)
            this.ptr := this.comobj.ptr
           ; 通过 ITaskbarList 请求初始化.
           this.HrInit()
       }
    }

dftg