close
http://www.ithome.com.tw/itadm/article.php?c=56857

利用可攜性介面隱藏平臺差異
文/iThome (記者) 2009-09-17



程式碼的可攜性 第4回條件編譯和可攜性介面各有優、缺點,設計者必須權衡應用的情境,拿捏利害關係,選擇適合的方案

針對程序式的程式語言,想要撰寫具可攜性的程式碼,可利用條件編譯的指令,逐一隔開那些具有平臺相依性的程式碼。但利用這個方法有個缺點,也就是當想要支援的平臺數一多,或者平臺相異的部分變多的時候,整個程式就會變得一團混亂。

因此,我們必須倚靠來設計解決這個問題。簡單來說,就是憑藉著一道或更多道的可攜性介面,來隔出與平臺相依及無關的部分。最簡單具體的實作方式,是定義出一組函式,抽離各個平臺的具體實作細節,也隱藏住它們的差異。

對使用這組函式的高階(客戶端)程式碼而言,便對這些差異不知情,自然也就碰觸不到,因而也就獨立於平臺之外,成了具可攜性的程式碼。

魚與熊掌不可兼得,權衡應用情境是設計者的責任
在每個平臺上,我們都針對這個介面中的函式進行具體的實作,只要他們都符合這些函式的規格(定義相同的輸入參數、得到相同的執行效用),那麼基於這個介面所撰寫而成的程式碼,自然可攜。

採用條件編譯的方式來分隔各個平臺相依的程式碼,雖然有缺點,但倘若平臺差異僅有少數甚至僅有數行程式碼的時候,是個精簡的好方法。

倘若各個平臺在完成某個功能的函式上僅有細微的差異,卻因此將此函式置於可攜性介面中,便會造成每個平臺上都必須實作這個函式。然而由於它們之間的差異其實極小,這使得各個平臺的函式實作中程式碼重複的情況嚴重,這反而衍生出「重複程式碼」的問題。

條件編譯有其優點、缺點,而在可攜性介面下為各個平臺提供各種實作,同樣也有其優點、缺點。魚與熊掌不可兼得,必須權衡應用的情境,拿捏其中的利害關係,條件編譯手法當用則用,不當用則不用,這就是設計者的責任。

資料和變數也會有可攜性的問題
有些時候,不單是函式本身會有可攜性問題,就連程式中會需要用到的資料、變數,都會有可攜性的問題。可攜性的問題,同樣要利用介面的觀念解決。只不過,介面不再是一組函式,而是若干個資料結構。

就以SDL為例好了,SDL針對視窗定義了一個名為SDL_WindowData的結構,它代表著每個平臺上都必須自行定義的資料結構,就和可攜性介面中的函式一樣,SDL在每個平臺上都提供一份SDL_WindowData的定義,恰好滿足該平臺上的需求。以Windows為例,它的SDL_WindowData長的像這樣:

typedef struct
{
SDL_WindowID windowID;
HWND hwnd;
HDC hdc;
WNDPROC wndproc;
SDL_bool created;
int mouse_pressed;
struct SDL_VideoData *videodata;
} SDL_WindowData;

而在Mac OS X上(使用Cocoa framework),則像是:

struct SDL_WindowData
{
SDL_WindowID windowID;
NSWindow *window;
SDL_bool created;
Cocoa_WindowListener *listener;
struct SDL_VideoData *videodata;
};

你可以看得出來,各個平臺上因為特性不同,所以必須記錄不同的資訊,像Windows上就必須記錄視窗的handle(HWND型別)、視窗的事件處理常式(WNDPROC型別)等,而在Mac OS X上,則又有所不同。

使用資料結構來做為這些因平臺而異的資料及變數有若干好處,最主要的當然是可以將這些資料及變數,集結、統整在一塊,有利於管理。當然,它做為一個可攜性介面的企圖也就更明顯,而使用上也就更便利了。

你可以使用幾個函式做為可攜性介面的一部分,並利用一個.h標頭檔來加以定義。這樣的做法雖然簡單,但是有個你或許會在意的缺點,也就是這些函式之間的關係其實是滿鬆散的。在前一段中提到了利用一個結構來集結資料及變數的手法,同樣可以運用在函式之上。

同樣以SDL為例,在SDL中為不同平臺的視訊設備各自定義了一組操作,這些操作像是VideoInit ()(初始化視訊設備)、CreateWindow()(建立視窗)等,在各平臺上各有其實作。例如在Windows上就是WIN_VideoInit()與WIN_CreateWindow(),而在Mac OS X上則是Cocoa_VideoInit ()及Cocoa_CreateWindow()。而SDL定義了一個叫做SDL_VideoDevice的結構,它的定義方式像是:

struct SDL_VideoDevice
{
const char *name;
int (*VideoInit) (_THIS);

int (*CreateWindow) (_THIS, SDL_Window * window);
int (*CreateWindow) (_THIS, SDL_Window * window);
int (*CreateWindowFrom) (_THIS, SDL_Window * window, const void *data);
void (*SetWindowTitle) (_THIS, SDL_Window * window);
void (*SetWindowIcon) (_THIS, SDL_Window * window, SDL_Surface * icon);
void (*SetWindowPosition) (_THIS, SDL_Window * window);
void (*SetWindowSize) (_THIS, SDL_Window * window);

};

你可以看到,它將因平臺而異的動作,全部定義成這個結構中的函式指標。

也就是說,它本身就是一個可攜性的介面,對於高階、想要獨立於平臺之外的程式碼而言,只需要從相對應平臺的這個結構取得資料,接著使用其中的函式指標,便可以呼叫該平臺相對應的函式。

透過這個方式,可攜性介面的存在更為明顯,因為它已經化成一個具體的結構,在程式中被運用著,不再只是一組較為分散且獨立存在的函式。在SDL中,運用SDL_VideoDevice的方式,就像是這樣:

  static SDL_VideoDevice *_this;
  …
  if (_this->SetWindowTitle) {
   _this->SetWindowTitle(_this, window);
  }

它定義一個叫_this的SDL_VideoDevice指標,接著在程式中想要執行設定視窗標題的動作時,便檢查_this->SetWindowTitle函式指標是否為NULL,倘若不是Null便呼叫。使用起來,並不會太過費力,而且,懂得物件導向程式設計的程式人,毋需看到_this這個變數名稱,基本上已經可以看出來這是利用C語法模擬物件導向的機制。

物件導向的觀念對於撰寫可攜性程式碼有幫助
這樣子的做法,不單只是將函式封裝在結構中,同時,還具備了多型的作用。

行文至此,你不免發現,即使是使用程序性的程式語言,當我們想要撰寫具可攜性的程式碼時,在設計上,很容易就傾向於使用物件導向的一些觀念。這或許意謂著,這些觀念對於撰寫具可攜性程式碼是大有幫助的。

下一回我將會探討,運用正宗的物件導向程式語言,又該如何撰寫具可攜性的程式碼。


作者簡介─王建興
清華大學資訊工程系的博士研究生,研究興趣包括電腦網路、點對點網路、分散式網路管理、以及行動式代理人,專長則是Internet應用系統的開發。曾參與過的開發專案性質十分廣泛而且不同,從ERP、PC Game到P2P網路電話都在他的涉獵範圍之內。

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 Chery0805gamania 的頭像
    Chery0805gamania

    Chery0805gamania

    Chery0805gamania 發表在 痞客邦 留言(0) 人氣()