CLR 分析工具和 Windows 市集應用程式

本主題討論撰寫診斷工具以分析在 Windows 市集應用程式內執行的 Managed 程式代碼時,需要思考什麼。 它也提供修改現有開發工具的指導方針,以便在針對 Windows 市集應用程式執行它們時繼續運作。 若要瞭解這項資訊,最好是熟悉 Common Language Runtime 程式代碼剖析 API,您已在針對 Windows 傳統型應用程式正確執行的診斷工具中使用此 API,而您現在有興趣修改工具以針對 Windows 市集應用程式正確執行。

簡介

如果您已將它貼到簡介段落,則您熟悉 CLR 分析 API。 您已撰寫適用於受控傳統型應用程式的診斷工具。 現在您好奇該怎麼做,讓工具與受控 Windows 市集應用程式搭配運作。 也許您已經嘗試進行這項工作,並發現這不是一項直接的工作。 事實上,有些考慮對所有工具開發人員可能並不明顯。 例如:

  • Windows 市集應用程式會在許可權大幅降低的內容中執行。

  • 相較於傳統受控模組,Windows 元數據檔案具有獨特的特性。

  • Windows 市集應用程式習慣在互動停止時暫停自己。

  • 您的進程間通訊機制可能因為各種原因而無法再運作。

本主題列出您需要注意的事項,以及如何正確處理它們。

如果您不熟悉 CLR 分析 API,請跳到本主題結尾的資源,以尋找更好的簡介資訊。

提供特定 Windows API 的詳細數據,以及其使用方式也超出本主題的範圍。 請考慮本主題的起點,並參閱 MSDN 以深入了解這裡參考的任何 Windows API。

架構和術語

一般而言,診斷工具具有類似下圖所示的架構。 它會使用「分析工具」一詞,但許多這類工具遠遠超出了一般效能或記憶體分析的範圍,例如程式代碼涵蓋範圍、模擬物件架構、時間移動偵錯、應用程式監視等等。 為了簡單起見,本主題會繼續將所有這些工具稱為分析工具。

本主題中會使用下列術語:

應用程式

這是分析工具正在分析的應用程式。 一般而言,此應用程式的開發人員現在使用分析工具來協助診斷應用程式的問題。 傳統上,此應用程式會是 Windows 傳統型應用程式,但在本主題中,我們會查看 Windows 市集應用程式。

分析工具 DLL

這是載入至所分析應用程式進程空間的元件。 這個元件也稱為分析工具「代理程式」,會 實作 ICorProfilerCallback ICorProfilerCallback介面(2,3 等)介面,並取 用 ICorProfilerInfo(2,3 等)介面來收集所分析應用程式的相關數據,並可能修改應用程式行為的各個層面。

分析工具 UI

這是分析工具使用者與其互動的桌面應用程式。 它負責向使用者顯示應用程式狀態,併為使用者提供控制分析應用程式行為的方法。 此元件一律會在自己的進程空間中執行,與所分析應用程式的進程空間分開。 Profiler UI 也可以做為「附加觸發程式」,也就是呼叫 ICLRProfiling::AttachProfiler 方法的程式,導致分析的應用程式在分析工具 DLL 未在啟動時載入分析工具 DLL 的情況下載入分析工具 DLL。

重要

您的 Profiler UI 應該保持 Windows 傳統型應用程式,即使它用來控制及報告 Windows 市集應用程式。 請勿預期能夠在 Windows 市集中封裝和寄送診斷工具。 您的工具需要執行 Windows 市集應用程式無法執行的動作,而且其中許多專案都位於 Profiler UI 內。

在本檔案中,範例程式代碼假設:

  • 您的 Profiler DLL 是以 C++ 撰寫,因為它必須是原生 DLL,視 CLR 分析 API 的需求而定。

  • 您的分析工具 UI 是以 C# 撰寫。 這不是必要的,但因為分析工具 UI 程式的語言沒有需求,為什麼不挑選簡潔且簡單的語言?

Windows RT 裝置

Windows RT 裝置已完全鎖定。 第三方分析工具根本無法載入這類裝置。 本檔著重於 Windows 8 計算機。

取用 Windows 執行階段 API

在下列各節討論的一些案例中,您的 Profiler UI 傳統型應用程式需要取用一些新的 Windows 執行階段 API。 您將想要參閱檔,以瞭解哪些 Windows 執行階段 API 可從傳統型應用程式使用,以及從傳統型應用程式和 Windows 市集應用程式呼叫時,其行為是否不同。

