属性存储注意事项

IPropertyStorage::ReadMultiple 读取 在 rgpspec 数组中指定的属性数与属性集中的相同数目。 只要读取请求的任何属性,检索不存在的属性的请求就不是错误。 相反,这必须在返回时将此属性的VT_EMPTY写入 rgvar[] 数组。 当请求的属性不存在时,该方法应返回S_FALSE,并在每个 PROPVARIANT 中设置VT_EMPTY。 如果返回任何其他错误,则不会检索任何属性值,调用方无需担心释放它们。

rgpspec 参数是 PROPSPEC 结构的数组,它为每个属性指定其属性标识符或字符串标识符(如果已分配)。 可以通过调用 IPropertyStorage::WritePropertyNames 将字符串映射到属性标识符。 但是,使用属性标识符可能比使用字符串要高效得多。

字符串名称 (PRSPEC_LPWSTR) 请求的属性将不区分大小写地映射到属性标识符 (ID) ,因为它们是在当前属性集 (中指定的,并且根据当前系统区域设置) 。

当属性类型为VT_LPSTR并且从 ANSI 属性集读取该属性时,即属性集的代码页设置为除 Unicode 以外的内容时,属性的值使用与属性集相同的代码页。 从 Unicode 属性集读取VT_LPSTR属性时,该属性的值使用系统当前的默认 ANSI 代码页,即从 GetACP 函数返回的代码页。

PROPVARIANT(指向流和存储的指针除外)称为简单 PROPVARIANT。 这些简单的 PROPVARIANT按值接收数据,因此对 IPropertyStorage::ReadMultiple 的调用将提供调用方随后拥有的数据副本。 若要创建或更新这些属性,请调用 IPropertyStorage::WriteMultiple

相比之下,VT_STREAM、VT_STREAMED_OBJECT、VT_STORAGE和VT_STORED_OBJECT的变体类型是非简单属性,因为 该方法不会提供值,而是检索指向指示接口的指针,然后可以从该接口读取数据。 这些类型允许通过单个属性存储大量信息。 使用非简单属性时会出现几个问题。

若要创建这些属性,对于其他属性,请调用 IPropertyStorage::WriteMultiple。 但是,与其调用相同的方法进行更新,不如先调用 IPropertyStorage::ReadMultiple 以获取指向流或存储的接口指针,然后使用 IStreamIStorage 方法写入数据。 通过属性打开的流或存储始终在直接模式下打开,因此不会引入额外的嵌套事务级别。 但是,根据通过 IPropertySetStorage 打开或创建该属性的方式,该属性集可能仍存在一个事务。 此外,打开或创建属性集时指定的访问和共享模式标记将传递给基于属性的流或存储。

基于属性的流或存储指针的生存期,尽管理论上独立于其关联的 IPropertyStorageIPropertySetStorage 指针,但实际上实际上依赖于它们。 通过流或存储可见的数据与从中检索它的属性存储对象上的事务相关,就像存储对象 (支持 IStorage) 包含的流和存储子对象一样。 如果已中止父对象上的事务,则不再可访问从属该对象的现有 IStreamIStorage 指针。 由于 IPropertyStorage 是属性存储对象上的唯一接口,因此包含的 IStreamIStorage 指针的有用生存期由 IPropertyStorage 接口的生存期限制。

实现还必须处理通过同一 IPropertyStorage 接口实例多次请求同一流值或存储值属性的情况。 例如,在 COM 复合文件实现中,打开将成功或失败,具体取决于属性是否已打开。

另一个问题是事务处理模式下的多个打开。 结果取决于通过调用 IPropertySetStorage 方法( (OpenCreate 方法)指定的隔离级别,以及打开属性存储时) STGM 标志。

如果对打开属性集的调用指定读写访问权限,则始终使用读写访问权限打开 IStorageIStream 值属性。 然后,可以通过这些接口写入数据,更改 属性的值,这是更新这些属性的最有效方法。 属性值本身没有额外的事务嵌套级别,因此更改的范围在事务 ((如果属性存储对象上有任何) )。

存储和流属性

若要将流或存储对象写入属性集,属性集必须已创建为非简单。 有关简单和非简单属性集的详细信息,请参阅标题 为属性集的存储和流对象部分。 在 rgvar 数组元素的 vt 字段中指定的以下属性类型为流类型或存储类型:VT_STREAM、VT_STORAGE、VT_STREAMED_OBJECT VT_STORED_OBJECT。

