代码改变世界

为什么无法从外部访问VSTO对象?

2012-04-19 11:41  slmk  阅读(1438)  评论(3编辑  收藏  举报

为什么要从外部获取VSTO对象?

  通常的场景是我们用VSTO创建了一个office应用程序,里面用托管代码定义了一些方法,我们想通过外部程序打开该VSTO程序,获取VSTO对象引用,然后调用这些方法,以达到控制Office文档的目的。

直接获取失败

  以下代码试图从外部程序获取VSTO对象引用,然而失败了!

using System;

using System.Diagnostics;

using System.Globalization;

using System.IO;

using System.Reflection;

using System.Threading;

using Microsoft.Office.Tools.Excel.Extensions;

using IExcel = Microsoft.Office.Interop.Excel;

using TExcel = Microsoft.Office.Tools.Excel;

namespace ClientApplication

{

   class Program

   {

      static int Main(string[] args)

      {

         /* To avoid any problem (exception of type "Invalid format") when manipulating the 

          * workbook, we'll specify here the culture en-US to avoid forcing the client to 

          * have a language pack for excel

          */

         Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");

         //Get the application and set it visible (ie, seeing the excel front end)

         IExcel.Application application = new IExcel.Application();

         application.Visible = true;

         //Let's create the path to the file and then : open it

         string basePath = @"D:\Projects\PDA - Blog\Blog Research\VSTO - WCF\ServerApplication";

         string fileName = "ServerApplication.xlsx";

         IExcel.Workbook book = application.Workbooks.Open(Path.Combine(basePath, fileName),

            Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing,

            Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing,

            Type.Missing, Type.Missing, Type.Missing, Type.Missing);


         /* Let's now use the .NET 3.5 SP1 functionality to convert an interop

          * object to a VSTO object

          * As it is an extension method, we could also use

          * TExcel.Workbook workbook = book.GetVstoObject();

          */

         TExcel.Workbook workbook = WorkbookExtensions.GetVstoObject(book);

         //Let's now check that the conversion did work

         Debug.Assert(workbook != null, "The conversion to VSTO did not work");

         //It will fail ! 

         return 0;

      }

   }

}

  也就是说我们从VSTO程序外部,试图获取的VSTO对象总是为空!

原因其实很简单:

  我们在试图获取另一个进程中的对象引用,而一个进程是不可以直接访问另一个进程的内存空间的。这个道理我们都知道,我们对进程间通讯的方法也耳熟能详,什么消息发送、命名管道、匿名管道、共享内存和Socket等。令我们不解的往往是:既然能取得Interop的Appliction、WorkBook等对象,为什么不能取得托管的VSTO对象呢?如果我们了解COM就不难理解了,.net与office的Com的互操作其实也是进程间的一种通信机制,只不过是通过COM组件掩盖了底层的通信机制,看来进程间通信的最高境界就是通过COM组件的方式。也就是说Interop的Appliction等对象是由COM接口公开出来的,本例中后台其实启动了一个Excel进程作为COM服务器,我们的外部程序,本例中的命令行程序充当了一个COM客户端。而我们的VSTO对象没有公开为COM可见的形式。

MSDN的解释:

  其实本文所说的VSTO对象,在MSDN的官方名称是宿主控件和宿主项。通过编程方式在运行时创建宿主项是有限制的,参加MSDN文章:Programmatic Limitations of Host Items and Host Controls

  大体意思是很多宿主项只能在设计时创建,运行时不能创建。

如何实现从外部调用VSTO程序的托管方法呢?

  答案就是使用进程间通信,有兴趣的可以参考这篇文章:http://www.pedautreppe.com/post/How-can-we-manipulate-the-VSTO-objects-when-opening-a-VSTO-document-from-command-line-.aspx  ,是通过.net remoting实现的。

参考资料

COM互操作教程

可以通过OLE-COM Object Viewer查看COM组件公开的接口