自己实现IDispatch::Invoke方法

2019-02-21  本文已影响0人  bingo_hzy

因为种种原因,在只能得到一个IWebBrowser指针的情况下要接收javascript的window.external.XXX调用,不得已自己实现了IDocHostUIHandler和IDispatch,为了使用方便,自己又需要实现类似MFC的DISPATCH_MAP:首先用一个结构体保存每个DISPATCH方法的ID,名字,this指针,函数地址,返回值类型,参数类型列表。这些都没有什么难度。然后在IDispatch::GetIDsOfNames里面通过方法名称查找到方法ID返回。最关键的是IDispatch::Invoke方法的实现,怎么实现可变参数调用。

  1. 根据Invoke方法的参数与对应的保存的结构体信息对参数个数,参数类型,返回值类型做安全校验。
  2. 因为保存的函数地址指针,没法确定函数原型(和MFC的DISPATCH_MAP一样,参数个数、类型和返回值类型都是不定的),只好想用汇编的call指令来直接调用,但参数入栈又是一个难题。
  3. 后来想了一个办法,因为VC++里面标准__thiscall调用约定,会按顺序把参数压入栈,把this指针写入ECX寄存器,所以我先根据参数类型把所有参数序列化到一个buffer里,得到所有参数总长度size,然后在栈上分配长度为size的空间,把buffer直接copy到分配好的栈里。然后直接用call指令调用函数,这其中还有要注意的一些东西,一是原寄存器的保存,及调用后的还原。二是不要修改EBP,不然在后面的汇编指令里不能直接使用原来的局部变量了,因为临时变量和参数实际都是通过EBP这个栈帧来快速访问(访问参数用EBP+XX、访问局部变量用EBP-XX)。三是保持栈平衡,寄存器入栈、出栈,分配的栈空间释放等。
  4. 返回值的获取,32位返回值会保存在EAX里,64位返回值分别在EDX和EAX保存高低32位,我的做法是直接用一个64位变量取出EDX和EAX值,然后根据函数实际的返回值类型取对应的8/32/64位,因为在汇编里做这些判断是很麻烦的,而这个取EDX和EAX的值对程序又不会有影响。

下面是关键代码:

_asm{
        push ecx;
        mov ecx, pthis;  //把this指针压入ECX
        push ebx;
        mov ebx, esp;   //保存栈帧,不要使用ebp, 会导致后面不能直接使用栈变量
        sub esp, liSize;   //移动栈指针 分配liSize大小的栈空间

        push esi;       //memcpy begin 把序列化好的参数copy到分配好的栈里
        push edi;
        push ecx;
        mov ecx, liSize
        mov esi, pStack;
        mov edi, ebx;
        sub edi, liSize
        rep movsb;
        pop ecx;
        pop edi;
        pop esi;        //memcpy end

        call fn;       //调用函数
        mov esp, ebx;
        pop ebx;
        
        mov dword ptr[result], eax; //取返回值 如果没返回值 这个也不会有影响
        lea eax, [result];
        add eax, 4;
        mov dword ptr[eax], edx;   //假定都返回64位返回值 这里取高32位
    }

后来才知道,微软有一个API实现了这个功能。

HRESULT DispInvoke(
  void *_this,
  ITypeInfo *ptinfo,
  DISPID dispidMember,
  WORD wFlags,
  DISPPARAMS *pparams,
  VARIANT *pvarResult,
  EXCEPINFO *pexcepinfo,
  UINT *puArgErr
);
上一篇下一篇

猜你喜欢

热点阅读