因為種種原因,在只能得到一個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
);