ComCall

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

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

参数

Index

类型: 整数

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

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

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

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

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

ComObj

类型: 整数对象

目标 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 接口方法中使用.

返回值

类型: 字符串整数

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

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

备注

以下 DllCall 主题也适用于 ComCall:

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()
   }
}