访问智能文档内容 为了能起到作用,你的智能文档操作DLL一般需要访问(并且可能修改)下层的Office文档。在例子中,它是Excel项目进程表电子表格。为了这个目的,ISmartDocument的几个方法为你下层文档提供了一个IDispatch COM接口指针。在Office上编写COM的人知道,Idispatch提供了进入Office对象模型的通道。在Visual Basic中,使用Idispatch和类型库是相当自动化的,但是在C++中稍微复杂一些。
起先我准备使用#import指令,它允许Visual C++为类型库中的所有接口生成ATL智能指针包装。但是要让它正确的编译需要做大量的工作,它常常提示有东西出错了。果真,我找到了知识库文章“Office Application Remains in Memory After Program Finishes”,它描述了在Office类型库中使用#import指令所遇到的知名的问题以及相应的建议。
最后,我决定建立自己的类CexcelWorkbook来包装需要的Excel方法。这个类继承自ATL模版CcomDispatchDriver,这使它相对容易通过IDispatch调用Excel对象模型上的方法。使用CcomDispatchDriver的方法GetIDOfName,你可以得到一个给定的属性或方法(例如,Excel对象模型中的ActiveSheet的范围)的DISPID。你一旦有了DISPID(为了效率更高,我在类中对它进行了缓冲处理),就可以使用某个其它的CcomDispatch方法(例如GetProperty或InvokeN,此处的N是参数的数量)访问对象模型中的属性和方法。注意Exce Range值是作为VBA属性(而不是方法)暴露的,但是它也需要一个参数(范围地址)。因为CcomDispatchDriver没有提供为GetProperty调用传递的参数的途径,我在自定义类(CExcelWorkbook)中实现了一个新的方法(GetProperty1),它用于处理这种情况。
调试智能文档DLL 一切都在预料之中,在我尝试建立智能文档操作DLL的时候也没有出现异常。我试图在Excel中附加一个XML大纲的时候,收到了一个错误信息“XML扩展包逻辑丢失或无效”。为了调试这个错误,我首先使用ListDLLs(http://www.sysinternals.com上的一个方便的工具)确定Excel是否载入了我的库,这样清单才能看起来足够好,它可以让Excel找到该DLL的。我退出Excel,接着修改了项目的Debugging属性(右键点击解决方案并选择“属性”)告诉Visual Studio使用Excel作为该DLL的EXE容器。我浏览Excel.exe并选择它作为Command值。
接着,我按下Ctrl-B打开“New Breakpoints”对话框,在Function字段中输入DllMain,当出现“disambiguate symbol”窗口(显然有两个重载的DllMain函数)的时候选择了它们两个,通过这些操作在DllMain中设置了一个断点。我的目的是当Excel第一次调用该DLL的时候得到控制权。接着我按下F5,Excel启动了。Visual Studio警告没有Excel符号,但是我早就知道了。我打开智能文档,使用Data | XML | XML Expansion Packs试图再次附加XML大纲。Visual Studio同ATL生成的DllMain中的断点一起出现了。
这个时候我的目标是确定DLL中是否有方法、哪些方法被调用了。我在自己的IsmartDocument接口实现中的所有方法上设置了断点,以确定它们其中的哪些被调用了。实际上,有几个方法被调用了,并且通过逐步运行我找到了一个普通的索引问题,传递到get_SmartDocXmlTypeName的控件索引是从1开始的,但是C++代码把它处理为从0开始的,因此对最后一个元素的调用返回了E_INVALIDARG。后来我给所有的接口方法的入口点添加了ATLTRACE2宏,使自己更容易知道正在调用什么、什么时候调用。
改变通知 对于示例解决方案,我需要知道用户什么时候选择了电子表格的数据项区域中的某行,这意味着他希望编辑该事务的相关信息。接着我从当前行中抓取信息并填充事务面板,允许他输入本周的工作和下一周的计划工作的相关信息。当他改变了事务面板中的某些东西的时候,我将使用Excel对象模型把新的信息复制回原工作表行。但是我如何知道什么时候选择了新的行?有一种比较复杂的解决方案,即使用Excel事件(在Visual Basic中容易,但是在C++中不是太容易),但是我发现这是没有必要的。在Excel中无论选择什么时候发生了改变,都会调用IsmartDocument接口方法,因此无论什么事情,你仅仅需要改变通知。通过更新每次改变后的进度表事务内部视图,我能够忽略行选择的变化。
安全性 因为宏病毒和其它的脚本技术的恶意使用变得很普遍,Office在两个途径做了修改:通过提高默认的安全性设置,防止运行大多数没有签名的、潜在的恶意代码;通过增加安全性设置的数量,为即使没有数字签名的解决方案的运行提供了更多的管理权限。这考虑了现实情况:很多Office解决方案(嵌入Word或Excel文档中的宏)都已经广泛的布署在大型组织中,要让它们完全安全将花费一定时间。如果新版本Office突然要求所有的解决方案必须有数组签名(理想的解决方案),这将给很多组织带来布署方面的障碍。但是,Office安全性设置的增加也使安全性更加复杂,在本文中我没有谈到这个主题。
应用于宏和插件程序的相同的Office安全性设置也可应用于智能文档。这些设置包括:
·用户宏的安全性设置(高、中或低,在Tools | Macro | Security中设置;默认的是高)。
·智能文档是否从可信(trusted)位置载入:可信的文件系统目录(例如每个用户的或工作组模版目录)、公司局域网上的Web服务器或可信的Internet站点。
·智能文档组件是否是数字签名的,如果是,发行人是否在Tools | Macro | Security | Trusted Publishers设置为可信发行人。
·是否在Tools | Macro | Security | Trusted Publishers中选择了“相信所有安装了的插件程序和模版”。
默认情况下,Tools | Macro | Security中的安全性层次被设置高,Office阻止任何没有数字签名的插件程序DLL(包含智能文档操作DLL)的载入。上面列举的安全性设置的其它组合可以允许智能文档被载入(可能给用户显示一个安全性提示),但是保证组织中的安全性的最好途径仍然是使用从VeriSign或GTE CyberTrust得到的数字证书对所有的已布署的解决方案(包括智能文档组件)进行数字签名。微软2003 智能文档SDK中的XMLSign.exe工具可以用于对XML智能文档清单文件进行数字签名。
此外,如果某个Office智能文档解决方案是从Web服务器上运行的,微软Internet Explorer和Office安全性设置都会影响解决方案是否能运行。如果服务器上的XML扩展包清单文件既不在Internet Explorer可信站点中,也不在局域网区域,就不会试图检索它,也不会给用户提示把该站点添加到Internet Explorer可信列表站点列表中。如果XML扩展包清单文件位于可信的服务器或局域网上,清单是否被载入依赖于它是否签名了。如果它是签名了的,并允许运行,它仍然受到用户Office安全性设置的约束。
在智能文档解决方案开发过程中,你可以通过编辑注册表的下述键下面的REG_DWORD值“DisableManifestSecurityCheck”激活或禁止XML扩展包清单文件安全性检查:
HKEY_LOCAL_MACHINE\Software\Microsoft\Office\Common\Smart Tag |
如果它的值为1将禁止XML扩展包清单文件安全性检查,如果值为0就激活了它。当你试图引用某个XML扩展包清单文件的时候,如果这个注册表设置是1(禁止安全性检查),将出现一个对话框警告用户禁止安全性是危险的。它同时提供一个“确定”按钮,这个按钮允许你立即重新激活XML扩展包安全性检查。你应该仅仅在开发解决方案的时候把开发计算机的注册表的这个值设置为1(这个时候每次编译每个组件后进行数字签名可能不方便),但是要确保在签名后的组件的最后测试中激活它。在用户计算机上禁止XML扩展包清单文件安全性检查是非常不可取的。
尽管本文讨论了在C++中智能文档操作DLL的建立,但是用Visual Basic .NET或C#建立受控操作DLL也是可行的。在那种情况下,将应用.NET框架组件安全性模型,但是这超出了本文讨论的范围。
生成状态报告 我的示例依据Excel对象模型使用Idispatch访问来自进度表智能文档工作表的信息以生成状态报告。我也可以选择开发一些C++代码来驱动Word对象模型建立状态文档,但是我选择了另一种途径,它也利用了Word 2003中的新XML特性的优点。我把状态报告生成为XML文件,并使用XSLT在Word中打开它以提供良好的格式化。这种方法利用了使用XSLT很容易把几个XML文件(可能是整个小组的状态报告)合并成一个大的报告的优点,如果它们作为Word文档保存就很难合并了。
在花费一段时间研究如何实现XML保存后,我决定定义一个类来管理将显示在状态报告上的数据项列表。当开发者更新进度表中的信息的时候,智能文档操作DLL中的代码就会建立并更新这个事务列表,这与文档操作工作面板中放置的控件对应。最后,开发者点击工作面板中的控件生成XML状态报告。每个类负责使用MSXML DOM正确地保持存储在XML中的信息。处理这种情行的处理方法是CScheduleTaskList和CScheduleTask。我用于格式化它的Word XSL样式表是ScheduleReport.xsl,它在示例代码中提供了。当你安装智能文档解决方案的时候,这个XLST文件就会被安装好,并让Word知道它,这样当你在Word中打开XML状态报告的时候,它将自动应用这个样式表。你也可以使用Word中的XML数据视图事务面板来选择这个样式表。
在我生成的XML中有一个指令值得提起:
<?mso-application progid="Word.Document"?> |
这条指令告诉Windows资源管理器这个XML文件的默认的应用程序是Word。你也可能注意到结果是这个文件的图标改变了。
让这个Word样式表精确地显示状态报告需要花一段时间,因此此处我没有提供详细信息。但是,我要警告你,如果你查看我提供的样式表,你会对显示的这个Word XML大纲的复杂程度感到惊奇。其中的很多是样板代码,很多都是在Word中建立示例状态文档(我希望通过这种方式设置样式和字体)并把该文档保存为XML格式的时候获得的。我把生成的XML中的大部分复制到自己的XSLT样式表中。但是,我不能确定这种办法生成的XML是在Word中生成良好格式化的状态报告所需要的最小的XML.
我可以根据自己的经验为开发XSLT提供一些建议。要记住XML是一种非常精确的、大小写敏感的语言。在这方面它与C++没有不同,但是如果出错了,你接收到的诊断信息可能是某种类型的“不能应用样式表”信息,而不是编译器提供的详细描述。如果你对某些已经在运行的东西做大范围的修改,应该逐步增加并做好版本控制,这样才能回滚到以前的修改去。我遇到了一个问题,即样式表引用了ScheduleSmartDocument名字空间,但是由于操作DLL中出了错误,在生成的XML状态报告中该名字空间的名称变成了ScheduleSmartDoc。结果当我在XML状态报告上应用该XSLT的时候,Word简单地显示了一个空文档,因为样式表中使用的XPATH与XML中的不匹配。
让Word自动地应用正确的样式表需要花点力气。最后,我让安装智能文档操作DLL的清单也安装该样式表。为了让Word知道这个样式表,清单中的解决方案条目必须含有context属性,它引用该Word XML名字空间(http://schemas.microsoft.com/office/word/2003/wordml)。
最后一步是把智能文档URI(示例中的是ScheduleSmartDocument)作为清单、样式表和生成的XML状态报告文档的XML名字空间。在例子中,我把ScheduleSmartDocument作为名字空间的名称。你应该根据组织中的唯一的URI来使用名字空间。
反向操作,从Word文档生成XML看起来是个好的办法。但是,幅面和切割结果的Word输出的时候要非常小心。例如,我试图减小列表部分的复杂性(它描述了文档中使用的格式列表),因为我的状态报告只需要一个简单的、一层的符号列表。在一个小时以后,我放弃了,留下大量的未改动的部分,虽然我确信其中的很多是我不需要的。
如果你在Visual Studio中打开一个生成好的Word XML文件,你会发现它对于Visual Studio .NET大纲查看器来说太复杂了。因此,我建议当XML打开的时候,首先点击“数据视图”,强制Visual Studio分解该XML,接着返回“XML视图”查看良好格式化的版本(它在Notepad中打开的时候,实际上是不可阅读的)。
我的最后一条建议是在调整样式表的时候可以使用一个非常强大的工具。MSXSL.EXE命令行工具允许你给XML文件应用样式表并查看生成的XML输出。你可以从链接http://msdn.microsoft.com/library/en-us/dnxml/html/msxsl.asp处下载它。
结论 我首先是在C++中开始开发操作DLL的,这是因为目前Office编码的太多示例都是用Visual Basic编写的。但是,使用C++执行一些甚至于很简单的事务(例如引用Excel中的一个单元值)也比使用Visual Basic明显复杂多了。图11显示了与Visual Basic中ActiveSheet.Range("A1").Value语句等同的C++代码(注意这个方法的示例代码有不同的版本,因为我用设置值方法把它重新编写为共享代码)。我提供了智能文档处理程序希望对Excel对象模型执行的少量操作的C++代码,但是我编写的示例根本没有因为使用C++而受益。我鼓励读者认真的考虑使用C++与Visual Basic之间的代价。
C++中的ActiveSheet.Range("A1").Value
HRESULT CExcelWorkbook::HrCellValue(
int iRow,
int iColumn,
CComVariant *pValue)
{
USES_CONVERSION;
HRESULT hr;
ATLASSERT(p); // IDispatch必须在调用方法前设置
if (pValue == NULL)
return E_INVALIDARG;
pValue->Clear();
AssureDispidRange();
TCHAR tzRangeReference[20];
ATLASSERT(iColumn<26); // 支持的不能多于26个,
// 为"CurrentRow"处理特定的值
if (iRow == iCurrentRow && FAILED(hr=GetSelectedRow(iRow)))
return hr;
wsprintf(tzRangeReference, "%c%d", 'A'+(iColumn-1), iRow);
CComVariant varRangeName(T2COLE(tzRangeReference));
CComVariant varRange;
if (SUCCEEDED(hr=GetProperty1(m_dispidRange, &varRangeName,
&varRange)))
{
ATLASSERT(varRange.vt == VT_DISPATCH);
ATLASSERT(varRange.pdispVal);
LPDISPATCH lpRange = varRange.pdispVal;
CComDispatchDriver dispRange(lpRange);
AssureDispidRangeValue(dispRange);
hr = dispRange.GetProperty(m_dispidRangeValue, pValue);
}
return hr;
} |
Office 2003中的另一个选择是C#,它是有吸引力的,但是请你确保自己清楚了解非受控代码(Office)调用受控代码(用C#或Visual Basic .NET编写的操作处理程序部件)的性能问题。有可能使用Visual Basic是最好的办法。
智能文档为开发者提供了非常强大的用户界面范例。你可以在利用微软Office应用程序的所有功能的时候,同时提供无缝地集成到Office事务面板用户界面中的自定义UI和行为。我认为智能文档将很快在大型组织中广泛使用,以简化业务过程,如同目前使用的带有宏的电子表格一样。【完】
查看本文来源