元数据 API 的编码约定

本主题讨论元数据 API 使用的编码约定。

处理字符串参数

元数据 API 会以 Unicode 格式公开所有字符串 (实际上,磁盘上用于符号名称的格式是 UTF-8,但此格式对于元数据 API 客户端是隐藏的)。每个返回的字符串都由三个参数组成(实际参数名称各异):

  • [in] ULONG cchString — 要在其中返回字符串(包括终止 null 字符)的缓冲区的大小(以字节为单位)。

  • [out] LPCWSTR wzString — 一个指针,指向在其中返回字符串的缓冲区。

  • [out] ULONG *pchString — 一个指针,指向返回的字符串(包括终止 null 字符)的大小。 如果缓冲区因过小而无法存储整个字符串,将截断返回的字符串并返回一个错误指示。如果需要,客户端可以重新分配缓冲区并重试。

符号名称

字符串参数的符号名称适用下列约定:

  • 属于符号名称的字符串参数始终假定为以 Null 结尾,并且不需要任何 [in] 长度参数。 不支持嵌入的 null 字符。

  • 如果 [in] 参数字符串过大,以致需要截断才能将其保留,将返回错误。

用户字符串

用户提供的字符串参数适用下列约定:

  • 用户字符串中可以嵌入 null 字符,但不能具有 null 结束符。

  • cchString 参数中必须提供长度。 缓冲区的大小必须是要存储的字符串的确切长度。

存储默认值

可以将常数作为字段、参数和属性的默认值存储到元数据中。 有三个参数用于指定常数(实际参数名称各异):

  • [in] DWORD dwCPlusTypeFlag — 一个 CorElementType 枚举值,指定默认值的类型。

  • [in] void const *pValue — 一个指向实际默认值的指针。 例如,如果一个指针指向存储 0x0000002A 的 4 字节 DWORD,则该指针将在元数据中存储一个 DWORD 值 42(十进制)。 默认值的类型(在 dwCPlusTypeFlag 中指定)仅限基元或字符串。 如果 dwCPlusTypeFlag 为 ELEMENT_TYPE_CLASS,则默认值将为 Null。

  • [in] ULONG cchValue — pValue 指向的字节序列中的 Unicode 字符数。 仅当 dwCPlusTypeFlag 中指定的类型为 ELEMENT_TYPE_STRING 时,才需要此参数。 在所有其他情况下,将根据类型推断出长度。

默认值不会自动插入到初始化代码或静态初始化的数据区域中。 它们只记录在元数据中。

若要指示您不希望指定默认值,请设置 dwCPlusTypeFlag 的所有位(即,将该值设置为 -1)。

返回参数的 null 指针

由于元数据 API 会执行尽可能少的错误检查,因此在下列情况中,它们需要返回参数具有非 null 指针:

  • 在 Define 方法中,返回的标记需要具有非 null 指针。 这些方法将创建要定义的项并为该项返回一个标记。 如果不需要该标记,可以选择丢弃它。

  • 只要 Find 方法成功找到该项的标记,便会返回该标记。

  • 在 Get 方法中,可以向不需要返回的参数传递 Null。

  • 在 Set 方法中,通常不存在返回值。 在传入要更新的项的标记以及要更新的值后,这些方法将执行更新。

要忽略的参数值

元数据 API 中有几个方法允许更改以前定义的项的属性。 下面的示例使用 IMetaDataEmit::SetFieldProps 方法更改某个字段的属性,这些属性是以前通过调用 IMetaDataEmit::DefineField 提供的:

HRESULT SetFieldProps(mdFieldDef fd, DWORD dwFieldFlags,
        DWORD dwDefType, void const *pValue, ULONG cchValue)

有时,可能希望更改 dwFieldFlags 而不是 pValue(或与之相反)。 在此情况下,即使您不希望更改该值,也必须传递一个参数值才能避免发生错误。 但是,如果不希望更改它的值,可以传递一个指定应忽略该参数的特定值。 元数据 API 使用下列约定来指示应忽略的方法参数:

  • 如果该参数属于指针类型,请传递一个 null 指针。

  • 如果该参数属于值类型(通常为标志位屏蔽),请传递一个设置了所有位的值 (–1)。

错误返回

IMetaDataDispenserExIMetaDataEmitIMetaDataImport 接口中的所有方法几乎都返回一个 HRESULT 值来指示其结果。 如果操作成功,则其值为 S_OK。 如果操作不成功,它将返回另一个值,以描述操作失败的原因。

在所有元数据 API 中,一种常规模式是:如果调用方提供的字符串缓冲区因过小而无法存储结果,则 API 将复制能够容纳的最大字符数,但返回的 HRESULT 值为 CLDB_S_TRUNCATION 而不是 S_OK。

IMetadata 接口的调用方是编译器或工具。 这些调用方必须始终检查每次调用的返回状态,才能检测到错误。 在此类情况下,错误条件将反映直接调用方(例如编译器)的问题,而不是用户(例如应用程序)的问题。

内存管理

一般 COM 的默认情况是,调用方会释放被调用方分配的内存。 但是,元数据方法的操作方式不同。

许多元数据方法都将 [out] 指针返回到内存块。 该内存是模块的元数据堆的一部分,并且由公共语言运行时 (CLR) 拥有。 因此,您将得到一个直接指入 CLR 的元数据内存中存储区的指针,并且应用程序无需释放内存。

泛型支持

在 .NET Framework 2.0 版中,元数据 API 在支持泛型(有时也称为“参数化多态性”)方面已得到显著扩展。 泛型在某种程度上类似于 C++ 模板。 下面是一个用 C# 定义泛型类的示例:

public class Dictionary<Key, Val> { . . . }

在此例中,Dictionary 类使用两个泛型参数(名为 Key 和 Val)进行参数化。 在实例化该类时,用户应选择泛型参数的类型,如下面的示例所示:

Dictionary<string, int> NameToPhone = new Dictionary<string, int>();
Dictionary<int, string> PhoneToName = new Dictionary<int, string>();

请参见

概念

元数据概述