使用中繼資料 API 和語彙基元
更新:2007 年 11 月
中繼資料 API 可以從 C++ 呼叫。中繼資料 API 的使用方式一部分取決於使用它們的用戶端類型。大部分中繼資料 API 用戶端分成下列兩種類型:
編譯器 (如 Visual C++ 2005 中的編譯器),它會建置暫時性 .obj 檔案,然後使用獨立的連結器階段,將個別編譯單位合併成一個目標可攜式可執行檔 (PE)。
快速應用程式開發 (Rapid Application Development,RAD) 工具,它會管理工具環境中的所有程式碼和資料結構直到建置階段,此時這些工具會以單一步驟建置和發出 PE 檔。
其他用戶端可能以介於這兩者之間的方式使用中繼資料 API。有些工具可能會讓中繼資料引擎執行最佳化,但卻對語彙基元重新對應資訊不感興趣。或者,它們可能只要某些語彙基元型別但不要其他型別的重新對應資訊。事實上,即使發出 .obj 檔案,編譯器也可能不會執行最佳化。
編譯和連結樣式
在編譯和連結的互動樣式中,編譯器前端會使用 IMetaDataDispenserEx API 建立記憶體中的中繼資料範圍,然後搭配中繼資料語彙基元概觀中描述的中繼資料抽象,使用 IMetaDataEmit API 宣告型別和成員。不過,前端將無法提供方法實作資訊 (例如,實作為 Managed 或 Unmanaged、MSIL 或機器碼) 或相對虛擬位址 (RVA) 資訊,因為在編譯階段無法判斷該資訊。但稍後當程式碼已編譯並且發出至 PE 檔案時,後端 (或連結器) 就必須提供此資訊。
在此編譯是指後端工具必須能取得中繼資料二進位的目標儲存大小資訊,以便在 PE 檔中保留其空間。不過,已知方法 RVA 和模組層級靜態資料成員 RVA 並且已發出至中繼資料之後,工具才會準備將中繼資料二進位碼儲存至檔案。為了正確計算目標儲存大小,中繼資料引擎必須先執行任何儲存前最佳化,因為理想上這些最佳化會讓目標二進位碼更小。當參考目前範圍中所宣告的型別或成員時,最佳化可能包含排序資料結構以進行更快速搜尋,或最佳化改變 (早期繫結) mdTypeRef和 mdMemberRef 語彙基元。這些種類的最佳化可能導致重新對應中繼資料語彙基元,工具必須能夠重複使用它們,以便發出實作和 RVA 資訊。結果,工具和中繼資料引擎必須合作追蹤語彙基元重新對應。
因此,編譯期間為了保存中繼資料所執行的呼叫順序如下:
IMetaDataEmit::SetHandler,提供中繼資料引擎可用來查詢 IID_IMapToken 的 IUnknown 介面,告知用戶端語彙基元的重新對應資訊。建立中繼資料範圍之後,可能會在任何時候呼叫 SetHandler,但確定會在呼叫 IMetaDataEmit::GetSaveSize 之前呼叫。
IMetaDataEmit::GetSaveSize,可取得中繼資料二進位的儲存大小。GetSaveSize 會使用 IMetaDataEmit::SetHandler 中提供的 IMapToken 介面,告知用戶端語彙基元的重新對應資訊。如果未使用 SetHandler 來提供 IMapToken 介面,則不會執行最佳化。這可讓發出暫時 .obj 檔案的編譯器略過不必要的最佳化,因為在連結和合併階段之後可能會重做最佳化。
IMetaDataEmit::Save,可在必須使用 IMetaDataEmit::SetRVA 和其他 IMetaDataEmit 方法以發出最終實作中繼資料之後,保存中繼資料二進位。
下一個編譯層級是在連結器階段,此時多個編譯單位會合併成一個整合式 PE 檔案。在此情況下,不只中繼資料範圍需要合併,而且當發出新的 PE 檔案時 RVA 還會再次變更。在合併階段中,IMetaDataEmit::Merge 方法 (會搭配每個呼叫使用單一匯入和單一發出範圍) 會將匯入範圍中的中繼資料語彙基元重新對應至發出範圍中。此外,合併處理可能遇到必須能傳送至用戶端的連續性錯誤。在完成合併之後,發出最後的 PE 檔案包含 IMetaDataEmit::GetSaveSize 的呼叫和另一回合的語彙基元重新對應。
連結器為了發出和保存中繼資料所執行的呼叫順序如下:
IMetaDataEmit::SetHandler,提供 IUnknown 介面,讓中繼資料引擎不只用來查詢 IID_IMapToken (同上) 而且還能查詢 IID_IMetaDataError。後者介面是用來向用戶端告知合併後發生的任何連續性錯誤。
IMetaDataEmit::Merge,可將指定的中繼資料範圍合併至目前的發出範圍。Merge 會使用 IMapToken 介面,告知用戶端語彙基元重新對應資訊,並且使用 IMetaDataError 告知用戶端持續性錯誤。
IMetaDataEmit::GetSaveSize,取得中繼資料二進位碼的目標儲存大小。GetSaveSize 會使用 IMetaDataEmit::SetHandler 中所提供的 IMapToken 介面,向用戶端告知任何語彙基元重新對應。您必須準備工具處理 Merge 中的語彙基元重新對應,而且在執行格式最佳化之後,再次處理 GetSaveSize 中的語彙基元重新對應。最後的語彙基元告知表示工具應信賴的最後對應。
IMetaDataEmit::Save,在必要時使用 IMetaDataEmit::SetRVA 和其他 IMetaDataEmit 方法發出最後的實作中繼資料之後,以保存中繼資料二進位碼。
RAD 工具樣式
如同編譯和連結的互動樣式,RAD 工具會使用 IMetaDataDispenserEx 介面建立記憶體中的中繼資料範圍,然後搭配中繼資料語彙基元概觀中描述的中繼資料抽象,使用 IMetaDataEmit 介面宣告型別和成員。
與編譯和連結樣式相反的是,RAD 工具一般會在一個步驟中發出 PE 檔案。它可能會在一個行程中發出宣告和實作資訊,而且可能永遠不需要呼叫 IMetaDataEmit::Merge。因此,RAD 工具可能需要處理複雜的語彙基元重新對應的唯一原因是,利用目前由 IMetaDataEmit::GetSaveSize 執行的儲存前最佳化。
一般而言,可發出完全最佳化中繼資料的工具,不需要中繼資料引擎即可發出合理最佳化的檔案。不過,未來的中繼資料引擎和檔案格式實作可能會讓部分最佳化策略過時,所以如何發出最佳化中繼資料有一套清楚的規則。
在發出中繼資料宣告和實作資訊之後,呼叫順序如下:
IMetaDataEmit::SetRVA 和其他 IMetaDataEmit 方法,必要時發出最後的實作中繼資料。
IMetaDataEmit::Save,保存中繼資料二進位碼。