探索 Word 2007 开发(四):上传图片
Written by Allen Lee
故事缘起
作为Word 2007的一个新特性,博客功能一直让我跃跃欲试,然而,正如大家所知道的,它的一个bug致使通过MetaWeblog API在博客园发布带图片的文章时出错。Ken和TianFang都分别在他们的文章中提到了这个问题,TianFang更进一步地提出了一个解决方案,使得用Word 2007在博客园发布带图片的文章成为可能,另外,五年也在TianFang的基础上把程序包装成Windows服务。从大家对这件事的反应中不难看出,这样一个小bug就让Word 2007和博客园擦肩而过未免太可惜了。
增值服务区 |
Why blogging from Word 2007 to Dasblog does not work,Ken |
用word2007在博客园发布带图片的blog,TianFang |
用word2007在博客园发布带图片的blog,五年 |
除了TianFang的办法,我也另外想过下面两个办法:
- 在一个侧边栏上提供发布功能,把Word文档转换成XHTML然后发布,然而,这是一个费力不讨好的办法,当然,如果你熟悉OOXML和XSLT,并且希望对底层HTML有更多的控制权,那么这也不失为一个考虑。
- 寻找一个FTP服务器用于图片上传,这个办法显然是最省力的,然而,稳定、保障且免费的FTP是很少的,当然,如果博客园提供FTP服务,那么这将成为最优解,但同时会加重博客园的负担,这又是大家所不希望看到的。
那么,是否还有别的办法呢?
蛛丝马迹
如果你曾经在Word 2007里设置博客帐号,那么你应该不会对下面这个对话框感到陌生:
Figure 1
留意到Picture provider下拉列表右边有一个Refresh List超链接,这意味着列表里面的内容并非固定的,我猜Word Team应该为此留下一些扩展的余地,然而,怎样才能得知如何扩展呢?还记得我们曾经在"我的博客"侧边栏里用Documents.Open() 方法打开存档草稿吗?或许Documents类里面会有一些线索!浏览Documents类的成员方法,发现一个AddBlogDocument() 方法,它的效果相当于点击Office Button\Publish\Blog后创建的Word窗口,然而,这并非我想要找的。接着,我把Word 2007的Word Object Model Reference里的所有条目浏览一遍,还是没有发现。如果真有这个扩展机制的话,它不在Word的对象模型里,难道在Office 2007的公共部分?抱着试试看的态度,我又浏览了2007 Office Suites里的Object Model Reference,发现两个带有"Blog"字眼的接口:
难道这就是我一直在找的真命天子?赶紧Google以下,发现Andrew写了一篇文章介绍Office 2007引入用于扩展的新接口,从文中可以得知,IBlogPictureExtensibility接口确实是Picture provider的扩展点,遗憾的是,Andrew无法确定该接口是否能在VSTO Runtime上正常工作。
增值服务区 |
Office 2007 New Extensibility Interfaces,Andrew Whitechapel |
既然看到了希望之光,就不应该随便放弃;既然Andrew他们没有对该接口进行测试,那我就来做一下,即便最后不成功。然而,实现这个接口的对象应该放在哪里,Word 2007才能正确识别并使用其中的功能呢?
突然,一个问题在我脑海里油然而生:同样作为扩展接口,其它接口,例如IRibbonExtensibility,又是如何被Word 2007识别的呢?由于Visual Studio 2008 Beta 2自带的VSTO已经把关于Ribbon的很多东西封装起来了,所以要看出它如何让实现IRibbonExtensibility的对象被Word 2007识别并不容易。此时,我想到在VSTO 2005 SE上创建Ribbon时,IRibbonExtensibility还是"可见"的,于是找了个代码来看看:
真相终于大白了,原来它是通过AddIn.ReguestService() 方法做到的!下面,我们来做第一个吃螃蟹的人。
着手实现
首先,添加一个CnblogsPictureProvider类,并让它实现IBlogPictureExtensibility接口:
接着,在BlogPictureProviderProperties() 方法里添加如下代码:
然后,在项目里添加如下所示的窗口:
其中,Provider下拉列表的数据源来自PictureProviders.xml文件:
这样,你便可以通过配置文件让CnblogsPictureProvider支持博客园旗下的所有子站。而用户添加的图片上传帐号则储存在PictureAccounts.xml文件里:
其中,blogaccount是使用该图片上传帐号的博客ID,它是一个有Word 2007在设置博客时自动生成的GUID;serviceurl是urltemplate和username组合之后的url,这也是博客园的组合规则。由于这里涉及到敏感信息,你应该对它们进行加密处理。
这两个文件将会和Blogs.xml放在同一目录里。当Picture Account对话框显示时,将会加载PictureProviders.xml里面的数据并显示在Providers下拉列表里:
当用户点击OK时,我们需要对数据进行验证,并把数据存到PictureAccounts.xml里面。目前,我们只是简单验证两个编辑框是否为空,而错误信息将通过ErrorProvider显示。
Picture Account对话框是在CreatePictureAccount() 方法里调用的:
现在轮到PublishPicture() 方法了,它的工作就是从PictureAccounts.xml文件里面读取所需数据,然后调用博客园的MetaWeblog API上传图片。然而,当我正要着手实现该方法时,出现了一个让我一筹莫展的问题:Image参数究竟是什么?如果无法得知它的具体类型,恐怕这条路是走不下去了。它会不会是某种COM的图片类型呢?突然,我脑海里闪出Andrew的一篇关于图片转换的文章,里面提到基于COM的IPictureDisp和System.Drawing.Image之间的转换,于是尝试把Image转换成IPictureDisp,异常!用Reflector查看该接口,发现它打上了 [MarshalAs(UnmanagedType.IUnknown)]。再想想,这是一个关于COM和.NET之间传递数据的问题,有没有可能在Marshaling的相关主题中找到线索呢?从MSDN到Google找了很久,未果,于是向众多MS的人发邮件请教,并在MSDN论坛发帖子,未有音信。每个COM接口都会有一个GUID,有没有可能从这个入手进一步了解PublishPicture() 呢?通过元数据获得IBlogPictureExtensibility接口的GUID,然后搜索注册表,发现该接口的Key和另一个GUID,搜索那个GUID,发现"PSOAInterface"字眼,于是Google一下,发现Dinesh Ahuja的一篇关于Marshaling的文章,其中就提及到Type Library Marshaling。再次Google,发现此内容在《Applying COM+》一书的第五章有述,从中了解到COM的Late Binding。既然Image是作为IUnknown封送的,那么它必定实现了这个接口,也必定支持这个接口的QueryInterface() 方法,顿时,一个奇怪的主意在我脑海中萌生了:通过Marshal.QueryInterface() 方法试探它到底实现了什么接口。以下是实验代码:
结果是让人欣喜的,原来Image实现了IStream接口(该接口的托管定义可以在System.Runtime.InteropServices.ComTypes命名空间里找到),这样,我们就可以继续走下去了。
增值服务区 |
Converting between IPictureDisp and System.Drawing.Image,Andrew Whitechapel |
Platform Invocation Services in .NET Framework,Dinesh Ahuja |
有了这些准备,我们就可以写一个辅助方法读取Image的数据了:
需要说明的是,IStream.Stat() 方法的第二个参数可取STATFLAG枚举的任一值,我尝试给它传个0,以便获取图片名字用于稍后的上传,无奈只得一个null,只好换回1,让它无需在返回的STATSTG结构中包含pwcsName成员的值。至于图片的上传,我选择使用Cook Computing的XML-RPC.NET类库来访问博客园的MetaWeblog API。
下载xml-rpc.net.2.1.0.zip压缩包,从中解出CookComputing.XmlRpcV2.dll和MetaWeblogAPI.cs两个文件,修改一下MetaWeblogAPI.cs中IMetaWeblog接口,使它继承自IXmlRpcProxy接口(否则后面没有办法指定MetaWeblog API的URL),接着把它们都添加到项目里,然后就可以实现PublishPicture() 方法了:
现在万事俱备,只欠在ThisAddIn类中重写RequestService() 方法了:
至此,上传图片问题的探讨要告一段落了。此时,有人可能会问,这个扩展能工作吗?是的,到目前为止,这个系列的文章都是在Word 2007上完成并发布的。虽然这个扩展还有很多地方需要完善,不过作为一个探索的手段,我想这已经足够了。开发并不是软件的全部,要使软件成功发挥作用,我们就不能忽略/忽悠部署这个环节。下一回,最终回,我们将探讨插件的部署以及其它相关的问题。