附錄四 Windows XP 視覺風格

民國九十年八月微軟發行的 Windows XP 除了改善 Windows 9x/Me 系列容易當機的缺點之外,它的畫面也讓當時許多人為之驚艷,這有一大部分歸功於『視覺風格』(Visual Style)。諸如視窗標題欄的兩個角落變成圓弧形、當使用者把滑鼠游標移到按鈕上時按鈕會略微改變…等等效果,都是 XP 的視覺風格所致,這些視覺風格不須特別安裝軟體,只要 XP 系統安裝完成後,就能享受視覺風格帶來的好處。Windows XP 的接班人,Vista 以及 Windows 7,也沿用視覺風格。在 Windows XP 堙A如果您不習慣,也可以在把滑鼠游標移到桌面上任何一處,按下滑鼠右鍵,在彈出選單中選按『內容』會出現下圖的對話盒,在『主題』頁面的複合框可以選擇『Windows 傳統配色』恢復成與原來 Windows 9x/Me 相同的桌面,這時 XP 就不啟動視覺風格,桌面看起來就醜醜的了。( 如果您使用 Windows 7 作業系統,也可以在桌面上任何一處,按滑鼠右鍵,選擇『個人化』,然後選擇『Windows 傳統配色』,就會變成與 Windows 9x/Me 一樣的佈景了。)


Windows XP 及其後的作業系統是透過 uxtheme.dll 與第六版的 comctl32.dll 兩個動態連結程式庫達到全新的視覺風格,前者是處理有關佈景主題的程式庫;而後者則是處理控制項的程式庫。不過在這一章堙A小木偶僅僅討論控制項的新視覺風格,因此不對 uxtheme.dll 多做描述。我們先來看看具有 Windows XP 視覺風格的按鈕有什麼差異?底下的四個視窗都是第一章的 message 在 Windows XP 及 Windows 7 中執行時的情形,左邊的兩個視窗,其按鈕具有 Windows XP 視覺風格,右邊兩個視窗的按鈕不具 Windows XP 視覺風格;上面的兩個視窗是在 Windows XP 中執行的結果,下面的兩個視窗是在 Windows 7 堸鶡瑼熊痕G:
   
   

在 Windows 9x/Me 作業系統堙A控制項分成使用者控制項與通用控制項,前者由 user.dll 負責處理,後者則是透過 5.x 版的 comctl32.dll 處理。在 Windows XP 及其以後的作業系統,在預設的情形下 ( 也就是一安裝好 ),系統自動開啟新的視覺風格。非工作區部分都自動以新的視覺風格顯示;而工作區部分則可自由的選擇控制項是否具有 XP 視覺風格,這是微軟為了讓舊程式也能在 XP 上執行。為了達到這個目的,XP 的非工作區改由 uxtheme.dll 處理;工作區部分則是較為複雜。Windows XP 提供了兩套的 comctl32.dll,其版本分別是第五版與第六版,前者在 『\WINDOWS\system32\』子目錄,專門負責處理不具視覺風格的通用控制項;後者在『\WINDOWS\WinSxS\x86_Microsoft.Windows.Common-Controls_XXXXXXXX\』子目錄,負責處理具有新的視覺風格的使用者控制項與通用控制項;而不具視覺風格的使用者控制項仍由user32.dll 處理。由上面說明可知,在預設的情形下,非工作區自動就會具有新視覺風格;而在工作區的控制項享受不到這樣的好處,除非我們在程式中特別指示。

附帶一提,像 Windows XP 這樣讓兩個檔名相同但版本、內容不同的 dll 或其他類型的檔案共同存於同一個系統堛漣瑋N稱為『Side-by-side Assembly Sharing』。這堛 Assembly 不是組合語言的意思,而是組件、元件的意思。