如果您的 Profiler UI 是以 Managed 程式代碼撰寫,您將需要執行一些步驟,才能輕鬆取用這些 Windows 執行階段 API。 如需詳細資訊,請參閱受控傳統型應用程式和 Windows 執行階段 一文。

載入 Profiler DLL

本節說明 Profiler UI 如何讓 Windows 市集應用程式載入您的 Profiler DLL。 本節中討論的程式代碼屬於您的 Profiler UI 傳統型應用程式,因此牽涉到使用適用於傳統型應用程式的 Windows API,但不一定適用於 Windows 市集應用程式的安全。

Profiler UI 可能會導致 Profiler DLL 以兩種方式載入應用程式的進程空間:

您的第一個障礙之一,就是取得 Profiler DLL 的啟動載入和附加負載,以搭配 Windows 市集應用程式正常運作。 這兩種載入形式共用一些常見的特殊考慮,因此讓我們從它們開始。

啟動和附加載入的常見考慮

簽署分析工具 DLL

當 Windows 嘗試載入 Profiler DLL 時,它會驗證您的 Profiler DLL 是否已正確簽署。 如果沒有,則載入預設會失敗。 作法有二:

  • 請確定您的 Profiler DLL 已簽署。

  • 告訴使用者,他們必須先在其 Windows 8 電腦上安裝開發人員授權,才能使用您的工具。 這可以從 Visual Studio 自動完成,或從命令提示字元手動完成。 如需詳細資訊,請參閱 取得開發人員授權

檔案系統許可權

Windows 市集應用程式必須具有從其所在檔系統位置載入和執行 Profiler DLL 的許可權,Windows 市集應用程式在大部分目錄上沒有這類許可權,而且載入 Profiler DLL 的任何失敗嘗試都會在 Windows 應用程式事件記錄檔中產生類似如下的專案:

NET Runtime version 4.0.30319.17929 - Loading profiler failed during CoCreateInstance.  Profiler CLSID: '{xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}'.  HRESULT: 0x80070005.  Process ID (decimal): 4688.  Message ID: [0x2504].

一般而言,Windows 市集應用程式只能存取磁碟上一組有限的位置。 每個 Windows 市集應用程式都可以存取自己的應用程式資料資料夾,以及所有 Windows 市集應用程式都獲授與存取權之檔案系統中的其他幾個區域。 最好在 Program Files 或 Program Files (x86) 底下安裝 Profiler DLL 及其相依性,因為所有 Windows 市集應用程式預設都有讀取和執行許可權。

啟動載入

一般而言,在傳統型應用程式中,您的 Profiler UI 會藉由初始化包含必要 CLR 分析 API 環境變數的環境區塊來COR_PROFILERCOR_ENABLE_PROFILINGCOR_PROFILER_PATH提示 Profiler DLL 的啟動載入,然後使用該環境區塊建立新的進程。 Windows 市集應用程式也是如此,但機制不同。

不要執行提升許可權

如果進程 A 嘗試繁衍 Windows 市集應用程式進程 B,進程 A 應該在中等完整性層級執行,而不是在高完整性層級執行(也就是未提高)。 這表示您的 Profiler UI 應該在中型完整性層級執行,或必須在中等完整性層級繁衍另一個桌面程式,才能負責啟動 Windows 市集應用程式。

選擇要分析的 Windows 市集應用程式

首先,您會想要詢問分析工具用戶要啟動哪個 Windows 市集應用程式。 針對傳統型應用程式,您可能會顯示 [瀏覽] 對話框,而且用戶會尋找並選取.exe檔案。 但 Windows 市集應用程式不同,使用 [瀏覽] 對話框並無意義。 相反地,最好向用戶顯示安裝供該用戶選取的 Windows 市集應用程式清單。

您可以使用 類別 PackageManager 來產生此清單。 PackageManager是傳統型應用程式可用的 Windows 執行階段 類別,事實上它只適用於傳統型應用程式。

下列程式代碼範例來自以 C# 撰寫為傳統型應用程式的假設 Profiler UI,會使用 PackageManager 來產生 Windows 應用程式清單:

string currentUserSID = WindowsIdentity.GetCurrent().User.ToString();
IAppxFactory appxFactory = (IAppxFactory) new AppxFactory();
PackageManager packageManager = new PackageManager();
IEnumerable<Package> packages = packageManager.FindPackagesForUser(currentUserSID);

