竞赛:| 全国大学生数模竞赛 | 全国研究生数模竞赛 | 全国大学生电工数模竞赛 | 美国"MCM/ICM" 竞赛 |
 资讯:| 数学理论 | 交叉学科 | 基础教育 | 考研数学 | 学术动态 | 编程交流 | 网络安全 | 经验技巧 |
 下载:| 数 学 篇 | 算 法 篇 | 建 模 篇 | 编 程 篇 | 数 据 篇 | 软 件 篇 | 考 研 篇 | 交叉学科 |
 视频:| 大学数学 | 大学英语 | 计 算 机 | 法律课程 | 政治课程 | 经济管理 | 数学建模 | 高考数学 |
 论坛:| 建模竞赛 | 学校协会 | 资源交流 | 数学门类 | 数学软件 | 管理应用 | 论坛公告 | 娱乐论坛 |


 
会员中心
社区论坛
加入收藏
联系我们
您现在的位置: 数学中国 >> 资讯无限 >> 计算机技术 >> 经验技巧 >> 文章正文
【字体:           ★★
 
在ATL中调试接口(并剖析其内幕)
作者:未知    文章来源:本站原创    点击数:    更新时间:2006-10-30

ATL中调试接口(并剖析其内幕)

ATL提供了在运行时在你的对象的任何接口中反馈QueryInterface,AddRef,Release结果信息在VCDebugoutput窗口上显示,这个特征不在默认范围里,且只能在调试模式下,在Microsoft Visual C++没有办法让它们通过用户界面来,代替的方法,你应该在stdafx.h中定义宏来输出调试信息,现在有两个选项可用的:QueryInterface调试,需要定义_ATL_DEBUG_QI来进行预处理,引用计数调试,需要定义_ATL_DEBUG_INTERFACES来进行预处理,两个选项可同时或单独使用,但你要记住这两个选择应用于该组件的所有对象,之后在stdafx.h中插入它们(在atlbase.h之前)(意思就是要你在stdafx.h的文件中在#include 前面加入这两个选项的定义),然后重新编译项目。

调试QueryInterface

如果你要观察QueryInterface调用后的结果状况,你需要在组件中定义_ATL_DEBUG_QI预处理宏,任何调用该组件的QueryInterface后的调试信息会发送到VC的调试输出窗口,信息包含了类名,接口名。当调用QueryInterface后,如果接口名没有注册,将被接口ID代替,如果调用QueryInterface失败,调试窗口输出“failed”,以下是在定义了_ATL_DEBUG_QI的输出情况(前提是你要调用组件):

定义_ATL_DEBUG_QI后的输出

Microsoft Visual Base中调用组件对象:

VB的客户端代码.

QueryInterface 依靠所用QueryInterface都封装在CComObjectRootBase::InternalQueryInterface,当宏调用(定义了预处理宏)后储存相关调用结果,以下是InternalQueryInterface代码:

static HRESULT WINAPI InternalQueryInterface(void* pThis,
    const _ATL_INTMAP_ENTRY* pEntries, REFIID iid,
    void** ppvObject)
{
    ATLASSERT(pThis != NULL);
    // First entry in the COM map should be a simple map entry
    ATLASSERT(pEntries->pFunc == _ATL_SIMPLEMAPENTRY);
#if defined(_ATL_DEBUG_INTERFACES) || defined(_ATL_DEBUG_QI)
    LPCTSTR pszClassName = (LPCTSTR) pEntries[-1].dw;
#endif // _ATL_DEBUG_INTERFACES
    HRESULT hRes = AtlInternalQueryInterface(pThis, pEntries,
        iid, ppvObject);
#ifdef _ATL_DEBUG_INTERFACES
    _Module.AddThunk((IUnknown**)ppvObject, pszClassName, iid);
#endif // _ATL_DEBUG_INTERFACES
    return _ATLDUMPIID(iid, pszClassName, hRes);
}
最后一行源程序中进行输出,使用了_ATLDUMPIID,这个宏是有条件编译的(是否定义了_ATL_DEBUG_QI),宏的原型:

#ifdef _ATL_DEBUG_QI
#define _ATLDUMPIID(iid, name, hr) AtlDumpIID(iid, name, hr)
#else
#define _ATLDUMPIID(iid, name, hr) hr
#endif

如果你没调试QueryInterface(即没有定义预处理宏),AtlInternalQueryInterface该宏简单地返回HRESULT;否则,它产生了调用AtlDumpIID,通过接口ID,类名,及QueryInterface的结果。AtlDumpIID输出类名并尝试在注册表中使用接口ID来取得接口名,如果没有找到,就把接口进行输出。

AtlDumpIID也同样是用来跟踪接口的引用计数,通过这个方法来取得更多的信息。

接口引用计数调试

ATL提供了跟踪接口的引用计数的基本情况,要使用这各特征,你仅需要定义预处理宏_ATL_DEBUG_INTERFACES.任何时候在接口中调用AddRefRelease的话,引用计数,类名与接口名将发送到VC调试输出窗口,如下图片:

定义_ATL_DEBUG_INTERFACES后的输出信息。

上面的信息中“>”符号指示调用AddRef,“<”符号指示调用ReleaseATL在每一个组件接口中产生调试结构信息,该结构中在接口的调用中截取信息并输出,截取处理开始于当客户端调用ATL的对象QueryInterface接口后,将产生调用CComObjectRootBase::InternalQueryInterface与看调试接口信息的方法 一样。 可是,此时代替的是在InternalQueryInterface中调用AddThunk,如下:

#ifdef _ATL_DEBUG_INTERFACES
    _Module.AddThunk((IUnknown**)ppvObject, pszClassName, iid);

在这里,引用在相同的级别上间接地加入到虚函数的调用顺序来到达输出调试信息的目的,这后完成调试信息的输出,调用是在前面原始的目标函数。如里调用中没有AddRefRelease,它通过thunk码到目标对象,AddThunk在接口开始询问的时候开始创建了新的_QIThunk结构,如果不存在基它COM定义,_QIThunk隐藏了接口指针与所有IUnknown方法的V Table登记表,任何调用接口将先调用thunk,因为ATL不知道接口的任何方法,除了IUnknown的必要方法,其它方法与前面提到的1024之中的占位符都在_QIThunk(扣掉IUnknown的个数),额外的个数定义了方法占位符个数,如下:

STDMETHOD(f3)();
...
STDMETHOD(f1023)();
STDMETHOD(f1024)();

第一个占位符有相应的实现,IMPL_THUNK原型如下:

IMPL_THUNK(3)
...
IMPL_THUNK(1023)
IMPL_THUNK(1024)

IMPL_THUNK展开后内联了汇编代码,前面的方法调用者在这个对象的当前V-Table表中,如下:

#define IMPL_THUNK(n)
_ _declspec(naked) inline HRESULT _QIThunk::f##n()
{
    _ _asm mov eax, [esp+4]
    _ _asm cmp dword ptr [eax+8], 0
    _ _asm jg goodref
    _ _asm call atlBadThunkCall
    _ _asm goodref:
    _ _asm mov eax, [esp+4]
    _ _asm mov eax, dword ptr [eax+4]
    _ _asm mov [esp+4], eax
    _ _asm mov eax, dword ptr [eax]
    _ _asm mov eax, dword ptr [eax+4*n]
    _ _asm jmp eax
}
在这个函数里,ESP寄存器是用来修改对象的QIThunk来隐藏pUnk接口指南,VTable登记表在宏IMPL_THUNK的参数中传来的参数来进行重新计算偏移量,这段代码依靠在_QIThunk的内部成员来隐藏IUnknown来指向目标对象(pUnk,在内存中的VTable指针之后放它的开始指针。

正常AddRefRelease方法是不通过IMPL_THUNK,代替的是,它们是直接在前面的对象中加入,在里面以调用AtlDumpIID 来产生调试信息,_QIThunk::AddRef 的原型:

STDMETHOD_(ULONG, AddRef)()
{
    if(bBreak)
        DebugBreak();
    pUnk->AddRef();
    return InternalAddRef();
}
ULONG InternalAddRef()
{
    if(bBreak)
        DebugBreak();
    ATLASSERT(m_dwRef >= 0);
    long l = InterlockedIncrement(&m_dwRef);
    ATLTRACE(_T("%d> "), m_dwRef);
    AtlDumpIID(iid, lpszClassName, S_OK);
    if(l > m_dwMaxRef)
        m_dwMaxRef = l;
    return l;
}
通过IMPL_THUNK代码,就没有明确地使用AddRefRelease,它允许ATL在取得前面没有IUnknown方法的接口后来捕获AddRef Release.

文章录入:babybluema    责任编辑:madio  
  • 上一篇文章:

  • 下一篇文章:
  • 发表评论】【加入收藏】【告诉好友】【打印此文】【关闭窗口
    推 荐 文 章
    更多内容
     
    热 门 文 章  
    更多内容
     

    关于鼓励研究生深入…
    相 关 文 章
    更多内容
     
    ATL问题集
    | 设为首页 | 加入收藏 | 联系站长 | 友情链接 | 版权申明 | 管理登录 |