概述
從Windows 1.0 到Windows 10,對于普通使用者來說,顯而易見地,使用者界面已經發生了很多變化。
對于Windows的開發者來說,伴随着Windows系統的不斷發展,API技術演化也已經在不知不覺經曆了好幾代的變化。
演化
為了便于比較和讨論,本文将API技術分為5代。
第一代:C風格的API(Win32API)
Windows主要是用C語言開發的,第一代的API使用C風格也是意料之中。這一類API看上去簡單高效,但由于缺少封裝,參數往往過于複雜。比如說,建立視窗的函數CreateWindow,要填寫11個參數。在沒有代碼智能提示的時代,要記住每個函數的參數也是醉了。
第二代:C++Class Library (MFC)
面向對象風潮來襲,微軟用C++對Win32 API進行了一次改頭換面的“裝修”,建立了Windows平台開發的基礎類庫,也就是後來著名的MFC。從此,程式員們告别了建立視窗需要11個參數的日子,程式有 “對象” 了,世界也美好了。但是,由于C++缺少一個二進制級别的标準,卻給C++類庫風格的API帶來了意想不到的隐患(我将會在後續的小實驗中做展示)。
第三代:COM(DirectX,Office)
COM通過分離接口和實作彌補了C++沒有二進制級别标準的不足,確定了子產品的穩定性。它的首次應用是OLE2.0 。接口化程式設計和引用計數的思想對當時面向對象的C++程式員來說是個思維上的沖擊,這讓當時的開發者們有些不太适應(COM本質論的作者DON當時也認為使用COM是一個 “噩夢” ,直到他頓悟了COM的精髓,寫下COM本質論)。另外,由于微軟當時急于推出OLE2.0,導緻COM文檔的缺乏,這給COM的使用和推廣帶來了不利。
第四代:.NET(WindowsForm, WPF)
.NET的出現,開啟了Windows開發的托管語言時代。從API的角度來說,它的穩定性和易用性上比前三代API都要出色,但它有一個硬傷---性能。雖說随着硬體的發展,相比于.NET所帶來的開發效率, 它所帶來的性能損耗似乎 “情有可原” 。但在移動開發時代,使用.NET作為基礎API所帶來的性能損耗就不容小觑了。
第五代:WindowsRuntime API
在前四代API的基礎上,WindowsRuntime在穩定性,易用性和性能三者中找到了一個新的平衡點。它本身建立在COM之上,遺傳了COM的性能優勢;受.NET的啟發,WindowsMetadata技術大大簡化了 COM使用的複雜性,使得WindowsRuntime類的使用如普通的.NET類一樣簡單,在C#中使用WINRT的類,幾乎看不出差别。
實驗
下面我們做一個小實驗,體驗一下5代API的演化過程。
假設現在有這樣一個需求:查找給定字元串在文本中的位置,并傳回文本的總長度。
C風格API設計如下:
int FindInString(charconst * pcText,charconst * pcFind,int * piLength);
一個如此簡單的功能,就需要3個參數,如果功能再複雜一些,比如設定查找選項(是否忽略大小寫),那就隻能通過添加新的參數了。
為了便于使用,我們将它封裝為C++的類庫:
class __declspec(dllexport)StringFinder
{
char const * m_psz;
public:
StringFinder(const char * psz);
~StringFinder();
int Length(void)const;
int Find(constchar * psz)const;
};
看上去好多了,把這個API命名為SF1.0。
假設Length函數的實作是每次都調用strlen方法去計算字元串的長度。由于查找的文本是不變的,從性能優化的角度,我們隻需計算一次長度,并将結果緩存在一個私有成員變量中。由于是私有成員變量,對原來的公開API并并不會造成破壞。是以開發人員果斷而又快速地修改了代碼:
class __declspec(dllexport)StringFinder
{
int m_length;
char const * m_psz;
public:
StringFinder(const char * psz);
~StringFinder();
int Length(void)const;
int Find(constchar * psz)const;
};
在編譯和測試完成後,把這個版本命名為SF1.1,開發者将老的DLL替換為新的DLL以提高程式性能。但當使用者運作程式後,尴尬的事情發生了。。。
由于添加了新的成員變量,老的成員變量在記憶體中的位置已經産生了偏移,由于原先的程式依然使用老的偏移量來尋址,導緻了錯誤的發生。最後,開發人員隻能将新的DLL命名為SF1.1.DLL來和原先的SF.DLL進行區分(這也是MFC解決版本問題的方式)。這樣,每個版本的API都通過版本名稱被相應地加載進去,雖然看上去有點糟糕,但至少勉強解決了問題。
反思根本原因,是因為在C++類庫版本的API中暴露了實作類StringFinder。那麼有沒有辦法不暴露實作類而又能對功能進行封裝呢?為了解決這個問題,我将StringFinder類拆封為接口和實作兩個部分。
// public interface
struct IStringFinder {
virtual int Length(void)const = 0;
virtual int Find(constchar * psz)const = 0;
};
__declspec(dllexport)void CreateStringFinder(
IStringFinder ** finder,
const char * psz);
// implement class
class StringFinder :publicIStringFinder
{
int m_length;
char const * m_psz;
public:
StringFinder(const char * psz);
~StringFinder();
int Length(void)const;
int Find(constchar * psz)const;
};
由于實作類StringFinder不對外暴露,是以類内部的實作對于調用者來說是透明的,這就是COM接口的基本思想。
C++沒有二進制标準的遺留的問題解決了。但是從API的使用者角度來說,COM這種接口式程式設計和引用計數的方式比較繁瑣,影響開發效率。有沒有一種讓API的穩定性和生産力兼備的API技術呢?
讓我們來看一下.NET的實作方式:
public classStringFinder
{
private readonlystring text;
StringFinder(string t)
{
text = t;
}
public int Length {get { … } }
public int Find(string find)
{
…;
}
}
這種實作方式中,API的安全性和開發效率上都有所提高,但在程式的性能卻要遜色于前3代的API技術。
最後,讓我們使用WindowsRuntime (CX)來進行實作:
namespace WRCStringFinder
{
public refclassStringFindersealed
{
Platform::String^ m_text;
public:
StringFinder(Platform::String^ text);
int Length();
int Find(Platform::String^ find);
};
}
現在,我們甚至可以在.NET, C++和JavaScript三個語言平台上調用這套API,并且具備C++的性能和.NET的效率。
小結
上面簡單讨論了WindowsAPI技術演化過程,個人了解,歡迎拍磚。每種技術都有其各自出現的背景和要解決的問題,不能說那種絕對好,哪種絕對不好。
在接下來的文章中,我會具體介紹Windows Runtime的基本原理和使用實踐。