指定自定義環境區塊

新的 COM 介面 IPackageDebug 設定 可讓您自定義 Windows 市集應用程式的執行行為,讓某些形式的診斷更容易。 其中 一種方法 EnableDebugging 可讓您在啟動 Windows 市集應用程式時將環境區塊傳遞至 Windows 市集應用程式,以及其他有用的效果,例如停用自動進程暫停。 環境區塊很重要,因為您需要指定 CLR 用來載入 Profiler DLL 的環境變數 (COR_PROFILERCOR_ENABLE_PROFILINGCOR_PROFILER_PATH)) 。

請考慮下列程式碼片段:

IPackageDebugSettings pkgDebugSettings = new PackageDebugSettings();
pkgDebugSettings.EnableDebugging(packageFullName, debuggerCommandLine,
                                                                 (IntPtr)fixedEnvironmentPzz);

有幾個專案需要正確:

  • packageFullName 可以在逐一查看套件並抓取 package.Id.FullName時判斷。

  • debuggerCommandLine 更有趣一點。 若要將自定義環境區塊傳遞至 Windows 市集應用程式,您必須撰寫自己的簡單虛擬調試程式。 Windows 繁衍暫停的 Windows 市集應用程式,然後使用如下列範例所示的命令行啟動調試程式來附加調試程式:

    MyDummyDebugger.exe -p 1336 -tid 1424
    

    其中 -p 1336 表示 Windows 市集應用程式有進程標識碼 1336,而 -tid 1424 表示線程標識碼 1424 是暫停的線程。 您的虛擬調試程式會從命令行剖析 ThreadID、繼續該線程,然後結束。

    以下是一些要執行此作業的 C++ 程式代碼範例(請務必新增錯誤檢查!):

    int wmain(int argc, wchar_t* argv[])
    {
        // …
        // Parse command line here
        // …
    
        HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME,
                                                                  FALSE /* bInheritHandle */, nThreadID);
        ResumeThread(hThread);
        CloseHandle(hThread);
        return 0;
    }
    

    您必須將此虛擬調試程式部署為診斷工具安裝的一部分,然後在 參數中 debuggerCommandLine 指定此調試程序的路徑。

啟動 Windows 市集應用程式

啟動 Windows 市集應用程式的時刻終於到了。 如果您已經自行嘗試這麼做,您可能已經注意到 CreateProcess 不是建立 Windows 市集應用程式程式的方式。 相反地,您必須使用 IApplicationActivationManager::ActivateApplication 方法。 若要這樣做,您必須取得您要啟動之 Windows 市集應用程式的 App 使用者模型識別碼。 這意味著你需要做一點挖掘指令清單。

在稍早的啟動載入一節中逐一查看套件時,您會想要擷取目前套件指令清單中包含的一組應用程式:

string manifestPath = package.InstalledLocation.Path + "\\AppxManifest.xml";

AppxPackaging.IStream manifestStream;
SHCreateStreamOnFileEx(
                    manifestPath,
                    0x00000040,     // STGM_READ | STGM_SHARE_DENY_NONE
                    0,              // file creation attributes
                    false,          // fCreate
                    null,           // reserved
                    out manifestStream);

IAppxManifestReader manifestReader = appxFactory.CreateManifestReader(manifestStream);

IAppxManifestApplicationsEnumerator appsEnum = manifestReader.GetApplications();

是,一個套件可以有多個應用程式,而且每個應用程式都有自己的應用程式使用者模型標識碼。 因此,您會想要詢問使用者要分析的應用程式,並從該特定應用程式擷取應用程式使用者模型標識碼:

while (appsEnum.GetHasCurrent() != 0)
{
    IAppxManifestApplication app = appsEnum.GetCurrent();
    string appUserModelId = app.GetAppUserModelId();
    //...
}

最後,您現在有啟動 Windows 市集應用程式所需的專案:

IApplicationActivationManager appActivationMgr = new ApplicationActivationManager();
appActivationMgr.ActivateApplication(appUserModelId, appArgs, ACTIVATEOPTIONS.AO_NONE, out pid);

請記得呼叫 DisableDebugging

當您呼叫 IPackageDebug 設定::EnableDebugging 時,您承諾在呼叫 IPackageDebug 設定::D isableDebugging 方法之後自行清除,因此請務必在分析會話結束時執行此動作。

