📄 csdn_文档中心_com样例(二)——样例结构设计.htm
字号:
<TABLE bgColor=#eeeeee border=0 cellPadding=0 cellSpacing=0 width=600>
<TBODY>
<TR bgColor=#ffffff>
<TD align=middle height=10 width=50></TD>
<TD align=right><A href="http://www.csdn.net/">CSDN</A> - <A
href="http://www.csdn.net/develop/">文档中心</A> - <FONT
color=#003399>Visual C++</FONT> </TD></TR>
<TR>
<TD align=middle height=5></TD>
<TD align=middle width=500></TD></TR>
<TR>
<TD align=middle bgColor=#003399 height=10><FONT
color=#ffffff>标题</FONT></TD>
<TD><B> COM样例(二)——样例结构设计</B> lop5712(原作)
</TD></TR>
<TR>
<TD align=middle height=5></TD>
<TD align=middle width=500></TD></TR>
<TR>
<TD align=middle bgColor=#003399><FONT color=#ffffff>关键字</FONT></TD>
<TD width=500> COM 样例 编程模型</TD></TR>
<TR>
<TD align=middle height=5></TD>
<TD align=middle width=500></TD></TR></TBODY></TABLE><!--文章说明信息结束//-->
<TABLE border=0 width=600>
<TBODY>
<TR>
<TD align=left><BR><FONT
style="FONT-FAMILY: '宋体'; FONT-SIZE: 10pt; LINE-HEIGHT: 150%">
<P align=center><FONT face=楷体_GB2312
size=5><STRONG>COM样例(二)</STRONG></FONT></P>
<P align=right><FONT face=楷体_GB2312
size=4><STRONG>——样例结构设计</STRONG></FONT></P>
<P>
本文为《COM样例》系列文章的第二篇,说明样例的结构,解释COM的编程思想,并帮助理解样例代码。</P>
<P><BR><FONT face=楷体_GB2312 size=4><STRONG>样例目标</STRONG></FONT></P>
<P>
欲给一个公司做一个信息管理系统,也就是公司中所有部门的信息可以被输入电脑,并可进行分布式查询,即总经理可随时查询最新的订单情况和出货情况。由于使用COM作为此信息管理系统的基架,可以很容易的解决分布式问题,并且由于COM对安全的包装,使得提供访问控制也变得容易。下面先说明COM提供的编程思想,再以此编程思想设计各接口。</P>
<P><BR><FONT face=楷体_GB2312
size=4><STRONG>COM编程模型</STRONG></FONT></P>
<P>
见过不少这种说法:“COM是更加地面向对象,封装地更彻底”。这里要纠正这种错误的思想,虽然可以说对,但是是错误的应用。这就好像牛刀可以杀鸡,但并不应该被说对。<BR>
面向对象编程思想是一种思想,指导如何设计程序架构的。其主打思想就是将被操作数据看成一个个对象。而所谓的对象就是具有状态,并对外提供了接口以暴露其可以提供的服务。其是状态和功能通过语义的混合体。<BR>
其和日常生活很像,比如电视机就既提供了服务——搜台,又提供了状态——哪个频道是哪个台。因此在使用面向对象编程思想时会从对象的概念出发来定义数据结构,这和COM完全不一样。<BR>
COM叫做组件对象模型,从名字看其异常明显地表示了最开始引号中的话的正确性,这是个误解。COM最突出的贡献不是组件这个概念,而是接口。<BR>
接口表示功能的集合,其不是状态。与面向对象正好相反,其完全不看重对象的实现,甚至淡化对象这个概念,极力强调接口的概念,这在各本COM教科书中表现地很明显——里面第一个讲的就是IUnknown接口,极力强调没有对象指针,只有接口指针。<BR>
这看起来有点混乱,如果认为面向对象强调的是状态和功能的混合,COM强调的就是功能集的集合。而类就是只实现了一个接口的COM组件(不包括IUnknown),这从根本上说是COM的退化。因此当设计中的每个COM组件都只实现一个接口时,此时根本不是设计一个COM应用,只是在二进制代码级上应用C++提供的编程思想而已。<BR>
由于COM做地并不是那么好,以至于会产生前面所说的误解。其强调功能的概念没有体现出来,而更表现为组件,以至于很容易认为组件是积木,而整个程序就是用不同的组件搭建的房子。这是对象级上的模块化编程。COM不会设计到最后反而跑回老路上去。<BR>
搭积木的重点是积木,是以积木来搭建房屋。而COM提供的并不是积木,是积木间衔接的形状,它主张在搭积木前先搭一个架子,不同的积木能放到架子上形成的不同的格子里,架子搭好后再根据架子上形成的格子的形状做积木,最后将积木放到架子上。而不是先做积木,然后根据积木搭房屋(这个比喻并不是非常准确)。<BR>
思考这个问题:欲实现任务和任务管理器的功能,设计两个接口ITask和ITaskManager,考虑ITask的功能定义。其代表的是能够作用于任务上的功能,不是任务本身,因此其有如下两个方法:TerminateTask和GetProcessRateOfTask以分别终止任务和得到任务进度。但是很明显,任务是需要启动的。如果按照面向对象的思想,在不考虑设计模式的情况下,很容易想到将任务的发起这个动作作为ITask中的一个方法:StartTask,这样ITask的实现者就是一个完全的任务,如果使用线程进行任务操作,其也就连那个线程的操作也一起包装起来,形成一个任务。这不是一个好的设计,ITask是个接口,代表的是功能,不是对象。接口以为实现它的对象就可以照其定义进行操作,因此ITask的实现者是可以被相当于任务一样的操作,而不是任务这个东西。前者具有更好的可扩展性,如可以通过按遥控器来操作东西,但那个东西不一定必须是电视机,而后者就一定要求其是电视机。<BR>
因此COM里重点的不是组件,而是接口,这是一种可扩展性相当好的设计思想,可以称做面向接口编程思想。它本身是没有什么缺点的,但其实现方式由于使用对象的概念,则一定和状态关联,这在数据量很大时是不好的。如订单会很容易地就被设计成一个类,然后提供诸如订单结帐、提货等多种服务(即成员函数)。这里的问题就是订单如此之多,如果使用一个数组作为其容器显然性地问题严重,而链表更是应该判死刑。因此这里将订单设计成一个类是很不明智的选择。对于此,应该专门仔细研究如何处理大数据量的技术,并将功能与状态拆开,然后数据变成原材料,而功能变成机器,通过流水线生产以提高效率。即面向对象是个人主义,当数据量大时,就需要分工合作来提高效率了。对于此,Microsoft早已提供了MTS来帮助开发,其中提供的编程思想就是专门针对这种大数据量而设计的,提倡无状态组件,即状态和功能的分离,其对于开发大数据量的应用提供了非常好的支持(关于MTS,可参考我写的另一篇文章《MTS基础教程》)。</P>
<P><BR><FONT face=楷体_GB2312 size=4><STRONG>样例设计</STRONG></FONT></P>
<P>
前面已经说明了COM提供的编程模型,下面就本样例说明如何设计接口。<BR>
程序员考虑最多的事应该是如何偷懒,并且美其名曰“代码重用”,但现在又被更好听的名词所替代——“具有可扩展性”。样例是一个公司的信息管理系统,里面人事部门的信息处理和营销部门的将会千差万别,信息有完全不同的流程。因此是肯定需要一个一个编的。但它们能够被称做部门,就一定有共通的地方,这正是程序员最厉害的地方——归纳能力,然后推演出其他东西以达到偷懒的目的。<BR>
照前面的说法,由于数据量巨大,因此决定选择数据库而不是建立对象。由于各个部门没有什么同一性(其实还是有的,后叙),最后认为唯一相同的是同属一个公司旗下,故决定提供一个基本框架界面,其提供最基本的如错误处理、日志记录等功能,欲通过在同一个基架下以表现得各部门在同一公司旗下。<BR>
基本框架需要提供错误日志的记录以方便系统的维护和查错;需要提供界面框架以容纳不同的部门组件的操作界面,即需要提供菜单命令的提供,也出于Windows界面的想法而提供工具条和快捷键;需要提供任务管理器,因为在海量的数据中查找那么一两条信息不是瞬间的事,因此可能总经理发起了一个人事查找后,又发起一个订单查找或客户查找,但却由于等得不耐烦而终止了前面的人事查找;需要提供数据库系统的相关信息,以使得部门组件可以将数据存储到统一的地方,方便备份等管理。<BR>
部门组件需要提供界面以进行信息操作(如录入、查找等),作为Windows界面,常规性地需要提供菜单、工具条的维护性操作(如命令的说明字符串的提供);需要提供任务执行进度,以提高操作者的忍耐限度。<BR>
经过上面的决定,基本框架应有4个接口,而部门组件应有2个接口。但请注意,一个接口表示一个功能集合,如果一个组件实现了一个接口就表示其所有功能都实现了,但COM非常可惜地提供了E_NOTIMPL这个错误代码,因此导致了错误的接口设计——里面的方法可以有未实现的。这个错误代码准确的说应该是为将来扩展而预留的,即方法中的某个参数代表功能的种类,如是画圆形还是画矩形,但其可以指定为画椭圆形,这种形状暂时不支持,但相信以后版本将会支持,这才是E_NOTIMPL的真正含义,却被错误的应用了,比如:<BR>
上面提到的部门组件应该支持一个接口,其提供包含部门组件界面、菜单、工具条和快捷键的提供。完全有可能一个部门组件不使用工具条进行操作,完全使用一个对话视搞定一切,那么当可怜的基本框架错误地以为其需要工具条的空闲处理而调用了相应方法时却得到一个E_NOTIMPL时,应该怎样?因此应该将一定会同时存在的功能归为一个接口,因此出于上面的考虑,应该再提供三个接口:快捷键处理、菜单处理、工具条处理。由于快捷键没有处理,只是获得即可,不像菜单还需提供菜单状态的处理等操作,所以无须快捷键处理的接口,因此部门组件应该具有4个接口,其中三个是可选的。<BR>
上面的接口分工显得有些牵强,不过这只是粒度粗细的问题。如果愿意粗粒度,也可以说成基本框架只需一个接口,如果要细粒度,也可再定个工具条处理接口和菜单处理接口,这里就是见仁见智的地方了。但还是建议至少要保证接口中的方法如果实现一个,则其他的逻辑上也都需实现。<BR>
最后其IDL定义文件如下:</P>
<P><FONT color=blue face=宋体>import</FONT> "oaidl.idl";<BR><FONT
color=blue face=宋体>import</FONT> "ocidl.idl";<BR><BR><FONT
color=green face=宋体>//
基本框架实现IModuleSite,其提供基本的操作</FONT><BR>[<BR> <FONT
color=blue face=宋体>object</FONT>,<BR> <FONT
color=blue
face=宋体>uuid</FONT>(1A201ABA-A669-4ac7-9DF8-2DA772E927FC),<BR>
<FONT color=blue face=宋体>pointer_default</FONT>(<FONT color=blue
face=宋体>unique</FONT>)<BR>]<BR><FONT color=blue
face=宋体>interface</FONT> IModuleSite : IUnknown<BR>{<BR><FONT
color=green face=宋体>//
供部门组件改变当前显示模块,如点击了营销模块中的订单查找结果中的</FONT><BR><FONT color=green
face=宋体>// 办理人字段后自动跳转到人事模块中显示办理人的相关信息</FONT><BR>
HRESULT ChangeModule( [<FONT color=blue face=宋体>in</FONT>] REFCLSID
clsid, <FONT color=green face=宋体>//
模块的CLSID</FONT><BR>
<SPAN style="COLOR: green">//
模块名字,仅用于提示</SPAN><BR>
[<SPAN
style="COLOR: blue">in</SPAN>, <SPAN
style="COLOR: blue">string</SPAN>] WCHAR
*pModuleName,<BR>
<SPAN style="COLOR: green">//
模块命令,指明欲让模块执行的命令,由模块解释</SPAN><BR>
[<SPAN style="COLOR: blue">in</SPAN>] ULONG
command,<BR>
[<SPAN style="COLOR: blue">in</SPAN>] ULONG param ); <SPAN
style="COLOR: green">// 模块命令的相关参数</SPAN><BR>
HRESULT GetFrameWindow( [<SPAN style="COLOR: blue">out</SPAN>] HWND
*pHwnd ); <SPAN style="COLOR: green">//
返回主框架窗口</SPAN><BR>};<BR><BR><FONT color=green face=宋体>//
基本框架实现IErrorReport,其提供报告错误的功能</FONT><BR>[<BR>
<SPAN style="COLOR: blue">object</SPAN>,<BR> <SPAN
style="COLOR: blue">uuid</SPAN>(1A201ABA-A669-4ac7-9DF9-2DA772E927FC),<BR>
<SPAN style="COLOR: blue">pointer_default</SPAN>(<SPAN
style="COLOR: blue">unique</SPAN>)<BR>]<BR><FONT color=blue
face=宋体>interface</FONT> IErrorReport: IUnknown<BR>{<BR><FONT
color=green face=宋体>// 报告温和型错误,相当于警告</FONT><BR><FONT color=green
face=宋体>//
fileName代表源代码文件的名字,row代表错误所在行</FONT><BR> HRESULT
ReportSoftError( [<SPAN style="COLOR: blue">in</SPAN>, <SPAN
style="COLOR: blue">string</SPAN>] WCHAR
*fileName,<BR>
[<SPAN style="COLOR: blue">in</SPAN>] ULONG
row,<BR>
[<SPAN style="COLOR: blue">in</SPAN>, <SPAN
style="COLOR: blue">string</SPAN>] WCHAR *errorString );<BR><FONT
color=green face=宋体>// 报告暴力型错误,相当于错误</FONT><BR>
HRESULT ReportHardError( [<SPAN style="COLOR: blue">in</SPAN>, <SPAN
style="COLOR: blue">string</SPAN>] WCHAR
*fileName,<BR>
[<SPAN style="COLOR: blue">in</SPAN>] ULONG
row,<BR>
[<SPAN style="COLOR: blue">in</SPAN>, <SPAN
style="COLOR: blue">string</SPAN>] WCHAR *errorString
);<BR>}<BR><BR><FONT color=green face=宋体>//
基本框架实现ICompanyInfo,其提供数据库服务器信息</FONT><BR>[<BR>
<SPAN style="COLOR: blue">object</SPAN>,<BR> <SPAN
style="COLOR: blue">uuid</SPAN>(1A201ABA-A669-4ac7-9DFA-2DA772E927FC),<BR>
<SPAN style="COLOR: blue">pointer_default</SPAN>(<SPAN
style="COLOR: blue">unique</SPAN>)<BR>]<BR><FONT color=blue
face=宋体>interface</FONT> ICompanyInfo: IUnknown<BR>{<BR><FONT
color=green face=宋体>//
返回数据库服务器的相关信息,主机IP、服务器名字及密码</FONT><BR> HRESULT
GetDataServerInfo( [<SPAN style="COLOR: blue">in</SPAN>, <SPAN
style="COLOR: blue">string</SPAN>] WCHAR
*loaction,<BR>
[<SPAN style="COLOR: blue">in</SPAN>, <SPAN
style="COLOR: blue">string</SPAN>] WCHAR
*server,<BR>
[<SPAN style="COLOR: blue">in</SPAN>, <SPAN
style="COLOR: blue">string</SPAN>] WCHAR *password
);<BR>}<BR><BR><FONT color=green face=宋体>//
基本框架实现ITaskManager,其提供任务的操作</FONT><BR><FONT color=blue
face=宋体>interface</FONT> ITask;<BR>[<BR> <SPAN
style="COLOR: blue">object</SPAN>,<BR> <SPAN
style="COLOR: blue">uuid</SPAN>(1A201ABA-A669-4ac7-9DFB-2DA772E927FC),<BR>
<SPAN style="COLOR: blue">pointer_default</SPAN>(<SPAN
style="COLOR: blue">unique</SPAN>)<BR>]<BR><FONT color=blue
face=宋体>interface</FONT> ITaskManager: IUnknown<BR>{<BR><FONT
color=green face=宋体>// 添加任务</FONT><BR> HRESULT
AddTask( [<SPAN style="COLOR: blue">in</SPAN>, <SPAN
style="COLOR: blue">string</SPAN>] WCHAR *taskString, <SPAN
style="COLOR: green">//
任务说明字符串</SPAN><BR>
[<SPAN style="COLOR: blue">in</SPAN>] ITask
*pTask,
<SPAN style="COLOR: green">// 任务的指针</SPAN><BR>
<SPAN
style="COLOR: green">//
返回标识一个任务的cookie</SPAN><BR>
[<SPAN style="COLOR: blue">out</SPAN>] DWORD* pCookie
);<BR>};<BR><BR><FONT color=green face=宋体>//
基本框架实现ITaskNotify,其提供任务的通知</FONT><BR>[<BR> <SPAN
style="COLOR: blue">object</SPAN>,<BR> <SPAN
style="COLOR: blue">uuid</SPAN>(1A201ABA-A669-4ac7-9DFC-2DA772E927FC),<BR>
<SPAN style="COLOR: blue">pointer_default</SPAN>(<SPAN
style="COLOR: blue">unique</SPAN>)<BR>]<BR><FONT color=blue
face=宋体>interface</FONT> ITaskNotify: IUnknown<BR>{<BR><FONT
color=green face=宋体>// 通知指定任务的进度已经变化</FONT><BR>
HRESULT ProcessRateChange( [<SPAN style="COLOR: blue">in</SPAN>]
DWORD cookie );<BR><FONT color=green face=宋体>//
通知任务已经结束</FONT><BR> HRESULT TaskOver( [<SPAN
style="COLOR: blue">in</SPAN>] DWORD cookie );<BR>};<BR><BR><FONT
color=green face=宋体>//
部门组件必须实现IModule,其提供模块的操作</FONT><BR>[<BR> <SPAN
style="COLOR: blue">object</SPAN>,<BR> <SPAN
style="COLOR: blue">uuid</SPAN>(1A201ABA-A669-4ac7-9DFD-2DA772E927FC),<BR>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -