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