言歸正傳,剛才說 Windows XP 包含了第五版和第六版的 comctl32.dll。在預設的情形下,使用者控制項是經由 user32.dll 處理,通用控制項才透過第五版的 comctl32.dll 處理,這時候的控制項是不具視覺風格,與以前的 Windows 9x/Me 一樣,以達回溯相容的目的。如果程式要使用新的視覺風格,必須呼叫第六版的 comctl32.dll,這時候不管是使用者控制項,還是通用控制項都由第六版的 comctl32.dll 負責處理。要應用程式呼叫第六版的 comctl32.dll 必須先建立一個稱為『應用程式資訊清單 ( application manifest )』的檔案,這個檔案包含了一些版本資料,所以能指定要載入哪一種版本的 comctl32.dll。有兩種方式使用這個檔案:

  1. 第一種方式是把這個檔案放在與執行檔相同子目錄並使主檔名與執行檔相同,副檔名為『.manifest』,即可獲得新的視覺享受而不必重新組譯。
  2. 第二種方式是把 application manifest 檔加入資源描述檔中,再重新組譯連結成新的可執行檔。

不管是否要重新組譯連結,都得先準備好副檔名為『.manifest』的『應用程式資訊清單』檔案,底下小木偶就來介紹 manifest 檔。


應用程式資訊清單檔案 ( Manifest 檔 )

我們知道,應用程式一般都會呼叫動態連結程式庫 ( Dynamic-Link Library,就是 DLL 檔啦 ),這些動態連結程式庫都是共享的。所謂共享,就是說如果安裝某應用程式時,發現該程式所需的 DLL 檔已經安裝在系統上了,該程式就不需再安裝這個 DLL 檔了,直接使用就可以了。如果該應用程式所需要的 DLL 檔比系統上現有的 DLL 檔版本更高的話,於是該應用程式的安裝程式就會以較高版本的 DLL 檔代替舊版本。事實上,微軟在一些 Service Pack 也常更換 DLL 檔;另一個例子是如果您使用 XP 且曾安裝過 OllyDebug 1.10 版,它也會提醒『系統上的 PSAPI.DLL 比 OllyDebug 自行攜帶的還新』,於是問您要使用系統上的還是 OllyDebug 1.10 自行攜帶的?

所以,系統中的 DLL 時常在不知不覺中就被替換,假如新版的 DLL 與舊版不相容,而且系統中只能有一個版本的話,那麼就有可能會發生原本執行得很好的程式,在安裝某些軟體後無法執行或是當掉。這就是有名的 DLL Hell。為了某種程度上解決這個問題,微軟提供了兩種方法:Isolated Applications 和 Side-by-Side Assembly Sharing。

Isolated Application 就是應用程式在發行時,就已攜帶了該程式所有 DLL 的隔離版本 ( Isolated Version ),這樣就不會受其他程式影響了。但是必須重寫原始碼,代價太高。 Side-by-Side Assembly Sharing 可以讓新舊版本的 DLL 共存於同一系統中。Windows XP 中的許多共享 DLL 就是以 Side-by-Side 的方式來編寫的。程式設計師可以撰寫『應用程式資訊清單』來達到 Side-by-Side Assembly Sharing 的好處。應用程式資訊清單內描述了程式所呼叫的 DLL 版本資訊,所以作業系統可以根據這些資料正確的安裝共享資源。例如應用程式會呼叫第五版的 COMCTL32.DLL,並在應用程式資訊清單堳明了第五版的 COMCTL32.DLL ,即使系統上有別的版本,作業系統也仍將為該程式載入第五版的 COMCTL32.DLL。

『應用程式資訊清單』的語法是以 XML 格式書寫,而 XML 檔案都是純文字檔,亦即您可以使用『記事本』或『UltraEdit 32』這種文書處理軟體建立。XML 的第一行一定是宣告這是一份 XML 檔案,內容如下:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

就如同 HTML 檔案一開始會有

<html>

一樣,不過 HTML 檔在結束時,還會有一個 </html> 表示檔案到此結束,而 XML 的宣告則不必有 </xml?>。您可以參考『勞虎』和『胭脂虎』共同撰寫的『無廢話 XML』堶惇O介紹 XML 的觀念,是一篇 XML 入門的好文章。此處小木偶僅就現在用到的部份說明。<?xml version="1.0" encoding="UTF-8" standalone="yes"?> 中有三部份,第一個是『version="1.0"』,很明顯是這宣告 XML 版本;第二個部份是『encoding="UTF-8"』,顯然是指定編碼方式;第三個部份是『standalone="yes"』是指此份 XML 的 DTD 檔案是使用解析器預設的,詳細情形請參閱『無廢話 XML』。不過對我們現在要使應用程式具有視覺風格而言,Manifest 檔的第一行都是像上面這樣,您可視為固定不變的。底下我們先看看整個 Manifest 檔的長相:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
    <assemblyIdentity
        version="1.0.0.0"
        processorArchitecture="X86"
        name="pinocchio.assembly.win32.message1"
        type="win32"
    />
    <description>Test application for xp style.</description>
    <dependency>
        <dependentAssembly>
            <assemblyIdentity
                type="win32"
                name="Microsoft.Windows.Common-Controls"
                version="6.0.0.0"
                processorArchitecture="X86"
                publicKeyToken="6595b64144ccf1df"
                language="*"
            />
        </dependentAssembly>
    </dependency>
