第一篇:COM多线程和DCOM学习心得160217
COM多线程和DCOM 1 COM多线程模型
COM提供的线程模型共有三种:Single-Threaded Apartment(STA 单线程套间)、Multithreaded Apartment(MTA 多线程套间)和Neutral Apartment/Thread Neutral Apartment/Neutral Threaded Apartment(NA/TNA/NTA 中立线程套间,由COM+提供)。虽然它们的名字都含有套间这个词,这只是COM运行时期库(注意,不是COM规范,以下简称COM)使用套间技术来实现前面的三种线程模型,应注意套间和线程模型不是同一个概念。COM提供的套间共有三种,分别一一对应。而线程模型的存在就是线程规则的不同导致的,而所谓的线程规则就只有两个:代码是线程安全的或不安全的,即代码访问公共数据时会或不会发生访问冲突。
1.1 COM套间
套间(Apartment)是线程模型的一个实现者,就像在操作系统课程中讲到的线程只是一个数学模型,而Windows的线程、进程是它(数学模型的线程、进程)的实现者。套间只是逻辑上的一个概念,实现时只是一个结构(由COM管理)而已,记录着相关信息,如它的种类(只能是上面那三个,至少现在是),并由COM根据那个结构进行相应的处理。下面说明这三种套间的实现方式:
STA套间:一个套间如果是STA,那么那个套间有且只有一个线程和其关联,有多个对象或没有对象和其关联,就像有多个线程和一个进程关联一样,也就是说套间那个结构和某个线程及多个对象之间有关系,关系具体是什么由COM说得算,幸运的是COM正是按照上面的线程模型来定义互相之间关系的。根据上面的算法,很容易就知道只有这个线程可以访问这个套间里的对象。COM是通过在STA套间里的线程中创建一个隐藏窗口,然后外界(这个套间外的线程)对这个对象的调用都转变成对那个隐藏窗口发送消息,然后由这个隐藏窗口的消息处理函数来实际调用组件对象的方法来实现STA的规则的。之所以使用一个隐藏窗口是为了方便组件代码的编写——只需调用DispatchMessage即可将方法调用的消息和普通的消息区分开来(通过隐藏窗口的消息处理函数)。外界对这个对象的调用都将转变成对这个隐藏窗口的消息发送来实现同步。至于COM如何截获外界对对象的调用,则是利于代理对象,后面再说明。
MTA套间:这种类型的套间可以和多个线程及多个或没有对象相关联。根据上面的MTA模型,可知只有这个套间里的线程才能访问这个套间里的对象,和STA不同的只是可以多个线程同时访问对象。外界(不属于这个套间的线程)对这个套间里的对象的调用将会导致调用线程(外界线程,也就是STA线程,因为NA没有线程)挂起,然后向RPC管理的一个线程池请求一个线程(RPC线程,并已经进入了这个MTA套间)以调用那个对象的方法。对象返回后,调用线程被唤醒,继续运行。虽然可以让STA线程直接调用对象(而不用像前述的挂起等待另一个线程来调用对象),但这是必须的,因为可能会有回调问题,比如这个MTA线程又反过来回调外界线程中的组件对象(假设客户本身也是一个组件对象,这正是连接点技术),如果异步回调将可能发生错误。反过来,MTA的线程访问STA里的对象时,COM将把调用转换成对STA线程里那个隐藏窗口的一个消息发送,返回后再由COM转成结果返回给MTA的线程(如果使用标准汇集法生成标准代理对象,则发生的具体情况就如上面STA套间所述)。因此STA和MTA都是只能由它们关联的线程调用它们关联的对象。而根据上面所说,当MTA调STA或STA调MTA,都会发生线程切换,也就是说一个线程挂起而换成执行另一个线程。这是相当大的消耗(需要从内核模式向用户模式转换,再倒转好几回),而NA就是针对这个设计的。
NA套间:这种套间只和对象相关联,没有关联的线程,因此任何线程都可以直接访问里面的对象,不存在STA的还是MTA的。外界(其实就是任何线程)对这个套间里面的调用都不需要挂起等待,而是进入NA套间,直接调用对象的方法。NA套间是由COM+提供的,COM+中的每个对象都有一个环境和其相绑定,环境记录了必要的信息,并监听对对象的每一次调用,以保证当将对象的接口指针成员变量进行传递或回调时其操作的正确性(保证执行线程在正确的套间内,MTA线程就是通过将自己挂起以等待STA线程的消息处理完毕来保证的),从而避免了调用线程的挂起,因此这个代理(其实也就是环境的一部分)被称作轻量级代理(相对于STA套间和MTA套间的重量级代理——需要挂起调用线程,发生线程切换)。
1.2 套间实现规则
COM的套间机制要成功实现,必须服务器(组件)、客户和COM运行时期库三方面合力实现,其中有任何一方不按着规矩来,将不能实现套间机制的功能,不过这并不代表什么错误,套间机制不能运作并不代表程序会崩溃,只是不能和其他COM应用兼容而已。比如:对象中的属性1在设计的算法中肯定不会被两个以上的线程写入,只是会被多个线程同时读出而已,因此不用同步,可以用MTA,但对象的属性2却可能被多个线程写入,因此决定使用STA。从而在客户端,通过前面说的CoMarshalInterface和CoUnmarshalInterface将对象指针传到那个只会写入对象的属性1的线程,其实这时就可以直接将对象指针传到这个线程,而不用想上面那样麻烦(而且增加了效率),但是就破坏了COM的套间规矩了——两个线程可以访问对象,但对象在STA套间中。所以?!什么事都不会发生,因为已经准确知道这个算法不会捅娄子(线程访问冲突),即使破坏COM的规矩又怎样?!而且组件仍可以和其他客户兼容,因为不按规矩来的是客户,与组件无关。不过如果组件破坏规矩,那么它将不能和每一个客户兼容,但并不代表它和任何客户都不兼容。这里其实就是客户和组件联合起来欺骗了COM运行时期库。
STA 当一个组件是STA时,它必须同步保护全局变量和静态变量,即对全局变量和静态变量的访问应该用临界段或其他同步手段保护,因为操作全局和静态变量的代码可以被多个STA线程同时执行,所以那些代码的地方要进行保护。比如对象计数(注意,不是引用计数),代表当前组件生成的对象个数,当减为零时,组件被卸载。此变量一般被类厂对象使用,还好ATL和MFC已经帮我们实现了缺省类厂,这里一般不用担心,但自定义的全局或静态变量得自己处理。
主STA 与STA唯一的不同是这是傻瓜型的,连静态和全局变量都可以不用线程保护,因为所有不是安全访问静态和全局变量的对象都通过主线程(第一个调用CoInitialize的线程)的消息派送机制运行,因此不安全的访问都被集中到了一个线程的调用中,因而调用被序列化了,也就实现了对静态和全局变量的线程保护。至于为什么是主线程,因为进程要使用STA,则一定会创建主线程,所以一定可以创建主STA。因此主STA并不是什么第四种套间,只是一个STA套间,不过关联的是主线程而已,由于它可以被用作保护静态和全局变量而被单独提出来说明。因此一个进程内也只有一个主STA套间。2 分布式DCOM DCOM(分布式组件对象模型,分布式组件对象模式)是一系列微软的概念和程序接口,利用这个接口,客户端程序对象能够请求来自网络中另一台计算机上的服务器程序对象。DCOM基于组件对象模型(COM),COM提供了一套允许同一台计算机上的客户端和服务器之间进行通信的接口。
2.1 DCOM对象的创建
DCOM使得组件的位置对你来说完全透明,无论它是位于客户的同一进程中或是在地球的另一端。在任何情况下,客户连接组件和调用组件的方法的方式都是一样的。DCOM不仅无需改变源码,而且无需重新编译程序。一个简单的再配置动作就改变了组件组件之间相互连接的方式。DCOM的位置独立性极大地简化了将应用组件分布化的任务,使其能够达到最合适的执行效果。例如,设想某个组件必需位于某台特定的机器上或某个特定的位置,并且此应用有许多小组件,你可以通过将这些组件配置在同一个LAN上,或者同一台机器上,甚至同一个进程中来减少网络的负载。
创建COM对象可以在C OM库的基础上,通过远程调用SCM来实现。
客户调用创建函数COM库(OLE32.DLL)远程创建SCM(RPCSS.EXE)RPCSCM(RPCSS.EXE)组件创建进程和对象
图创建DCOM组件对象
2.2 DCOM的连接和并发管理
DCOM使用ORPC协议实现远程通信。连接具有可传递性,因为接口的列集数据(OR或者OBJREF)包含机器相关的信息;连接传递与创建传递含义不同,DCOM不支持创建传递, 可用连接传递间接支持创建传递,利用连接传递性可实现动态负载平衡。
服务器1①请求创建对象A客户机②pA->CreateObjectB④返回对象B⑤释放对象A⑥客户直接与对象B连接服务器3名字开放服务器③选择服务器3并创建对象B服务器2 图 DCOM连接管理
客户程序或者组件程序实现过滤器对象,然后用CoRegisterMessageFilter函数指定使用自定义的消息过滤器;否则COM使用缺省的过滤器对象。
2.3 DCOM安全性
DCOM使用了Windows NT提供的扩展的安全框架。Windows NT提供了一套稳固的内建式安全模块,它用来提供从传统的信用领域的安全模式到非集中管理模式的复杂的身份确认和鉴定机制,极大地扩展了公钥式安全机制。安全性框架的中心部分是一个用户目录,它存储着用来确认用户凭据(用户名、密码、公钥)的必要信息。大多数并非基于Windows NT平台的系统提供了相似或相同的扩展机制,我们可以使用这种机制而不用管此平台上用的是哪种安全模块。大多数DCOM的UNIX版本提供了同Windows NT平台相容的安全模块。
第二篇:多线程实验报告
宁波工程学院电信学院计算机教研室
实验报告
课程名称: Java 2 姓 名: *** 实验项目: 多线程实验 学 号: **** 指导教师: **** 班 级: **** 实验位置: 电信楼机房 日 期:
一、实验目的
1、掌握多线程编程的特点和工作原理;
2、掌握编写线程程序的方法
3、了解线程的调度和执行过程
4、掌握线程同步机理
二、实验环境
windows记事本,java jdk 1.60版本,cmd命令运行窗口
三、实验内容 实验一:
应用Java中线程的概念写一个Java程序(包括一个测试线程程序类TestThread,一个Thread类的子类PrintThread)。在测试程序中用子类PrintThread创建2个线程,使得其中一个线程运行时打印10次“线程1正在运行”,另一个线程运行时打印5次“线程2正在运行
源程序:
public class A { public static void main(String args[]){
Test1 A1;
Test2 A2;
A1=new Test1();
A2=new Test2();
A1.start();
A2.start();} } class PrintThread extends Thread { } class Test1 extends PrintThread { public void run(){
for(int i=1;i<=10;i++)
{
System.out.println(“线程1正在运行!”);
} } } class Test2 extends PrintThread { public void run(){
for(int i=1;i<=5;i++)
{
System.out.println(“线程2正在运行!”);
} } } 运行结果:
实验二:
将上述程序用Runnable接口改写,并上机验证源程序 public class D { public static void main(String args[]){
Move move=new Move();
move.test1.start();
move.test2.start();} } class Move implements Runnable { Thread test1,test2;Move(){
test1=new Thread(this);
test1.setName(“线程1正在运行!”);
test2=new Thread(this);
test2.setName(“线程2正在运行!”);} public void run(){
if(Thread.currentThread()==test1)
{
for(int i=1;i<=10;i++)
{
System.out.println(test1.getName());
} } } else { for(int i=1;i<=5;i++){
System.out.println(test2.getName());} } 运行结果:
实验三:
import java.awt.*;import java.awt.event.*;public class E
{ public static void main(String args[])
{ new FrameMoney();
} } class FrameMoney extends Frame implements Runnable,ActionListener { int money=100;
TextArea text1,text2;
Thread 会计,出纳;
int weekDay;
Button start=new Button(“开始演示”);
FrameMoney()
{ 会计=new Thread(this);
出纳=new Thread(this);
text1=new TextArea(12,15);
text2=new TextArea(12,15);
setLayout(new FlowLayout());
add(start);
add(text1);
add(text2);
setVisible(true);
setSize(360,300);
validate();
addWindowListener(new WindowAdapter()
{ public void windowClosing(WindowEvent e)
{System.exit(0);
}
});
start.addActionListener(this);
}
public void actionPerformed(ActionEvent e)
{ if(!(出纳.isAlive()))
{ 会计=new Thread(this);
出纳=new Thread(this);
}
try
{ 会计.start();
出纳.start();
}
catch(Exception exp){}
}
public synchronized void 存取(int number)//存取方法
{ if(Thread.currentThread()==会计)
{ text1.append(“今天是星期”+weekDay+“n”);
for(int i=1;i<=3;i++)//会计使用存取方法存入90元,存入30元,稍歇一下
{ money=money+number;
//这时出纳仍不能使用存取方法
try { Thread.sleep(1000);//因为会计还没使用完存取方法
}
catch(InterruptedException e){}
text1.append(“帐上有”+money+“万n”);
}
}
else if(Thread.currentThread()==出纳)
{ text2.append(“今天是星期 ”+weekDay+“n”);
for(int i=1;i<=2;i++)//出纳使用存取方法取出30元,取出15元,稍歇一下
{ money=money-number/2;
//这时会计仍不能使用存取方法
try { Thread.sleep(1000);//因为出纳还没使用完存取方法
}
catch(InterruptedException e){}
text2.append(“帐上有”+money+“万n”);
}
}
}
public void run()
{ if(Thread.currentThread()==会计||Thread.currentThread()==出纳)
{ for(int i=1;i<=3;i++)//从周一到周三会计和出纳都要使用帐本
{ weekDay=i;
存取(30);
}
}
} }
运行结果:
}
四、实验心得与小结
通过本次实验,基本了解了线程的概念,作用,方法以及使用规则。1.首先:java 程序是建立在线程之上的。.2.创建线程必须继承 Thread class 它已经为线程的创建和运行做了必要的配置。run是线程就重要的方法。你必须覆写这个方法达到你想要的目的。3.run方法所包含的代码就是和其他线程同时运行的代码以达到同一时刻运行多段代码的目的。当终止了 run以后。这个线程也就结束了。调用线程的 start方法才会执行 run方法。
4.线程的生命周期:新建——Thread.State.NEW:当一个 Thread 类或者其子类的对象被声明并创建时,新的线程对象处于新建状态,此时它已经有了相应的内存空间和其他资源start方法尚未被调整用就绪可执行状态——Thread.State.RUNNABLE:处于新建状态的线程被启动后,将进入线程队列排队,这个时候具备了运行的条件,一旦轮到 CPU 的时候,就可以脱离创建它的主线程独立开始自己的生命周期运行:就绪的线程被调度进入运行状态,每一个 Thread 类及其子类的对象都有一个重要的run方法,当线程对象被调度执行的时候,它将自动调用本对象的 run方法,从第一句代码开始执行。
第三篇:六一报.com
杨树岭小学庆祝“六一”国际儿童节活动方案
在“六一”国际儿童节来临之际,为了让全体学生过一个“快乐祥和、有趣有味、有文化有艺术”的“六一”国际儿童节,为了开展丰富多彩的文化娱乐活动,激发学生热爱生活、热爱校园、热爱艺术的丰富情感,为学生提供表现自我的舞台,让学生积极参与并发挥其个性特长,充分展示我校的艺术教育成果。现根据学校的要求和实际,特制定“杨树岭小学庆祝„六一‟国际儿童节活动方案”。
一、活动时间:2013年 6 月 1 日(具体时间另行通知)。
二、活动地点:中心校。
三、活动主题:放飞梦想 拥抱希望。
四、活动组织: 总 策 划:李校长。副总策划:白玉贤。执行策划:王元华、赵广忠、李慧慈。
项目负责白玉贤 :庆“六一”文艺汇演节目的安排主持人、报幕等; 李校长:奖品的准备、颁发等; 杨建国:摄像、录像; 音响、节目背景音乐播放;关文良:校园安全、学生安全。
五、活动内容: 以反映小学素质教育成果,展现小学儿童健康向上的精神风貌,体现“六一”儿童节活泼欢快的节日气氛为主要目的。要求服装统一,提倡大胆创新,活泼新颖。各校自行组织汇演由中心校组织人员督查评选参加镇汇演。
A、节目形式: ①歌舞类:独唱、合唱、舞蹈、歌伴舞等。
②曲艺类:相声、小品、魔术、武术、课本
剧、英语剧等。
③语言类:诗朗诵、三句半等。
④器乐类:独奏、合奏等。注:以歌舞、歌伴舞、课本剧、英语剧为主,其他节目在评选时降格。
B、节目安排: 本次庆“六一”文艺汇演节目的准备,由小学校长负责,统筹安排所负责小学的节目。要求每个小学准备一台节目,中心校将派评委到各小学评出 2 个优秀节目参加镇汇演,小学部 1 个,幼儿园 1 个,小学校长要认真落实指导老师、协助老师。各小学教学点、幼儿园、于 5 月 25 日前将各完小的节目安排报与中心校。
C、节目评奖: 由学校邀请评委,对参加汇演的节目按评分标准进行评分,根据得分的多少为序,评出一、二、三等奖。
六、活动过程
领导致词并宣布活动开始。
节目演出
七、注意事项。
各校带学生来演出需有两名以上老师带队,往返要注意安全。
杨树岭小学
2013年5月
杨树岭小学庆“六一”活动总结
为了让少年儿童度过一个愉快而有意义的节日,我校统一安排工作,以活跃少年儿童校园文化生活,提高少年儿童自身素质为主导,在学校艺术节期间,开展了庆“六·一”文艺节目演出活动,活动内容包括校鼓号队方阵表演、秧歌表演、文艺演出等活动,通过活动,使学生陶冶了情操,锻炼了能力,提高了本领,增强了竞争意识,促进了他们身心的健康发展,使学校少先队工作呈现出一片勃勃生机。现将 “六·一”活动的开展情况做以下总结:
一、活动的组织:
为了搞好庆祝活动,学校先后多次召开会议,专题研究艺术节活动方案,围绕“六一”庆祝这一主题,积极探索,因地因时地制定了整个活动计划。最后形成了“六一”学校进行大型庆祝活动的安排,并将筹备工作落实到人,形成人人参与的局面。
为了确保活动的顺利进行,从开会布置,落实到人,到联系场地,联系音响,到最后整个活动的正常进行,校长室、德育处、总务处做了大量的准备工作。艺术组的音乐老师加班加点组织学生排练节目。各班的班主任老师克服了教学任务繁重、没有排练场地等困难、认真组织节目的排练,为文艺演出的顺利进行做出了很大的贡献。
二、活动的评价:
从总体来看,这次活动达到了预期的效果,活动是圆满成功的。文艺演出,从学生出发,以学生为本、本着既能让学生过
一个有意义的节日,又能激发学生勤学向上、积极进取的优良品质的目的进行。在庆祝会上学校领导及来宾代表做了讲话,祝贺少年儿童的节日,校领导表彰了在艺术节期间表现优秀的少先队员。六一当天整个杨树岭小学鼓号阵阵、气氛热烈,充分展示了我校的精神风貌。
本次演出300多名学生参加,参与面广,充分为学生提供了展示才能的机会,文艺演出节目类型丰富,节目既有舞蹈类,又有语言类、更有器乐类,充分发挥了我校的文艺特长。一开始的鼓号队表演,给浓浓的节日增添了勃勃生机,整个校园在欢乐的气氛之中。器乐队的演奏,使周围围观的群众啧啧称赞。校舞蹈队的《各族儿童心连心》更是将活动推向了高潮。其它节目也发挥了学生的潜力,展示了孩子的才能。演出整体质量较高,向社会展示了我校真实水平。
三、不足及活动折射的问题
准备工作虽然较充分,但活动中还是出现了许多的问题,如:有些学生组织纪律性较差,部分学生随意讲话、合唱节目演出时,音响不够响。这些问题都需在以后的活动中注意,切实把工作做的更加细致、更加深入,使以后的活动更加有序、更加完美。
杨树岭小学
2013年6月
第四篇:COM技术总结
COM技术总结
1.运行mbuild-setup,设置编译器;
2.运行deploytool命令;
3.设置COM组件的name,Target(选“Generic COM Component”);
4.点击“Build”,创建COM组件;
5.点击“Package”,创建打包生成.exe文件;
6.运行distrib文件夹下的-install.bat文件或者第5步生成的.exe文件,注册.dll文件;
7.打开LabVIEW,新建vi,在程序框图选“互连接口”中的“ActiveX”下的“打开自动化”;
8.在“打开自动化”函数的“自动化引用句柄”处右击,选择“选择Active类”,选中名
为“COM组件名 Type Labrary Version 1.0”的类,连接;
9.在“打开自动化”的“错误输出”端口右击选择“ActiveX选板”—“调用节点”;
10.“调用节点”左侧的“引用”和“错误输入”分别与“打开自动化”的“自动化引用句
柄”和“错误输出”相连;
11.右击“调用节点”—“选择类”—选择和COM组件对应(名称接近)的类;
12.右击“调用节点”,选择相关的变量;
13.“nargout”连接一个常量(输出量的个数),“nargint”连接一个常量(输入量的个数);
14.为下面的参数赋相应的值或者连接输入变量;
15.仿照第7步,打开“关闭引用”函数,其中“错误输入”和“引用”分别和“调用节点”的“错误输出”和“引用输出相连”;
16.打开“互连接口”—“ActiveX”—“变体至数据”;
17.创建一个和期望的输出数据相同类型的常量(如数值、数组等),连接至“变体至数据”的“类型”,“打开自动化”中的输出量连接至“变体至数据”的“变体”,注意所用的量的“表示法”要和COM组件运行的结果相匹配;
注意:
1.MATLAB每次要选择“以管理员身份运行”;
2.第 3步中COM组件的存储文件夹要用MATLAB默认的;
第五篇:COM接口与对象学习心得150901
COM对象与接口学习心得 引言
COM是Microsoft提出的一种二进制兼容构件的规范,只要遵守这种规范,不管用什么编程语言和工具开发的COM构件,也不管是否运行在同一台机器上,还是运行在不同的机器上,都可以被使用。COM是建立在二进制层次上的调用标准,与编程语言和开发工具无关;COM定义了大量的标准规范接口(如IUnknown、IClassFactory、IDispatch等等)用于各种不同的用途。如今,COM成了微软跟上因特网快速发展的重要基础技术,在windows平台上,COM随处可见。
COM对象是指符合面向对象设计中对象的基本概念,通过COM接口提供服务的COM组件的实例。COM接口是客户与对象之间的通信协议,对象实现COM接口,客户调用COM接口。COM对象由GUID唯一标识,通常可以实现多个接口。COM接口由CLSID标识,它不能被独立使用,要求必须存在于某个COM对象上,COM接口提供的功能是与语言、平台和编译器无关的。静态链接与动态链接
C++程序的链接按照时间可以分为静态链接和动态链接。动态链接是在运行时与库函数进行链接,它使得不同的程序开发者能够相对独立地开发和测试自己的程序模块,从某种意义上来讲大大促进了程序的开发效率,原先限制程序的规模也随之扩大;静态链接是在编译的时候与库函数进行连接,会将静态库中的所有方法都编译到应用程序中。C++的动态链接是符合COM接口设计需要的。
静态链接的优点:代码装载速度快,执行速度略比动态链接库快; 只需保证在开发者的计算机中有正确的.LIB文件,在以二进制形式发布程序时不需考虑在用户的计算机上.LIB文件是否存在及版本问题,可避免DLL地狱等问题。缺点:使用静态链接生成的可执行文件体积较大,包含相同的公共代码,造成浪费。
动态链接优点:更加节省内存并减少页面交换;DLL文件与EXE文件独立,只要输出接口不变(即名称、参数、返回值类型和调用约定不变),更换DLL文件不会对EXE文件造成任何影响,因而极大地提高了可维护性和可扩展性;不同编程语言编写的程序只要按照函数调用约定就可以调用同一个DLL函数;适用于大规模的软件开发,使开发过程独立、耦合度小,便于不同开发者和开发组织之间进行开发和测试。缺点:使用动态链接库的应用程序不是自完备的,它依赖的DLL模块也要存在,如果使用载入时动态链接,程序启动时发现DLL不存在,系统将终止程序并给出错误信息。而使用运行时动态链接,系统不会终止,但由于DLL中的导出函数不可用,程序会加载失败;速度比静态链接慢。3 客户与对象之间的通信
C++的封装是语法上的封装,而不是二进制封装,其对象创建与释放的运算符new/delete是编译器相关的,编译器不仅要知道类的public信息,也要知道private信息。C++对象的二进制结构是编译器相关的,即使客户看到的C++类公开接口没有变化,但是C++类的实现改变了,仍然会打破客户与对象之间的连接。
在对COM接口进行设计时,应使客户与C++对象之间的连接点越小越好,尽可能地只有接口部分必要的信息才放入接口,把C++类的实现细节与接口分开,提取出针对所有编译器都不变的因素作为客户与对象共享的接口信息。
C++纯虚基类的设计可以很好地解决COM接口的设计问题。纯虚基类只包含了虚函数,限定每个虚函数的调用规则,在给定的平台上所有的编译器都会产生同样的二进制结构。对于跨平台的情形,需要通过中间层,这里暂不考虑。纯虚基类的使用解决了方法实现的命名冲突,也解决了C++类中二进制布局不兼容的问题,客户只能看到vtable,没有看到其他的实现细节,保证了不同语言编写的程序可以互操作,也可以在不改变接口的情况下,可以单独升级客户或者对象。
例如定义一个接口IString,它包含了两个纯虚方法。class IString {
virtual const char*Find(const char *psz)=0;
virtual int Length()=0;};
类CMyString实现了IString定义的接口(具体实现略)。class CMyString : public IString { private:
char *m_psz;public:
CMyString(const char * psz);
~CMyString();
const char*Find(const char *psz);
int Length();};
如果不使用C++的运算符,客户如何得到IString的虚表接口呢?可以提供一个引出函数供客户使用,隐藏创建对象的内部细节。
extern “C” _declspec(dllexport)IString *CreateString(const char *psz);extern “C” IString *CreateString(const char *psz){
return new CMyString(psz);}
客户访问vtable接口举例: #include “istring.h” typedef IString *(*PfnCreateString)(const char *psz);void main(){
IString *p;
HANDLE h = LoadLibrary(“c: empmystring.dll”);
if(NULL!=h){
PfnCreateString pfn =
(PfnCreateString)GetProcAddress(h,“CreateString”);
if(pfn){
p = pfn(“Hello”);
if(p){
const char*psz = p->Find(“llo”);
int n = p->Length();
}
}
} };
至此,客户避开了C++运算符的编译器相关性,访问了IString接口,这样就建立了对象与客户之间的通信方式。IUnknown接口
COM定义的每一个接口都由IUnknown接口继承而来,由于IUnknown接口提供了两个重要的特性:生存期控制和接口查询。客户程序只能通过COM接口与COM对象通信,虽然客户不用关心对象内部对方法的实现细节,但是它必须控制对象的存在与否。class IUnknown { public: virtual HRESULT__stdcall QueryInterface(const IID& iid, void **ppv)= 0;virtual ULONG __stdcall AddRef()= 0;
virtual ULONG __stdcall Release()= 0;};
每个COM对象要管理一个被称为引用计数(reference count)的整数值。每个引用从被有效赋值开始,一直到生命周期结束,这期间被称为:outstanding reference。为了有效地管理对象的生命周期,COM提供一些规则和操作,供客户遵守和使用:保持引用计数的确切含义,也就是记录当前outstanding reference的数目;引用计数从0开始,首次把接口递交给客户时为1,以后由客户管理,当引用计数回到0时,删除自己。
当客户通过复制获得新的接口指针时,引用计数加一,当某个接口不用时,减一。
3.1 COM接口的内存模型
COM接口的内存模型如下图所示,客户首先通过创建对象来获得接口指针,然后利用接口指针来访问vtable中的方法,vtable方法由COM对象内部实现。
3.2 查询接口
一个COM对象可以实现多个接口。从一个接口到另一个接口的访问途径可以通过函数QueryInterface(iid, ppv)来实现。初始得到了一个接口指针之后,调用它的QueryInterface函数,获得另一个接口指针。这里要注意:IUnknown必须是个静态接口指针,其他接口指针可以是动态的;QueryInterface在返回接口之前,必须调用新接口的AddRef函数,以示该接口的引用计数加1。
3.3 对象生存期与引用计数
引用计数是为了控制对象的生命周期,多个客户可以独立地控制对象的生存周期。引用计数反映了被客户引用的个数Outstanding references。引用计数在对象被创建时赋值为0,在接口被复制时加1,接口释放时减一,当引用计数为0时,表示没有客户在使用对象或者接口,则释删除对象或接口。
引用计数的实现由三个层次,组件级、对象级、接口级。组件级引用计数分辨率太粗糙,不容易控制,而接口级引用计数分辨率太细,效率较低。
引用计数规则:在顺序执行过程中,如果要对一个接口指针变量赋值,则对赋值后的接口指针变量调用AddRef,并且,如果赋值前的接口指针变量还没有结束,则赋值前必须对它调用Release以便先结束它的使用;如果要结束使用一个接口指针变量,以后不再用到它了,则调用Release函数。
引用计数注意事项:在整个生存期内,AddRef与Release一定要配对,否则漏掉AddRef,程序出错,漏掉Release,对象永不释放,造成内存泄露;对象的释放由Release内部实现,AddRef和Release的返回值并不可靠,不能准确的反映Outstanding references。