My friends, my life, my style - James S.F. Hsieh

11/09/2011

從 COM 到 Windows Runtime Component

Windows Runtime Component 是 COM 的一種延伸我想這是錯的, 但如果說 WinRT Component 是 COM 的一種演化, 我想這是正確的. 由於 COM 被賦予不少期待所以它很複雜, 從 binary-level interface (在 WinRT 稱 abstract binary interface), system-wide activation, automatic memory management , reflection and Type Library (在 WinRT 為 Windows Runtime metadata), threading model (在 WinRT 稱 concurrency model) 到 out-of-process object 都是 COM 想一併解決的問題. 我猜 Windows Runtime Component 的目標與 COM 相近, 並且想簡化 COM 的複雜度.

Abstract Binary Interface
由於 WinRT Component 可以使用 Pure C++ 搭配 Windows Runtime C++ Template Library (WRL) 來實作, 所以 binary-level interface 跟 COM 是相同的, 換句話說就是跟 Visual C++ 的 VTable 完全同. 不過 ABI 限制了可以傳遞的 type, 所有可以傳遞的 type 必須是 Windows Runtime types. 概念上來說如果不是一個 POD type, 那就必須是一個實作 IInspectable interface 的 COM, IInspectable 是用來做為 language projecting 用的, 簡單的說 IInspectable 可以傳回該 object 所有支援的 interfaces (IInspectable::GetIids) 還有 object 本身的 class fully-qualified name (IInspectable::GetRuntimeClassName). 有了這兩個資訊就足夠查詢 Windows Runtime metadata (*.winmd). WRL::Details:RuntimeClass 會去實作 IInspectable 這個介面.


Object Activation
COM Activation (COM DLL)有幾個步驟 :
  1. COM DLL 可以經由呼叫 DllRegisterServer 來註冊 COM 到系統中
  2. 根據 CLSID 從 HKEY_CLASSES_ROOT\CLSID 找出 object 對應的 binary path.
  3. 載入 binary 並且呼叫 DllGetClassObject function 來取得 Class object.
  4. 呼叫 Class object 的 IClassFactory::CreateInstance 來 create object.
WinRT DLL Component  則是:
  1. 註冊 WinRT DLL Component 的方式跟 COM 很不相同, 我猜這跟 GAC 應該會很像.
    [Update 2011/11/09]
    目前了解是放在 Activation Store (registry)
    HKEY_CURRENT_USER\Software\Classes\ActivatableClasses
    HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsRuntime\ActivatableClassId
  2. 根據 activatable class ID 找到對應的 binary path
  3. 載入 binary 並且呼叫 DllGetActivationFactory function 來取得 factory.
  4. 呼叫 factory 的 IActivationFactory::ActivateInstance 來 create object.
目前我看到的 WinRT Component 都必須被 package 到 Metro style app 之中也就是 local side-by-side, 不知道微軟允不允許 WinRT Component 也能做為 cross application 的 share component.

Automatic Memory Management 
WinRT Component 採用的 automatic memory management 是與傳統 COM 一樣的 reference counting 而非 .NET Framework 的 mark and sweep garbage collector, 我想這是可想而知的, 因為 C++ 並沒有 runtime 的幫忙來 GC 所以必須靠每個 object 自己來管理. 由於 WinRT Component 被 JavaScript, C#, VB.NET 中被使用所以 reference cycles 這個重要的課題必須被解決, 所以微軟為 WinRT Component 引入了 weak reference 的能力, 基本上使用 WRL 實作的 component 由於繼承了 WRL::Details:RuntimeClass 所以都會實作 IWeakReferenceSource. 當然這必須是設計 component 的 developer 有意識的去使用 weak reference 才能解決 reference cycles 問題. 有興趣實作的人可以去看 WRL::Detial::WeakReference 這個 class 就能夠了解這是如何辦到的. 以下描述一個 reference cycles 如何使用 weak reference 來打斷這個 cycle, 假設有 A, B 兩個 objects:



Reflection, Projection 
IDL 是用來描述 COM interface 跟 class 的描述語言, 使用 C++ (非 C++/CX )實作 WinRT Component 還是必須使用 IDL 來描述 object 的 metadata, MIDL compiler 會根據 IDL 來產生 Type Library (TLB) 與 Windows Runtime metadata (winmd). 有趣的是你可以直接用 .NET Reflector 開啟 winmd 檔案. 以下是微軟 Metro style app 的 sample DLL server authoring sample


