從 Windows 8 回顧微軟平臺的各種技術

作者: lixiong  來源: 博客園  發布時間: 2011-12-24 13:36  閱讀: 5195 次  推薦: 12   原文鏈接   [收藏]  

  我安裝好Win8 CTP后做的第一件事情就是用調試器研究Win8各個組件的協作關系。從我半天的研究結果看來,Win8真是一個讓我愛不釋手的產品。Win8里面涉及到的很多技術正好也是我的興趣所在。這篇文章簡單回顧一下這些技術的變遷,優缺點,和對Win8的影響.

  注意,下面提到的對Win8的分析, 是基于公開的Win8 CTP來做的。相信Win8面世的時候,這些技術和細節,都會發生重大改變。所以這篇文章不具備實踐上的指導價值。

  COM - Common Object Model 通用組件模型

  COM是上個世紀中期設計出來的偉大產品。COM旨在解決軟件復用的問題。在COM以前,大家都是用代碼級別的復用,常見的就是C/C++的庫,無論是原代碼庫還是lib庫,都是需要編譯后才能重用的。COM使得技術人員可以在二進制上進行復用。從Win95, OLE32和Office95系列開始,COM就是微軟平臺上的一個技術基石,無論是DirectX API, 還是最常見的剪貼板,以及后來.NET Framework的host接口,都離不開COM。但任何偉大的產品,都有局限的一面。COM在局限性在下面一些地方。

  STA/MTA/NTA等等線程模型過于復雜

  線程模型,特別是STA,設計的目的是方便使用者。但COM的線程模型嚴重依賴于太多系統組件,比如Win32 Message, RPC和Windows系統服務,使得程序員需要熟悉和了解太多系統知識才可以正確地使用線程模型。否則用STA導致死鎖簡直就是家常便飯。

  開發工具沒有提供足夠支持

  COM和Visual Studio 6.0的關系,就如同現在CLR2/VS2005, CLR3.5/VS2008和CLR4/VS2010的關系一樣鐵。使用COM開發,當時的選擇要么是VB6,要么用ATL。這兩者都有天生的局限。VB6適合企業開發,特別是當時流行的MIS系統,數據庫系統這樣的CS應用,但是VB6不夠靈活。而且VB6里面由于缺少多線程支持,無法用MTA的模型。ATL功能強大,足夠靈活,但是使用起來特別復雜。每次實現一個接口,都要做一大堆C++的儀式性工作,比如實現多重繼承,定義新的模板,使用大量的C宏。神經再粗大的程序員,都經不起這樣用C++的。

  無止境地擴充到DCOM, COM+, DTC, MSMQ以及后來的.NET Remoting/WCF, 使得最后的復雜度無法控制

  為了更好地適應企業級別的開發,COM被進一步延伸和演化成了DCOM和COM+。所謂"企業級別",其實是指對安全性和可伸縮性的更高要求。經典的COM是一個進程內模型,無法讓不同的代碼運行在不同的帳號下的,因為同一個進程只能啟動在唯一的帳號下。雖然通過impersonate等方法可以適當地解決問題,但是為了讓安全模型更為全面,正確的做法是讓不同的接口實現,能夠跨越進程甚至跨越機器等安全邊界運行,要能夠賦予不同的接口不同的安全級別,能夠和域帳號集成,支持不同等級的加密等等。遠程通用組件模型,也就是DCOM就這樣誕生了。讀者可以嘗試運行以下dcomcnfg.exe這個工具,展開一些節點,看看屬性頁,就能體會到DCOM的功能是多么讓人眼花繚亂了。對于可伸縮性,微軟更上一層樓,在DCOM的基礎上加入了對象池和新的同步模型做成了COM+。風靡十年的ASP,就是運行在COM+框架下的最好例子。更讓人嘆為觀止的是,微軟把DTC和MSMQ的設計也和COM+模型綁定起來。陌生的讀者可能不了解DTC是個多么nb的東西。簡單說DTC是可以讓程序員一行代碼都不用寫,就讓SQL Server和Oracle的數據庫操作運行在同一個事務邊界里面。可以想象,在MSSQL Server剛進入市場的時候,微軟推出這樣的功能,對于搶占Oracle的市場份額有多么重要。DTC和COM+當時成為了很多大公司的標準配置,以至于后來設計.NET Remoting和WCF的時候,都還是要考慮對DTC的支持。這些強大而且復雜的功能,讓本來就復雜的COM更加恐怖。這樣的復雜度雖然也體現了當時的市場需求,但由于缺乏配套的開發工具,新的開發語言和更優美的抽象,使得這條路最后越走越窄。

  .NET Framework/CLR

  在我眼中,CLR的各方面簡直是無可挑剔的。但可能正是因為CLR太好了,讓微軟從2003年開始,對unmamanged world的投資就不大了。

  為了爭取企業客戶,全力推廣CLR是最正確的做法。畢竟絕大多數的程序員,一輩子都是和數據庫,和UI代碼打交道。你總不能讓他們一輩子都用C去管理內容,創建窗口句柄吧。CLR的性能也很有競爭力,與之對應的編程語言和開發工具也非常給力,每個新版本都帶來長足進步。但是,這些再好,也無法掩飾一個悖論: 要用C#寫出性能可以和C++一個數量級的程序,不是不可能,而是花費的代價往往比直接用C++寫更大。這里面有很多原因,比如系統最底層的API還是unmanaged的,通過CLR作interop的性能損失無法忽略。比如用C#的話,程序員的控制力很弱,從工具和語言層面上很難對性能精雕細作,一不注意就box/unbox了。比如JIT編譯器為了實現自己的安全模型和異常處理,每次訪問成員函數的都是都要生成代碼確認this指針是否為空。這個世界上還是有很多程序,是需要對性能做嚴格控制的。在CLR高速發展的幾年中,對應的系統平臺,Win32 API, C++開發工具,只有完善性的改善,并沒有重大突破。這個問題對Windows平臺本身影響不大,但如果寄希望于在移動設備上,大家都指著C#來開發,就有點天方夜譚了。這樣的失衡,使得微軟在移動設備和消費者產品這兩個嚴重需要unmanaged和系統投入的領域緩慢發展了很長時間。

  WPF

  在我看來,WPF是一個設計得很美的產品。WPF解決了傳統Win32 UI程序的四大局限:1) Win32的繪圖是由各自Window元素獨立控制,基于GDI的。WPF引入了rendering thread來提高性能,優化算法,借用GPU加速。2) Win32依賴于GDI Object,在開發復雜窗口程序的時候,很容易就遭遇資源泄露和資源不足。比如早期的淘寶旺旺,開到幾十個窗口的時候,程序就會出問題。所以淘寶針對這個問題,使用了統一控制臺,合并多個窗口到標簽頁的方法來解決。而WPF只有最外面的窗口使用了Win32 Window和GDI,內部的元素都是抽象成了WPF自己的元素,不額外占用Win32 GDI資源的。3) Win32窗體程序嚴重依賴Windows Message模型。這要求程序員對系統知識有深入的了解。而且Win32 API并不是非常利于使用,比如要進行UI thread和Worker thread之間的通信,往往需要和SendMessage這樣的API打交道。在WPF中,引入了Dispatcher類和BeginInvoke方法,把這些復雜問題抽象了。加上CLR提供了更方便高效的開發環境,使用WPF是很愉快的工作。4) Win32缺乏數據,設計和代碼三者之間的模式抽象。這三者在WPF中對應了數據綁定,XAML文件,以及后臺代碼。在WPF中可以更直觀地使用各種模式比如MVC和MVVM。這些都體現了WPF設計上的優美。

  再優美的東西都還是有局限性的。WPF的問題在于過多的模式和對CLR過度的依賴。了解WPF框架的人都知道,就一個簡單的dependent property, 就把設計模式這本書里面的模式用掉一大半了。分析WPF框架代碼的話,簡直就是看一本設計模式的百科全書。我曾經統計過,關于mouse click這樣一個event回調,WPF里面有7種不同的實現方法,分別各有好處,旨在解決不同問題。在這樣高度靈活的背后,犧牲的是程序性能。無論是五花八門的模式,還是最常用的數據綁定,背后的主力都是CLR的reflection。過度依賴于reflection導致WPF程序規模一大,性能上就出問題。就算再怎么優化,也總找不到原生Win32程序那般流利的感覺。使用reflection也體現了對CLR的依賴。所以前面CLR的局限性,也適用于WPF。

  微軟產品的互操作性

  微軟的產品線雖又長又多,但是各個產品之間一定是能夠互操作的,比如C#可以和C++互相調用。任何語言開發的程序都可以嵌入Browser Control來借用IE的功能。Office暴露了VBA接口,通過VBScript都能夠自動化Office程序。各種管理工具無論是Explorer還是MMC,都暴露了編程接口可以讓程序員添加自己的功能。我見過客戶用ASP.NET在后臺用VBScript生成Excel表格,然后把表格嵌入在IE瀏覽器中,再使用JavaScript來幫助客戶編輯,最后提交回MSSQL數據庫做完成報銷功能的。完善的互操作性充分保護了用戶的投資,使得客戶對微軟平臺用一次就上癮,幾乎沒有不可能完成的任務。仔細分析,其實這些互操作有一個共性,都是把暴露COM接口作為內部實現原理。這個做法導致了三個局限性,首先是牽涉到了前面提到的COM的復雜度。其次是潛在的性能損耗。最后是在具體開發的時候,都需要一些儀式性的工作來引入或者定義COM接口,使得開發過程不夠自然流暢。在CLR和COM互操作的調用棧里面,CLR的RCW, CCW, 安全處理, 列集拷貝等等,耗費的時間帶來的性能開銷簡直是可以到了肉眼可察覺的地步(聽硬盤的聲音和看任務管理器里面CPU的波動)。

  Win8

  在討論完這些技術背后的故事后,再看看為啥我就對Win8愛不釋手了。

  Win8引入了Windows Runtime, 簡稱WinRT. WinRT是一個操作系統模塊,運行在用戶態,介于Win32的上層和應用程序的下層,目的在于提供更高效友好的開發接口供Win8的程序員使用。WinRT在二進制模型上基本就是照搬了經典的COM。WinRT和CLR互不依賴,WinRT可以被CLR使用。WinRT通過C/C++實現,效率高是一個方面,更重要的時Win8引入了projection的概念,就是可以把WinRT的API用最直接最高效的方法,提供給上層的編程語言調用。這個語言可以是C#, C或者JavaScript。

  對于第一次接觸Projection的朋友,可以把Projection認為是一種新的Windows API模型。傳統的操作系統API,要么是暴露DLL的方法,要么是通過COM接口。無論是哪一種,在CLR中調用的時候都有不小的開銷。使用這些傳統API的效率,比調用一個C#自己的方法,效率差了多個數量級,根本的原因在于CLR的安全模型、內存模型和傳統的unmanaged模型不兼容,所以跨越邊界的調用需要額外的代碼來處理。而Projection提供的模型,是在提供新功能的同時,還針不同編程模型和語言,提供了最利于它們調用的方法。這樣就主動避免了不同模型之間為了互相兼容導致的開銷,也使得程序員寫代碼的時候非常自然流暢,調用的時候根本感覺不到和調用本地函數的區別。當然,能夠實現這一點, 也是得益于CLR, C#語言和VS開發工具這十年的長足發展. 舉個例子, C# 5.0中引入了await關鍵字, WinRT中引入了async operation. Projection技術把C#中的await語句轉換為WinRT async operation的調用,而且這個調用直接從managed code直接跳到unmanaged code,中間沒有任何冗余,也不需要CLR Engine的介入。進一步的信息, 可以參考Build大會關于WinRT的多個演講。后面的callstack也提供了直觀的例子.

  前面提到了COM的局限性在于一個輕量的二進制模型,被硬生生的擴展成一個無所不能的框架。WinRT取其精華,去其糟粕,借用了COM的輕便,舍棄了復雜性,在擴展性上依托于上層的編程語言和工具。WinRT通過projection的技術解決了傳統互操作性效率不高使用不方便的問題。以前的路線是希望所有的產品和技術最后都統一到CLR上來。現在修正為底層模型通過WinRT和C來實現,然后把這一層高效的組件無縫提供給上層的開發技術比如CLR來使用。這個轉變重新重視unmanaged層面的二進制模型。歸納為,unmanaged模型的優勢在于執行效率高,可以通吃所有場景,缺點在于開發和使用成本也高。CLR的優勢在于開發成本低,缺點在于無法通吃各種需求。現在微軟自己用unmanaged來做WinRT,然后把WinRT提供給上層語言,這兩者就可以取長補短了。

  有了WinRT,有了unmanaged的回歸,再加上微軟開發工具和C#語言的長足發展,前面介紹的各種技術在Win8里面就相得益彰了。Metro是如何修復WPF的缺陷就顯而易見了: Win8的metro程序繼續使用WPF中引入的rendering模型和XAML, 但是在control的基礎設計和實現上,從CLR轉移到了C,然后通過WinRT來暴露給使用者。至于使用的靈活性,比如要不要實現數據綁定,就看上層使用者自己的選擇了。

  附錄

  下面是我研究過程中看到的一些Win8的callstack. 基于這些callstack和我以往的經驗,才寫了上面的文章.

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

12
0
 
 
 

文章列表

arrow
arrow
    全站熱搜

    大師兄 發表在 痞客邦 留言(0) 人氣()