MAPI 中的结构化存储

适用于:Outlook 2013 | Outlook 2016

结构化存储是指使用 COM 引入的存储的分层组织。 在结构化存储环境中,存储分为两种或三种类型的对象:

  • Stream 对象

  • 锁定字节对象

  • 存储对象

Stream和锁字节是直接访问数据的较低级别对象。 Stream 对象实现 IStream 接口,该接口定义用于读取、写入、定位和复制数据字节的方法。 Lock bytes 对象实现另一个 COM 接口 ILockBytes,以使用字节数组访问数据。 字节数组通常用于提供对基础存储的自定义访问。

存储对象分层在流或锁定字节对象之上;它们可以包含一个或多个这些对象以及其他存储对象。 存储对象实现 IStorage 接口,该接口定义用于创建、访问和维护嵌套对象的方法。

由于 IStreamILockBytesIStorage 是 COM 接口而不是 MAPI 接口,因此其方法返回 COM 错误值而不是 MAPI 值。 在这些接口中调用方法的客户端和服务提供商必须使用 API 函数 MapStorageSCode 将这些值转换为 MAPI 错误值。 有关详细信息,请参阅 MapStorageSCode

客户端和服务提供商使用结构化存储处理太大而无法使用 IMAPIProp 方法维护的属性,通常是大型字符串和二进制属性。 客户端或服务提供商访问它们的常见方式之一是在调用 IMAPIProp::OpenProperty 方法时将 IStreamIStorage 指定为接口标识符。 例如,客户端调用 OpenProperty,PR_ATTACH_DATA_BIN作为 属性标记,IID_IStream作为接口标识符来访问邮件中的二进制附件。

客户端和服务提供商可以实现自己的流和存储对象,或者调用 API 函数来访问 MAPI 或 COM 提供的实现。 由于提供的实现最适用于大多数目的,因此客户端和服务提供商很少需要创建自己的实现。

当客户端对 MAPI 对象调用 OpenProperty 以通过存储对象访问其属性之一时,服务提供程序通常会在直接模式下打开存储对象。 但是,这是典型的行为,而不是必需的行为。 客户端应假定服务提供程序打开或创建的所有存储对象都已交易,并且需要调用 IStorage::Commit。 他们还应该记住,在最终提交保存 MAPI 对象后调用 IMAPIProp::SaveChanges 之前,对存储对象的更改不会永久更改。 有关详细信息,请参阅 IMAPIProp::SaveChanges

MAPI 和 COM 提供了多个 API 函数,用于定义或访问存储和流对象。 下表介绍了常用的函数。

用于访问存储和Stream对象的函数

函数 说明
HrIStorageFromStream
创建存储对象以访问流或锁定字节对象。
OpenIMsgOnIStg
创建消息对象以访问存储对象。
OpenStreamOnFile
创建用于访问文件的流对象。
WrapCompressedRTFStream
创建一个流对象,该对象包含包含消息的富文本的流的压缩或未压缩版本。

检索给定子存储中流的名称

  1. 调用子存储的 IStorage::EnumElements 方法以获取 IEnumSTATSTG 接口。

  2. 使用一次尽可能多的 STATSTG 结构调用 IEnumSTATSTG::Next 如果一次请求 100 个, Next 通常会返回S_FALSE,并将 pceltFetched 的内容设置为实际检索到的数字。

  3. 检查使用STGTY_STREAM标记的 STATSTG 结构。

  4. 释放 pwcsName 参数。

创建存储对象以访问现有流或锁定字节对象

创建消息对象以访问现有存储对象

  • 服务提供商和客户端调用 OpenIMsgOnIStg。 创建的消息对象不同于通常由消息存储提供程序创建的消息对象,因为它不支持所有 IMessage : IMAPIProp 接口方法,例如 IMessage::SubmitMessageOpenIMsgOnIStg 的可选输入参数是符合 MSGCALLRELEASE 原型的回调函数。 当消息的引用计数达到零时,新消息对象将调用此函数。 实现 MSGCALLRELEASE 函数对于在新消息完全删除之前执行最终处理非常有用。

OpenStreamOnFile 通常用于存储文件附件,因为它创建从文件读取和写入文件的流。 使用 PR_ATTACH_DATA_BIN 作为属性标记的 OpenProperty 创建用于存储二进制附件数据的流。

压缩或解压缩包含 RTF 格式的消息文本的流

  • 客户端调用 WrapCompressedRTFStreamWrapCompressedRTFStream 创建包装 RTF 流的流。 包装流不实现所有 IStream 方法;排除以下方法: SeekSetSizeRevertLockRegionUnlockRegionStatClone。 这是因为 WrapCompressedRTFStream 创建的流对象不支持 SetSizeStat,因此没有一种简单的方法来扩展或检索其大小。 最佳策略是选择合理的缓冲区大小并在循环中读取或写入。

注意

COM 具有基于字节数组的存储对象实现,该字节数组从 EnumElements 方法返回有问题的 IEnumSTATSTG 对象。 具体而言, QueryInterface 方法无法正常工作。 使用 COM 实现实现自己的存储对象的服务提供商应为 IEnumSTATSTG 对象创建一个精简包装器,该对象将调用转发到基础 IEnumSTATSTG 方法,但实现自己的 AddRefReleaseQueryInterfaceClone 方法。