大家都知道COM接口,COM就是一个虚函数表指针,VB6虽然支持COM的thiscall调用,可必需通过引用,而如果一些COM接口没有给基于VB6的引用,那VB6就残了,比如IStream之类的,这次的ITaskbarList3也是一样。
我们先看看看一个COM接口在内存中的结构,这里我用VB6的方式写,大家理解起来会很简单。
如果我们声明一个变量,这个变量是一个COM对象,然后调用xx函数真正初始化了这个对象,COM指针就指向一个虚函数表,此时COM在Win32内存中是这样的,伪代码如下:
Type Com
Type IUnknown
QueryInterface + 0
AddRef + 4
Release + 8
End Type
Function1 + 12
Function2 + 16
Function3 + 20
···
End Type
恩,可以理解为COM指针就是一个表的内存区域,然后这个内存区域里面就是这样一张虚函数表,调用时,编译器根据索引*4取得需要调用的虚函数的地址,然后进行调用。
我们再追寻下一个COM对象的诞生过程:
声明COM对象变量(x86下这是一个4字节的指针)
调用**函数初始化这个变量,**函数进行一个new class操作,IUnknown引用+1,初始化class内的各种此对象需要内部变量之类的等等,这就是为什么call这个COM成员函数时需要压入COM指针,因为那就是一个伪class指针,记录了一些这个class需要使用的数据。
此时一个新的class数据被分配出来,建立虚函数表,把class的基地址写入4字节的程序内变量。
程序调用class的某个成员函数,根据虚函数表取到函数地址,压入class基地址,函数代码内从这个class提取相关变量等,执行代码。
我们再来看看一个COM函数的原形:
void test(int a,int b);
真正的原形是:
void test(this,int a,int b);
this即为class指针,这是对程序员不可见的。我们称此为thiscall,也就是stdcall的一个变种。(可以去看看cdecl、stdcall、thiscall的文章)
好了,理解了这些基本的东西,我们就可以动手了。
ITaskbarList3这个COM对象的声明在Windows 7 SDK的Shobjidl.h文件中,C style如下:
typedef struct ITaskbarList3Vtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( __RPC__in ITaskbarList3 * This,/* [in] */ __RPC__in REFIID riid,/* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( __RPC__in ITaskbarList3 * This); ULONG ( STDMETHODCALLTYPE *Release )( __RPC__in ITaskbarList3 * This); HRESULT ( STDMETHODCALLTYPE *HrInit )( __RPC__in ITaskbarList3 * This); HRESULT ( STDMETHODCALLTYPE *AddTab )( __RPC__in ITaskbarList3 * This,/* [in] */ __RPC__in HWND hwnd); HRESULT ( STDMETHODCALLTYPE *DeleteTab )( __RPC__in ITaskbarList3 * This,/* [in] */ __RPC__in HWND hwnd); HRESULT ( STDMETHODCALLTYPE *ActivateTab )( __RPC__in ITaskbarList3 * This,/* [in] */ __RPC__in HWND hwnd); HRESULT ( STDMETHODCALLTYPE *SetActiveAlt )( __RPC__in ITaskbarList3 * This,/* [in] */ __RPC__in HWND hwnd); HRESULT ( STDMETHODCALLTYPE *MarkFullscreenWindow )( __RPC__in ITaskbarList3 * This,/* [in] */ __RPC__in HWND hwnd,/* [in] */ BOOL fFullscreen); HRESULT ( STDMETHODCALLTYPE *SetProgressValue )( __RPC__in ITaskbarList3 * This,/* [in] */ ULONGLONG ullCompleted,/* [in] */ ULONGLONG ullTotal); HRESULT ( STDMETHODCALLTYPE *SetProgressState )( __RPC__in ITaskbarList3 * This,/* [in] */ TBPFLAG tbpFlags); HRESULT ( STDMETHODCALLTYPE *RegisterTab )( __RPC__in ITaskbarList3 * This,/* [in] */ __RPC__in HWND hwndTab,/* [in] */ __RPC__in HWND hwndMDI); HRESULT ( STDMETHODCALLTYPE *UnregisterTab )( __RPC__in ITaskbarList3 * This,/* [in] */ __RPC__in HWND hwndTab); HRESULT ( STDMETHODCALLTYPE *SetTabOrder )( __RPC__in ITaskbarList3 * This,/* [in] */ __RPC__in HWND hwndInsertBefore); HRESULT ( STDMETHODCALLTYPE *SetTabActive )( __RPC__in ITaskbarList3 * This,/* [in] */ __RPC__in HWND hwndMDI,/* [in] */ DWORD dwReserved); HRESULT ( STDMETHODCALLTYPE *ThumbBarAddButtons )( __RPC__in ITaskbarList3 * This,/* [in] */ UINT cButtons,/* [size_is][in] */ __RPC__in_ecount_full(cButtons) LPTHUMBBUTTON pButton); HRESULT ( STDMETHODCALLTYPE *ThumbBarUpdateButtons )( __RPC__in ITaskbarList3 * This,/* [size_is][in] */ __RPC__in_ecount_full(cButtons) LPTHUMBBUTTON pButton); HRESULT ( STDMETHODCALLTYPE *ThumbBarSetImageList )( __RPC__in ITaskbarList3 * This,/* [in] */ __RPC__in_opt HIMAGELIST himl); HRESULT ( STDMETHODCALLTYPE *SetOverlayIcon )( __RPC__in ITaskbarList3 * This,/* [in] */ __RPC__in HICON hIcon,/* [string][unique][in] */ __RPC__in_opt_string LPCWSTR pszDescription); HRESULT ( STDMETHODCALLTYPE *SetThumbnailTooltip )( __RPC__in ITaskbarList3 * This,/* [string][unique][in] */ __RPC__in_opt_string LPCWSTR pszTip); HRESULT ( STDMETHODCALLTYPE *SetThumbnailClip )( __RPC__in ITaskbarList3 * This,/* [in] */ __RPC__in RECT *prcClip); END_INTERFACE } ITaskbarList3Vtbl;SetProgressValue为第10个函数,那函数地址就是(10-1)*4,为了更易懂,我们可以把一个COM函数的Vtbl列成VB6里面的Enum。
添加一个Module1,复制代码:
Private Declare Function CallWindowProcW& Lib "user32" (ByVal lpPrevWndFunc As Long,ByVal hWnd As Long,ByVal Msg As Long,ByVal wParam As Long,ByVal lParam As Long) Private Declare Function LocalAlloc& Lib "kernel32" (ByVal f&,ByVal s&) Private Declare Function LocalFree& Lib "kernel32" (ByVal m&) Private Declare Sub PutMem1 Lib "msvbvm60" (ByVal Ptr As Long,ByVal NewVal As Byte) Private Declare Sub PutMem2 Lib "msvbvm60" (ByVal Ptr As Long,ByVal NewVal As Integer) Private Declare Sub PutMem4 Lib "msvbvm60" (ByVal Ptr As Long,ByVal NewVal As Long) Private Declare Sub PutMem8 Lib "msvbvm60" (ByVal Ptr As Long,ByVal NewVal As Currency) Public Function CallCOMInterface&(ByVal CComPtr&,ByVal dwMemberIndex&,ParamArray pParam()) Dim i%,offset& Dim hMem& hMem = LocalAlloc(0,((UBound(pParam) + 2) * 5) + 5 + 6 + 1) '//申请代码内存 offset = hMem For i = UBound(pParam) To 0 Step -1 '//压入参数 PutMem1 offset,&H68 'push Param offset = offset + 1 PutMem4 offset,pParam(i) offset = offset + 4 Next PutMem1 offset,&H68 'push COM point,压入COM指针 PutMem4 offset + 1,CComPtr offset = offset + 5 PutMem1 offset,&HA1 'mov eax,dword ptr ds:CComPtr,eax=CComPtr指针第一个函数地址 PutMem4 offset + 1,&HFF 'call dword ptr ds:eax + dwMemberIndex * 4,根据Win32下COM表结构,一个函数地址长度4字节 PutMem1 offset + 1,&H90 PutMem4 offset + 2,dwMemberIndex * 4 offset = offset + 6 PutMem1 offset,&HC3 'retn PutMem1 offset + 1,&H90 '//nop一行代码 CallCOMInterface = CallWindowProcW(hMem,0) 'call LocalFree hMem '//释放内存 End Function再添加一个Form1,一个Command1,复制代码:
Private i& Private Enum ITaskbarList3 QueryInterface AddRef Release 'IUnknown HrInit AddTab DeleteTab ActivateTab SetActiveAlt MarkFullscreenWindow SetProgressValue SetProgressState RegisterTab UnregisterTab SetTabOrder SetTabActive ThumbBarAddButtons ThumbBarUpdateButtons ThumbBarSetImageList SetOverlayIcon SetThumbnailTooltip SetThumbnailClip End Enum Private Type GUID Data1 As Long Data2 As Integer Data3 As Integer Data4(7) As Byte End Type Private Declare Function IIDFromString& Lib "ole32 " (ByVal ID As Long,ByVal IDs As Long) Private Declare Function CLSIDFromString& Lib "ole32 " (ByVal ID As Long,ByVal IDs As Long) Private Declare Function CoCreateInstance& Lib "ole32 " (ByVal CLSID As Long,ByVal Outer As Long,ByVal Context As Long,ByVal IID As Long,Obj As Any) Private Function CreateW7Task&() Dim CID As GUID,IID As GUID,objW7Task& CLSIDFromString StrPtr("{56FDF344-FD6D-11d0-958A-006097C9A090}"),VarPtr(CID) IIDFromString StrPtr("{EA1AFB91-9E28-4B86-90E9-9E9F8A5EEFAF}"),VarPtr(IID) CoCreateInstance VarPtr(CID),1,VarPtr(IID),objW7Task CreateW7Task = objW7Task End Function Private Sub Command1_Click() Dim j& For j = 0 To 10000 Me.Caption = CallCOMInterface(i,SetProgressValue,Me.hWnd,j,10000,0) Next End Sub Private Sub Form_Load() i = CreateW7Task End Sub点击Command1测试效果。 这代码就是根据ITaskbarList3的CLSID和IID通过CoCreateInstance创建出来一个COM对象指针,然后调用里面的虚函数,实现Windows7下任务栏进度条效果。 关于ITaskbarList3的其他函数说明自己看MSDN就行了,理解套用代码就一样调用,其他的COM接口也一样。比如什么XMLLite的那些东西。。。 有人会问,上面的SetProgressValue不是3个参数么,为什么我们调用的时候传入了5个参数?! 看原形,SetProgressValue是一个dword的hwnd,2个ULONGULONG的进度参数,ULONGULONG这个玩意是8字节,压栈的时候分2个4字节压,所以我们就写成了4个参数,也就是分开了。就像那个FILETIME差不多。再简单一点,一个ULONGULONG=2个ULONG=2个dword 好了、长篇大论就写到这里,撸管睡觉去。