附加負載

當您的 Profiler UI 想要將其 Profiler DLL 附加至已經開始執行的應用程式時,它會使用 ICLRProfiling::AttachProfiler。 Windows 市集應用程式也是如此。 但除了稍早列出的常見考慮之外,請確定目標 Windows 市集應用程式並未暫停。

EnableDebugging

如同啟動載入,請呼叫 IPackageDebug 設定::EnableDebugging 方法。 您不需要它傳遞環境區塊,但您需要其中一項其他功能:停用自動程序暫停。 否則,當您的 Profiler UI 呼叫 AttachProfiler 時,目標 Windows 市集應用程式可能會暫停。 事實上,如果用戶現在正在與您的 Profiler UI 互動,而且 Windows 市集應用程式在任何使用者的畫面上都沒有作用中,就有可能發生這種情況。 如果 Windows 市集應用程式暫停,它將無法回應 CLR 傳送給它以附加 Profiler DLL 的任何訊號。

因此,您會想要執行如下動作:

IPackageDebugSettings pkgDebugSettings = new PackageDebugSettings();
pkgDebugSettings.EnableDebugging(packageFullName, null /* debuggerCommandLine */,
                                                                 IntPtr.Zero /* environment */);

這是您針對啟動載入案例所做的相同呼叫,但您未指定調試程式命令行或環境區塊。

DisableDebugging

一如往常,別忘了在分析會話完成時呼叫 IPackageDebug 設定::D isableDebugging

在 Windows 市集應用程式內執行

因此,Windows 市集應用程式最後已載入您的 Profiler DLL。 現在,您的 Profiler DLL 必須教導如何依 Windows 市集應用程式所需的不同規則來播放,包括允許哪些 API,以及如何以降低的許可權執行。

堅持 Windows 市集應用程式 API

當您瀏覽 Windows API 時,您會發現每個 API 都記載為適用於傳統型應用程式、Windows 市集應用程式或兩者。 例如,InitializeCriticalSectionAndSpinCount 函式檔的需求區段指出函式僅適用於傳統型應用程式。 相反地, InitializeCriticalSectionEx 函式適用於傳統型應用程式和 Windows 市集應用程式。

開發您的 Profiler DLL 時,請將它視為 Windows 市集應用程式,並只使用記載為 Windows 市集應用程式的 API。 分析您的相依性(例如,您可以針對 Profiler DLL 執行 link /dump /imports 以稽核),然後搜尋檔以查看哪些相依性正常且不是。 在大部分情況下,只要將違規取代為安全的較新 API 形式即可修正(例如,將 InitializeCriticalSectionAndSpinCount 取代InitializeCriticalSectionEx)。

您可能會注意到您的 Profiler DLL 會呼叫一些僅適用於傳統型應用程式的 API,但即使您的 Profiler DLL 載入 Windows 市集應用程式,它們似乎仍可運作。 請注意,載入 Windows 市集應用程式程式時,使用任何未記載在 Profiler DLL 中搭配 Windows 市集應用程式使用的 API 是有風險的:

  • 在 Windows 市集應用程式執行的唯一內容中呼叫時,不保證這類 API 能夠運作。

  • 從不同的 Windows 市集應用程式進程呼叫時,這類 API 可能無法一致運作。

  • 這類 API 在目前版本的 Windows 中,可能會從 Windows 市集應用程式正常運作,但在未來的 Windows 版本中可能會中斷或停用。

最好的建議是修正所有違規行為,並避免風險。

您可能會發現,您絕對無法在沒有特定 API 的情況下執行,而且找不到適合 Windows 市集應用程式的替代專案。 在這種情況下,至少:

  • 測試、測試、測試您使用該 API 的使用方式,測試活生生的日光。

  • 瞭解 API 可能會在未來的 Windows 版本中從 Windows 市集應用程式內呼叫時突然中斷或消失。 Microsoft 不會將此視為相容性考慮,且支援其使用方式不會是優先順序。

降低的許可權

