天天看點

Windows API技術的演化

概述

從Windows 1.0 到Windows 10,對于普通使用者來說,顯而易見地,使用者界面已經發生了很多變化。

Windows API技術的演化

對于Windows的開發者來說,伴随着Windows系統的不斷發展,API技術演化也已經在不知不覺經曆了好幾代的變化。

演化

為了便于比較和讨論,本文将API技術分為5代。

第一代:C風格的API(Win32API)

Windows主要是用C語言開發的,第一代的API使用C風格也是意料之中。這一類API看上去簡單高效,但由于缺少封裝,參數往往過于複雜。比如說,建立視窗的函數CreateWindow,要填寫11個參數。在沒有代碼智能提示的時代,要記住每個函數的參數也是醉了。

Windows API技術的演化

第二代: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以提高程式性能。但當使用者運作程式後,尴尬的事情發生了。。。

Windows API技術的演化

由于添加了新的成員變量,老的成員變量在記憶體中的位置已經産生了偏移,由于原先的程式依然使用老的偏移量來尋址,導緻了錯誤的發生。最後,開發人員隻能将新的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的基本原理和使用實踐。

繼續閱讀