在这篇文章里面,我将向大家介绍如何在.Net中访问Office所公开的编程接口。其实,不管是使用哪种具体的技术来针对Office进行开发(比如VSTO,或者用C#编写一个Office Add-in,或者在一个WinForms程序中调用Office的功能,甚至在一个ASP.NET应用的服务器端启动一个Excel进程),只要是基于.Net平台,这篇文章所描述的内容都是有价值的。
在这篇文章以及后续的文章中,所有的演示都将基于Office 2003 Professional和Visual Studio 2005。使用Visual Studio 2005并不代表我们不能在Visual Studio 2003中使用这些方法来访问Office编程接口,相反,这篇文章以及后续文章中的几乎所有演示,都能在Visual Studio 2003中完成。笔者使用Visual Studio 2005的原因只是因为喜欢它更好的IDE特性。:)
一、Office PIA
在第一篇文章中曾经说过,Office的编程接口都是通过COM组件公开的,任何访问Office编程接口的程序,实际上都必须要最终调用Office COM组件。如果你是一个刚从事Windows平台开发不太久,一开始就是学习和使用.Net的程序员,也许你又要感到郁闷了。所幸的是,.Net能够让开发人员非常方便的访问COM组件。我会用尽量简洁明了的描述,让大家理解我们在.Net中是如何访问Office COM组件的。
(一)Interop Assembly
在.Net Framework中,提供了一项叫做COM Interop的技术,这项技术就是专门用于让我们能够在.Net代码中直接访问COM组件的。它的基本原理是,.Net Framework能够自动针对某一个COM组件,帮助开发人员生成一个Interop Assembly(互调用程序集,后面简称IA),IA是一个完全的托管程序集。IA中的名称空间、类、方法等等,都是针对那个COM组件对应的。在我们的.Net程序中,我们可以直接引用这个IA,并且调用里面相关的方法,这时IA就会帮我们再去访问真正的COM组件里面的相应方法。最后的效果就是,在我们自己的应用程序中,只需要调用一个纯粹.Net的IA即可。
下面的图片说明了IA是如何帮我们的程序访问到COM组件的。
生成一个IA的方法非常简单,当我们在Visual Studio开发环境中添加一个新的COM组件引用时,Visual Studio就会自动帮我们生成一个相应的IA。如下图,我们示范在一个项目中引用Microsoft XML 5.0组件。
当在上图中点击OK按钮,Visual Studio就已经自动为我们生成了一个针对Microsoft XML 5.0组件的IA。打开项目目录的obj\debug目录,就能够看到一个名称为Interop.MSXML2.dll的程序集文件,这个文件就是自动生成的IA,并且在项目中,Visual Studio也自动引用了这个IA程序集。如下图。
(二)Primary Interop Assembly
我们在自己的应用程序中,访问Office COM组件的方法的基本原理,就如同下面所述,都是通过COM Interop,透过IA间接的访问到Office中的COM组件。但是针对Office这个软件,则有一点点特殊的区别,那就是我们不应该自己在Visual Studio中生成一个“自己的”访问Office COM的IA,而需要使用微软提供的“官方的”PIA。
PIA的意思可以理解为“官方互操作程序集”,它和IA最主要的区别如下:
1、IA是由开发人员在开发机器上通过向导自动生成的,PIA是由软件厂商(针对Office这个软件而言,就是指微软)提供的;
2、PIA经过了厂商的优化处理,使之更容易被.Net调用;
(三)Office Primary Interop Assembly
所以,我们都应该使用PIA来访问Office COM组件,而不应该使用IA。那么如何把Office PIA安装到我们的电脑上呢?
如果我们的机器上已经安装了.Net Framework,那么在安装Office时,在安装向导的高级自定义选项中,我们在每个组件(Word、Excel、PowperPoint等)的子选项中,都能看到一个“.NET可编程性支持”,选择安装它,Office2003的安装程序就会自动把PIA安装到我们的计算机上。
另外,对于Office PIA的客户端分发(就是说,给我们软件的用户都统一装上PIA),微软专门提供了一个安装包。可以在http://www.microsoft.com/downloads/details.aspx?FamilyID=3c9a983a-ac14-4125-8ba0-d36d67e0f4ad&DisplayLang=en下载到这个分发安装包。
Office PIA按照Office的各个组件(Word、Excel、PowerPoint、Outlook等),分成多个单独的程序集。比如Word对应的PIA程序集是Microsoft.Office.Interop.Word.dll(程序集里面的类都放在命名空间Microsoft.Office.Interop.Word中),Excel对应的程序集是Microsoft.Office.Interop.Excel.dll(程序集里面的类都放在命名空间Microsoft.Office.Interop.Excel中)。另外,Office公用的一些组件(比如菜单栏)放在一个单独的程序集中:Office.dll(对应的命名空间是Microsoft.Office.Interop.Core)。
如果我们的开发机器上已经安装好了Office PIA,那么当我们通过上面所述的方法,在Visual Studio中引用Office COM组件时,Visual Studio会检测到本机已经安装了Office PIA,然后,它会直接引用安装好了的PIA,而不会再自动生成一个新的IA。
如下图,我们在Visual Studio中添加一个对Word COM组件的引用(Word在COM组件列表中是“Microsoft Word 11.0 Object Library”,相似的,Excel、Outlook、PowerPoint的COM组件名称都遵循这个规律)。
在上图中点击OK按钮后,在项目管理器中就可以看到,Visual Studio已经帮我们引用了需要引用的组件。实际上,除了我们选择要引用的Word组件外,其他额外但是必需的诸如Microsoft.Office.Core、stdole、VBIDE等组件也已经被自动引用进来了。
在上图的Word组件引用上点击鼠标右键,查看它的属性,在它的路径属性中,我们可以看到这个PIA文件其实是在“C:\Windows\assembly\...”目录中,这个目录也就是我们机器上的全局程序集缓存(GAC,Global Assembly Cache)所在的目录。这是因为Office PIA是被安装到机器上的GAC中,所以对Office PIA的引用会直接指向GAC中的相应文件。
二、深入浏览Office PIA
如果读者曾经使用过VBA进行过开发(或者使用其他的开发工具诸如VB/VC/Delphi直接调用过Office),那么其实你已经对Office COM接口有了一定的了解,因为在VBA编辑器中所编写的操作诸如Application、Document、Range的代码,其实正是在操作Office COM组件中的Application、Document、Range这些类。
我们已经知道,在Office PIA中,已经把Office COM组件进行了封装,所以我们可以预见,对于每一个Office COM组件中的类或者接口,在Office PIA的程序集中,我们应该都能找到一个对应的类或者接口。接下来,我们就用对象浏览器直接打开Office COM组件,然后再打开Office PIA,这样我们就可以对照它们,更清楚的理解它们。
在Visual Studio中,打开视图菜单中的对象浏览器,然后点击对象浏览器中的添加其他组件按钮,在出现的选择窗口中,选择COM组件中的“Microsoft Word 11 Object Library”,这时对象浏览器就直接打开了Word 2003的COM组件,如下图。
在上图所示的Word COM组件成员列表中,可以看到我选中了Word中的Application类的Quit()方法。Application类可以说是各个Office组件的核心类,不管是Word、Excel、PowerPoint,都存在一个对应的Application类,对应Word、Excel、PowerPoint主程序。如果要在我们的程序中直接打开Word,就需要创建这个Application类的一个实例,如果要关闭掉这个新打开的Word程序,就调用这个新创建的Application对象的Quit()方法。
接下来,我们再用对象浏览器打开Office PIA中的Word所对应的程序集。在前面的操作步骤中,我们已经在项目中引用了Word的PIA,在项目管理器的引用列表中选中Word,点击鼠标右键,选择在对象浏览器中查看,就可以在对象浏览器中打开Word的PIA了。如下图。
如果在上图所示的Microsoft.Office.Interop.Word命名空间所包含的类中做一些浏览,相信读者会发现一个很有意思的事情。那就是其实Word的PIA中的类、接口,并不是和Word的COM组件中的类、接口一一对应的。比如,我们在Word COM组件中能够看到一个叫做Application的类,但是在Word的PIA中,我们只能找到一个叫做Application的接口,和一个叫做ApplicationClass的类。
出现这个情况的原因,在于.Net的COM Interop(具体说就是.Net SDK中的TlbImp.exe这个命令行工具)帮我们根据COM组件生成Interop Assembly时,其实是不会一一对照COM组件来生成.Net类和接口的。相反,它会根据一定的规则,来生成对应的.Net类和接口。
由于Application是Word编程接口中最重要的部分,所以我具体针对Word中的Application这个接口,把它的转换规则简要的说明一下(实际生成的接口和类比下面描述的要更多,相关的关系更复杂)。首先,Word PIA中会生成一个_Application接口,这个_Application接口基本描述了Word COM组件中的Application类中的所有操作和属性,然后,Word PIA中还会生成多个ApplicationEvents_Event系列接口(ApplicationEvents2_Event、ApplicationEvents3_Event、ApplicationEvents4_Event接口…我们可以不用管这些具体的细节),这个接口基本描述了Word COM组件中的Application类中的所有事件。然后,Word PIA中会生成一个Applicatin接口,它实现了_Application接口和ApplicationEvents_Even接口,这样,Application接口就基本描述了Word COM组件中的Application类中的所有操作、属性、事件等等。最后,Word PIA中生成了一个具体的ApplicationClass类,这个类实现了Applicatin接口。
如果你已经被上面那一段描述搞得头昏脑胀,那么只需要记住:在Word PIA中,我们有一个Application接口和一个ApplicationClass类,Application接口描述了对应的Word COM组件中的Application类的所有成员,而ApplicationClass类是具体的实现类。
三、Code WalkThrough:一个.Net WinForms程序
终于,在你忍受了N久,勉强看完了上面那些罗嗦的文字之后,总算可以看到一个具体的示范了。我们要用C#写一个Windows应用程序,在这个程序中,启动Word,用代码操作它做一些操作,然后再关闭掉它。
首先,我们创建一个新的C# Windows应用程序,然后通过上面介绍过的方法,在项目中引用Word的PIA(在添加引用的界面中,选择COM组件列表中的Microsoft Word 11 Object Library)。
在自动创建的启动窗体上,放两个Button控件,一个叫做btnStartWord,另外一个叫做btnStopWord。我们希望当用户点击btnStartWord时,我们的程序自动启动Word,然后创建一个新的Word文档,然后将其自动保存在磁盘上,当用户点击btnStopWord时,就关闭掉Word。窗体设计视图如下。
在这个主窗体类的源代码中,我们引用Word PIA的名称空间,我们使用MSWord来替代Microsoft.Office.Interop.Word这个完整的命名空间名称:
using MSWord = Microsoft.Office.Interop.Word;
在主窗体类的源码中,添加一个类级别的成员,_wordApp是一个Application类型的对象(记住:MSWord.Application是一个接口!):
private MSWord.Application _wordApp = null;
然后在btnStartWord按钮的点击事件代码中,添加如下代码,代码创建一个新的Word实例,然后显示它:
_wordApp = new MSWord.Application();
_wordApp.Visible = true;
看到这里,很多人有一个非常大的疑惑,那就是MSWord.Application实际上是一个接口,那么我们怎么可能通过“new MSWord.Application()”来创建一个Word实例呢?难道我们不应该使用“new MSWord.ApplicationClass()”来做吗?毕竟ApplicationClass才是实现Application接口的具体类啊。
在这里,Office PIA为我们提供了一个小小的“cookie”,我们实际上的确可以使用“new MSWord.Application()”来创建一个Word程序实例的,我们只需要知道,Office PIA会在底下自动帮我们创建一个真正的Word程序实例。
接下来,我们在btnStartWord按钮的事件代码中,再添加如下的代码。
Object missing = Type.Missing;
Object sFileName = "C:\\Sample.doc";
MSWord.Documents docs = _wordApp.Documents;
MSWord.Document doc = docs.Add(ref missing, ref missing, ref missing, ref missing);
doc.SaveAs(ref sFileName, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing);
doc.Close(ref missing, ref missing, ref missing);
在上面的代码中,我们通过访问Application的Documents属性,得到一个Documents类,然后通过Documents类的Add()方法,创建一个新文档,并得到对应这个新文档的类型为Document的对象,然后通过Document类的SaveAs()方法将文档保存在磁盘上,最后通过Document类的Close()方法关闭这个新文档。
嗯,我知道我其实解释得不够彻底,上面代码中的那些ref、missing之类的东东,到底是什么意思,为什么要用它们,相信不少人都非常迷惑(特别是曾经用VBA或其他语言访问过Office COM组件接口的程序员而言)。这其实牵涉到用C#语言调用Office编程接口的一个“语法兼容”问题。就是说,Office的产品开发组在对编程接口进行设计时,实际上是专门设计为被VBA调用的,所以接口都非常配合VBA的语法,使VBA程序员尽量感到方便。但是由于C#语法和VB语法有很多不同,所以在用C#访问Office编程接口时,就会感到非常的“别扭”。
这这里,我只想对上面的代码做如下简要的额外解释:
(1)很多的Office编程接口中的方法,都带了非常多的参数(比如Document.SaveAs()方法有16个参数!!!),而实际上我们调用它们的时候,并不是每一个参数都需要明确给一个特定的值的(比如Document.SaveAs()方法只需要明确给定第一个参数,即保存到哪里),那么对于不需要给定明确值的参数,我们可以直接传一个.Net类库中自带的静态对象:Type.Missing就可以了。
(2)很多的Office编程接口中的方法,其参数都必需传引用,而不能传值,所以,调用这些方法的时候,对于参数都需要加上C#中的ref关键字。比如上面代码中的Document.Add()、Document.SaveAs()、Document.Close()方法,它们的参数都必须传引用,所以每个参数前面都加上了ref关键字。
对于使用C#语言调用Office编程接口时,对“语法兼容”问题的更全面的描述,请参看《Office with .Net(二)之外传―――C#访问Office编程接口时的“语法兼容”问题》。
继续为我们的项目添加代码。在btnStopWord按钮的事件代码中,填充下面的代码:
Object missing = Type.Missing;
_wordApp.Quit(ref missing, ref missing, ref missing);
_wordApp = null;
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
上面的代码通过调用Application.Quit()方法,退出Word程序。更值得关注的部分是如何释放掉Word程序实例。上面的代码用了一个简单但是很有效的方法,让Word程序进程被关闭回收,首先将对象_wordApp重新赋值为null,这样底下的那个Word Application对象将不再被任何变量引用,最后通过强行调用.Net Framework中的垃圾收集方法,使Application对象能够被垃圾回收器回收掉。(实际上,.Net Framework的垃圾回收器回收的只是Office PIA的一个包装类对象而已,但是这个包装类对象被回收后,对应Word程序的COM组件对象会发现自己不再被其他任何对象引用,引用计数变为了0,于是,那个COM组件就会被真正释放掉了。)
如何保证在自己的应用程序中关闭Office程序其实是一个不小的问题,上面描述的方法并不是100%有效的,对这个话题更完整的描述请参考《Office with .Net(二)之外传―――“彻底干净的”关闭Office程序》一文。
我们的第一个示范程序到这里就已经写完了,现在我们可以运行一下这个程序,然后先点击第一个按钮启动Word,并操作Word创建一个新文档后再保存到磁盘上,接着点击第二个按钮关闭掉Word。
(四)总结
这篇文档简要描述了如何在.Net中访问Office的编程接口,讲解了Office PIA的概念和使用方法。从这篇文章可以看出,在.Net中操作Office是非常简单而直接的,微软通过提供Office PIA,大大简化了.Net程序员的工作。
转载自:http://hi.baidu.com/sammyhxm/item/fd16be28dcdd6fc0ee10f1a6