</assembly>

大致上,XML 的標籤格式如下圖:
    
XML 和 HTML 的標籤很類似,都以一對“<”、“>”包住,每個標籤稱為『元素』,包含『元素名』和『屬性名』,此外有些元素像一個容器,以另一個“</……>作為結束,在這之間的空間,可以包含一些文字,也可以沒有任何內容,也可以是某些『子元素』。像上面

<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
……
</assembly>

之間,容納了許多『子元素』,最後才以一個“</assembly>”為結束。一般而言,在 XML 堶悸漱葛嚏A都應該要像這樣以一對“<元素名>”、“</元素名>”才算完整;但也有不需結尾標籤的元素,像這樣的元素稱為『空元素』( empty element ),例如 assemblyIdentity 就是空元素,它們在最後一個屬性以『/>』當成結尾,它們長得像下面:
    

XML 檔案堛熔臚@個元素,稱為『根元素』( root element ),在『應用程式資訊清單』的根元素是 assembly,此處的 assembly 並非組合語言的意思,而是組件、元件的意思,或許我們可以想像成應用程式是由圖示、選單、副程式等許多的組件所組成,assembly 有兩個屬性名稱,xmlns 和 manifestVersion,分別代表命名空間 ( name space ) 和版本,這兩個屬性也都無法省略,也都按照上面的屬性值,不須更改。

assembly 可以包含 noInherit、assemblyIdentity、dependency、file 許多子元素,其中 noInherit 和 file 可以省略,請參考下表:

元素可以有的屬性意義
<noInherit> noInherit 必須是 assembly 元素的第一個『子元素』,其後必定接 assemblyIdentity 子元素。noInherit 可以省略,如果省略的話,assemblyIdentity 就變成第一個 assembly 的『子元素』了。
<assemblyIdentity>type表明應用程式或組件在哪一系統中執行,必須是小寫的『win32』。不能省略。
name應用程式或組件名稱,必須是唯一的且不能省略。必須是以『組織名稱.部門名稱.程式名稱』的方式命名。
language應用程式或組件所使用的語系,可省略,其指定方式是依照 HTML 方式指定語系,如果是臺灣正體中文是 zh-tw、大陸簡體中文是 zh-cn、美國英文是 en-us。如果是多語系的應用程式,應該省略指定這個屬性;發展成多語系的組件,應該指定成『*』。
processorArchitecture指定 CPU,如果是 Win32 應用程式,應使用『x86』;如果是 Win64 應用程式,使用『IA64』;如果是其他平台,可以省略。
version應用程式或組件的版本,不可省略,應使用『主要版本.次要版本.建置版本.修正版本 ( major.minor.build.revision ) 格式四部份組成,每一部份的數值可以從 0∼65535。
publicKeyToken十六位數值,以十六進位表示。Side-by-side Assembly 的共用元件才需要指定這個屬性,在本章堻ㄛO用『6595b64144ccf1df』。
<dependency> <dependency> 沒有屬性,可省略;但如果不省略時,至少要包含一個 <dependentAssembly> 子元素。在 <dependency> 內包含的子元素所描述的屬性,就代表程式相關的組件。
<dependentAssembly> <dependentAssembly> 必須在 <dependency> 堙A亦即 <dependentAssembly> 是 <dependency> 的子元素。<dependentAssembly> 的第一個子元素必須是 <assemblyIdentity>。<dependentAssembly> 沒有屬性。
<file>name 檔案名稱,例如『comctl32.dll』。
hashalg
hash

演示

在說了一些背景知識之後,最重要的便是實作了。小木偶打算把第一章的『第一個 Win32 組合語言程式』為例子,說明如何實現 XP 視覺風格。依據前面所說的,有兩種方法:

