leonwxqian 发布的文章

Internet Explorer 完全解析 [2]

第二章 元素之源
2.1 CElement
介绍了mshtml的一个重量级明星CBase之后,这一步,我们需要看看一看CElement,同样,可以猜测它是很多元素的基类。事实如何?我们深入看一下这个类。

本节中,由于CElement会引入许多之前未介绍的类,因此,在这节中,当引用到一个新类的时候,我都会在之后另开一章介绍,而且会修改本文指向那一个类。

2.2 巨人CElement的结构
CElement是CBase的派生类。不同的元素通过派生和重载此类来区别。CElement作为元素的基类,结构十分庞大,从自身来说,它实现了IDispatchEx、IProvideMultipleClassInfo、IServiceProvider、IRecalcProperty、IMsoCommandTarget等接口。这些我们稍后都会看到。

CElement的成员变量中,__pvChain是其最重要的关联成员,这是一个void*格式的变量,在CElement构造时,这个变量将会被赋值。如果一个element有layout(见参考,简单的说,就是有layout的元素自己会处理自己的大小之类的东西,参见layout _2)的话,__pvChain会指向这个layout,如果它没有layout,而它在一个树中,那么它会指向树中下一个ped,其他情况下,它指向当前document。

__pNodeFirstBranch指向第一个分支的node。还有两个成员变量DWORD _fMark1、DWORD _fMark2,它们用位来标记作用,分别如下:
_fMark1:
0~7 element tag; 8~14 如果旁视列表有指针的话为TRUE; 15 元素有name或者id属性; 16~31 元素锁定标记
_fMark2:
0 元素有一个Markup指针时为TRUE
1 元素有一个layout指针时为TRUE
2 有挂起状态的filter任务时为TRUE
3 有挂起状态的recalc任务时为TRUE
4 元素是一个site或者从未有过layout时为TRUE
5 元素控制着所有其下的函数运转时为TRUE
6 元素用来继承site和一些特殊格式时为TRUE
7 元素占1行,即使没有任何文字
8-11 未使用
12 元素没有边框
13 元素与tabindex关联(_aryTabIndexInfo)
14 元素有至少一个图像上下文
15 图片变化时需要强制重置大小
16 元素有关闭tag (例如<p></p>
17 用户创建时为FALSE,代码生成时TRUE
18 未使用
19 元素的行为像按钮
20 为init中的TestClassFlag而设置
21 元素默认就有layout
22 默认的确认按钮
23 默认的取消按钮
24 元素提交的事件被挂起
25 其他元素或者对象需要一个连接点或者设置一个事件
26 _pAA中加入了FilterCollectionPtr
27 元素有一个挂起的Exittree消息
28 GetFirstCommonAncestor会用到这个位,标记是否为第一个祖先
29-30 随机位
31 元素有style expression

为了做到普适性,这个CElement不得不称作一个巨人,下面我们介绍它的成员函数。

2.3 CElement的成员函数

  • CElement::CElement。构造函数,将当前CElement所属pDoc赋给__pvChain,调用pDoc->SubAddRef(),增加一个Reference。并保存传入的ELEMENT_TAG类型数据到自身的_etag中,标记当前CElement的真正Tag。
  • CElement::~CElement。 析构函数,Passivate()中做了清理,这儿几乎没动作。
  • CElement::CLock::CLock。 设置某个CElement元素的ELEMENTLOCK_FLAG。
  • CElement::CLock::~CLock。 将设置的Lock取消。 CElement::Passivate。
    清理函数,当调用这个函数时,CElement
      (1)不会在一棵树中(如果在树中就不应该被清理);
      (2)延迟队列中不能有当前CElement,否则会重复释放。
      当满足上述2个要求时,程序检查是否有pending task,如果有的话,则从pDoc中获取View,然后调用删除任务的函数,将pending task清除。
      如果当前CElement有任何sub Markup,那么将它们删除,然后清除master Makrup,再Release释放计数。如果当前CElement是一个peer holder(节点持有者),那么在当前CElement上也只应该有1个peer,否则不应该调用这个清理函数,有的话,随后将这1个peer的Reference清除。
      接着,清除peer Manager(如果有),data bindings(如果有),element capture,methods,images,layouts(如果有),accessible objs(如果有),pending filter tasks(如果有),recalculate task(如果有),filter collections pointer(如果有,AA的),cached style pointer(如果有,调用super::Passivate来清除关联的属性数组),all lookasides(也即上述这些)。
      最终,减少CElement的计数和obj count。
  • CElement::PrivateQueryInterface。 IUnknown的QueryInterface实现。
  • CElement::PrivateAddRef。 IUnknown的AddRef实现。
  • CElement::PrivateRelease。IUnknown的Release实现。
  • CElement::PrivateEnterTree。 进入树处理时调整引用计数。会把在Markup中的当前CElement父类引用加一,MarkupPtr的引用也随之加一。
  • CElement::PrivateExitTree。 退出树处理时调整引用计数。与上述操作相反,引用数大于1时,调用父类的Relase将引用计数减一,同时将传入的Markup引用减一(如果引用大于1时)。
  • CElement::contains。 IHTMLElement的函数。传入一个IHTMLDocument*类型的参数,如果CElement里包含这个IHTMLDocument*类型的对象,将返回一个VARIANT_BOOL。
    判断方式:
    (1)获取一个CTreeNode*(从传入的PIHTMLDocument中QueryInterface:CLSID_CTreeNode),如果失败,则获取第一个分支CElement * (QueryInterface:CLSID_CElement),调用获取到的CElement*的GetFirstBranch()函数。
    (2)从(1)步获得的这个Node开始向上遍历,直至遍历到根Node,或者当前Node为止;
    (3)如果(2)步的结果是遍历到了根Node,那么返回VB_FALSE,如果是遍历到了当前Node,返回VB_TRUE,这个逻辑很好理解:
    f1.gif
  • CElement::ClearRunCaches。获得当前CElement所在的Markup,调用Markup的ClearRunCaches函数。
  • CElement::IsFormatCacheValid。遍历当前所在树中第一个branch开始的所有branch(First->>Next->>Next...),进行有效性检查。
  • CElement::EnsureFormatCacheChange。当当前函数不在一个树中或者没有设置需要清理cache时,返回S_OK;如果设置了标记位,那么调用ClearRunCaches。
  • CElement::OnPropertyChange。 onpropertychange事件。notenote: onproprty事件是IE专属
    (1)当前CElement的onpropertychange事件刚刚被设置时应该执行事件;
    例如:
    <div id="txt"> www.nul.pw</div> <script> function d(){alert(5);} document.getElementById('txt').onpropertychange=d(); </script>
    将会触发alert(5)。 (IE11可能不一定会正确触发,请在IE10-,或者兼容性视图中测试)
    (2)当前CElement不在任何tree中是不可以触发的,只有当前CElement在放置到tree中之后才能触发事件;
    <div id="txt"> www.nul.pw</div> <script> function d(){alert(5);} p = document.createElement("div"); p.onpropertychange=d(); p.innerText="123"; document.getElementById('txt').appendChild(p); </script>
    例如上述代码不能触发onpropertychange事件。
    <div id="txt"> www.nul.pw</div> <script> function d(){alert(5);} p = document.createElement("div"); p.onpropertychange=d(); document.getElementById('txt').appendChild(p); p.innerText="123"; </script>
    则可以触发事件。
     
      以及一些额外的操作,例如cache调整,根据传入的dispid来设置对应应该触发事件的对象,或者调整branch。因为onpropertychange是在一个元素的任意属性改变时触发的,所以这儿的判断流程十分长,例如background-image(背景图片)、list-style-image(列表图片)、z-index(顺序)、left,right,bottom,top(位置)、disabled(有效性)、visiablilty(可见性)、marginleft,right,bottom,top(边缘对齐)、clipleft,right,bottom,top(剪裁绝对元素)等等等等,几乎是所有样式操作导致属性变化都会有对应的方式来处理,这一部分可以参考w3school的对应部分。例如;clip:rectlist-style-image等。
     
      当propertychange发生时,如果当前CElement有layout的话,将获取离得最近的layout的视图,然后向它发送一个property changed 的notify。
     
      如果pDoc中的current元素指向自身,但是获取不到layout,可标记里还是提示有layout;或者获取到了layout,但是标记里面提示没有layout,那么将最近的一个layout的视图的owner变成当前元素的子元素。这个逻辑是:标记有layout但是获取不到layout的时候,表明当前元素有子元素,但是获取不到layout,证明子元素没有设置owner标记; 获取到了layout,证明子元素可达,而且有layout标记,但是标记里没有layout,这时应当也设置一次owner标记,让其也标记上layout。
     
      这步操作之后,会设置上RemeasureInParent标记,告知Parent应该重新测量自己的子元素属性。同时去除SIZECHANGED属性。
     
      RemeasureInParent之后应该设置DisplayChange通知,将标记元素祖先为脏的数据,因此只有ZParent全部计算完成之后,ZParentChange才会被调用,之前都一直会在队列中排着。
    (注:DisplayChange请参见CNotification节中的内容,将在后续介绍。 )
     
      除非是在一个non-site的元素上进行的z-index改变,否则z-index的改变将CSite的z-order处理函数FixZOrder来处理。
     
      当一个property改变而且会引起继承的值都会出现改变时(通过dwFlags确认),会调用Invalidate()通知parent,然后发送一个notification以通知后代。
     
      当触发onpropertychange之后,之前获取到的layout就不再可靠了,因为脚本可能会改变页面内容,所以此时,还应当测试性的调用一次onchage事件(当然,不一定触发,这里面还是会检查是否应当触发事件的),以及通知当前document数据变化了,最后会做一些其他小的触发行为。根据ie版本不同而不同,ie11以及兼容性视图行为差异很大。
  • CElement::OpenView。打开与document关联的view(这个view是与最近的一个layout相关联的)。
  • CElement::*Element。 发送消息用的辅助函数。InvalidateElement、MinMaxElement、ResizeElement(没有layout、当前标记为clean、它的父容器没有设置过它的大小时,这个消息才会送达)、RemeasureElement、RemeasureInParentContext、RepositionElement、ZChangeElement。
  • CElement::SendNotification。重载函数,传入一个CNotification*,向关联的element发送一个notification,做法是:先获取Markup,然后调用Markup的Notification函数发送。
  • CElement::SendNotification。重载函数,带NTYPE和NFLAGS的,当场组合一个CNotification,然后发送。
  • CElement::DirtyLayout。 标记当前元素关联的layout为脏。
  • CElement::HitTestPoint。 和常见的WM_NCHITTEST的目的差不多,它的作用是确定某个点是否在某个CElement上。判断方式:获取最近的layout,获取其View(CView),然后转而调用View的HitTest函数。
  • CElement::GetRange。 返回这个CElement的文字范围,包括结束节点。计算方式:获取起始节点和最终节点的范围,相减后加一。
  • CElement::GetFocusShape。返回当一个控件被选中时应该被重绘的区域范围,区域是一个从CShape派生来的对象。默认的TextRange是用这个的(GetClientRect),其他的例如多选框之类的,都是从这儿重载的,否则无法提供正确的区域范围。
  • CElement::GetImageUrlCookie。返回向指定Doc的图像url缓存里面添加一个url的结果,称为新cookie。
  • CElement::AddImgCtx。向AA中添加在ImgCtxInfo中一个指定的信息。如果AA中现在已经有值了,先会把它释放掉。具体步骤:先找到AAIndex,这时候会得到一个cookie,根据它来检索,如果有ImgCtx(image context),释放旧的,然后添加新的cookie。
  • CElement::ReleaseImageCtxts。 释放所有与此CElement关联的图像上下文,例如背景图像。
  • CElement::DeleteImageCtx。同上,只不过删掉的是所有dispid指定的与CElement关联的图像上下文。
  • CElement::GetSourceIndex。 获得当前元素的SourceIndex。由于ROOT node的存在,获取的SourceIndex会减一修正。
  • CElement::CompareZOrder。 与另一个CElement比较ZOrder。返回值:>0,这个元素z-index比较大;<0,这个元素的z-index比较小;0,元素的z-index相等。
  • CElement::GetTreeExtent。遍历上下文chain,然后获取第一个和最后一个node的位置。
  • CElement::GetLastBranch。 获得最后一个branch。从first branch一直获取nextbranch即可。
  • CElement::ReplacePtr。 替换两个Ptr,同时修正引用。
  • CElement::ReplacePtrSub。 类似,只不过修正的是subref。
  • CElement::SetPtr。 类似,只不过修正一个元素的ref。
  • CElement::StealPtrSet。 SetPtr的封装,修正引用。
  • CElement::StealPtrReplace。 ReplacePtr的封装,修正引用。
  • CElement::ClearPtr。 置NULL,Release ref。
  • CElement::ReleasePtr。 Relase ref。
  • CElement::IsBlockElement。 返回这个CElement是否是Block Element
  • CElement::IsOwnLineElement。 返回这个CElement是否是内联元素。
  • CElement::IsBlockTag。 返回是否是Block Tag,例如css里面设置的display:block的元素。
  • CElement::BreaksLine。 返回这个节点是否起新行,例如hr,div。
  • CElement::HasFlag。 返回这个节点是否有指定的flag。
  • CElement::SetTagNameAndScope。 设置tag,然后scope一个元素的属性。
  • CElement::TagName。 返回tag名称。
  • CElement::Namespace。 返回GetAAscopeName。
  • CElement::NamespaceHtml。 返回namespace,如果没有namespace,返回“HTML”。
  • SameScope。 重载函数,参数为CTreeNode,CElement 或者 CElement, CTreeNode 或者两个CTreeNode ; 检查CTreeNode所包含的元素是否就是后面的CElement。
  • CElement::NameOrIDOfParentFor。如果有的话,返回一个对象的父form的id或者name,具体方式是获取第一个branch,然后从branch到root搜索任意一个tag为ETAG_FORM的对象,这个即可算作父form。
  • CElement::SaveAttribute。将一个属性存入流(CStreamWriteBuff类)中。也就是加入一个属性="值"的对,同时请注意到IE中的一个特性是属性中的部分中的回车都不会忽略
  • CElement::SaveUnknown。把所有的expando(AAType为CAttrValue::AA_Expando)的值输出到流中。
  • CElement::SaveUnknown。重载函数,输出到IPropertyBag中。没有值的属性不会被保存。例如<script defer>的defer,但是建议写成defer="defer"
  • StoreLineAndOffsetInfo。将对象的AA属性的行号等存储起来。
  • CElement::GetLineAndOffsetInfo。 从某个AA中返回某个属性的行号和偏移值。
  • CElement::ConnectEventHandler。从AA中获取指定的dispid对应的字符串,然后从这个字符串里构建一段代码,然后把对应的dispid和这段代码放置到AA中。当倒数第二个参数为TRUE时,表明这是普通的事件,例如onclick,AA中的AA_Attribute以dispid来区分;为FALSE时,代表dispid指代的是AA的AA_Expando节。
  • CElement::InitAttrBag。从CHtmTag里面取值放入bag中。bag就是AttrBag,存储属性-值对的一个“包”。
  • CElement::MergeAttrBag。将CHtmTag中任何不在attrbag的属性放到bag中。
  • CElement::Init2。 元素级别的初始化。
  • CElement::Save。将元素保存到流中。
  • CElement::SaveAttributes。 将属性保存到流/IPropertyBag中。
  • CElement::HandleMessage。 处理特定元素的消息。将对应消息转交给CLayOut去处理。
  • CElement::DisallowSelection。 当选区必须不能被选中时返回TRUE。对话框中,只有能编辑的部分可以被选中。
  • CElement::CloseErrorInfo。 调用Doc的CloseErrorInfo,以便返回它的clsid而不是控件的clsid。
  • CElement::SetCursorStyle。响应类似这样的onmouseover="this.style.cursor='pointer';this.style.cursor='hand'" 的"element.style.cursor"属性,在发生WM_CURSOR消息时处理消息。
  • IsIDMSuperscript/IsIDMSubscript/IsIDMBold/IsIDMCharAttr。 判断tag是否是上标(sup)/下标(sub)/加粗(b/strong)/文字(这样的话当用户选择移除文字格式时,可以判断这个元素是否可以删除)
  • IsIDMItalic/IsIDMUnderlined。判断tag是否为斜体(i)/下划线(u)
  • CElement::WriteTag。向流中写入一个完整的tag,也就是包括<xxx>www.nul.pw</xxx>的tag对。
  • CElement::scrollIntoView。 对ScrollIntoView的封装。
  • CElement::DeferScrollIntoView。 两个重载,延迟ScrollIntoView,其实是把消息post进队列了,也是ScrollIntoView的封装。
  • CElement::GetAtomTable。获取AtomTable,如果一个CElement没有atom table,那么可以证明它没有和CDoc关联。
  • CElement::GetPlainTextInScope。 返回这个CElement的所有文字部分,像是innerText的东西,这个函数的参数传入NULL的话返回的是文字长度。
  • CElement::MatchAccessKey。 接受键盘消息并判断是否为快捷键。
  • CElement::ShowMessage。 CElement::ShowMessageV的封装。
  • CElement::ShowMessageV。 CDoc的ShowMessageV的封装。
  • CElement::ShowLastErrorInfo。 CDoc的ShowLastErrorInfo的封装。
  • CElement::ShowHelp。 CDoc的ShowHelp的封装。
  • CElement::GetOmWindow。 返回CDoc的OmWindow成员。
  • CElement::UndoManager。CDoc的UndoManager的封装,获取撤销管理器的接口(IOleUndoManager)。
  • CElement::QueryCreateUndo。 CDoc的QueryCreateUndo的封装。判断是否应该建立一个撤销对象,会影响到CDoc的内容。
  • CElement::ShowTooltip。 显示站点的tooltip提示,如果有标题的话,那标题就是tooltip,可以把鼠标移动到tab上面看一看,一会儿出来的黄色方框就是tooltip。
  • CElementFactory::PrivateQueryInterface。 QueryInterface的实现。
  • CElementFactory::InvokeEx。 InvokeEx的实现。
  • CElement::MergeAttributes。 将指定元素的属性复制到另一个元素里面,只有相同元素名的元素才可以做此操作,这个操作发生在编辑元素时将一个元素以另一个替代时。merge的方式是将一个inline元素的aa给merge到另一个里面,对CAttrArray的Merge的封装。
  • CElement::GetNameSpaceParent。获取命名空间层次上的parent。通常发生在FORM里面,具体做法:从node的parent向上遍历所有branch到第一个form。当当前元素不在任何markup(即标识)中或者在主markup中,将返回omdoc作为命名空间,即返回Markup的IDispatchEx的query结果。非主markup时,返回Markup的DefaultDispatch的IDispatchEx。
  • CElement::CanShow。元素此时是否可以展示。加载完成时可以展示。
  • CElement::OnCssChangeStable。 当前元素在markup中时,向markup发送OnCssChange事件。
  • CElement::Invalidate。 Invalidate当前元素的区域。
  • CElement::GetElementRc。 返回元素位置。
  • CElement::ComputeHorzBorderAndPadding。 计算水平边框宽度和padding,位置计算辅助函数。
  • CElement::Clone。 字面意思,复制一个一模一样的元素。
  • CElement::StealAttributes。 从另一个元素里面“偷”来属性。计算完所有属性,创建完属性包之后如果需要替换元素的话,就会调用这个函数。
  • CElement::AddExtension。创建一个css扩展对象(css filter),alpha/blur/dropshadow这些滤镜,ie中它们是和一个clsid对应的,使用时都要CoCreateInstance创建对应实例才可以,其中,被实例化的对象必须标记为safe for initsafe for script,IPersistPropertyBag2或者IPersistPropertyBag中可以查询到SAFETY_INIT、SAFETY_SCRIPT的结果。
  • CElement::get_filters。返回filter collection。
  • CElement::toString。 BSTR转为CString。
  • CElement::setCapture。 设置鼠标捕获。
  • CElement::releaseCapture。 释放鼠标捕获。
  • CElement::WantTextChangeNotifications。返回该element是否应该有文本变换时的通知。
  • CElement::Notify。 通知处理函数。
  • CElement::EnterTree。 进入树,连接上事件处理器并保存。
  • CElement::ExitTree。 退出树,并发送notify,以及做维护工作。
  • CElement::IsEqualObject。 IObjectIdentity的实现,判断对象是否相等。
  • CElement::QueryService。 IServiceProvider的实现。
  • CElement::ContextThunk_Invoke。 IDispatch对应的Invoke。
  • CElement::ContextThunk_InvokeEx。 读取context(放到eax),然后传给ContextInvokeEx。
  • CElement::GetDispID。 获得DispID。
  • CElement::GetExpandoDispID。 获取Expando的Dispid。
  • CElement::GetNextDispID。 获取下一个DispID。
  • CElement::GetMemberName。 从dispid获取成员名。
  • CElement::GetMultiTypeInfoCount。 IProvideMultipleClassInfo的实现。
  • CElement::CreateTearOffThunk。 对::CreateTearOffThunk的封装。
  • CElement::get_dir/CElement::put_dir。 获取/设置dir属性。
  • CElement::GetLookasidePtr/CElement::SetLookasidePtr/CElement::DelLookasidePtr。当页面有LookasidePtr时,获取/设置/删除Lookasideptr。
  • CElement::put_onresize/CElement::set_onresize。 设置/获取onresize事件属性。
  • CElement::Fire_onfocus。 触发onfocus事件,FireEvent的封装,事件名是“focus”。
  • CElement::Fire_onblur。 触发onblur事件。
  • CElement::CreateAccObj。 根据不同的CElement tag(例如body、frameset、a,etc)创建CAccElement可访问对象。
  • CElement::GetParentAncestorSafe。 获取某个etag的祖先,有有效性判断。
  • CElement::GetIDHelper。 CElement的collection辅助函数。获取dispid。
  • CElement::SetIdentifierHelper。 辅助函数。 非script的标签,将所有关联的脚本全部unhook,设置name或者id,如果没有的话,从aa中获取指定的字符,命名设置标识符,然后hook上脚本。
    其中,第二个参数为DISPID_CElement_id、第三个参数为STDPROPID_XOBJ_NAME时设置ID,第2个参数为STDPROPID_XOBJ_NAME时设置NAME。第四个参数为STDPROPID_XOBJ_NAME时设置unique name(一个name只允许出现1次)。
  • CElement::SetIDHelper。 对SetIdentifierHelper的封装。设置ID。
  • CElement::GetnameHelper。 获取name。
  • CElement::SetnameHelper。 对SetIdentifierHelper的封装。设置name。

参考
layout http://msdn.microsoft.com/en-us/library/ie/ms530764(v=vs.85).aspx
layout #2 http://www.sitepoint.com/web-foundations/internet-explorer-haslayout-property/
ped 结构中的一个个体
node 节点
site 显示出来的一个实节点
style expression style中包含有expression表达式
clip:rect http://www.w3school.com.cn/cssref/pr_pos_clip.asp
list-style-image http://www.w3school.com.cn/cssref/pr_list-style-image.asp
ZParent, 按z-index排列的parent
non-site 是个元素,但是并没有展现出来(根本没有加入到文档里,而不是不可见之类的操作)
Block Element 块元素,块级元素生成一个元素框,(默认地)它会填充其父级元素的内容,旁边不能有其他元素。换句话说,他在元素框之前和之后生成了“分隔”符。我们最熟悉的HTML元素是p和div。
safe for initsafe for script http://msdn.microsoft.com/en-us/library/aa751977%28VS.85%29.aspx

Internet Explorer 完全解析 [1]

Internet Explorer 是一个广泛使用的浏览器,囿于微软的闭源策略,Internet Explorer的许多特性无法被人深入理解,现在,我要大概使用40章的篇幅来完全解析一下Internet Explorer的方方面面,最终我会给出修改好的pdf,40章我是随读随写的,所以不免会有问题,请以最终的pdf为准。

历史等介绍前略

文章来源 www.nul.pw

第1章 intro
1.1 Internet Explorer的结构
关于这部分,网上有一个很经典的图,大致描述起来就是 宿主-shdocvw.dll-mshtml.dll-html文档 的四层结构
mshtml是一个活动文档解释器,或者更通俗的说,mshtml负责解析html。既然是浏览器,无论是平时的分析还是漏洞测试,页面解析这块必然是重头戏,因此mshtml.dll是我这次首先开始解析的部分。

1.2 初识CBase
mshtml.dll中,可以作为“基类”存在的类为CBase,CBase实现了IDispatchEx、IProvideMultipleClassInfo、ISupportErrorInfo、IOleCommandTarget、ISpecifyPropertyPages这些基本的自动化接口,还有IObjectIdentity接口,用于判断两个对象是否相同。CBaseCF类实现了类厂(Class Factory)功能。

CDispParams类用来协助CBase的实现,它用来对DISPPARAMS进行二次封装(因此无疑它继承了DISPPARAMS),方便Invoke和InvokeEx这两个函数的调用(它们有一个参数是DISPPARAMS类型)。
CDispParams的成员变量都继承自父类DISPPARAMS,为rgvarg、rgdispidNamedArgs、cArgs、cNamedArgs。
关于DISPPARAMS的定义可以参见微软MSDN的介绍*1。其结构如下:

typedef struct FARSTRUCT tagDISPPARAMS {
  VARIANTARG FAR*  rgvarg; //参数数组
  DISPID FAR* rgdispidNamedArgs; //已命名参数的DISPID(Dispatch identifiers)
  unsigned int cArgs; //参数个数 
  unsigned int cNamedArgs; //已命名的参数的个数
} DISPPARAMS;

其成员函数及作用列举如下:
【1】HRESULT CDispParams::Create(DISPPARAMS*)
这个函数即会从DISPPARMS*中获取对应参数信息,参数数组的初始化值是VT_NULL,命名参数数组的初始化值是DISPID_UNKNOWN。

【2】HRESULT CDispParams::MoveArgsToDispParams (DISPPARAMS *pOutDispParams, UINT cNumArgs, BOOL fFromEnd)
这个函数的作用是把参数数组的内容给移动到pOutDispParams中。其实它虽然调用了memcpy,但是事实上只是把参数列表(CDispParams的成员变量:rgvarg[])的指针给复制到了pOutDispParams->rgvarg里,所以现在pOutDispParams和CDispParams事实上拥有了同样的对象(当然,释放的话只能释放一次)。最后一个参数指定了复制时是从最后(index: cArgs - cNumArgs)开始复制还是从最开始(index: 0)开始复制。

1.3 CBase的_pAA,大家的_pAA

在介绍CBase之前,也许非常有必要去解释一下常见的_pAA参数,这个参数实际上是“样式属性数组的指针”的缩写(pointer of attribute array),它是CAttrArray类的一个实例,之所以将它提前介绍,是因为_pAA就是元素的样式表,因此它的身影几乎贯穿在所有的类中间。CAttrArray继承于CAttrValue
CAttrValueflags数据结构如下:

  31              24  23              16  15             8  7           0
  -----------------------------------------------------------------------
  |                 |                   |                 |             |
  |     VT_TYPE     |              AAExtraBits            |    AATYPE   |
  |                 |                   |                 |             |
  -----------------------------------------------------------------------

struct AttrFlags {
    BYTE    _aaType;
    WORD    _aaExtraBits;
    BYTE    _aaVTType;
};

_aaType指定了这个AA是什么类型的,_aaExtraBits指定了这个AA的具体类型,例如important类型(“important”)、隐含式(“background: none”)之类的,最后一个_aaVTType指定VT_TYPE。这个类具体来说就是样式表的解析器。

CAttrArray继承自CAttrValue(CDataAry)(see formsary),CBase是它的友类。这个类综合了多个AttrValue,然后提供了一组函数便于查找、增删、对比和修改AttrValue的函数。

1.4 CBase的成员函数

  • CBase::CBase。构造类中初始化了所有变量。
  • CBase::CBaseCheckThread。
    如果CBase被线程A创建,那么只有A能访问这个创建的CBase对象。这个函数的用途是检查引用CBase对象的线程是否是创建这个被引用的CBase对象的线程。
  • CBase::Init。检查是否正确初始化。
  • CBase::Passivate。通过释放对其他对象的引用、清理资源使用来释放main对象。主对象引用数为0的时候会调用这个函数。主对象和所有子对象的引用为0时会调用析构函数。在析构时,Passivate还会删除_pAA对象。
  • CBase::IsEqualObject。实现了IObjectIdentity,判断一个对象是否和自身相同,具体方式:自己和待比较对象分别获取IID_IUnknown接口,比较最终结果是否相同。
  • CBase::PrivateQueryInterface。IPrivateUnknown中的实现,和每个IIDTable中的值相比较,实现查询指定栈接口功能。如果传递的是非栈接口,那么代表着很可能所有派生类都需要在各自的PrivateQueryInterface中处理这些消息。
  • CBase::PrivateAddRef/CBase::PrivateRelease。IPrivateUnknown中的实现,AddRef/ReleaseRef用。
  • CBase::SubAddRef/CBase::SubRelease。SubBase的Add/Release Ref用。
  • CBase::CLock::CLock/CBase::CLock::~CLock。增加/释放CBase对象中资源锁用的函数。
  • CBase::QueryService。空函数,需要由派生类重载。目的是为了查询Service,传入的参数分别是:service的GUID、service interface的IID、service的pointer of pointer。
  • CBase::GetPages。ISpecifyPropertyPages中的实现,用来指定这个对象可以接受的属性页,传来的参数指向一个UUID(CLSID)的数组。CLSID分为两个部分,每组都以NULL结尾,第一组用作浏览模式(run-mode),第二组用于编辑模式(design mode)。
  • CBase::HasPages。返回这个对象是否支持任何属性页,返回值是bool类型。其实就是检查property pages中是否有元素。
  • CBase:GetClassInfo。IProvideMultipleClassInfo的实现,返回控件的coclass 信息。
  • CBase:GetGUID。IProvideMultipleClassInfo的实现,接受两个参数,第一个是guid的类型,第二个是接受guid的缓冲区的指针,根据第一个guid类型返回对应的guid。
  • CBase::GetMultiTypeInfoCount。IProvideMultipleClassInfo的实现,返回这个对象上type info的数量。
  • CBase:GetAggMultiTypeInfoCount。IProvideMultipleClassInfo的聚合体(无构造函数、无非公开的成员函数、无基类、无虚函数的数组、类或者结构体*2)的帮助函数。函数接受聚合体的IUnkown接口,通过读取其IID_IProvideMultipleClassInfo接口获得一个IProvideMultipleClassInfo实例,然后调用其GetMultiTypeInfoCount获取其type info 的数量。
  • CBase:GetInfoOfIndex。IProvideMultipleClassInfo的实现,从index的type info中获取信息。
  • CBase::GetAggInfoOfIndex。在一个聚合体中,根据index获取一些type info。
  • CBase:InterfaceSupportsErrorInfo。ISupportErrorInfo的实现,当传入的接口(传入的是IID)支持错误信息的时候返回true。
  • CBase::GetClassID。 IPersist的实现。
  • CBase::InvokeDispatchWithThis。 它的作用是,接受IDispatch*的变量,通过把这个Dispatch的IUnknown加到参数列表里面来引用(invokes)它。它的动作为:
    1. 获取传入的pDisp的IDispatchEx(/IDispatch)接口;
    2. 将其信息保存到DISPPARAMS的rgvarg中去;
    3. 调用其InvokeEx(/Invoke)函数 (DISPID_VALUE);*3
    4. 如果需要传递这个参数,而且参数数量>1时,用原始参数替代现在的参数。
      其实,主要的就是在第三步 。
  • CBase::InvokeDispatchExtraParam。依然是invoke pDispatch,多了一个额外参数。
  • CBase::Invoke。 IDispatch中的标准实现。
  • CBase::GetIDsOfNames。 IDispatch中的标准实现。
  • CBase::InvokeAA。 辅助函数。
    类型为PROPERTY_GET、实际参数为0个的时候,从dispidMembers中找到对应的aaType,返回这个VARIANT。
    类型为PROPERTY_PUT、实际参数为1个的时候,dispMembers中找到对应的aaType,把已知(未知)的aaType替换掉(加到)dispMembers的ArgList里面的对应位置,通知dispidMember有OnPropertyChange。
    flags为GET/METHOD但是执行失败时,试着invoke一次。具体做法:dispMembers找到对应的aaType(返回AAINDEX),如果是已知的AAINDEX,获取该位置上对应的dispatch对象,然后调用InvokeDispatchWithThis来invoke。
  • CBase::ContextInvokeEx。真的InvokeEx实现,用传入的参数来调用函数。~
  • CBase::GetDispID。IDispatchEx中的实现。
  • CBase::DeleteMemberByName/CBase::DeleteMemberByDispID。 IDispatchEx中的实现。
  • CBase::GetMemberProperties/CBase::GetMemberName。IDispatchEx中的实现。后者返回ExpandoName或者ExposedName或者Name。
  • CBase::GetNextDispID。IDispatchEx中的实现。从ITypeInfo中遍历所有项目的值,然后枚举所有expando的属性。这个函数会枚举所有普通的property和所有expando。
  • CBase::GetNameSpaceParent。IDispatchEx的实现。
  • CBase::GetInternalDispID。 GetIDsOfName的封装。
  • CBase::GetExpandoDispID。 获取Expando的DispID。
  • CBase::GetInternalNextDispID。 获取NextDispID。
  • CBase::NextTypeInfoProperty。 获取下一个TypeInfo Property(通过INVOKE_PROPERTYGET/PUT来区分)。
  • CBase::AddExpando。 辅助函数, 将一个expando property添加到attr array中。
  • CBase::SetExpando。 辅助函数,顾名思义,Set Expando的property。
  • CBase::GetNextDispIDExpando。 获取dispid的下一个expando。~
  • CBase::GetExpandoName。 获取expando的名字。
  • CBase::NextProperty。 辅助函数,遍历用。遍历范围和GetNextDispID一样。
  • CBase::FindNextAttach。 遍历找到下一个attach event的attr array。
  • CBase::GetTheDocument。 从IID_IHTMLDocument2获取接口,然后从接口中获取IID_IDispatchEx,再获取"document"的dispID,然后Invoke,并从中返回一个IHTMLDocument2*,写入*ppDoc(传入时类型是IHTMLDocument2** ppDoc)。
  • CBase::FindEventName。 从event table中找到对应的Event。
  • CBase::FireEvent。 从主dispatch中触发一个与之关联的事件。仔细看一下这个函数。
    1.首先触发任何会导致这个event的property (除了OnError);
    2.触发任何附加的事件;
    3.触发任何通过普通连接点监听着的property,它们通过一个内部的dispid存在AA里;这一步里,遍历所有AA,然后存储起来,最后通过ITridentEventSink的FireEvent调用它们;
  • CBase::FireAttachEvents。触发所有通过attachEvent(脚本)附加的事件。
  • CBase::FireAttachEventV。FireAttachEvents的封装。根据arrayType将va_list转为DISPPARAMS。
  • CBase::FireEventV。跟上面的类似,FireEvent的封装。
  • CBase::FirePropertyNotify。 触发Property OnChanged或者OnRequestEdit事件。
  • CBase::FireCancelableEvent。试图触发事件,然后返回脚本的返回值。封装的是FireEventV。
  • CBase::GetEnabled/CBase::GetValid。 辅助函数,多数函数都会重载它。
  • CBase::FindAAType。 AttrArray的辅助函数。_pAA中查找AAIndex并返回查找到的序数。
  • CBase::DidFindAAIndexAndDelete。 查找到AA之后把它删除,对FindAAType的封装。
  • CBase::FindNextAAIndex。 通过给定的dispid和AAType查找AAIndex。
  • CBase::GetStringAt。 获取指定AAINDEX上的字符串。
  • CBase::GetIntoBSTRAt/CBase::GetIntoStringAt。 获取指定AAINDEX上的字符串到BSTR/LPCTSTR变量中。
  • CBase::GetPointerAt/CBase::GetVariantAt。获取指定AAINDEX上的指针/VARIANT。
  • CBase::FetchObject。 找到AttrValue对象后,如果类型是VT_UNKNOWN/VT_DISPATCH给其IUnknown/IDispatch接口增加1个计数。
  • CBase::GetObjectAt。 找到对象后调用FetchObject。
  • CBase::GetCookieAt/CBase::SetCookieAt。 找到对象后获取/设置其Cookie,Cookie是一个DWORD类型的变量。
  • CBase::GetAAtypeAt/CBase::GetVariantTypeAt。 找到对象后获取其AAType/VariantType。
  • CBase::Add*****。 各种添加函数,封装了Set函数。
  • CBase::AddUnknownObjectMultiple/CBase::AddDispatchObjectMultiple/CBase::AddDispatchObject。 把一个对象加入attr array,表明这个对象在指定的DISPID下允许有多个入口。(VT_UNKNOWN/VT_DISPATCH的区别)
  • CBase::StoreEventsToHook。 对CAttrValue的StoreEventsToHook的封装。
    1.先调用CAttrArray::EnsureHeader,这个函数的作用是将this转为CAttrValue*类型,然后获取自己的GetAVType (AttrValue Type),如果确认类型是CAttrValue::VT_AAHEADER,则返回转换后的CAttrValue*,否则,调用SetHeader()设置一个头,然后返回转换后的CAttrValue*,失败的话返回NULL。
    2.调用CAttrValue*的SetEventsToHook函数,传入这个函数的参数,类型为InlineEvts*。CAttrValue::SetEventsToHook其实就做了一件事:GetAAHeader()->_pEventsToHook = pEventsToHook;。
  • CBase::GetEventsToHook。与Set一样,最后获取到_pEventsToHook的值,返回。
  • CBase::ChangeSimpleAt。 修改指定AAIndex位置的VARIANT为一个VT_I4类型的值,值为long类型的dwSimple(由参数传入)。
  • CBase::ChangeStringAt/CBase::ChangeUnknownObjectAt/CBase::ChangeDispatchObjectAt/CBase::ChangeVariantAt/CBase::ChangeAATypeAt。 同上,换成了VT_LPWSTR/VT_UNKNOWN/VT_DISPATCH/some variant/some aatype。
  • CBase::FindVTableEntryForName。 从vtable中找到对应名字的项目并返回。返回VTABLEDESC*类型,这是一个结构体。
  • CBase::getAttribute。获取指定的DISPID,然后FindPropDescFromDispID从中找到Property返回。 ~
  • CBase::removeAttributeDispid/CBase::setAttribute。 从指定的DISPID删除/设置属性。
  • CBase::IsExpandoDISPID。 判断某个DISPID是否是一个Expando DISPID。
  • CBase::toString。将DISPID_VALUE获取到的内容转为 BSTR。
  • CBase::FindPropDescFromDispID。通过DISPID找到PROPERTYDESC。 先获取vtable array(struct VTABLEDESC*),然后从property desc中找到对应的dispid。
  • CBase::DefaultMembers。 调用Default将所有prop desc list的所有类成员设置为成员。
  • CBase::GetEnumDescFromDispID。IPerPropertyBrowsing的辅助函数,从给定的dispid(property)中获取enumdesc。
  • CBase::GetDisplayString。 IPerPropertyBrowsing::GetDisplayString,获取指定的dispid(property)对应的字符串值。
  • CBase::MapPropertyToPage。IPerPropertyBrowsing::MapPropertyToPage的实现。
  • CBase::GetPredefinedStrings。 IPerPropertyBrowsing::GetPredefinedStrings的实现。返回一个带字符数量的string array(也即LPOLESTRS指针),里面是支持的所有可用的dispid(property)。
  • CBase::GetPredefinedValue。 IPerPropertyBrowsing::GetPredefinedValue的实现。从某个dispid返回关联到某个预定义字符串的variant。
  • CBase::attachEvent。把一个AA_AttachEvent添加到attrarray里面,以支持多播onXXXX事件。~
  • CBase::detachEvent。遍历attrarray中的AA_AttachEvent,移除第一个发现的COM标识和传入的pDisp一样的项目。
  • STDAPI MatchExactGetIDsOfNames。遍历ITypeInfo里面的所有property,导出函数,shdocvw.dll会使用。
  • LONG GetArgsActual。 辅助函数,从dispparms的cArgs中获取准确的参数数量;如果有DISPID_THIS的话,数量减一。

1.5 总结:CBase
CBase类是mshtml.dll中提供的一个基础类,提供了许多功能函数的支持,同时也支持重要的事件操作(例如支持javascript中的addEventListener) ,之后还会看到很多与之有关联的类,本节到此结束。

引用
1 http://msdn.microsoft.com/en-us/library/aa912051.aspx
2 http://msdn.microsoft.com/en-us/library/sdwe79a4.aspx
3 http://msdn.microsoft.com/en-us/library/asd22sd4(v=vs.94).aspx
引申
expando http://msdn.microsoft.com/en-us/library/ie/ms533747(v=vs.85).aspx
attachEvent http://msdn.microsoft.com/en-us/library/ie/ms536343(v=vs.85).aspx
标记
~ wait for further analysis
property 可以简单的理解为dispid