Coding Conventions for Metadata API
This topic discusses the coding conventions used by the metadata API.
Handling String Parameters
The metadata API exposes all strings in Unicode format. (The format on disk for symbol names is actually UTF-8, but that is hidden from metadata API clients.) Every returned string is a triple of three parameters (actual parameter names vary):
[in] ULONGcchString - The size, in bytes, of the buffer in which the string, including the terminating null character, is to be returned.
[out] LPCWSTRwzString - A pointer to the buffer in which the string is returned.
[out] ULONG *pchString - A pointer to the size of the returned string (including the terminating null character). If the buffer is too small to store the full string, the returned string is truncated, an error indication is returned, and the client can re-allocate the buffer and retry if desired.
Symbol Names
The following conventions apply to symbol names for string parameters:
String parameters that are symbol names are always assumed to be null-terminated, and no [in] length parameter is needed. Embedded null characters are not supported.
If an [in] parameter string is too large to persist without truncation, an error will be returned.
User Strings
The following conventions apply to user-provided string parameters:
User strings may have embedded null characters and should not have a null terminator.
A length must be supplied in the cchString parameter. The size of the buffer must be the exact length of the string that will be stored.
Storing Default Values
Constants can be stored into metadata as default values for fields, parameters, and properties. Three parameters are used to specify a constant (actual parameter names vary):
[in] DWORDdwCPlusTypeFlag - A value of the CorElementType enumeration that specifies the type of the default value.
[in] void const *pValue - A pointer to the actual default value. For example, a pointer to the 4-byte DWORD holding 0x0000002A will store a DWORD value of 42 decimal in the metadata. The type (specified in dwCPlusTypeFlag) of the default value is limited to a primitive or a string. If dwCPlusTypeFlag is ELEMENT_TYPE_CLASS, the default value will be null.
[in] ULONGcchValue - The number of Unicode characters in the byte sequence to which pValue points. This is required only if the type, specified in dwCPlusTypeFlag, is ELEMENT_TYPE_STRING. In all other cases, the length is inferred from the type.
Default values are not automatically inserted into initialization code or into statically initialized data areas. They are simply recorded in metadata.
To indicate that you do not want to specify a default value, set all bits of dwCPlusTypeFlag (that is, set the value to -1).
Null Pointers for Return Parameters
Because the metadata APIs do a minimum of error checking, they expect a non-null pointer for return parameters under the following circumstances:
In Define methods, a non-null pointer is required for the returned token. These methods create the item that you want defined and return a token for the item. You may choose to discard the token if you don't need it.
Find methods always return the token for the item if it is successfully found.
In Get methods, you can pass null in parameters that you do not need back.
In Set methods, there is generally no return value. You pass in the token for the item to be updated along with the values to update, and these methods perform the update.
Parameter Values to Be Ignored
Several methods in the metadata API enable you to change the properties of an item that was defined earlier. The following example uses the IMetaDataEmit::SetFieldProps method to change a field's properties, which were previously supplied in a call to IMetaDataEmit::DefineField:
HRESULT SetFieldProps(mdFieldDef fd, DWORD dwFieldFlags,
DWORD dwDefType, void const *pValue, ULONG cchValue)
Sometimes you might want to change dwFieldFlags but not pValue (or vice versa). In that case, you must pass a parameter value in order to avoid an error, even if you do not want to change that value. However, you can pass a particular value that designates that the argument should be ignored if you do not want to change its value. The metadata APIs use the following conventions to indicate that a method argument should be ignored:
If the parameter is a pointer type, pass a null pointer.
If the parameter is a value type (typically a flags bitmask), pass a value of all bits set (–1).
Error Returns
Almost all methods in the IMetaDataDispenserEx, IMetaDataEmit, and IMetaDataImport interfaces return an HRESULT value to indicate their result. This has the value S_OK if the operation was successful. If the call was unsuccessful, it returns another value to describe the reason why the operation failed.
A general pattern across all the metadata APIs is that if the caller provides a string buffer that is too small to hold the results, the APIs copy as many characters as will fit, but return the HRESULT value of CLDB_S_TRUNCATION instead of S_OK.
Callers of the IMetadata interfaces are compilers or tools. It is important that these callers always check the return status from each call to detect errors. In these cases, error conditions reflect a problem on the part of the direct caller (such as a compiler) rather than the user (such as an application program).
Memory Management
The generic COM default is that the caller frees the memory that the callee allocates. However, the metadata methods operate differently.
Many metadata methods return [out] pointers to blocks of memory. That memory is part of the module's metadata heap and is owned by the common language runtime (CLR). Therefore, you are given a pointer directly into the CLR’s in-memory storage of the metadata, and your application is not required to free the memory.
Generics Support
In the .NET Framework version 2.0, the metadata APIs have been extended significantly to support generics (also sometimes called "parametric polymorphism"). Generics are somewhat similar to C++ templates. An example of defining a generic class in C# might be as follows:
public class Dictionary<Key, Val> { . . . }
In this case, the Dictionary class is parameterized with two generic parameters named Key and Val. When the class is instantiated, the user selects types for the generic parameters, as in the following example:
Dictionary<string, int> NameToPhone = new Dictionary<string, int>();
Dictionary<int, string> PhoneToName = new Dictionary<int, string>();