若要将流或存储对象作为非简单属性集中的属性写入,请调用 IPropertyStorage::WriteMultiple。 虽然还可以调用此方法来更新简单属性,但它不是更新属性集中的流和存储对象的有效方法。 这是因为通过调用 WriteMultiple 更新其中一个属性会在属性存储对象中创建传入数据的副本,并且 IStorageIStream 指针不会保留到此调用的持续时间之后。 通常,通过首先调用 IPropertyStorage::ReadMultiple 获取指向流或存储的接口指针,然后通过 IStreamIStorage 方法写入数据,直接更新流或存储对象的效率更高。

例如,可以调用 IPropertyStorage::WriteMultiple 来写入 NULL 流或存储对象。 然后,该实现将在属性集中创建一个空对象。 然后,可以通过调用 IPropertyStorage::ReadMultiple 获取对此对象的访问权限。 完成此对象的更新后,无需将其写入属性集,因为更新将直接进入属性集。

通过属性打开的流或存储始终在直接模式下打开,因此不会引入额外的嵌套事务级别。 在属性集上可能仍有一个事务集为一个整体。 (例如,如果 IPropertyStorage 是通过调用 IPropertySetStorage::Open 获取的,且在 grfmode 参数中设置了STGM_TRANSACTED标志。) 此外,在给定属性集上的 模式的情况下,基于属性的流或存储将在读写模式下打开(如果可能);否则,使用读取模式。

如前所述,将流或存储对象写入使用 WriteMultiple 方法设置的属性时,将创建该对象的副本。 在流对象上创建此类副本时,复制操作将从源的当前查找位置开始。 查找位置在失败时未定义,但在成功时位于流的末尾;搜寻指针不会还原到其原始位置。

如果已从具有 ReadMultiple 的属性集读取流或存储属性,则仍保持打开状态,并且对同一属性的 WriteMultiple 进行后续调用, 则 WriteMultiple 操作将成功。 以前打开的流或存储属性处于还原状态, (对它的所有调用都将返回STG_E_REVERTED错误) 。

如果 WriteMultiple 方法在写入属性数组甚至单个非简单属性时返回错误,则实际写入的数据量是不确定的。

引用属性

如果指定的 PROPVARIANT 结构在其 vt 成员中包含 VT_BYREF 标志,则关联的属性是引用属性。 在将值写入属性集之前,会自动取消引用引用属性。 例如,如果 PROPVARIANT 结构的 vt 成员指定类型的值VT_BYREF |VT_I4,写入的实际值是VT_I4类型。 后续对 IPropertyStorage::ReadMultiple 方法的调用将返回一个值作为VT_I4。 使用引用属性类似于调用 VariantCopyInd 函数。 VariantCopyInd 释放目标变体并创建源 VARIANTARG 的副本,如果指定源要VT_BYREF,则执行必要的间接寻址。 当需要变体的副本时,此函数非常有用,并保证它不会VT_BYREF,例如,在 IDispatch::Invoke 的实现中处理参数时。

调用方说明

建议通过不设置 IPropertySetStorage::CreategrfFlags 参数中的 PROPSETFLAG_ANSI 标志,将属性集创建为 Unicode。 此外,建议避免使用VT_LPSTR值,改用VT_LPWSTR值。 当属性集代码页为 Unicode 时,VT_LPSTR字符串值在存储时转换为 Unicode,并在检索时转换为多字节字符串值。 当属性集的代码页不是 Unicode 时,属性名称、VT_BSTR字符串和非简单属性值在存储时将转换为多字节字符串,并在检索时转换回 Unicode,所有这些都使用当前系统 ANSI 代码页。

实施者说明

分配属性标识符时,实现可以选择属性标识符的属性集中当前未使用的任何值,只要它不是 0 或 1 或大于 0x80000000,所有这些值都是保留值。 propidNameFirst 参数为集中的属性标识符建立一个最小值,并且必须大于 1 且小于 0x80000000。 请参阅上面的“备注”部分。

IPropertyStorage-Compound 文件实现

IPropertyStorage-NTFS 文件系统实现

IPropertyStorage-独立实现