本主題的範圍之外,可列出 Windows 市集應用程式許可權與傳統型應用程式不同的所有方式。 但當您的 Profiler DLL 每次載入 Windows 市集應用程式時,與傳統型應用程式相比,行為當然會有所不同, 會嘗試存取任何資源。 檔系統是最常見的範例。 在磁碟上,有一些地方允許指定的 Windows 市集應用程式存取(請參閱檔案存取權和許可權(Windows 執行階段 應用程式),而您的 Profiler DLL 將會受到相同的限制。 徹底測試您的程序代碼。

內含式通訊

如本文開頭的圖表所示,您的 Profiler DLL(載入 Windows 市集應用程式進程空間)可能需要透過您自己的自定義進程通訊 (IPC) 通道,與 Profiler UI 進行通訊(在個別的桌面應用程式進程空間中執行)。 Profiler UI 會將訊號傳送至 Profiler DLL 以修改其行為,而 Profiler DLL 會將分析的 Windows 市集應用程式的數據傳回 Profiler UI,以供後續處理及顯示給分析工具使用者。

大部分分析工具都需要以這種方式運作,但當您的 Profiler DLL 載入 Windows 市集應用程式時,IPC 機制的選擇會更加有限。 例如,命名管道不是 Windows 市集應用程式 SDK 的一部分,因此您無法使用這些管道。

但當然,檔案仍然處於,儘管以更有限的方式。 您也可以使用事件。

透過檔案通訊

大部分的數據可能會透過檔案在 Profiler DLL 和 Profiler UI 之間傳遞。 密鑰是挑選您的 Profiler DLL(在 Windows 市集應用程式內容中)和 Profiler UI 具有讀取和寫入許可權的檔案位置。 例如,暫存資料夾路徑是 Profiler DLL 和 Profiler UI 可以存取的位置,但無法存取其他 Windows 市集應用程式套件(因此會防護您從其他 Windows 市集應用程式套件記錄的任何資訊)。

您的 Profiler UI 和 Profiler DLL 都可以獨立判斷此路徑。 您的 Profiler UI,在逐一查看為目前使用者安裝的所有套件時(請參閱稍早的範例程式代碼),會取得 類別的存取 PackageId 權,而暫存資料夾路徑可從中衍生類似此代碼段的程式代碼。 (一如往常,省略錯誤檢查以求簡潔。

// C# code for the Profiler UI.
ApplicationData appData =
    ApplicationDataManager.CreateForPackageFamily(
        packageId.FamilyName);

tempDir = appData.TemporaryFolder.Path;

同時,您的 Profiler DLL 基本上可以執行相同的動作,不過它可以使用 ApplicationData.Current 屬性更輕鬆地進入 ApplicationData 類別。

透過事件進行通訊

如果您想要在 Profiler UI 和 Profiler DLL 之間使用簡單的訊號語意,您可以在 Windows 市集應用程式和傳統型應用程式內使用事件。

從您的 Profiler DLL 中,您可以直接呼叫 CreateEventEx 函式,以任何您想要的名稱建立具名事件。 例如:

// Profiler DLL in Windows Store app (C++).
CreateEventEx(
    NULL,  // Not inherited
    "MyNamedEvent"
    CREATE_EVENT_MANUAL_RESET, /* explicit ResetEvent() required; leave initial state unsignaled */
    EVENT_ALL_ACCESS);

接著,您的 Profiler UI 必須在 Windows 市集應用程式的命名空間下尋找該具名事件。 例如,您的 Profiler UI 可以呼叫 CreateEventEx,並將事件名稱指定為

AppContainerNamedObjects\<acSid>\MyNamedEvent

<acSid> 是 Windows 市集應用程式的 AppContainer SID。 本主題的先前章節示範如何逐一查看為目前使用者安裝的套件。 您可以從該範例程式代碼取得 packageId。 而且,您可以從 packageId 取得 <acSid> ,其程式代碼類似如下:

IntPtr acPSID;
DeriveAppContainerSidFromAppContainerName(packageId.FamilyName, out acPSID);

string acSid;
ConvertSidToStringSid(acPSID, out acSid);

string acDir;
GetAppContainerFolderPath(acSid, out acDir);

沒有關機通知

在 Windows 市集應用程式內執行時,您的 Profiler DLL 不應該依賴 ICorProfilerCallback::Shutdown 或甚至 呼叫 DllMainDLL_PROCESS_DETACH以通知 Profiler DLL Windows 市集應用程式即將結束。 事實上,您應該預期永遠不會呼叫它們。 在過去,許多 Profiler DLL 都使用這些通知作為方便的地方,將快取排清到磁碟、關閉檔案、將通知傳回 Profiler UI 等等。但現在您的 Profiler DLL 需要以稍微不同的方式組織。

您的 Profiler DLL 應該會記錄資訊。 基於效能考慮,您可能會想要在記憶體中批處理資訊,並將它排清到磁碟,因為批次的大小超過某些閾值。 但假設尚未排清到磁碟的任何資訊都可能會遺失。 這表示您想要明智地挑選臨界值,而且必須強化您的 Profiler UI,才能處理 Profiler DLL 所撰寫的不完整資訊。

Windows 執行階段 元數據檔案

這份檔範圍之外,可詳細說明 Windows 執行階段 元數據 (WinMD) 檔案是什麼。 本節僅限於 CLR 分析 API 在分析 Profiler DLL 的 Windows 市集應用程式載入 WinMD 檔案時的反應方式。

受控和非受控 WinMD

如果開發人員使用Visual Studio來建立新的 Windows 執行階段元件專案,該專案的組建會產生WinMD檔案來描述開發人員撰寫的元數據(類別、介面等的類型描述)。 如果這個專案是以 C# 或 Visual Basic 撰寫的 Managed 語言專案,則相同的 WinMD 檔案也會包含這些類型的實作(這表示它包含從開發人員原始程式碼編譯的所有 IL)。 這類檔案稱為Managed WinMD檔案。 有趣的是,它們同時包含 Windows 執行階段 元數據和基礎實作。

相反地,如果開發人員為 C++ 建立 Windows 執行階段 元件專案,該專案的組建會產生只包含元數據的 WinMD 檔案,而實作會編譯成個別的原生 DLL。 同樣地,在 Windows SDK 中隨附的 WinMD 檔案只包含元數據,而實作會編譯為 Windows 隨附的個別原生 DLL。

下列資訊適用於同時包含元數據和實作的 Managed WinMD,以及只包含元數據的非受控 WinMD。

WinMD 檔案看起來像 CLR 模組

就 CLR 而言,所有 WinMD 檔案都是模組。 因此,CLR 程式代碼剖析 API 會告知您的 Profiler DLL WinMD 檔案載入時,以及其 Module 識別符與其他受控模組的相同方式。

您的 Profiler DLL 可以呼叫 ICorProfilerInfo3::GetModuleInfo2 方法來區分 WinMD 檔案與其他模組,並檢查pdwModuleFlagsCOR_PRF_MODULE_WINDOWS_RUNTIME旗標的輸出參數。 (只有在ModuleID代表 WinMD 時,才會設定它。

從 WinMD 讀取元數據

WinMD 檔案,例如一般模組,包含可透過元數據 API 讀取的元數據。 不過,CLR 會在讀取 WinMD 檔案時,將 Windows 執行階段 類型對應至 .NET Framework 類型,讓以 Managed 程式代碼進行程式設計並取用 WinMD 檔案的開發人員可以有更自然的程式設計體驗。 如需這些對應的一些範例,請參閱適用於 Windows 市集應用程式的 .NET Framework 支援和 Windows 執行階段

因此,當您的分析工具使用元數據 API 時,會取得哪個檢視:原始 Windows 執行階段 檢視或對應的 .NET Framework 檢視? 答案:這是由你決定的。

當您在 WinMD 上呼叫 ICorProfilerInfo::GetModuleMetaData 方法以取得元數據介面,例如 IMetaDataImport 時,您可以選擇在 參數中dwOpenFlags設定 ofNoTransform 來關閉此對應。 否則,預設會啟用對應。 一般而言,分析工具會保持啟用對應,讓 Profiler DLL 從 WinMD 元數據取得的字串(例如,型別的名稱)看起來會很熟悉且自然。

從 WinMD 修改元數據

不支援修改 WinMD 中的元數據。 如果您呼叫 WinMD 檔案的 ICorProfilerInfo::GetModuleMetaData 方法,並在 參數中dwOpenFlags指定 ofWrite,或要求 IMetaDataEmit 等可寫入的元數據介面,GetModuleMetaData 將會失敗。 這對 IL 重寫分析工具特別重要,需要修改元數據以支援其檢測(例如,若要新增 AssemblyRefs 或新的方法)。 因此,您應該先檢查 COR_PRF_MODULE_WINDOWS_RUNTIME (如上一節所述),並避免要求這類模組上的可寫入元數據介面。

使用WinMD 解析元件參考

許多分析工具需要手動解析元數據參考,以協助進行檢測或類型檢查。 這類分析工具必須知道 CLR 如何解析指向 WinMD 的元件參考,因為這些參考會以與標準元件參考完全不同的方式解析。

記憶體分析工具

在 Windows 市集應用程式和傳統型應用程式中,垃圾收集行程和受控堆積基本上不同。 不過,分析工具作者需要注意的一些細微差異。

ForceGC 會建立受控線程

執行記憶體分析時,Profiler DLL 通常會建立個別的線程,以呼叫 ForceGC 方法方法 。 這沒什麼新鮮事。 但令人驚訝的是,在 Windows 市集應用程式內執行垃圾收集的動作可能會將您的線程轉換成受控線程(例如,分析 API ThreadID 將會針對該線程建立)。

若要瞭解其後果,請務必瞭解 CLR 分析 API 所定義的同步和異步呼叫之間的差異。 請注意,這與 Windows 市集應用程式中異步呼叫的概念非常不同。 如需詳細資訊,請參閱部落格文章 為什麼CORPROF_E_UNSUPPORTED_CALL_SEQUENCE

相關點是,即使您的 Profiler DLL ICorProfilerCallback 方法實作外部呼叫,您分析工具所建立之線程上的呼叫一律會被視為同步。 至少,這曾經是這種情況。 由於您呼叫 ForceGC 方法,CLR 已將分析工具的線程轉換成受控線程,該線程已不再被視為分析工具的線程。 因此,CLR 會強制執行更嚴格的定義,其限定為該線程的同步,也就是呼叫必須源自您其中一個 Profiler DLL 的 ICorProfilerCallback 方法,才能限定為同步。

實際上這是什麼意思? 大部分 的 ICorProfilerInfo 方法只能安全地同步呼叫,否則會立即失敗。 因此,如果您的 Profiler DLL 會針對通常針對分析工具建立的線程(例如,對 RequestProfilerDetachRequestReJITRequestRevert)進行的其他呼叫重複使用 ForceGC 方法線程,您將遇到問題。 即使是從 Managed 線程呼叫時,DoStackSnapshot 之類的異步安全函式也有特殊規則。 (請參閱部落格文章 分析工具堆棧步行:基本概念和更新 版本以取得詳細資訊。

因此,我們建議您將 Profiler DLL 建立的任何線程都用於呼叫 ForceGC 方法,以便觸發 GC,然後回應 GC 回呼。 它不應該呼叫分析 API 來執行其他工作,例如堆疊取樣或中斷連結。

ConditionalWeakTableReferences

從 .NET Framework 4.5 開始,有新的 GC 回呼 ConditionalWeakTableElementReferences,讓分析工具更 完整的相依句柄資訊。 這些句柄會有效地將來源對象的參考新增至目標物件,以便進行 GC 存留期管理。 相依句柄並不是什麼新鮮事,而且在 Managed 程式代碼中撰寫程式的開發人員,即使在 Windows 8 和 .NET Framework 4.5 之前,也能夠使用 System.Runtime.CompilerServices.ConditionalWeakTable<TKey,TValue> 類別來建立自己的相依句柄。

不過,受控 XAML Windows 市集應用程式現在會大量使用相依句柄。 特別是 CLR 會使用它們來協助管理 Managed 物件與 Unmanaged Windows 執行階段 對象之間的參考週期。 這表示記憶體分析工具現在比以往更加重要,讓記憶體分析工具知道這些相依句柄,以便與堆積圖形中的其餘邊緣一起可視化。 您的 Profiler DLL 應該同時使用 RootReferences2ObjectReferencesConditionalWeakTableElementReferences 來形成堆積圖形的完整檢視。

結論

您可以使用 CLR 分析 API 來分析在 Windows 市集應用程式內執行的 Managed 程式代碼。 事實上,您可以採用您正在開發的現有分析工具,並進行一些特定變更,以便以 Windows 市集應用程式為目標。 您的分析工具 UI 應該使用新的 API,在偵錯模式中啟用 Windows 市集應用程式。 請確定您的 Profiler DLL 只會取用適用於 Windows 市集應用程式的 API。 您的 Profiler DLL 與 Profiler UI 之間的通訊機制應該以 Windows 市集應用程式 API 限制撰寫,並瞭解 Windows 市集應用程式所具備的限制許可權。 您的 Profiler DLL 應該知道 CLR 如何處理 WinMD,以及垃圾收集行程的行為與 Managed 線程的不同。

資源

The Common Language Runtime

CLR 與 Windows 執行階段 的互動

Windows 市集應用程式