各位高手:
   程序员对"友元"一般都不会陌生,但是我在用ATL编写COM程序时
遇到一个难题,就是不知如何使接口类的某些接口函数只能让部分
接口类访问,就是类似"友元"的关系。

解决方案 »

  1.   

    C++的友员只是语言级上的保护,不过COM规范本身并没有提供这方面的功能,不过可以使用此法,将不止是语言级的保护其实很简单,将你想让友员类访问的方法单独归成一个接口,这个接口的规格只有你的友员客户才知道,也就实现友员效果了
      

  2.   

    lop5712(LOP) :        你的方法“将你想让友员类访问的方法单独归成一个接口,
        这个接口的规格只有你的友员客户才知道”是个折中方案,实
        现也比较简单,但是感觉有一些不足。    1) 容易造成接口泛滥;
        2) 并不能真正防止接口类的访问;
        3) 不方便在模式设计时对结构的描述;
        4) 容易产生潜在的、不可预知的系统错误。        以上观点是否恰当,是否还有更好的方法,请大家继续积极
         讨论。
      

  3.   

    呵,对于楼主的批评我都没有语言了,除了1以外,另外3个我根本看不出来对于2,如何不能防止接口的访问?接口类ABC,实现接口IABCWrite,并只告诉输入服务器(只提供编辑服务的服务器),而对于客户暴露接口IABCRead,那么客户只能通过IABCRead,因为他(编写客户的供应商)根本不知道IABCWrite的存在,又不能通过IUnknown获知,如何不能防止接口的访问?如果楼主说通过类型库进行得知,由于接口的关系,都已经分为两个.h文件(一个供客户,一个供服务器),当然要提供两个类型库以分别对待。对于3,只不过多了一个功能的描述,即原来接口类ABC要提供功能1(假设为操作功能)和功能2,现在多了两个功能,写操作功能和读操作功能而已,并且功能1不存在了,实际只多了一个功能,有何不方便?对于4,简直无法理解,How??!!!!,使接口类多支持一个接口会...??如果楼主是说要出现两个.h文件和类型库文件。对于.h文件,并不修改声明,两个.h文件只是成包含的关系(一个是另一个的部分),对程序的编译不提供任何影响,也就不存在4的错误。对于类型库就更不用说了,它根本就是程序以外的东西。对于名字空间,我认为这根本不能称之为不足。多定义一个接口,肯定是在让接口泛滥,那难道就不定义接口了??楼主是否是怀疑多定义的接口存在的必要性?它是必须的。它从逻辑上要求服务器端可以提供编辑,客户端不能进行修改,这是软件系统的逻辑要求,而多出来的接口是为了实现这个效果的,必然存在。或者楼主又更好的逻辑规则使得满足系统的逻辑要求而又不多定义接口。最后,如果楼主想采用楼上的意见,在QueryInterface中做身份验证(如果是在方法中做身份验证,效率太低),很明显地违背COM规范,不过并无不妥,我倒挺支持这样。如果不想违反COM规范,可以另定义一个接口(或在已定义的接口上),在那个接口中提供一个方法,功能类似QueryInterface即可,这样做其实已经超出了“友员”的功能范围。至少C++中的“友员”功能是在编译时刻实现的,绝不是运行时刻,当然,并不是说就一定得编译时刻实现。
      

  4.   

    “友元”是对封装性的破坏。COM是封装得比较严格的。
    不建议在C++中使用友元,更
      

  5.   

    honghaozi(红耗子):
         同意““友元”是对封装性的破坏,COM是封装得比较严格的”。
         不同意“不建议在C++中使用友元”。如果看过《设计模式》这
     本书,会发现书中有些模式的实现是要用到“友元”的,逻辑上一些
     功能接口(函数、类)只允许某些功能接口(函数、类)访问,又不能用
     继承的方法实现时要用到“友元”,此类问题在“面向对象”编程中
     可能难以避免,不用“友元”如何实现这些模式设计?     lop5712(LOP):
         对于问题2,如果不能在编程中避免“友元”,你的意思是用拆分
     类型库的方法来分离接口,达到限制功能接口(函数、类)访问的目的,
     但是我所要求的限制是针对接口的,不是针对类型库的使用人的,如果
     这个类库中有N个需要限制的接口,是否需要将类库分成N个部分,那设
     计者和使用者岂不晕乎。如果不分能N个部分,使用者怎会不知道。
         对于问题3,意见同上所述“此类问题在“面向对象”编程中
     可能难以避免,不用“友元”如何实现这些模式设计?”
         对于问题4,如果不能从实现上满足逻辑上的限制要求,系统潜在的
     错误不能杜绝。
      

  6.   

    我并没有说拆分类型库及.h,就好象在C++中,你开发了一个类库,里面一个类指定了一个友员类,这个信息可以被第三方开发商串改(即只需修改类库的.h,让自己的一个类成为其友员类,这很普遍,经常有通过修改MFC源码的头文件使其可访问保护成员,不过这是不正确的),我所谓的分开只是让部分开发信息公开,这样可以防止第三方开发商做出越轨的行为,如果靠开发商自觉,那就和普通一样,只提供一个.h文件,这样和C++的友员就类似了。
    Windows的开发,就是将一些关键内核接口(这里指API)隐藏起来,只供官方自己使用,没有通过SDK文档发布。Win32 SDK和Win32 DDK就是这个技术的最好证明。只有SDK的开发商是无法开发出硬件驱动程序的,只有DDK的开发商是无法开发出界面应用程序的。其相当于用SDK的开发商是Win32关于硬件驱动程序方面的友员类,用DDK的开发商是Win32关于界面方面的友员类,这里的友员比C++的友员更好,可以分方面来进行友员,可进行更复杂的逻辑设计。友员只是逻辑上的友员,不是代码上的,因此应该提供一个.h和类型库,但在开发文档中应指明各接口的用途,和开发商合力实现友员的逻辑作用。此法完全没有违背“面向对象”的思想。多出来的接口可认为是专业接口。举例:
    “电视机”接口类,实现了“操作”接口、“维护”接口,对于维修人员其暴露了“维修”接口,但对于客户,它也暴露了“维修”接口(用户可以自行拆开修理),但是一般客户根本不想用它。而且现实中厂商经常在螺丝口帖一个封条,以阻止客户使用“维修”接口,这就正好是我的方法的思想。意思也就是说,将想让某些人使用的方法组织成一个接口,专门提供给它们,这种接口就相当于专业接口,也就是更面向对象化的友员(可以只友员部分,而不象C++中全部)。根据上述,其完全可以实现,且一切满足规范,并且能“从实现上满足逻辑上的限制要求”,按照楼主的逻辑就可以杜绝系统的潜在错误了。不过我得说明,潜在错误就不是逻辑错误,也就是程序员实力不够造成的。当“不能从实现上满足逻辑上的限制要求”的时候,也只是说明算法错误,与潜在错误是没有任何关系的。
      

  7.   

    Fanjj(顽石):
    我的“不建议”只是认为,在大多数情况下尽量通过不破坏封装性的方式实现功能。
    《设计模式》中,常见的一些模式好像都没有用到友元(?)你说的是那些模式请告诉我一声。
      

  8.   

    我觉得也没什么好的方法,照 LOP5712(LOP)说的去做,应该会避免一些问题,尽量去遵照COM的规范。
      

  9.   

    lop5712(LOP):
         非常感谢你的积极参与,可能我对该问题的表达不很清楚,所以看法上有些分歧,
     其实在提问前我的想法和你相似,只是有些问题没有想得很明白。
         该问题主要是针对COM的,我理解COM是为“组件”程序发明的,对于使用“组件”
     的用户使用是接口级的,设计时将逻辑屏蔽在接口内,用户在使用接口时是不太关心它
     对其它接口的影响,因为他们有理由(因为COM的封装性好)相信这是“组件”设计人
     员的事情,如果“组件”的封装性不好对使用者后果可想而知,对设计者要做到好的封
     装性却不是一件容易的事,特别对于COM编程,因为COM对象中所有属性数据都是通过
     接口访问的(除了对象自己内部访问),所以只要知道接口都可以访问,但逻辑上并不
     是可以任意访问的,你前面所述“就是将一些关键内核接口(这里指API)隐藏起来”
     的方法是不太适合COM的,因为Windows的API是一堆类和函数的集合,基本上没有封装,
     程序员只有费力才能正确使用(API中有数不清的要求、约定、不行、必须),所以出
     现了后面的MFC和COM,如果还要用指导使用API的方法来实现COM,COM的优点将失去。
     另外COM有单接口和双接口,如果是双接口在VB这些自动化程序中使用时,要想隐藏接
     口我还不知道怎么做。
         对于“此法完全没有违背“面向对象”的思想。多出来的接口可认为是专业接口。”
     有不同看法,“面向对象”不是“面向人员”,“面向对象”编程最好是不要因使用人员
     的不同而制约对象的逻辑关系。另外,我小的时候曾经有一次将家里唯一的电器“收音机”
     拆开,结果面目全非不可收拾被大人痛打一顿,我当时想如果我拆不开多好。
         我认为“潜在错误就不是逻辑错误”是对“潜在错误”的理解不同。
         能“从实现上满足逻辑上的限制要求”时,系统的潜在错误也不一定能完全杜绝(我
     用这句对上面不严谨的“对于问题4....”做一个更正说明)。     honghaozi(红耗子):
         《设计模式》中的“备忘录模式”要用到“友元”,而且还有其它模式要用到“备忘
     录模式”。
      

  10.   

    哈,首先感谢楼主用如此大的篇幅来做我的回应。不过下面的讨论已经超出“友员”的范围了。楼主好象认为组件就应该只暴露一个接口(顶多双接口,“双接口在VB这些自动化程序中使用”),不过COM规范并不是这样定的。一个组件暴露出多个接口以供客户使用,为何?不管是在哪,比如COM内部的代理占位机制,COM规范中所谓的聚合技术,Microsoft提出的ActiveX和OLE DB等各种基于COM技术的技术中,它们使用的COM对象都或多或少的暴露出多个接口,比如一个很简单的ActiveX控件就要暴露出7、8个接口(虽然还可以更少)。
    它们吃饱了撑了不只暴露出一个接口(象C++的类)来暴露所有的方法而将那些方法归到多个接口下分别暴露?实现多个接口要麻烦的多。不过这正好体现出逻辑的严密性,而且更需要系统程序员(这里指设计整个项目的系统框架的程序员)严密的逻辑设计。这种设计方法也被称做“面向组件”的设计。之所以多个接口是为了将方法进行归类管理,这样可极大的增加灵活性和伸缩性。在C++中,类的声明和定义一般都是由一个人完成的(指在一个工程下),即如果写类声明的人声明了100个方法,而写类定义的人实现了99个方法,这个类也别想生成一个实例。而COM就允许这样做!
    将方法分类,提供多个接口,虽然上面说的系统程序员说要实现10个接口,不过照顾到有些程序员的水平有限,有些接口不能实现,那么那些程序员就只实现部分接口,而相应的客户就要根据具体情况酌情降低对那些程序员所写的COM组件的功能要求。更可以客户升级了自己,对系统程序员做了更多的功能要求,但客户仍能通过上面的方式兼容原来的COM组件版本,而不是简单的全部抛弃。这就是COM规范所提供的强大的伸缩性能。
    将方法分类,提供多个接口的另一个重要原因是语义的需要(必须承认,对于语义的培养是要经过很长时间的,我是很久以后才能注重语义的需求)。不知楼主是否在自己的程序中大量使用const这个关键字来进行成员函数及成员变量的声明,这个关键字当用于成员函数的声明时可以说完全等于没有。低下的C++教科书说其是为了帮助程序员发现相应成员函数中对const的成员变量作出修改。这只是肤浅的认识。如果一个程序员连这种都发现不了,那他跟本就不会想到要去给那个函数加const。之所以这样就是为了语义的需求,因为逻辑上那个函数他本来就不需要修改成员函数。即在实现代码前,光是写类的声明时就可以知道到底需不需要cosnt。C++还专门提供了matable这个关键字来加强其语义的严密性。
    同理,在设计接口类应该包含哪些接口时,应该先预见这个接口类将会被哪些客户使用,如果它们之间出现了使用上的差别,即有可能一类客户正常情况下不使用另一类客户涉及到的方法,那么此时就应该将它们的差集找出,并设计接口(虽然完全可以用同一个接口暴露给这两类客户,但破坏了语义的完整性——一个客户多获得了它并不需要的功能,语义的含义就是物尽其用)。并且现在的这个接口可以设计的通用些(如:IPersistStreamInit、IMoniker等),这样就有可能也被其他组件使用,从这个层面看,接口相当于一个类,表现出COM规范强大的灵活性。真不好意思,一下搞忘就又写了那么多。对于其是否“面向对象”,准确说这种是“面向组件”,是“面向对象”的升级。可以认为每一个接口是一个类,多个接口共用同一个组件中的内容。
    而楼主自己所举的例子是希望能有“友员”效果,那么就可以使用我上面曾提到过的将.h和类型库等相关文件分成多份,提供给不同客户。这其实是治标不治本。不过有一点一定注意,“友员”只是逻辑上的,即它只是一个规则,如果有人想破坏这个规则,那楼主是阻止不了的,即使没有.h文件,我照样有方法从.dll文件中获知其COM接口的声明(虽然很麻烦)。这就好象下象棋,马只能走“日”字,不过是我让它走,那么我照样可以让马走“田”字,虽然对方不同意,也可以有数不清的方法让对方同意。因为规则是人定的,本身不具备任何强制性。
      

  11.   

    访问权限限定,1访问设定,是通过Dcomcnfg设置完成
    2进程设定,通过编程修改Proxy/Sub来完成,你可以查一下代理/存根获取接口
    信息的那个接口,好像是什么 Get Client Blanket进程级别的验证在com规范
    中是通过修改这个
      

  12.   

    to楼主:
    看到了备忘录模式,果然用到了友元。
    对于这个模式,如果要套用到COM上,我也只能想到用一宽一窄两个接口。
      

  13.   

    对不起!网络不通耽误了时间。     lop5712(LOP):
         你显然错误理解了我说的“双接口”的含意,一般“双接口”是指COM中实现
     了“IDispatch”的接口,也称作自动化接口。我并不认为“组件”只应该暴露一
     个接口,而应该根据“组件”的功能逻辑分配接口,但我不太赞同为了不同的客
     户需求的差集来分割设计接口,如此“组件”的接口可能因为客户需求而破坏接口
     间的逻辑关系,如果一定要根据客户的不同来设计接口,我建议建立两层接口集,
     第一层接口集主要是实现逻辑关系,第二层封装第一层接口集满足客户需求(当
     然这样可能会损失一点效率),我这个问题也主要是针对实现COM第一层接口时提
     出的,主要是维持逻辑关系的完整,另外,我相得到的“友元”关系还不象是C++
     的“友元”,不是想得到属性访问的特权(可以任意访问属性数据而破坏封装性)
     ,是想根据接口的身份来确认属性访问的权限,来构造接口的访问逻辑。
         我非常同意“因为规则是人定的,本身不具备任何强制性。”,正因为如此,
     用“规则”来限制一些东西是有局限的,它只能在一定的层面上发挥一定的作用,
     而且我认为“规则”不等同于“逻辑”,“规则”只是实现“逻辑”的一种方法,
     只用“规则”来实现“逻辑”是松散的、不可靠的(可不可靠取决于人),所以
     我们要寻找一种更可靠的方法来实现“逻辑”。对于程序设计而言,很多方法是
     程序语言环境提供的,COM在这方面显然不如C++,哎!可能有人要反驳我了,其
     实我也认为这是COM的不得以,毕竟“二进制”级与“代码”级重用的实现差异
     太大了,当然这也给了COM更多的灵活性,所以我感觉在COM中设计对象模式比在
     C++难,真是又爱又恨。      chxinheifeng(黑风):
     关于“访问权限限定”是否在DCOM中才有,能否详细说一下。
      
      

  14.   

    在前面对于“双接口”(一个折中方案)我并没有理解错误,不过这并不是重点。对于楼主关于“逻辑”和“规则”的关系,我有异议,不过在此并不打算讨论,毕竟我是来回答问题的,不是来讨论的。通过楼主的回复,楼主想要的并不是“友员”这个规则,而是访问权限。对于  所说的通过dcomcnfg.exe来配置COM组件的安全性,的确是只有DCOM才支持(不过不用担心,除了Win95和Win98以外,都不需要做额外的工作来支持DCOM)。可惜的是楼主需要的是接口级的访问安全,可惜通过dcomcnfg.exe只能配置组件级的激活权限和访问权限(还有一个配置权限),不能达到接口级的访问控制,更不用说方法级的了。不过DCOM可以实现接口级的安全性,不过楼主必须得自己编程序实现(通过调用DCOM提供的多个API)。还有一个不利的消息就是DCOM的安全性的实现是通过代理/占位来实现的,也就是说必须是进程外组件才能获得DCOM安全性的支持,进程内组件由于是直接访问接口(可以通过处于不同的套间来得到代理/占位),没有通过代理/占位转手,所以没有安全性的支持。不过通过让进程内组件支持IClassFactory2可以实现对组件的创建进行保护,不过也不是方法级的。要实现方法级的保护,服务器端和客户端都通过调用CoInitializeSecurity来设置自己的安全属性(这一步可以通过dcomcnfg.exe来达到同样效果,不过如果那样就不是动态的配置了),然后服务器端通过调用CoGetCallContext得到IServerSecurity接口,以进行调用端的权限判断(也可通过几个同样功能的API,名字记不到了),具体怎做不是一两句话搞得定的,而且IServerSecurity的函数的名字我记不清楚了,楼主还请自己查阅相关资料。
      

  15.   

    不过DCOM可以实现接口级的安全性,不过楼主必须得自己编程序实现(通过调用DCOM提供的多个API)。还有一个不利的消息就是DCOM的安全性的实现是通过代理/占位来实现的,也就是说必须是      进程外组件    才能获得DCOM安全性的支持,进程内组件由于是直接访问接口(可以通过处于不同的套间来得到代理/占位),没有通过代理/占位转手,所以没有安全性的支持。不过通过让进程内组件支持IClassFactory2可以实现对组件的创建进行保护,不过也不是方法级的。
    对于DCom组件进程内外都是采用代理,进程内的有一个规定的代理,这种访
    问限制的确是接口级别的,不过问题当中的访问限制如果不是这样的话,就
    不能算是符合com规范,要找寻支持就麻烦了,你可以自己写“Com API”
      

  16.   

    非常感谢各位的参与(特别是lop5712(LOP)),虽然问题没有完全解决,但是的确
     受益非浅,大家是否还有话说,我准备结帖了。
      

  17.   

    其实解决这个问题的难易全在于你对于"规范"、"通用"的偏执程度。
    我也遇到过这个问题,我的第一反应是查一下COM里有没有现成的方案,没有,于是信心十足的想自已创造一个Pattern,可以成为类似问题的通用而又规范解决方案。于是我埋头设计,开始创造了"友元接口"这个概念,后来又想到用"认证调用"的方法,还设计过一种最复杂的回调接口机制。在设计的过程中,我把调用这个COM组件的客户假想成这个COM组件的"敌人","敌人"会用各种办法绕过你的"防御",所以你要不断推翻自己的想法,强化你的"防御"。我越来越觉得客户是丑陋、无耻的,它会破坏一切正常秩序来调用它不该看到的方法。最后我精疲力尽了,我被自己折磨得快疯了。我不禁怀疑的问自己,"真的需要吗?","不需要吗?""需要吗?","不需要吗?"... ... 最后,我冷静的做了一次分析:这个COM,除了我的一位同事会调用外再也不会有人需要它了,它更不可能成为一个广为流行的COM,它只是在这套系统里扮演着一个普通的角色,并且谁也不能保证它能"活"多久,也许这套系统的下一个版本已经变成J2EE的了。客户端和COM都是用VC写的,源码也都在,没必要考虑其它语言调用。于是我确定了两个方案,一:在文档里写上"不要调用";二:像上面提到的,另分出一个接口,不公布在类型库中,只提供一个头文件;第一种方案跟本不在用程序解决的范畴之内,第二种也是在语言的范畴内解决的,不设计跨语言的COM层次。
        你猜我是怎么解决的?
        我和正在专心写代码的同事说,这个接口的某某方法你不要调用,那是设计给其它人用的。他心不在焉地说"知道了"。事情就这样解决了,我连文档都没改。
        我想说的是,规范固然重要,它也代表一个程序员的素质,但有时解决问题不要死钻牛角尖,要从实际出发、灵活处理、克服偏执的心理,做一个理智的程序员。
      

  18.   

    To chxinheifeng(黑风):
        你所说的“进程内的有一个规定的代理”,是特指远程服务器是进程内服务器,且服务器对应的注册表项(关于DLL委托的)没有设定,即通过dllhost.exe作为远程进程内服务器的容器时才有代理/占位。但是当在本地计算机内使用进程内服务器时,只要没有跨套间边界,就是直接指针,没有代理/占位。并不是“DCom组件进程内外都是采用代理”。