属性存储注意事项
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 来获取流或存储的接口指针,然后使用 IStream 或 IStorage 方法写入数据。 通过属性打开的流或存储始终以直接模式打开,因此不会引入额外的嵌套事务级别。 但是,整个属性上仍可能有一个事务,具体取决于它是如何通过 IPropertySetStorage打开或创建的。 此外,在打开或创建属性集时指定的访问和共享模式标记将传递给基于属性的流或存储。
基于属性的流或存储指针的生存期,尽管理论上与其关联的 IPropertyStorage 和 IPropertySetStorage 指针无关,但实际上实际上依赖于它们。 通过流或存储可见的数据与从中检索它的属性存储对象的事务相关,就像存储对象(支持包含的流和存储子对象 IStorage)一样。 如果父对象上的事务已中止,现有 IStream 和 IStorage 指向该对象的指针不再可访问。 由于 IPropertyStorage 是属性存储对象上的唯一接口,因此包含的 IStream 的有用生存期和 IStorage 指针受 IPropertyStorage 接口的生存期的约束。
实现还必须处理通过同一 IPropertyStorage 接口实例多次请求同一流或存储值属性的情况。 例如,在 COM 复合文件实现中,打开将成功或失败,具体取决于属性是否已打开。
另一个问题是在事务处理模式下多次打开。 结果取决于通过调用 IPropertySetStorage 方法(Open 或 Create 方法(通过 STGM 标志)在打开属性存储时指定的隔离级别。
如果打开属性集的调用指定读写访问权限,IStorage 和 IStream-valued 属性始终使用读写访问权限打开。 然后,可以通过这些接口编写数据,更改属性的值,这是更新这些属性的最有效方法。 属性值本身没有额外的事务嵌套级别,因此更改的范围在属性存储对象的事务(如果有)下。
存储和流属性
若要将流或存储对象写入属性集,必须将属性集创建为非simple。 有关简单和非简单属性集的详细信息,请参阅标题为属性集 存储和流对象部分。 在 rgvar 数组元素的 vt 字段中指定的以下属性类型是流或存储类型:VT_STREAM、VT_STORAGE、VT_STREAMED_OBJECT、VT_STORED_OBJECT。
若要将流或存储对象写入为非简单属性集中的属性,请调用 IPropertyStorage::WriteMultiple。 虽然你还将调用此方法来更新简单属性,但它不是更新属性集中的流和存储对象的高效方法。 这是因为通过调用 WriteMultiple 更新其中一个属性会在属性存储对象中创建传入数据的副本,IStorage 或 IStream 指针不会保留在此调用的持续时间之外。 通常,通过首先调用 IPropertyStorage::ReadMultiple 来获取流或存储的接口指针,然后通过 IStream 或 IStorage 方法写入数据,从而直接更新流或存储对象的效率更高。
例如,可以调用 IPropertyStorage::WriteMultiple 来编写 NULL 流或存储对象。 然后,该实现将在属性集中创建一个空对象。 然后,可以通过调用 IPropertyStorage::ReadMultiple来访问此对象。 完成此对象的更新后,无需将其写入属性集,因为更新将直接写入属性集。
通过属性打开的流或存储始终以直接模式打开,因此不会引入额外的嵌套事务级别。 整个属性上可能仍有一个事务。 (例如,如果使用 grfmode 参数中设置的STGM_TRANSACTED标志调用 IPropertySetStorage::Open 来获取 IPropertyStorage。此外,如果属性集上的模式,在读写模式下打开基于属性的流或存储(如果可能);否则,使用读取模式。
如前所述,将流或存储对象写入 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的实现中处理参数时。
给呼叫者的说明
建议通过将 grfFlags 参数 IPropertySetStorage::Create中的 PROPSETFLAG_ANSI 标志设置为 Unicode 来创建属性集。 还建议避免使用VT_LPSTR值,并改用VT_LPWSTR值。 属性集代码页为 Unicode 时,VT_LPSTR存储时将字符串值转换为 Unicode,并在检索时重新转换为多字节字符串值。 当属性集的代码页不是 Unicode 时,属性名称、VT_BSTR字符串和非简单属性值在存储时转换为多字节字符串,并在检索时转换回 Unicode,这一切都使用当前系统 ANSI 代码页。
实现者的说明
分配属性标识符时,实现可以选择属性标识符的属性集中当前未使用的任何值,前提是它不是 0 或 1 或大于 0x80000000,所有这些值都是保留值。 propidNameFirst 参数为集中的属性标识符建立最小值,并且必须大于 1 且小于0x80000000。 请参阅上面的“备注”部分。
相关主题