我安裝好Win8 CTP后做的第一件事情就是用調試器研究Win8各個(gè)組件的協(xié)作關(guān)系. 從我半天的研究結果看來(lái), Win8真是一個(gè)讓我愛(ài)不釋手的產(chǎn)品. Win8里面涉及到的很多技術(shù)正好也是我的興趣所在. 這篇文章簡(jiǎn)單回顧一下這些技術(shù)的變遷, 優(yōu)缺點(diǎn), 和對Win8的影響.
注意, 下面提到的對Win8的分析, 是基于公開(kāi)的Win8 CTP來(lái)做的. 相信Win8面世的時(shí)候, 這些技術(shù)和細節, 都會(huì )發(fā)生重大改變. 所以這篇文章不具備實(shí)踐上的指導價(jià)值.
COM -Component Object Model 通用組件模型
COM是上個(gè)世紀中期設計出來(lái)的偉大產(chǎn)品. COM旨在解決軟件復用的問(wèn)題. 在COM以前, 大家都是用代碼級別的復用, 常見(jiàn)的就是C/C++的庫, 無(wú)論是原代碼庫還是lib庫, 都是需要編譯后才能重用的. COM使得技術(shù)人員可以在二進(jìn)制上進(jìn)行復用. 從Win95, OLE32和Office95系列開(kāi)始, COM就是微軟平臺上的一個(gè)技術(shù)基石, 無(wú)論是DirectX API, 還是最常見(jiàn)的剪貼板, 以及后來(lái).NET Framework的host接口, 都離不開(kāi)COM. 但任何偉大的產(chǎn)品, 都有局限的一面. COM在局限性在下面一些地方
STA/MTA/NTA等等線(xiàn)程模型過(guò)于復雜
線(xiàn)程模型, 特別是STA, 設計的目的是方便使用者. 但COM的線(xiàn)程模型嚴重依賴(lài)于太多系統組件, 比如Win32 Message, RPC和Windows系統服務(wù), 使得程序員需要熟悉和了解太多系統知識才可以正確地使用線(xiàn)程模型. 否則用STA導致死鎖簡(jiǎn)直就是家常便飯.
開(kāi)發(fā)工具沒(méi)有提供足夠支持
COM和Visual Studio 6.0的關(guān)系, 就如同現在CLR2/VS2005, CLR3.5/VS2008和CLR4/VS2010的關(guān)系一樣鐵. 使用COM開(kāi)發(fā), 當時(shí)的選擇要么是VB6, 要么用ATL. 這兩者都有天生的局限. VB6適合企業(yè)開(kāi)發(fā), 特別是當時(shí)流行的MIS系統, 數據庫系統這樣的CS應用, 但是VB6不夠靈活. 而且VB6里面由于缺少多線(xiàn)程支持, 無(wú)法用MTA的模型. ATL功能強大, 足夠靈活, 但是使用起來(lái)特別復雜. 每次實(shí)現一個(gè)接口, 都要做一大堆C++的儀式性工作, 比如實(shí)現多重繼承, 定義新的模板, 使用大量的C宏. 神經(jīng)再粗大的程序員, 都經(jīng)不起這樣用C++的.
無(wú)止境地擴充到DCOM, COM+, DTC, MSMQ以及后來(lái)的.NET Remoting/WCF, 使得最后的復雜度無(wú)法控制
為了更好地適應企業(yè)級別的開(kāi)發(fā), COM被進(jìn)一步延伸和演化成了DCOM和COM+. 所謂"企業(yè)級別", 其實(shí)是指對安全性和可伸縮性的更高要求. 經(jīng)典的COM是一個(gè)進(jìn)程內模型, 無(wú)法讓不同的代碼運行在不同的賬號下的, 因為同一個(gè)進(jìn)程只能啟在唯一的賬號下. 雖然通過(guò)impersonate等方法可以適當地解決問(wèn)題, 但是為了讓安全模型更為全面, 正確的做法是讓不同的接口實(shí)現, 能夠跨越進(jìn)程甚至跨越機器等安全邊界運行, 要能夠賦予不同的接口不同的安全級別, 能夠和域賬號集成, 支持不同等級的加密等等.遠程通用組件模型, 也就是DCOM就這樣誕生了. 讀者可以嘗試運行以下dcomcnfg.exe這個(gè)工具, 展開(kāi)一些節點(diǎn), 看看屬性頁(yè), 就能體會(huì )到DCOM的功能是多么讓人眼花繚亂了. 對于可伸縮性, 微軟更上一層樓, 在DCOM的基礎上加入了對象池和新的同步模型做成了COM+. 風(fēng)靡十年的ASP, 就是運行在COM+框架下的最好例子. 更讓人嘆為觀(guān)止的是, 微軟把DTC和MSMQ的設計也和COM+模型綁定起來(lái). 陌生的讀者可能不了解DTC是個(gè)多么nb的東西. 簡(jiǎn)單說(shuō)DTC是可以讓程序員一行代碼都不用寫(xiě), 就讓SQL Server和Oracle的數據庫操作運行在同一個(gè)事務(wù)邊界里面. 可以想象, 在MSSQL Server剛進(jìn)入市場(chǎng)的時(shí)候, 微軟推出這樣的功能, 對于搶占Oracle的市場(chǎng)份額有多么重要. DTC和COM+當時(shí)成為了很多大公司的標準配置, 以至于后來(lái)設計.NET Remoting和WCF的時(shí)候, 都還是要考慮對DTC的支持. 這些強大而且復雜的功能, 讓本來(lái)就復雜的COM更加恐怖. 這樣的復雜度雖然也體現了當時(shí)的市場(chǎng)需求, 但由于缺乏配套的開(kāi)發(fā)工具, 新的開(kāi)發(fā)語(yǔ)言和更優(yōu)美的抽象, 使得這條路最后越走越窄.
.NET Framework/CLR
在我眼中, CLR的各方面簡(jiǎn)直是無(wú)可挑剔的. 但可能正是因為CLR太好了, 讓微軟從2003年開(kāi)始, 對unmamanged world的投資就不大了.
為了爭取企業(yè)客戶(hù), 全力推廣CLR是最正確的做法. 畢竟絕大多數的程序員, 一輩子都是和數據庫, 和UI代碼打交道. 你總不能讓他們一輩子都用C去管理內容, 創(chuàng )建窗口句柄吧. CLR的性能也很有競爭力, 與之對應的編程語(yǔ)言和開(kāi)發(fā)工具也非常給力, 每個(gè)新版本都帶來(lái)長(cháng)足進(jìn)步. 但是, 這些再好, 也無(wú)法掩飾一個(gè)悖論: 要用用C#寫(xiě)出性能可以和C++一個(gè)數量級的程序, 不是不可能, 而是花費的代價(jià)往往比直接用C++寫(xiě)更大. 這里面有很多原因. 比如系統最底層的API還是unmanaged的, 通過(guò)CLR作interop的性能損失無(wú)法忽略. 比如用C#的話(huà)程序員的控制力很弱, 從工具和語(yǔ)言層面上很難對性能精雕細作, 一不注意就box/unbox了. 比如JIT編譯器為了實(shí)現自己的安全模型和異常處理, 每次訪(fǎng)問(wèn)成員函數的都是都要生成代碼確認this指針是否為空. 這個(gè)世界上還是有很多程序, 是需要對性能做嚴格控制的. 在CLR高速發(fā)展的幾年中, 對應的系統平臺, Win32 API, C++開(kāi)發(fā)工具, 只有完善性的改善, 并沒(méi)有重大突破. 這個(gè)問(wèn)題對Windows平臺本身影響不大, 但如果寄希望于在移動(dòng)設備上, 大家都指著(zhù)C#來(lái)開(kāi)發(fā), 就有點(diǎn)天方夜譚了. 這樣的失衡, 使得微軟在移動(dòng)設備和消費者產(chǎn)品這兩個(gè)嚴重需要unmanaged和系統投入的領(lǐng)域緩慢發(fā)展了很長(cháng)時(shí)間.
WPF
在我看來(lái), WPF是一個(gè)設計得很美的產(chǎn)品. WPF解決了傳統Win32 UI程序的四大局限. 1) Win32的繪圖是由各自Window元素獨立控制, 基于GDI的. WPF引入了rendering thread來(lái)提高性能, 優(yōu)化算法, 借用GPU加速. 2) Win32依賴(lài)于GDI Object, 在開(kāi)發(fā)復雜窗口程序的時(shí)候, 很容易就遭遇資源泄露和資源不足. 比如早期的淘寶旺旺, 開(kāi)到幾十個(gè)窗口的時(shí)候, 程序就會(huì )出問(wèn)題. 所以淘寶針對這個(gè)問(wèn)題, 使用了統一控制臺, 合并多個(gè)窗口到標簽頁(yè)的方法來(lái)解決. 而WPF只有最外面的窗口使用了Win32 Window和GDI, 內部的元素都是抽象成了WPF自己的元素, 不額外占用Win32 GDI資源的. 3) Win32窗體程序嚴重依賴(lài)Windows Message模型. 這要求程序員對系統知識有深入的了解. 而且Win32 API并不是非常利于使用, 比如要進(jìn)行UI thread和Worker thread之間的通信, 往往需要和SendMessage這樣的API打交道. 在WPF中, 引入了Dispatcher類(lèi)和BeginInvoke方法, 把這些復雜問(wèn)題抽象了. 加上CLR提供了更方便高效的開(kāi)發(fā)環(huán)境, 使用WPF是很愉快的工作. 4) Win32缺乏數據, 設計和代碼三者之間的模式抽象. 這三者在WPF中對應了數據綁定, XAML文件, 以及后臺代碼. 在WPF中可以更直觀(guān)地使用各種模式比如MVC和MVVM. 這些都體現了WPF設計上的優(yōu)美.
再優(yōu)美的東西都還是有局限性的. WPF的問(wèn)題在于過(guò)多的模式和對CLR過(guò)度的依賴(lài). 了解WPF框架的人都知道, 就一個(gè)簡(jiǎn)單的dependent property, 就把設計模式這本書(shū)里面的模式用掉一大半了. 分析WPF框架代碼的話(huà), 簡(jiǎn)直就是看一本設計模式的百科全書(shū). 我曾經(jīng)統計過(guò), 關(guān)于mouse click這樣一個(gè)event回調, WPF里面有7種不同的實(shí)現方法, 分別各有好處, 旨在解決不同問(wèn)題. 在這樣高度靈活的背后, 犧牲的是程序性能. 無(wú)論是五花八門(mén)的模式, 還是最常用的數據綁定, 背后的主力都是CLR的reflection. 過(guò)度依賴(lài)于reflection導致WPF程序規模一大, 性能上就出問(wèn)題. 就算再怎么優(yōu)化, 也總找不到原生Win32程序那般流利的感腳. 使用reflection也體現了對CLR的依賴(lài). 所以前面CLR的局限性, 也適用于WPF.
微軟產(chǎn)品的互操作性
微軟的產(chǎn)品線(xiàn)雖又長(cháng)又多, 但是各個(gè)產(chǎn)品之間一定是能夠互操作的. 比如C#可以和C++互相調用. 任何語(yǔ)言開(kāi)發(fā)的程序都可以嵌入Browser Control來(lái)借用IE的功能. Office暴露了VBA接口, 通過(guò)VBScript都能夠自動(dòng)化Office程序. 各種管理工具無(wú)論是Explorer還是MMC, 都暴露了編程接口可以讓程序員添加自己的功能. 我見(jiàn)過(guò)客戶(hù)用ASP.NET在后臺用VBScript生成Excel表格, 然后把表格嵌入在 IE瀏覽器中, 再使用JavaScript來(lái)幫助客戶(hù)編輯, 最后提交回MSSQL數據庫做完成報銷(xiāo)功能的. 完善的互操作性充分保護了用戶(hù)的投資, 使得客戶(hù)對微軟平臺用一次就上癮, 幾乎沒(méi)有不可能完成的任務(wù). 仔細分析, 其實(shí)這些互操作有一個(gè)共性, 都是把暴露COM接口作為內部實(shí)現原理. 這個(gè)做法導致了三個(gè)局限性, 首先是牽涉到了前面提到的COM的復雜度. 其次是潛在的性能損耗. 最后是在具體開(kāi)發(fā)的時(shí)候, 都需要一些儀式性的工作來(lái)引入或者定義COM接口, 使得開(kāi)發(fā)過(guò)程不夠自然流暢. 在CLR和COM互操作的調用棧里面, CLR的RCW, CCW, 安全處理, 列集拷貝等等, 耗費的時(shí)間帶來(lái)的性能開(kāi)銷(xiāo)簡(jiǎn)直是可以到了肉眼可察覺(jué)的地步(聽(tīng)硬盤(pán)的聲音和看任務(wù)管理器里面CPU的波動(dòng)).
Win8
在討論完這些技術(shù)背后的故事后, 再看看為啥我就對Win8愛(ài)不釋手了.
Win8引入了Windows Runtime, 簡(jiǎn)稱(chēng)WinRT. WinRT是一個(gè)操作系統模塊, 運行在用戶(hù)態(tài), 介于Win32的上層和應用程序的下層, 目的在于提供更高效友好的開(kāi)發(fā)接口供Win8的程序員使用. WinRT在二進(jìn)制模型上基本就是照搬了經(jīng)典的COM. WinRT和CLR互不依賴(lài), WinRT可以被CLR使用. WinRT通過(guò)C/C++實(shí)現, 效率高是一個(gè)方面, 更重要的時(shí)Win8引入了projection的概念, 就是可以把WinRT的API用最直接最高效的方法, 提供給上層的編程語(yǔ)言調用. 這個(gè)語(yǔ)言可以是C#, C或者JavaScript.
對于第一次接觸Projection的朋友, 可以把Projection認為是一種新的Windows API模型. 傳統的操作系統API, 要么是暴露DLL的方法, 要么是通過(guò)COM接口. 無(wú)論是哪一種, 在CLR中調用的時(shí)候都有不小的開(kāi)銷(xiāo). 使用這些傳統API的效率, 比調用一個(gè)C#自己的方法, 效率差了多個(gè)數量級, 根本的原因在于CLR的安全模型, 內存模型和傳統的unmanaged模型不兼容, 所以跨越邊界的調用需要額外的代碼來(lái)處理. 而Projection提供的模型, 是在提供新功能的同時(shí), 還針不同編程模型和語(yǔ)言, 提供了最利于它們調用的方法. 這樣就主動(dòng)避免了不同模型之間為了互相兼容導致的開(kāi)銷(xiāo), 也使得程序員寫(xiě)代碼的時(shí)候非常自然流暢, 調用的時(shí)候根本感覺(jué)不到和調用本地函數的區別. 當然, 能夠實(shí)現這一點(diǎn), 也是得益于CLR, C#語(yǔ)言和VS開(kāi)發(fā)工具這十年的長(cháng)足發(fā)展. 舉個(gè)例子, C# 5.0中引入了await關(guān)鍵字, WinRT中引入了async operation. Projection技術(shù)把C#中的await語(yǔ)句轉換為WinRT async operation的調用, 而且這個(gè)調用直接從managed code直接跳到unmanaged code, 中間沒(méi)有任何冗余, 也不需要CLR Engine的介入. 進(jìn)一步的信息, 可以參考Build大會(huì )關(guān)于WinRT的多個(gè)演講. 后面的callstack也提供了直觀(guān)的例子.
前面提到了COM的局限性在于一個(gè)輕量的二進(jìn)制模型, 被硬生生的擴展成一個(gè)無(wú)所不能的框架. WinRT取其精華, 去其糟粕, 借用了COM的輕便, 舍棄了復雜性, 在擴展性上依托于上層的編程語(yǔ)言和工具. WinRT通過(guò)projection的技術(shù)解決了傳統互操作性效率不高使用不方便的問(wèn)題. 以前的路線(xiàn)是希望所有的產(chǎn)品和技術(shù)最后都統一到CLR上來(lái). 現在修正為底層模型通過(guò)WinRT和C來(lái)實(shí)現, 然后把這一層高效的組件無(wú)縫提供給上層的開(kāi)發(fā)技術(shù)比如CLR來(lái)使用. 這個(gè)轉變重新重視unmanaged層面的二進(jìn)制模型. 歸納為, unmanaged模型的優(yōu)勢在于執行效率高, 可以通吃所有場(chǎng)景, 缺點(diǎn)在于開(kāi)發(fā)和使用成本也高. CLR的優(yōu)勢在于開(kāi)發(fā)成本低, 缺點(diǎn)在于無(wú)法通吃各種需求. 現在微軟自己用unmanaged來(lái)做WinRT, 然后把WinRT提供給上層語(yǔ)言, 這兩者就可以取長(cháng)補短了.
有了WinRT, 有了unmanaged的回歸, 再加上微軟開(kāi)發(fā)工具和C#語(yǔ)言的長(cháng)足發(fā)展, 前面介紹的各種技術(shù)在Win8里面就相得益彰了. Metro是如何修復WPF的缺陷就顯而易見(jiàn)了: Win8的metro程序繼續使用WPF中引入的rendering模型和XAML, 但是在control的基礎設計和實(shí)現上, 從CLR轉移到了C, 然后通過(guò)WinRT來(lái)暴露給使用者. 至于使用的靈活性, 比如要不要實(shí)現數據綁定, 就看上層使用者自己的選擇了.
附錄
下面是我研究過(guò)程中看到的一些Win8的callstack. 基于這些callstack和我以往的經(jīng)驗, 才寫(xiě)了上面的文章.
Stack1:
Windows_UI_Immersive!`Windows::Internal::CMessageDialog::ShowAsync'::`50'::<lambda_32D66FEFF293CE6B>::<lambda_32D66FEFF293CE6B>
Windows_UI_Immersive!Windows::Internal::CMessageDialog::ShowAsync+0x1a0
image08ee0000!DomainNeutralILStubClass.IL_STUB_CLRtoCOM()+0x8c
application1!Application1.MainPage+<button_Click>d__0.MoveNext()+0xcd
application1!Application1.MainPage.button_Click(System.Object, Windows.UI.Xaml.RoutedEventArgs)+0x80
stack2:
combase!CComActivator::DoCreateInstance+0x121
combase!CoCreateInstanceEx+0x51
combase!CoCreateInstance+0x65
Windows_UI_Immersive!Windows::Internal::CPopupWindowImpl::_TryToUnregisterForIHMNotifications+0x3b
Windows_UI_Immersive!Windows::Internal::CPopupWindowImpl::_HideWindow+0x31
Windows_UI_Immersive!Windows::Internal::CPopupWindowImpl::HideWindow+0x9
Windows_UI_Immersive!Windows::Internal::CPopupWindowImpl::DestroyWindow+0x24
Windows_UI_Immersive!Windows::Internal::CPopupWindow::Destroy+0x57
Windows_UI_Immersive!Windows::Internal::CClosePopupCommandHandler::Invoke+0xe 0x73
Windows_UI_Immersive!Windows::Internal::CPopupWindowImpl::_OnButtonPress+0xc0
Windows_UI_Immersive!Windows::Internal::CPopupWindowImpl::OnMessage+0x18b
DUI70!DirectUI::NativeHWNDHost::WndProc+0x73
USER32!InternalCallWinProc+0x23
USER32!UserCallWinProcCheckWow+0x100
USER32!DispatchMessageWorker+0x3d4
USER32!DispatchMessageW+0x10
Windows_UI_Immersive!SHProcessMessagesUntilEventsEx+0xe2
Windows_UI_Immersive!Windows::Internal::PopupWindowOperation::Show+0x103
Stack3:
Windows_UI_Xaml!HWWalk::RenderChildren+0x7a
Windows_UI_Xaml!HWWalk::RenderContentAndChildren+0x2d1
Windows_UI_Xaml!HWWalk::Render+0x61e
Windows_UI_Xaml!HWWalk::RenderChildren+0x7a
Windows_UI_Xaml!HWWalk::RenderRoot+0x1c4
Windows_UI_Xaml!CCoreServices::NWDrawTree+0x698
Windows_UI_Xaml!CCoreServices::NWDraw+0x1d6
Windows_UI_Xaml!CRenderTarget::Draw+0x13
Windows_UI_Xaml!CWindowRenderTarget::Draw+0x40
Windows_UI_Xaml!CXcpBrowserHost::OnTick+0x88
Windows_UI_Xaml!CXcpDispatcher::Tick+0x184
Windows_UI_Xaml!CXcpDispatcher::OnReentrancyProtectedWindowMessage+0x133
Windows_UI_Xaml!CXcpDispatcher::ProcessMessage+0xa4
Windows_UI_Xaml!CXcpDispatcher::WindowProc+0x69
USER32!InternalCallWinProc+0x23
USER32!UserCallWinProcCheckWow+0x100
USER32!DispatchMessageWorker+0x3d4
USER32!DispatchMessageW+0x10
windows_ui!Windows::UI::Core::CDispatcher::ProcessMessage+0xc7
windows_ui!Windows::UI::Core::CDispatcher::ProcessEvents+0x6c
Windows_UI_Xaml!CJupiterWindow::RunCoreWindowMessageLoop+0x3b
Windows_UI_Xaml!CJupiterControl::RunMessageLoop+0x1d
Windows_UI_Xaml!CJupiterControlLight::RunMessageLoop+0x8
Windows_UI_Xaml!DirectUI::DXamlCore::RunMessageLoop+0x15
Windows_UI_Xaml!DirectUI::ViewProvider::Run+0x11
twinapi!Windows::ApplicationModel::Core::CoreApplicationView::ViewProviderThreadProc+0x27
This is the end