第一種:不需重新組譯

雖然說是不需重新組譯,但是有個條件,那就是如果在原來的程式中已經呼叫過 InitCommonControls,才可以不必重新組譯。如果沒有呼叫過 InitCommonControls,就得加上這一行指令,然後重新組譯。如下:

message1.asm 原始碼:

        OPTION  CASEMAP:NONE
        .386
        .MODEL  FLAT,STDCALL
INCLUDE         WINDOWS.INC
INCLUDE         KERNEL32.INC
INCLUDE         USER32.INC
INCLUDE         COMCTL32.INC
INCLUDELIB      KERNEL32.LIB 
INCLUDELIB      USER32.LIB
INCLUDELIB      COMCTL32.LIB

.DATA
szTitle BYTE    '第一個程式',0
szText  BYTE    '這是在 Win32 作業系統,用組合語言寫的程式。',0

.CODE
Start:  INVOKE  InitCommonControls
        INVOKE  MessageBox,NULL,OFFSET szText,OFFSET szTitle,MB_OK
        INVOKE  ExitProcess,NULL
END     Start

InitCommonControls 是初始化通用控制項的 API,在早期記憶體不大的情形下,通用控制項是不預先載入記憶體堛滿A等到需要時才以 InitCommonControls 載入;而 InitCommonControls 是包含在 comctl32.dll 檔案堙A因此必須再加上

INCLUDE         COMCTL32.INC
INCLUDELIB      COMCTL32.LIB

兩行。接下來建立 message1 的『應用程式資訊清單檔案』,也就是 message1.exe.manifest 檔內容如下:

<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>
<dependency>
   <dependentAssembly>
      <assemblyIdentity type='win32' 
                        name='Microsoft.Windows.Common-Controls'
                        version='6.0.0.0'
                        processorArchitecture='*'
                        publicKeyToken='6595b64144ccf1df'
                        language='*'
      />
   </dependentAssembly>
</dependency>
</assembly>

『應用程式資訊清單檔案』的命名是有一定格式的,『應用程式資訊清單檔案』的主檔名就是執行檔的完整名稱,也就是執行檔的主檔名加上副檔名;而『應用程式資訊清單檔案』副檔名就是『.manifest』。接著照一般方法組譯:

E:\HomePage\SOURCE\Win32>ml message1.asm [Enter]
Microsoft (R) Macro Assembler Version 6.14.8444
Copyright (C) Microsoft Corp 1981-1997.  All rights reserved.

 Assembling: message1.asm
Microsoft (R) Incremental Linker Version 5.12.8078
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.

/SUBSYSTEM:WINDOWS
"message1.obj"
"/OUT:message1.exe"

E:\HomePage\SOURCE\Win32>

最後把執行檔,message1.exe,和應用程式資訊清單檔案,message1.exe.manifest,放在同一目錄堙A就大功告成了。當然這種方法必須使執行檔與應用程式資訊清單檔在一起,才能顯示新的視覺風格,這是一大缺點。

第二種方法:建立資源檔

這種方法是把應用程式資訊清單 ( 此應用程式資訊清單和上面第一種方法的一樣 ) 加入到資源檔堙A再藉由連結器加入執行檔堙A所以最後得到的可執行檔即使不和應用程式資訊清單檔在一起,也可以表現出 XP 視覺風格。底下是 message1.rc 檔內容為:

#define RT_MANIFEST     24
1   RT_MANIFEST MOVEABLE PURE "message1.exe.manifest"

把這三個檔案存在同一子目錄,在命令提示字元輸入下面指令:

E:\HomePage\SOURCE\Win32\MessageBox>rc message1.rc [Enter]

E:\HomePage\SOURCE\Win32\MessageBox>ml message1.asm /link message1.res [Enter]
Microsoft (R) Macro Assembler Version 6.14.8444
Copyright (C) Microsoft Corp 1981-1997.  All rights reserved.

 Assembling: message1.asm
Microsoft (R) Incremental Linker Version 5.12.8078
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.

/SUBSYSTEM:WINDOWS
"message1.obj"
"/OUT:message1.exe"
"message1.res"

E:\HomePage\SOURCE\Win32\MessageBox>