没有找到合适的产品?
联系客服协助选型:023-68661681
提供3000多款全球软件/控件产品
针对软件研发的各个阶段提供专业培训与技术咨询
根据客户需求提供定制化的软件开发服务
全球知名设计软件,显著提升设计质量
打造以经营为中心,实现生产过程透明化管理
帮助企业合理产能分配,提高资源利用率
快速打造数字化生产线,实现全流程追溯
生产过程精准追溯,满足企业合规要求
以六西格玛为理论基础,实现产品质量全数字化管理
通过大屏电子看板,实现车间透明化管理
对设备进行全生命周期管理,提高设备综合利用率
实现设备数据的实时采集与监控
利用数字化技术提升油气勘探的效率和成功率
钻井计划优化、实时监控和风险评估
提供业务洞察与决策支持实现数据驱动决策
翻译|其它|编辑:郝浩|2004-02-04 11:39:00.000|阅读 1506 次
概述:
# 界面/图表报表/文档/IDE等千款热门软控件火热销售中 >>
Dale Rogerson
Microsoft
网络开发技术小组
摘要
这篇文章考察了使用C++巧妙的接口指针去访问元件对象模型(COM)接口。SICBENT样本应用程序与这篇文章相关。
介绍
很久以前,在非常黑暗的过去,Nigel
Thompson写了一系列关于被称为"OLE傻瓜书"(MSDN库,技术文章)的OLE编程的技术笔记。当时走廊的上上下下都能听到他痛苦地叫喊着忘记了要正确地添加或释放一个接口。我想本应该有一些方法在使用C++中巧妙的指针时能自动释放元件对象模型(COM)元件,使得元件对象模型元件的使用更为容易一些。不过,我开始研究的却是Microsoft
基础类库(MFC),在这个课题中引用计算并不是一个重点,因为它隐藏在MFC类之中。
在写完文章"MFC/COM对象8:重访无MFC的多重继承"之后,我决定再考虑使用巧妙的指针类简化使用元件对象模型(COM)接口。研究的结果并没有实现我的愿望,我开始怀疑是否能在我自己的代码中使用巧妙的指针接口。不过,你的元件对象模型(COM)项目可能与我的不一样,所以我决定不用元件对象模型(COM)元件也许并不会影响你。
在这篇文章中,我将讨论下列主题:
在这篇文章的源代码中,我使用前缀PI指示一个指向接口的指针,例如:
IPersist* pIPersist
;
I use the prefix SI to refer to a smart interface
pointer:
CSmartInterface<IPersist> SIPersist ;
创建一个巧妙的接口指针类的原因
我想要一个巧妙的接口指针类的原因是要自动地添加和释放接口指针。
使用元件对象模型(COM)接口指针时,你必须要遵循几条规则。首先,绝不要在一个接口指针上调用删除(delete)来代替Release。下面的代码是不正确的:
IDraw*
pIDraw ;
CoCreateInstance(...,IID,_IDraw, (void**)&pIDraw)
;
pIDraw->Draw(0,0,100,100) ;
delete pIDraw ; // Don't delete an
interface pointer.
下面的代码是正确的:
IDraw* pIDraw
;
CoCreateInstance(...,IID,_IDraw, (void**)&pIDraw)
;
pIDraw->Draw(0,0,100,100) ;
pIDraw->Release()
;
C++程序设计员通常delete一个对象指针。因为这个原因,C++程序员容易忘记并且在一个接口指针上用delete代替Release。它也是C++程序员很难发现的一个错误,因为删除指针太自然了。
第二条规则是在创建新的指针时调用AddRef 。下面的代码是不正确的:
IDraw* pIDraw
;
CoCreateInstance(...,IID,_IDraw, (void**)&pIDraw)
;
pIDraw->Draw(0,0,100,100) ;
delete pIDraw ; // Don't delete an
interface pointer.
下面的代码是正确的:
IDraw* pIDraw
;
CoCreateInstance(...,IID,_IDraw, (void**)&pIDraw)
;
pIDraw->Draw(0,0,100,100) ;
pIDraw->Release()
;
上面的例子太小了,但是,在复杂的代码中,这个错误是很难跟踪到的。
使用巧妙的接口指针类并不只有唯一一个原因。DonBox在C++报道中提到了其他的一些原因(请参阅本篇文章末尾的书目)。
如何创建一个巧妙的接口指针类
一个巧妙的接口指针类开始创建的方式与一个巧妙的指针类是一样的:通过为一个类执行操作符-》。这个处理过程也可被称为委派。通过覆盖操作符->,我们可以做一个类模仿一个指针调用。例如:
void
Draw()
{
CSmartInterface<IDraw>
SIDraw;
CoCreateInstance(...,IID,_IDraw, (void**)&SIDraw)
;
SIDraw->Draw() ;
}
在上面的代码中有几点需要注意。首先,一个模板类被用于执行巧妙的接口指针。这使得巧妙的指针接口类是个安全类型。第二,就象我们就要看到的,操作符-已经被超载去返回一个包括在CsmantInterface中的指针的地址。第三,即使SIDraw不是一个指针,我们也使用操作符->去调用Idraw接口中的成员。第四,我们不调用Release是因为在栈上已经创建了CxmartInterface,析构函数会自动调用Release。
下面是CsmartInterface头文件中重要的部分,所以的成员函数和操作符都在头文件中稍后的部分中定义了。
CsmartInterface包含着一个指向一个接口的指针。为了类型安全,它被定义为一个模板函数。CsmartInterface的实质是超载操作符->。
template
<class I>
class CSmartInterface
{
public:
//
Construction
CSmartInterface(I* pI = NULL) ;
// Copy
Constructor
CSmartInterface(const CSmartInterface<I>& rSI)
;
// Destruction
~CSmartInterface() ;
// Assignment from
I*
CSmartInterface<I>& operator=(I* pI) ;
//
//
Operators
//
// Conversion
operator I*() ;
// Deref
I*
operator->() ;
// Address of
I** operator&() ;
//
Equality
BOOL operator==(I* pI) const;
// Inequality
BOOL
operator!=(I* pI) const;
// Negation
BOOL operator!() const
;
protected:
I* m_pI
;
};
所以,来自前一个例子的SIDraw->Draw()导致了对SIDraw.m_PI->Draw()的调用。SIDraw把Draw调用委派给m-PI指向的接口。这种方式的强大之处在于CsmartInterface<Idraw>类不需要在每个新函数被增加到Idraw接口时被改变
。不过,就象我们看到的,CsmartInterface<Idraw>不能停止对Idraw接口的个别调用。
为了使CsmartInterface成为纯粹的C++指针的一个更为令人可信的模拟,处理操作符->还需要定义别的操作符。事实上,做一个巧妙的指针类的最困难的事情是确保所有用在指针上的操作符都已经定义并且有意义。例如:当我把下面的代码从if(PISimple==NULL)转变成if
(SISimple == NULL)。。。。。。,我就必须为我的巧妙的指针类 定义操作符==。生成的代码编译没有错误,不过,它包含着一个错误因为它把NULL
与SISimple比较而不是和我已经扩展的SISimple.m_PI比较。在我定义了操作符==后,这个错误消失了。你喜爱的C++编程书的目录应该列出了所以你需要定义的操作符,从而充当一个方便的查询表。为了安全起见,我定义了我认为我不需要的操作符--这样如果我视图用他们就会得到一条错误信息。类似于C/C++用户月刊中的Robert
Mashlan的"C++中被查过的指针
"的文章能真正帮助你理解巧妙的指针。超载绝大部分需要的操作符使很直接明白的。最有趣的是操作符=:
template <class I>
inline
CSmartInterface<I>& CSmartInterface<I>::operator=(I*
pI)
{
if (m_pI != pI) //OPTIMIZE: Same pointer AddRef/Release not
needed.
{
if (m_pI != NULL)
m_pI->Release() ; //Smart Pointers don't
use smart pointers :-)
m_pI = pI ;
if (m_pI !=
NULL)
m_pI->AddRef() ;
}
return *this ;
}
操作符=会自动地为你添加和释放接口。如果CsmartInterface已经指向一个接口,它将会释放它并且添加新的接口。这个操作符=的定义允许下列操作:
void
DrawThemAll()
{
CSmartInterface<IDraw> SIDraw ;
for (int i = 0 ;
i < MAX ; i++ )
{
SIDraw = pIDraw[i] ;
SIDraw->Forward(x)
;
}
}
上面的代码依靠CsmartInterface的析构函数去释放指针,方法是:
template <class I> inline
CSmartInterface<I>::~CSmartInterface()
{
if (m_pI !=
NULL)
{
m_pI->Release();
}
}
我们使用恰当的构造函数可以更好地利用析构函数:
template
<class I> inline
CSmartInterface<I>::CSmartInterface(I* pI
/*=NULL*/)
: m_pI(pI)
{
if (m_pI != NULL)
{
// AddRef if we are
copying an existing interface pointer.
m_pI->AddRef()
;
}
}
现在我们的例子可以改为:
void DrawThemAll()
{
for (int i = 0 ; i
< MAX ; i++ )
{
CSmartInterface<IDraw> SIDraw(pIDraw[i])
;
SIDraw->Forward(x)
;
}
}
上面的代码运行过一个Idraw接口指针的列表,添加他们,使用他们和释放他们。
关于这一点你还可以作的更多。DonBox在他的专题里把这一点阐述得更为深入。他定义了他的CsmartInterface的等价物去接受接口的ID和类型。然后他定义了一个构造函数,一旦有了来自不同接口的赋值,这个接口函数将调用Query
Interface:
CSmartInterface(IUnknown* pI)
: m_pI(NULL)
{
if (pI
!= NULL)
pI->QueryInterface(*piid, (void**)&m_pI)
;
}
上面的构造函数允许我们把例子改为:
void DrawThemAll()
{
for (int i = 0 ; i
< MAX ; i++ )
{
CSmartInterface<IDraw, &IID_Draw>
SIDraw(pIUnknown[i]) ;
SIDraw->Forward(x)
;
}
}
这个例子开始表现这种技术的强大之处,上面的代码类似于:
void
DrawThemAll()
{
IDraw* pIDraw ;
for (int i = 0 ; i < MAX ; i++
)
{
pIUnknown[i]->QueryInterface(IID_Draw, (void**)&pIDraw)
;
pIDraw->Forward(x) ;
pIDraw->Release()
;
}
}
操作符=也能用同样的方式扩展,所以类似于SIDraw=PIUnknown;这样的赋值将会调用QueryInterface。我并不热衷于把执行藏在看起来无害的操作符后
,尽管我不得不说用这种方式超载操作符=是非常令人信服的方式。Visual Basic
4.0版本在把一个元件对象模型分配给另一个时就要调用QueryInterface。
使用巧妙的接口指针类
使用CsmartInterface有两个主要的规则。首先,不要在CsmartInterface对象上调用Release。
void
Draw()
{
CSmartInterface<IDraw>
SIDraw;
CoCreateInstance(...,IID,_IDraw, (void**)&SIDraw)
;
SIDraw->Draw() ;
SIDraw->Release() ; // Will compile, but is a
bug.
}
当SIDraw->Release被调用,则SIDraw->m_pl被释放。当SIDraw->m_PI的析构函数被调用时,只要接口还没有
被释放,SIDraw->m_PI将再一次被释放。这个问题并不是难以调试,如果你有关于引用计数的问题,你可以查找一下所有发生的Release。另一个途径是适用#define
Release
BOGUS_DO_NOT_CALL_RELEASE!但是这样使用Release则会产生一个错误。当然,如果你的应用程序里其他带有"Release"这个词的函数(就象许多Win32应用程序接口[APIS],这个方法将行不通。
第二条规则是避免把CsmartInterface对象使用成指针。如果你这样做,情况将很混乱:
CSmartInterface<ISimple>*
pSISimple
= new CSmartInterface<ISimple>(m_pISimple)
;
(*pSISimple)->Inc() ;
delete pSISimple
;
使用typedef将能将这种情况稍稍整理一下:
typedef CSmartInterface<ISimple>
CSmartISimple ;
CSmartISimple* pSISimple = new CSmartISimple(m_pISimple)
;
(*pSISimple)->Inc() ;
delete pSISimple
;
不过,这并不能改变(*PSISimple)->Incc);实际上不是非常简单的事实。
如果我们考虑一下为什么我们想要一个指向接口的指针,我们可能会发现环绕着问题的一条路。我们想在程序中的一些任意点释放接口--而不是要等到CsmartInterface已经不在范围之内。那么问题将变成我们如何释放包含在CsmartInterface中的接口。答案非常的简单:SISimple=NULL;虽然这是一句很典型的C代码,但是这儿到底发生了什么却一点都不明显。
pISimple->Release()
;
and
delete pSISimple ;
都是更明显地指示对象已经消失的方法。
我不喜欢巧妙的接口指针类的原因
我为什么不打算使用巧妙的指针类有几点原因。所有这些理由的要点是CsmartInterface并不象C++。在一个对象上使用操作符->而不是指向对象的指针,这真的很奇怪。
一个相关的原因是使用指向CsmartInterface的指针并不直截了当--事实上它非常混乱。我的绝大部分元件对象模型使用隐藏的接口指针的容器,而且接口的生命周期也很少被限制在函数的范围内。我为一个接口调用QueryInterface,把它存储在容器里,使用它,最后释放它--所以这些都来自我的代码中的不同地方。使用这种结构类型,我需要分配和释放栈上的巧妙接口,而这就象我在前面章节中说明的那样非常混乱。
一个C++的程序员可能要删除一个接口指针,而一个元件对象模型(COM)程序员则可能要释放一个CsmartInterface对象。没有什么方便的方法可以阻止这一点。所以,我们的解决方法已经替换了一个带有类似相等问题的问题。当然,至少目前为止,C++程序员的人数还是要比元件对象模型(COM)程序员的人数要多。
我已经决定使用接口包装代替巧妙的接口指针。在我的"用接口包装调用元件对象模型(COM)对象"一文中对接口包装进行了描述。
总结
巧妙的接口指针是一种强有力的技术,它使得用元件对象模型(COM)对象工作更为简单并且更加bug_free。不过,我发现巧妙的接口指针是一种非常奇怪的东西。他们既不是纯粹的指针也不是纯粹的对象。他们也不能象我所期望的那样适合我的应用程序结构。不过,我还是强烈建议你试试在你的应用程序里使用巧妙的接口指针,看看他们是如何为你工作的。他们可能只是你需要让你的应用程序更快且问题更少地技巧而已。
本站文章除注明转载外,均为本站原创或翻译。欢迎任何形式的转载,但请务必注明出处、不得修改原文相关链接,如果存在内容上的异议请邮件反馈至chenjj@evget.com
面对“数字中国”建设和中国制造2025战略实施的机遇期,中车信息公司紧跟时代的步伐,以“集约化、专业化、标准化、精益化、一体化、平台化”为工作目标,大力推进信息服务、工业软件等核心产品及业务的发展。在慧都3D解决方案的实施下,清软英泰建成了多模型来源的综合轻量化显示平台、实现文件不失真的百倍压缩比、针对模型中的大模型文件,在展示平台上进行流畅展示,提升工作效率,优化了使用体验。
本站的模型资源均免费下载,登录后即可下载。模型仅供学习交流,勿做商业用途。
本站的模型资源均免费下载,登录后即可下载。模型仅供学习交流,勿做商业用途。
本站的模型资源均免费下载,登录后即可下载。模型仅供学习交流,勿做商业用途。
服务电话
重庆/ 023-68661681
华东/ 13452821722
华南/ 18100878085
华北/ 17347785263
客户支持
技术支持咨询服务
服务热线:400-700-1020
邮箱:sales@evget.com
关注我们
地址 : 重庆市九龙坡区火炬大道69号6幢
慧都科技 版权所有 Copyright 2003-
2025 渝ICP备12000582号-13 渝公网安备
50010702500608号