如果是使用 C++/CX 則它會自動產生 winmd 而不用去撰寫 IDL. Language Projection 完全仰賴 IInspectable 與 Windows Runtime metadata

Threading model
根據 WRL 的 source code 上來看 reference counter 已經確保是 thread-safety, 這有別於 ATL 的設計, 由於 WinRT component 沒有 registry 或是其他 metadata 來描述 COM 的 threading model, 所以我猜測 WinRT  已經不使用 apartment 的概念來管理 WinRT component, 換句話說, 可能所有的 thread 都是 MTA 所有的 component 都是 Free, 但是 , 令我訝異的是 RoInitialize 還是必須傳入, RO_INIT_SINGLETHREADED 或是 RO_INIT_MULTITHREADED, 來描述 thread 的 concurrency model. 目前我還不知道原因.

更讓我疑惑的是 old-style COM 該怎麼辦呢? 微軟文件上是說 old-style COM 在 Metro style app 下還是能夠有限度的被使用 (PS. 可以被使用的 COM CLSID 被記錄在HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsRuntime\AllowedCOMCLSIDs), 在使用 COM 之前還是必須呼叫 CoInitializeEx 且傳遞 COINIT_APARTMENTTHREADED 或 COINIT_MULTITHREADED 描述 thread 的 threading model, 根據我測試的結果 C++/CX and XAML 的 UI thread (不是 main thread 喔) 是 STA 並且內建了 message loop. 但如果有兩個 STA 要存取對方所擁有的 COM 時, Workder thread 所 host 的 COM 可能就無法正常運作, 因為我不知道怎麼在一個 Worker thread 實作一個 message loop 來推動 STA 下 Apartment 或是 Both 的 COM object, 因為 GetMessage/PeekMessage API 在 WinRT 已經不能用了.

[Update 2011/11/09]
看了這篇 Windows Runtime internals: understanding "Hello World" article 我才恍然大悟, Threading model 是存在的, 只不過 WinRT 隱藏了這些細節, WinRT 有一個預設的 Win32 exe 也就是 WWAHost.exe ,它被用來 host Metro style application. WWAHost.exe 也就是 application 的 Server, 以我所了解, host 可以是別的 exe, 這些資訊會被記錄在HKEY_CURRENT_USER\Software\Classes\ActivatableClasses\Server\XXX\ExePath. 根據我對這篇 article 的了解, WWAHost.exe 的第一個 thread 會是 MTA, 這個 thread 被用來做為 activation 之用. 所以 WWAHost!Host::Run 會去執行twinapi!Windows::ApplicationModel::Core::CoreApplicationFactory::RunWithBackgroundFactory 並且等待新的 activation request. 當新的 request 進來時, 它會 fork 一個新的 STA thread 並且執行 request 的需求. 這個需求可能任何一種 Contract ex: Launch, Search, BackgroundTasks ....


[Update 2011/11/25]
Introduction to background tasks - Guidelines for developers 對於 threading model 有特別的解釋
For non-JavaScript apps, the background tasks are hosted in an in-proc DLL which is loaded in a multi-threaded apartment (MTA) within the app. For JavaScript apps, background tasks are launched in a new single-threaded apartment (STA) within the WWA host process. The actual background task class can be STA or MTA. Because background tasks can run when an app is in a Suspended or Terminated state, they need to be decoupled from the foreground app. Loading the background task DLL in a separate apartment enforces separation of the background task from the app while allowing the background task to be controlled independently of the app.


Out-of-Process WinRT Component
依照 WRL 的 source code 的設計來看, WinRT Component 是允許 Out-of-Process 的形式, 但是我卻找不到實際的 sample, 如同上述的問題 GetMessage/PeekMessage API 已經不存在了, 這樣如何實作一個 Out-of-Process COM 呢?

[Update 2011/11/10]
我猜想 WinRT 可能提供類似 DLL Surrogates 的方式來實作 DLL Server, 也就是說我們不用寫一個 exe 來 host COM 而是使用 WinRT 提供既有的 host exe. 另外, WinRT 提供 Broker. 

還有許多問題需要釐清.... 如果我知道答案在分享出來 :D