应用COM和接口可用 .pdf
第六讲应用 COM 和接口前面我们已经说明,.NET 战略的一个重要方面,就是弱化COM 的应用,这似乎是说,我们不再需要研究COM 了,但事实并非如此,其中一个重要的原因,是COM 已经流行了十年,而且, COM 也是微软和其它众多软件开发的基础,更不用说,.NET 平台本身还使用了COM+ 技术。基于这一系列的原因,我们不得不来研究在.NET 中应用COM 和接口的有关问题。在这一讲里,我们将首先对COM 做一个简要的回顾,其实COM 的问题并不太复杂,但是令人费解的是,很多软件方面的著作,都把COM 作为一个重要的主题来研究,其实这是没有太多的必要的。我们研究这一讲的内容的时候,首先要记住的是,在很大程度上.NET 是从 COM 演变来的,按照进化的观点,COM 类似于 Lucy(古埃塞俄比亚的南猿),所以如果它显得比较笨的话,你不要太介意,因为没有笨拙的COM ,就没有流畅的.NET 。一、COM 简介COM (Component Object Model )也就是组件对象模型,是微软为编程创建语言独立性所做的第一次好的尝试。COM 是一种用于说明如何建立软件组件的规范,由于使用了统一的接口规范,不同的开发人员创建的COM 组件,可被组合进不同的应用程序中,而且这些COM 组件所使用的语言,可以是完全不同的。例如,你可以在VC+程序中调用VB 组件,反之亦然。这对于一个开发软件的集体来说,是有很大的意义的。DCOM (Distributed Component Object Model)分布式组件对象模型,这是一种分布式应用程序集成到网络的技术,一个分布式应用程序由多个进程组成,这些进程协作完成一项工作。DCOM 为 COM 组件之间的通信透明的提供可靠、安全和有效的支持,这些组件包括ActiveX ,脚本和Java小程序。DCOM可将其应用程序分布到最适合于其顾客和应用程序的位置。在很大程度上,DCOM是带宽、部署和防火墙问题的综合,所以也就决定了它不会为太多的人所认可。际上并不知道自己在开发COM 对象。讨论这个问题的意义在于,假定一个企业的系统,已经长期的很好的使用一个COM 对象,现在,企业系统要用.NET 升级,这就面另两个选择:第一种,一切重新开始,用新的平台特性,开发新的内容,当然这是很好的,但有时候并不一定合算。第二种,一些经过考验被认为合理的组件,还可以在.NET 平台上使用,这将会大大减少开发时间和难度,我们下面要讨论的就是这种方法。我们首先在VC+ 中开发一个COM 组件,然后讨论在.NET 上的应用问题。二、在 C+中开发 COM 组件VC+ 一直是专业开发人员最青睐的语言之一,现在的 VC+.NET , 不但保留了原来的VC+ 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 1 页,共 45 页 - - - - - - - - - 6.0 的能力,而且还提供的平台托管代码的编写能力,可谓如虎添翼。这里主要利用VC+ 开发标准的COM 组件,然后用C#调用。开发 COM 组件,首先必须生成四个标准函数:DllRegisterServer :实现在注册表中注册COM 服务的功能。DllUnregisterServer :和上面的功能刚好相反,实现在注册表中注销的功能。DllCanUnloadNow :用来检测是否可以从内存中卸载COM 服务器。如果COM 客户第一个调用COM 服务,系统会自动把COM 服务载入内存,但又有客户访问COM 服务的时候,只是增加COM 服务被调用的计数,当计数为0 时,则自动卸载。DllGetClassObject :返回 COM 类对象的指针。COM 技术的原理是比较复杂的,好在VC+ 提供了活动模板库(ATL) ,可以帮组我们完成标准的框架部分,我们只要关心我们必需处理的属性和方法就可以了。下面讨论一个最简单的例子,但是这个例子表达了VC+ 生产 COM 组件最重要的要素,通过这个例子,更复杂的东西也能够做出来。首先生成一个ATL 项目。名字为: myCom 因为要处理字符串(CString) ,所以选择支持MFC 。这里不需要属性化。添加一个类。选择: ALT 简单对象。给对象恰当的名字。注意,对象可以有多个。名字为: MyShow 添加一个属性:Name,返回值为BSTR。这是可以在COM 对象之间传递的字符串格式。名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 2 页,共 45 页 - - - - - - - - - 添加方法: HelloiWorld 。主要目的是把Name 属性的字符串处理以后输出。参数为: BSTR* ,这表达一个输出的字符串指针。加入一个求和方法:Sum 注意:返回类型必须是HRESULT ,真正的返回值是在参数属性中选择“类型*” (指针),然后,选择retval。不选则为void。in,表达为传入值,一般用值类型。out 为参数表的传出值(out) ,调用的时候必须out,retval 为返回值。乘法: Multiply 同样的方法,加入乘法的方法:Multiply 处理代码,文档:MyShow.cpp /保留属性的值。/因为要处理汉字,这里使用CStringW /此外还有CString、 CStringA CStringW strName; STDMETHODIMP CMyShow:get_Name(BSTR* pVal) AFX_MANAGE_STATE(AfxGetStaticModuleState(); 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 3 页,共 45 页 - - - - - - - - - / TODO: 在此添加实现代码/返回值,注意CString 向 BSTR 的转换*pVal=strName.AllocSysString(); return S_OK; STDMETHODIMP CMyShow:put_Name(BSTR newVal) AFX_MANAGE_STATE(AfxGetStaticModuleState(); /传入值strName=newVal; / TODO: 在此添加实现代码return S_OK; STDMETHODIMP CMyShow:HelloWorld(BSTR* s) AFX_MANAGE_STATE(AfxGetStaticModuleState(); / TODO: 在此添加实现代码/注意,字符串是如何拼接的CStringW message(你好,你的名字是:); message+=strName; *s=message.AllocSysString(); return S_OK; STDMETHODIMP CMyShow:Sum(DOUBLE x, DOUBLE y, DOUBLE* s) AFX_MANAGE_STATE(AfxGetStaticModuleState(); / TODO: 在此添加实现代码/注意,如何把处理的结果返回名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 4 页,共 45 页 - - - - - - - - - *s=x+y; return S_OK; STDMETHODIMP CMyShow:Multiply(DOUBLE x, DOUBLE y, DOUBLE* s) AFX_MANAGE_STATE(AfxGetStaticModuleState(); / TODO: 在此添加实现代码/同样要把处理的结果返回*s=x*y; return S_OK; 生成的时候同时注册。必要的时候,可以人工注册和卸载:Regsvr32/s .DLL - 注册Regsvr32/u .DLL - 注销在.NET 中应用。myComLib.IMyShow obj=new myComLib.MyShowClass(); /加法private void button1_Click(object sender, System.EventArgs e) 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 5 页,共 45 页 - - - - - - - - - textBox3.Text=obj.Sum(double.Parse(textBox1.Text),double.Parse(textBox2.Text).ToString(); /减法private void button2_Click(object sender, System.EventArgs e) textBox3.Text=obj.Multiply(double.Parse(textBox1.Text),double.Parse(textBox2.Text).ToString(); /写属性private void button3_Click(object sender, System.EventArgs e) obj.Name=textBox1.Text; /读属性private void button4_Click(object sender, System.EventArgs e) textBox3.Text=obj.Name; /调 HelloWorld private void button5_Click(object sender, System.EventArgs e) textBox3.Text=obj.HelloWorld(); 结果应该是没有问题的。三、在 Delphi 中开发 COM 组件很多语言都有生成COM 组件的能力,下面做一个Delphi 的例子。1新建 ActiveX 库名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 6 页,共 45 页 - - - - - - - - - file - new - other - ActiveX - ActiveX Libray OK 产生一个项目文件DllGetClassObject, DllCanUnloadNow, DllRegisterServer, DllUnregisterServer; 2创建 COM 对象File - new -Other -ActiveX -COM object OK 类对话框:输入类名:比如MyShow 出现一个统一的类生成器:IMyShow - 接口MyShow -实现这个接口的对象在 IMyShow 右键-New - Property 添加一个属性,Name 名字起为: Name 再添加一个方法:Method Name 名字起为: HelloName 关于属性,一般的形式是:protected function Get_name: WideString; stdcall; procedure Set_name(const Value: WideString); stdcall; end; . function TMyShows.Get_name: WideString; begin end; procedure TMyShows.Set_name(const Value: WideString); begin end; 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 7 页,共 45 页 - - - - - - - - - 也就是说返回的是function ,进入的是procedure。当使用字符串的时候( WideString),对应的为 BSTR。为了达到这个目的,在建立属性的时候,要注意:Get:Type-BSTR Parameters-删除set: Type-Void Parameters-Value BSTR in 这样就能造成上面的效果。refresh-刷新形成程序的框架,并在框架中写入自己的内容:具体处理的程序可以这样写:var Username:string; function TMyShows.Get_name: WideString; begin Result := WideString(Username); end; procedure TMyShows.Set_name(const Value: WideString); begin Username := String(Value); end; 关于方法,去掉返回值就是过程,否则是函数:procedure TMyShows.helloName; begin showmessage(您的名字是:+ Username); end; 这里要注意一个问题,messagebox(0,mb_ok)和 showmessage() 需要 Dialogs 库,需要把它Uses 进来。完成的源程序如下:- unit Unit1; 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 8 页,共 45 页 - - - - - - - - - $WARN SYMBOL_PLATFORM OFF interface uses Windows, ActiveX, Classes, ComObj, Project1_TLB, StdVcl,Dialogs; type TMyShows = class(TTypedComObject, IMyShows) protected function Get_name: WideString; stdcall; procedure Set_name(const Value: WideString); stdcall; procedure helloName; stdcall; end; implementation uses ComServ; var Username:string; function TMyShows.Get_name: WideString; begin Result := WideString(Username); end; procedure TMyShows.Set_name(const Value: WideString); begin Username := String(Value); end; procedure TMyShows.helloName; begin showmessage(您的名字是:+ Username); end; initialization TTypedComObjectFactory.Create(ComServer, TMyShows, Class_MyShows, ciMultiInstance, tmApartment); end. - File - Save all 保存Project - Build 编译名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 9 页,共 45 页 - - - - - - - - - Run - Register ActiveX Server 注册3)注册和注销组件使用 COM 组件以前, 先要确认它在计算机上是可以使用的,所以必须注册, 注册 DLL 有两种方法:1 在 delphi 中编译 DLL ,编译的同时自动在所编译的计算机上注册,(文件- 生成工程.DLL ) 。2使用Regsvr32.exe(在 Windowssystem 中,或 WINNTsystem32 中 ) ,方法: Windows开始- 运行Regsvr32/s .DLL - 注册Regsvr32/u .DLL - 注销比如上面的例子:Regsvr32 “ d:MyShow Helloname.dll”注册是注册在系统注册表中,所以也可以用regedit.exe 来查看, 对于注销组件, 可以在系统注册表中删除该组件的引用,然后再删除该组件的DLL 或 EXE 文件。注意: COM 类的注册表条目位于系统注册表的HKEY_CLASSES_ROOT中。四、在 .NET 中应用 COM 组件首先建立一个Windows 工程。Project1.MyShow obj=new Project1.MyShowClass(); private void Form1_Load(object sender, System.EventArgs e) 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 10 页,共 45 页 - - - - - - - - - private void button1_Click(object sender, System.EventArgs e) obj.Name=textBox1.Text; private void button2_Click(object sender, System.EventArgs e) obj.HelloName(); 可以看出来,和普通的类使用上是一样的。五、 Activex 控件ActiveX 控件是充分利用OLE 和 ActiveX 技术的自定义控件。ActiveX 控件是基于与应用程序无关的思想而设计的。可以简单的认为ActiveX 控件合并了VBX 技术和 ActiveX 标准。VBX 是 Visual Basic提出的一个技术标准。Microsoft 的 Visual Basic 是第一个引入向市场供应软件组件构想。但软件组件重复性使用的概念是早于Visual Basic 提出的, 而且它早已根植于面向对象编程的理论中。Visual Basic 提出了组件的概念,定义了建立与描述新控件的标准方式。Visual Basic 提出的第一个技术标准是VBX 。这是 16 位版本 Delphi 中完全可以使用的一个16 位标准。在 32 位的操作平台中,Microsoft 使用了功能更强,更为开放的Activex 控件来代替VBX 标准。ActiveX 控件的结构ActiveX 控件的好处在于它的可编程性和可重用性。它能够嵌入到目前大多数的开发环境中,包括Delphi 、Visual Basic 、Oracle、Developer 2000 等。它在这些开发环境中的表现,和 Delphi 的组件在IDE 环境中的表现完全相同,都有自己的属性、方法和事件。 ActiveX 控件最理想的用途是在网上作为传送小型程序的载体。何时使用ActiveX 控件没有可用的Delphi 组件能满足你特殊的需要。你想使用多中编程语言进行开发,想在多个开发平台之间共享一些控件。名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 11 页,共 45 页 - - - - - - - - - ActiveX 控件的缺点最明显的问题是,Delphi 组件能够直接内建到应用程序的可执行文件中,直接与应用程序和其他组件进行通信的;但ActiveX 控件是通过COM 与应用程序通信的。ActiveX 控件是一种“最小公分母”的解决方案,因而没有完全挖掘出开发工具的能力。第一个自制的ActivX 控件希望做一个ActiveX 控件,能显示系统时间,同时显示的周期可以由外部参数控制。一、首先清空Form;二、 File-New-Other-ActiveX-Active Form -Ok 三、其中遇到New ActiveXName :自己给控件起名;比如这里写上Showtime 。下面的多选框选择可选可不选。这样就产生了三个单元,其中第三个是类库(TLB ) ,一般不要动。四、在 System 页-加入Timer Standard页 -加入Label(改变字体和字号)五、 Timer 的 Interval 属性定为1000,表示每隔1000 毫秒触发一下。给Timer 的Timer1Timer 事件加一句显示系统时间:label1.Caption:=timetostr(Time); 六、现在要给控件增加一个属性,用外部数据来控制触发间隔,也就是 Timer 的 Interval属性。菜单 -Viwe-type Library- 可以看到Form 的属性增加属性:New proporty (注意是图标)-Name:属性的名字。如果是可读写的,就有两个属性函数,一个是Get,另一个是Set。刷新( Refresh Implementation )后在 ShowtimeImpl1 单元里可以看到两个get 函数和 set过程,把相应的程序写进去。var i:integer; 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 12 页,共 45 页 - - - - - - - - - procedure TShowtime.Button1Click(Sender: TObject); begin if i=1 then begin Panel1.Color:=clRed; i:=0; end else begin Panel1.Color:=clGreen; i:=1; end; end; function TShowtime.Get_mytime: Integer; begin result:=timer1.interval; end; procedure TShowtime.Set_mytime(Value: Integer); begin timer1.interval:=value; end; 七、在 project-build 进行编译、保存。八、注册: Run-Register ActiveX Server - 产生一个OCX 文件。实际上一个Activex 已经完成了。注意一下, ActiveX 控件有个新的属性AxBorderstyle- 边界风格:另外: mytime 是自己给出的控制内部元件的属性,这里是时钟的触发周期。在.NET 中使用这个控件。名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 13 页,共 45 页 - - - - - - - - - 可以看出来,由于COM 接口的标准化,不管你是用什么语言写的COM 组件,都可以很好的用在 .NET 系统里面,所需要注意的就是由于COM 组件的特点,这些组件使用以前必须经过注册,否则无法使用。六、在 .NET 程序中使用Word 微软的 Office ,是一套功能强大的办公软件,更重要的,它也是一套COM 自动化系统,你可以在任何支持COM 的语言体系中调用它的功能。首先引入 Word 的 COM 组件在 Microsoft Word 9.0 Object Libray。Dim WOR As New Word.ApplicationClass() 定义公文Dim WOD As New Word.DocumentClass() Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click WOD = WOR.Documents.Add WOR.Caption = 我的标题栏 WOR.Options.CheckSpellingAsYouType = False WOR.Options.CheckGrammarAsYouType = False WOD.Sentences.Last.Paste() WOD.Range.InsertAfter( 我的文字 ) WOD.Range.InsertAfter( 其它的文字 ) WOR.Visible = True WOR.PrintPreview() = True WOR.PrintOut(True, True) WOR.Documents.Save() 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 14 页,共 45 页 - - - - - - - - - End Sub Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click WOR.Visible = True WOR.PrintPreview() = True WOR.Visible = False End Sub Private Sub Form1_Closed(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Closed Try WOD.Close(False) Catch End Try Try WOR.Quit() Catch End Try End Sub Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click WOR.Documents.Save() End Sub Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click WOR.Visible = True End Sub Private Sub Button5_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button5.Click WOR.Visible = False End Sub Private Sub Button6_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button6.Click Dim doc As New Word.DocumentClass() 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 15 页,共 45 页 - - - - - - - - - doc = WOR.Documents.Add() Dim str As String str = Text Formatting: With WOR.Selection .Font.Size = WOR.Selection.Font.Size + 2 .Font.Bold = True .TypeText(str) .Font.Size = WOR.Selection.Font.Size - 2 .Font.Bold = False 换行.TypeParagraph() .Font.Color = Word.WdColor.wdColorDarkRed .Font.Italic = False .TypeText(This sentence will appear in red. ) 换行.TypeParagraph() .Font.Color = Word.WdColor.wdColorBlack .Font.Italic = True .Font.Size = WOR.Selection.Font.Size + 2 .TypeText(Text color was reset to black, & _ but the font size was increased by two points) End With Dim fName As String SaveFileDialog1.Filter = Documents|*.doc SaveFileDialog1.ShowDialog() fName = SaveFileDialog1.FileName If fName Then Try doc.SaveAs(fName) Catch exc As Exception MsgBox(Failed to save document & _ vbCrLf & exc.Message) End Try End If MsgBox( 这个公文容纳 & doc.Paragraphs.Count & 段 & vbCrLf & _ doc.Words.Count & 个词以及 & doc.Characters.Count & 个字符 ) doc.Close(Word.WdSaveOptions.wdDoNotSaveChanges) End Sub 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 16 页,共 45 页 - - - - - - - - - Private Sub Button7_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button7.Click Dim doc As New Word.DocumentClass() doc = WOR.Documents.Add() 送入文字With doc.Range .InsertAfter(A New Architecture & _ for Building Distributed Applications) .InsertParagraphAfter() .InsertAfter(ADO.NET is the latest data access & _ technology from Microsoft, geared towards & _ distributed applications. Unlike its predecessor, & _ ADO.NET uses disconnected recordsets.) .InsertParagraphAfter() .InsertAfter(The disconnected recordsets are called & _ DataSets and they may contain multiple tables. & _ If the tables are related, the DataSet knows & _ how to handle the relations and it provides & _ methods that allow you to move from any row of & _ a table to the related rows of the other tables.) .InsertParagraphAfter() .InsertParagraphAfter() .InsertAfter(ADO.NET uses XML to move rows between & _ the database and the middle tier, as well as & _ between the middle tier and the client. & _ XML passes through firewalls and its an ideal & _ candidate for moving binary information between & _ layers and/or different operating systems.) End With Range为可以多处的选择Dim selRange As Word.Range 选择第一行selRange = doc.Paragraphs.Item(1).Range selRange.Font.Size = 14 selRange.Font.Bold = True selRange.ParagraphFormat.Alignment = Word.WdParagraphAlignment.wdAlignParagraphLeft 选择第二行的第二句selRange = doc.Paragraphs.Item(2).Range.Sentences.Item(2) selRange.Italic = True 选择第三行的的第六个字selRange = doc.Paragraphs.Item(3).Range.Words.Item(6) selRange.Font.Bold = True 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 17 页,共 45 页 - - - - - - - - - 选择第五行的第五个字selRange = doc.Paragraphs.Item(5).Range.Words.Item(5) selRange.Font.Bold = True Dim fName As String SaveFileDialog1.Filter = Documents|*.doc SaveFileDialog1.ShowDialog() fName = SaveFileDialog1.FileName If fName Then Try doc.SaveAs(fName) Catch exc As Exception MsgBox(Failed to save document & _ vbCrLf & exc.Message) End Try End If MsgBox(The document contains & doc.Paragraphs.Count & paragraphs & vbCrLf & _ doc.Words.Count & words and & doc.Characters.Count & words) doc.Close(False) End Sub Private Sub Button8_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button8.Click Dim thisDoc As Word.Docume