win32

win32

Win32是指Microsoft Windows作業系統的32位環境,與Win64 都為Windows常見環境。如今的Win32作業系統可以一邊聽音樂,一邊編程,一邊列印文檔。Win32作業系統是一個典型的多執行緒作業系統

基本介紹

  • 中文名:Win32
  • 系統:Windows作業系統
  • 位數:32位
  • 套用:計算機
意義,進程執行緒,概念,二者關係,作業系統分類,引入執行緒的好處,進程作用,進程間通信(IPC),獲取進程信息,環境簡介,Dos彙編的特點,記憶體管理方式上的不同,記憶體的不同,程式結構方面的不同,編譯器,環境設定,基本知識,概念,通訊方式,創建基礎,結構語法,386,model flat stdcall,include 語句,data 或data,code,Windows 的資源檔案,在程式中使用資源,對話框,源程式,解析,創建視窗,過程,編程要點,常見套用,主要錯誤,

意義

單執行緒多進程是作業系統發展的一種必然趨勢,當年的DOS系統屬於單任務作業系統,最優秀的程式設計師也只能通過駐留記憶體的方式實現所謂的"多任務",而如今的Win32作業系統卻可以一邊聽音樂,一邊編程,一邊列印文檔。
理解多執行緒及其同步、互斥等通信方式是理解現代作業系統的關鍵一環,當我們精通了Win32多執行緒程式設計後,理解和學習其它作業系統的多任務控制也非常容易。許多程式設計師從來沒有學習過嵌入式系統領域著名的作業系統VxWorks,但是立馬就能在上面做開發,大概要歸功於平時在Win32多執行緒上下的功夫。
因此,學習Win32多執行緒不僅對理解Win32本身有重要意義,而且對學習和領會其它作業系統也有觸類旁通的作用。

進程執行緒

先闡述一下進程和執行緒的概念和區別,這是一個許多大學老師也講不清楚的問題。

概念

進程(Process)是具有一定獨立功能的程式關於某個數據集合上的一次運行活動,是系統進行資源分配和調度的一個獨立單位。程式只是一組指令的有序集合,它本身沒有任何運行的含義,只是一個靜態實體。而執行緒則不同,它是程式在某個數據集上的執行,是一個動態實體。它因創建而產生,因調度而運行,因等待資源或事件而被處於等待狀態,因完成任務而被撤消,反映了一個程式在一定的數據集上運行的全部動態過程。
執行緒(Thread)是進程的一個實體,是CPU調度和分派的基本單位。執行緒不能夠獨立執行,必須依存在應用程式中,由應用程式提供多個執行緒執行控制。

二者關係

執行緒和進程的關係是:執行緒是屬於進程的,執行緒運行在進程空間內,同一進程所產生的執行緒共享同一記憶體空間,當進程退出時該進程所產生的執行緒都會被強制退出並清除。執行緒可與屬於同一進程的其它執行緒共享進程所擁有的全部資源,但是其本身基本上不擁有系統資源,只擁有一點在運行中必不可少的信息(如程式計數器、一組暫存器和棧)。

作業系統分類

根據進程與執行緒的設定,作業系統大致分為如下類型:
(1)單進程、單執行緒MS-DOS就是這種作業系統;
(2)多進程、多執行緒,Win32(Windows NT/2000/XP等)、Solaris 2.x和OS/2都是這種作業系統;
(3)單進程、多執行緒,VxWorks是這種作業系統。

引入執行緒的好處

在作業系統中引入執行緒帶來的主要好處有:
(1)通過進程來創建、終止執行緒比單獨地通過應用程式來創建、終止執行緒要快;
(2)同一進程內的執行緒間切換比進程間的切換要快,尤其是用戶級執行緒間的切換。另外,執行緒的出現還有以下幾個原因:
並發程式的並發執行,在多處理環境下更為有效。一個並發程式可以建立一個進程,而這個並發程式中的若干並發程式段就可以分別建立若干執行緒,使這些執行緒在不同的處理機上執行。
②每個進程具有獨立的地址空間,而該進程內的所有執行緒共享該地址空間。這樣可以解決父子進程模型中,子進程必須複製父進程地址空間的問題。
執行緒對解決客戶/伺服器模型非常有效。

進程作用

進程間通信(IPC)

Win32進程間通信的方式主要有:
(1)剪貼簿(Clip Board);
(2)動態數據交換(Dynamic Data Exchange);
(3)部件對象模型(Component Object Model);
(4)檔案映射(File Mapping);
(5)郵件槽(Mail Slots);
(6)管道(Pipes);
(7)Win32套接字(Socket);
(8)遠程過程調用(Remote Procedure Call);
(9)WM_COPYDATA訊息(WM_COPYDATA Message)。

獲取進程信息

在WIN32中,可使用在PSAPI .DLL中提供的Process status Helper函式幫助我們獲取進程信息。
(1)EnumProcesses()函式可以獲取進程的ID,其原型為:
BOOL EnumProcesses(DWORD * lpidProcess, DWORD cb, DWORD*cbNeeded);
參數lpidProcess:一個足夠大的DWORD類型的數組,用於存放進程的ID值;參數cb:存放進程ID值的數組的最大長度,是一個DWORD類型的數據;
win32
參數cbNeeded:指向一個DWORD類型數據的指針,用於返回進程的數目;
函式返回值:如果調用成功,返回TRUE,同時將所有進程的ID值存放在lpidProcess參數所指向的數組中,進程個數存放在cbNeeded參數所指向的變數中;如果調用失敗,返回FALSE。
(2)GetModuleFileNameExA()函式可以實現通過進程句柄獲取進程檔案名稱,其原型為:
DWORD GetModuleFileNameExA(HANDLE hProcess, HMODULE hModule,LPTSTR lpstrFileName, DWORD nsize);
參數hProcess:接受進程句柄的參數,是HANDLE類型的變數;
參數hModule:指針型參數,在本文的程式中取值為NULL;
參數lpstrFileName:LPTSTR類型的指針,用於接受主調函式傳遞來的用於存放進程名的字元數組指針
參數nsize:lpstrFileName所指數組的長度;
函式返回值:如果調用成功,返回一個大於0的DWORD類型的數據,同時將hProcess所對應的進程名存放在lpstrFileName參數所指向的數組中;如果調用失敗,則返回0。
通過下列代碼就可以遍歷系統中的進程,獲得進程列表:
//獲取當前進程總數
EnumProcesses(process_ids, sizeof(process_ids), &num_processes);
//遍歷進程
for (int i = 0; i < num_processes; i++)
{
//根據進程ID獲取句柄
process[i] = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, 0,
process_ids[i]);
//通過句柄獲取進程檔案名稱
if (GetModuleFileNameExA(process[i], NULL, File_name, sizeof(fileName)))
cout << fileName << endl;
}

環境簡介

Dos彙編的特點

在Dos下編彙編程式,我們可以管理系統的所有資源,我們可以改動系統中所有的記憶體,如自己改動記憶體控制塊來分配記憶體,自己修改中斷向量表來截獲中斷等,對其他操作也是如此,如我們對鍵盤連線埠直接操作就可以把鍵盤禁止掉,可以這樣來描述Dos系統:系統只有一個特權級別,在編程上講,任何程式和作業系統都是同級的,所以在Dos下,一個編得不好的程式會影響其他所有的程式,如一個程式把鍵盤口中斷關掉了,所有程式就都不能從鍵盤獲得鍵入的數據,直到任何一個程式重新打開鍵盤為止,一個程式陷入死循環,也沒有其他程式可以把它終止掉。Dos下的編程思路是“單任務”的,你只要認為你的程式會按照你的流程一步步的執行下去,不必考慮先後問題(當然程式可能會被中斷打斷,但你可以認為它們會把環境恢復,如果中斷程式沒有把環境恢復,那是他們的錯)。

記憶體管理方式上的不同

記憶體管理方式上,Dos彙編和Win32彙編也有很多的不同:Dos工作在實模式下,我們可以定址1M的記憶體,定址時通過段暫存器來制定段的初始地址,每個段的大小為64K,超過1M的部分,就只能把他作為XMS使用,也就是說,只能用作數據存放使用而無法在其中執行程式。
而Windows在保護模式下執行,這裡所有的資源對應用程式來說都是被“保護”的:程式在執行中有級別之分,只有作業系統工作在最高級--0級中,所有應用程式都工作在3級中(Ring3), 在Ring3中,你無法直接訪問IO連線埠,無法訪問其他程式運行的記憶體,連向程式自己的代碼段寫入數據都是非法的,會在Windows的螢幕上冒出一個熟悉的藍螢幕來。只有對Ring0的程式來說,系統才是全開放的。

記憶體的不同

在記憶體方面,Windows使用了處理器的分頁機制,使得對應用程式來說,所有的記憶體都是“平坦”的,你不必用一個段暫存器去指定段的地址,因為在保護模式下,段暫存器的含義是不同的(可以參見80386手冊方面的書籍),你可以直接指定一個32位的地址來定址4GB的記憶體。

程式結構方面的不同

在程式結構方面,Windows程式也有很大的不同,它是“基於訊息”的,你可以想像這樣一個常見的Windows視窗,上面有幾個按鈕,如果你用Dos編程的思路去考慮,你會發現實現它很困難:滑鼠移動到視窗邊緣時拖動會改變視窗大小,滑鼠點擊按鈕時再做要做的事,你會發現,你的程式自開始執行後就在等待,你不知道滑鼠先會點什麼地方,實際上你是在等待所有可能的事情的發生。而在Dos下,你可以只顧自己先執行,需要用戶輸入時,再停下來,你不輸入我就不再執行,而且,我讓你輸入數據A你就不能輸入數據B。
好了,言歸正傳,因為以上是Win32編程的基礎,無論對Win32彙編還是VC++,它們都是一樣的,下面我們來看看有關Win32彙編的內容。

編譯器

Win32ASM的編譯器最常用的有兩種:Borland公司的Tasm5.0和Microsoft的Masm6.11以上版本,兩種編譯器各有自己的優缺點,Tasm帶了一個不大不小的Import庫,而Masm沒有帶,但Masm在代碼的最佳化上面好象比Tasm做得好,但它卻不帶Import庫。看來使用哪一種編譯器還是比較難選擇的,但Steve Hutchesson給了我們一個答案,他為Masm建立了一個很全的Import庫,基本上包括了Windows絕大部分的Api函式,這些庫、include檔案和其他工具還有Masm6.14版本一起做成了一個 Masm32編譯器 -- Masm32V5。這樣一來,我們用彙編編程就象用C一樣方便。
因為有了Masm32V5,所以就我個人而言,我推薦使用Masm作為Win32ASM的編譯工具,但Masm和Tasm的宏語法有很多的不同,我的這個教程是以Masm格式寫的。

環境設定

在Win32編程中,由於Windows有很多的數據結構和定義,這些都放在include檔案中,還有連線時要用到Import庫(通俗的講就是Windows提供的DLL檔案中的函式列表,也就是告訴程式到哪裡去調用API函式),這些都放在include 和lib目錄中。我們在編譯時要指定以下的系統環境:
set include=\Masm32v5\Include
set lib=\Masmv5\lib
set path=\Masmv5\Bin
這樣編譯器就會到正確的路徑中去找 include 檔案和 lib 檔案。你可以自己在 autoexec.bat 檔案中加上以上語句,為了產生Windows的PE格式的執行檔案,在編譯和連線中要指定相應的參數:
編譯: Ml /c /coff 檔案名稱.asm
連線: Link /SUBSYSTEM:WINDOWS OBJ檔案名稱.obj 資源檔案名稱.res
為了不在每次編譯時都要打這么多的參數,我們可以用 nmake 檔案來代為執行,nmake 是代碼維護程式,他會檢查 .asm .obj .exe .res 等檔案的時間,如果你更新了源程式,他會自動執行編譯程式連線程式產生相應的檔案。你可以在檔案名稱為 makefile 的檔案中指定使用的編譯器和連線程式以及相應的參數,下面是一個 makefile 檔案的例子:
NAME = Clock
OBJS = $(NAME).obj
RES = $(NAME).res
$(NAME).exe: $(OBJS) $(RES)
Link /DEBUG /SUBSYSTEM:WINDOWS $(OBJS) $(RES)
$(RES): $(NAME).rc
Rc $(NAME).rc
.asm.obj:
Ml /c /coff $(NAME).asm
檔案告訴 nmake程式,程式名為 clock,產生 clock.exe 檔案需要 clock.obj和 clock.res 檔案,而產生 clock.res 檔案需要 clock.rc 檔案,產生 clock.obj 檔案要用到 clock.asm 檔案,至於是否需要執行 ml, link 和 rc,程式會根據檔案的時間自動判斷。

基本知識

概念

視窗是螢幕上的矩形區域。一個視窗可以從鍵盤或者滑鼠接受用戶的輸入,並在其內部顯示圖形輸出。一個應用程式視窗通常包含程式的標題條、選單、框線,滾動條。其中,對話框也是一種視窗。不同的是,對話框表面通常包含幾個其它視窗,稱之為“子視窗”。這些子視窗的形式有壓入按鈕、單選按鈕複選框、文本輸入區域、列表框和滾動條等。 用戶將這些視窗看成螢幕上的對象,可以通過按下一個按鈕或者滾動一個滾動條與這些對象直接互動。

通訊方式

視窗以“訊息”的形式接收視窗的輸入,視窗也用訊息與其它視窗通訊。比如在程式視窗的大小改變時,字處理器會重新格式化其中的文本。視窗大小改變的細節是由作業系統處理的,但程式能夠回響這個系統功能。當用戶改變視窗的大小時,Windows給程式傳送一條訊息指出新視窗的大小。然後,程式就可以調整視窗中的內容,以回響大小的變化。程式創建的每一個視窗都有相關的視窗過程。也就是給這個視窗指定一個子程式(視窗過程),Windows通過調用它來給視窗傳送訊息。視窗過程再根據此訊息進行處理,然後將控制返回給Windows。

創建基礎

視窗在“視窗類”的基礎上創建的。Windows定義了預設的視窗過程,如果你對所有的訊息都讓Windows自己處理,那么你就能得到一個標準的視窗,同樣,你也可以選擇處理自己感興趣的訊息,這樣,相當於產生了不同的子類,也就形成了不同的應用程式。同樣,子視窗也是基於同一個視窗類,並且使用同一個視窗過程。例如,所有Windows 程式中的所有按鈕都基於同一視窗類。這個視窗類有一個處理所有按鈕訊息的視窗過程,但是,如果你按自己的構想設計一個按鈕,如想把按鈕的表面換成點陣圖,你就可以自己處理按鈕視窗的 WM_PAINT 訊息,當 Windows 需要畫按鈕表面的時候,你就可以隨自己的意思去畫。
Windows程式開始執行後,Windows為該程式創建一個“訊息佇列”。這個訊息佇列用來存放該程式可能創建的各種不同視窗的訊息。程式中有一段代碼,叫做“訊息循環”, 它用來從佇列中取出訊息,並且將它們傳送給相應的視窗過程。在沒有訊息發生的時候,你的程式實際上就在訊息循環中轉圈子。

結構語法

讓我們先來看看一個最簡單的Win32彙編程式:
.386
.model flat, stdcall
option casemap :none ; case sensitive
include windows.inc
include kernel32.inc
include user32.inc
includelib kernel32.lib
includelib user32.lib
.data
szCaption db 'Win32彙編例子',0
szText db 'Win32彙編,Simple and powerful!',0
.code
start:
invoke MessageBox,NULL,addr szText,addr szCaption,MB_OK
invoke ExitProcess,NULL
end start
這就是一個能執行的最簡單的Win32彙編程式,下面我簡單地介紹一下各部分的作用:

386

這條語句和Dos下彙編是一樣的,是告訴編譯器我們要用到80386的指令集,因為32位彙編程式要用到32位的暫存器如eax,ebx等,所以這一句是必須的,當然,你也可以用.486,.586等,當用到特權指令時,還可以用 .386p,.486p等等。

model flat stdcall

.model告訴編譯器程式的模式,編過Dos彙編的人可能知道在Dos程式的模式有tiny,small,...huge 等,它指定了程式記憶體定址模式,在huge等模式下,記憶體定址和子程式調用將用Far的格式,但在Win32彙編中,你只能使用一個模式即 flat 模式,因為對Win32程式來說,記憶體是連續的一個4GB的段,無所謂小或大的模式。而stdcall 告訴編譯器參數的傳遞方式,在調用子程式時,參數是通過堆疊傳遞的,參數的傳遞方式有三種,stdcall,c 和 pascal,stdcall 指定了參數是從右到左壓入堆疊的,比如說對一個Windows API 如 MessageBox,在手冊中是如此定義的:
int MessageBox(
HWND hWnd, // handle of owner window
LPCTSTR lpText, // address of text in message box
LPCTSTR lpCaption, // address of title of message box
UINT uType // style of message box
);
那么在彙編中我們就可以這樣調用它:
push uType
push lpCaption
push lpText
push hWnd
call MessageBox
大家要注意最右面的參數是最後一個進堆疊的,當然,我們不必這樣麻煩的調用一個 API,因為Masm中的一個宏語句不但幫助我們完成了所有的壓棧操作,還幫我們檢查參數的個數是否正確,那就是 invoke 語句,我們可以把上面的語句換成 invoke MessageBox,hWnd,lpText,lpCaption,uType 就行了。如本程式中代入實際參數就成了 invoke MessageBox,NULL,addr szText,addr szCaption,MB_OK。

include 語句

include 語句包含了一些系統的定義和API函說明,其中所有的Windows 數據結構定義和常量定義包含在 windows.inc 中,而其他 API函式的說明包含在 xxx.inc 中, 如查 Microsoft Win32 Programmer's Reference 知道 ExitProcess包含在kernel32.dll 中,那么我們就要在程式中包括 include kernel32.inc 和 includelib kernel32.lib語句,否則在編譯時會出現 API 函式未定義的錯誤。而 MessageBox 在 user32.dll 中,那么我們就要在程式中包括 include user32.inc 和 includelib user32.lib語句

data 或data

指明了接下來是數據段,.data 定義了預定義的變數,.data?定義了未初始化的變數,兩者的不同之處是 .data? 定義的變數並不占用 .exe 檔案的大小,而是在程式執行時動態分配,所以開始是不指定初始值的數據可以放在 .data? 段中,如一個1K大小的緩衝區,放在 .data?中,程式將不會增加一個位元組。

code

指明了接下來是代碼段,我們的所有代碼都放在這裡。最後的一句 start 語句指定了程式開始執行的語句。程式中的 ExitProcess 是一個標準的 Win32 API,對應 Dos彙編中的 int 20h 或 mov ah,4ch/int 21h,也就是程式退出。而 MessageBox 也是一個標準的 API,功能是在螢幕上顯示一個訊息框,具體的參數上面已經解釋過了還有要注意的是 invoke MessageBox,NULL,addr szText,addr szCaption,MB_OK 語句中, MB_OK 和 NULL 已經預定義在 Windows.inc 中。

Windows 的資源檔案

不管在Dos下編程還是在Windows下編程,我們總是要用到除了執行檔外的很多其他數據,如聲音數據,圖形數據,文本等等,在Dos下編程,我們可以自己定義這些檔案的格式,但這樣一來就造成了很多資源共享的問題,大家可能還記的Dos下的很多遊戲,它們的圖形都是按自己的格式存放的,你無法用標準的看圖軟體來看。也無法把它另外儲存為其他格式。雖然在Win32編程中,我們仍然可以這樣做,但Win32編程給了我們一個方案 ---- 就是格式統一的資源檔案,把字元串、圖形、對話框包括上面的按鈕,文本等定義到一個資源檔案中,就可以方便的在不同的檔案中使用它,最重要的是,如果我們用自己的檔案格式,使用時就要涉及到這些檔案的讀寫操作,比較複雜,但使用資源檔案時,Windows提供了一系列的API來裝入資源。非常方便。.rc,當它用資源編譯器編譯以後產生 .res 檔案就可以在 link的時候連入.exe 檔案中:
#include <Resource.h>
#define DLG_MAIN 1
DLG_MAIN DIALOGEX 0, 0, 236, 185
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "對話框模板"
FONT 9, "宋體"
BEGIN
DEFPUSHBUTTON "退出",IDOK,177,163,50,14
CONTROL "",-1,"Static",SS_ETCHEDHORZ,7,155,222,1
END
.rc檔案的語法:
#include <resource.h> -- resource.h檔案包括資源檔案的一些常量定義,如下面的 WS_POPUP,WS_VISIBLE 等視窗的風格等等
#define DLG_MAIN 1 -- 類似於 .asm 檔案的 equ 語句,和彙編源程式一樣,這些定義是為了程式的可讀性。
DLG_MAIN DIALOGEX 0,0,236,185
Windows的.rc檔案可以定義 BITMAP(點陣圖),CURSOR(游標),ICON(圖示),ACCELERATORS(加速鍵),DIALOG(對話框),MENU(選單),STRINGTABLE(字元串表),RCDATA(自定義資源)等8種資源,詳細的描述可以參考有關MFC的書籍,在Win32ASM中的資源編譯器的語法中,一般格式是這些資源的定義方法是:
點陣圖定義: nameID BITMAP [load-mem] filename
游標定義: nameID CURSOR [load-mem] filename
圖示定義: nameID ICON [load-mem] filename
加速鍵定義:
acctablename ACCELERATORS [optional-statements]
BEGIN event, idvalue, [type] [options]
. . .
END
等等,具體的定義和參數可以參考 Masm32v5 中的 Rc.hlp 幫助檔案。(可以在編程工具中下載),我們可以用資源編輯器來所見即所得地編輯資源,也可以在文本編輯器中用上面這些語句自己定義資源。

在程式中使用資源

在程式中,要使用資源之前必須先裝如記憶體,Windows定義了一系列的API來裝入資源,如 LoadMenu,LoadString,LoadBitmap 等等,如 LoadBitmap 的定義:
HBITMAP LoadBitmap(
HINSTANCE hInstance, // handle of application instance
LPCTSTR lpBitmapName // address of bitmap resource name
);
這些Load函式的返回值是一個句柄,調用參數中一般至少為兩項: hInstance 和 ResouceName,這個 ResouceName(如BitmapName,MenuName)就是在資源檔案中的 #define 指定的值,如果你用 #define MY_ICON 10/ MY_ICON ICON "Main.ico" 定義了一個圖示,那么在程式中要使用 Main.ico 圖示就可以用 LoadIcon(hInstance,10) 來裝入已經定義為10號的圖示檔案。另一個參數 hInstance 是執行檔案的句柄,它對應資源所在的檔案名稱,你可以在程式開始執行時用 invoke GetModuleHandle,NULL 獲得 hInstance。另外一些資源並不是顯式地裝入的,如對話框資源,它是在建立對話框的函式中由Windows自己裝入的,如下面例子中的 invoke DialogBoxParam,hInstance,DLG_MAIN,NULL,offset _ProcDlgMain,0 ,是在螢幕上顯示一個資源檔案中已經定義好了的對話框,就並不存在 LoadDialogBox 之類的API來先裝入對話框。

對話框

源程式

源程式如下:
.386
.model flat, stdcall
option casemap :none ; case sensitive
include windows.inc
include user32.inc
include kernel32.inc
include comctl32.inc
include comdlg32.inc
includelib user32.lib
includelib kernel32.lib
includelib comctl32.lib
includelib comdlg32.lib
DLG_MAIN equ 1
.data?
hInstance dd ?
szBuffer db 256 dup (?)
_ProcDlgMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
.data
.code
_ProcDlgMain proc uses ebx edi esi, \
hWnd:DWORD,wMsg:DWORD,wParam:DWORD,lParam:DWORD
mov eax,wMsg
.if eax == WM_CLOSE
invoke EndDialog,hWnd,NULL
.elseif eax == WM_INITDIALOG
.elseif eax == WM_COMMAND
mov eax,wParam
.if eax == IDOK
invoke EndDialog,hWnd,NULL
.elseif eax == IDCANCEL
invoke EndDialog,hWnd,NULL
.endif
.else
mov eax,FALSE
ret
.endif
mov eax,TRUE
ret
_ProcDlgMain endp
start:
invoke GetModuleHandle,NULL
mov hInstance,eax
invoke DialogBoxParam,hInstance,DLG_MAIN,NULL,offset _ProcDlgMain,0
invoke ExitProcess,NULL
end start

解析

_ProcDlgMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
PROTO 語句類似於C語言中的函式定義,在Win32彙編中,如果子程式的定義在引用以後,你就必須先定義,當然,這個定義是針對 invoke 語句和其他帶參數的調用的,如果你的子程式沒有參數,你就可以用 call 指令去調用它而不是用宏指令 invoke,這時候你就不必聲明這個函式。
_ProcDlgMain proc uses ebx edi esi, \
hWnd:DWORD,wMsg:DWORD,wParam:DWORD,lParam:DWORD
這個定義 proc 的語句應該是不陌生的,要重複講解一下的是 uses 和 下面的參數,uses 下的暫存器表示要編譯器自動插入保存及恢復這些暫存器的指令,\ 是在 Masm32 中接下一行的符號,表示下一行是本行的繼續內容,以避免一行中的內容過長。下面的 hWnd:DWORD 等語句定義了調用這個子程式的參數,如果有以下定義 MyProc proc dwPara1:DWORD,dwPara2:DWORD,dwPara3:DWORD,然後你用 invoke MyProc 1,2,3 來調用它,那么,1,2,3 將分別被賦值給 dwPara1,dwPara2,dwPara3,你可以在子程式中使用這些傳遞過來的參數。如果參數的類型是雙字,那么:DWORD 可以省略。
.if/.else/.elseif/.endif
這些語句是宏指令,實際上不說你也知道它們的意思,有了這些宏指令,我們就可以把彙編編得象C一樣結構清晰,而不必老是看到 jmp 指令了,當然,這只不過編譯器幫你做了這些事情而已,如果你去反彙編一下,你開始會看到一大堆 jmp 指令,.if 的格式如下
.if eax == 1 如果eax等於1
.if eax != 1 如果eax不等於1
.if eax != 1 && ebx != 2 如果eax不等於1且ebx不等於2
.if eax == 1 || ebx == 2 如果eax等於1或者ebx等於2
其他的宏指令還有 .while/.endw .break 等等,可以參考 Masm32V5 的幫助檔案 Masm32.hlp
最後要講到的就是 DialogBoxParam 這個API了,在Windows中,所有的視窗都要指定一個子程式,當Windows檢測到滑鼠、定時器等和這個視窗有關的動作時,它回調用這個子程式,這就是Windows基於訊息的體系的最基本的概念,換句話說,在Dos下,我們通過INT指令調用系統,而在Windows 下,有很多時候是你指定子程式地址讓Windows來調用你。 invoke DialogBoxParam,hInstance,DLG_MAIN,NULL,offset _ProcDlgMain,0中的 offset _ProcDlgMain 就指定了如果有訊息發生,Windows就來執行這個子程式,參數中的 DLG_MAIN 就是在資源檔案中定義的對話框模板編號。 hInstance 是對話框所在的資源檔案的句柄。
另外,在_ProcDlgMain 子程式中,Windows傳給我們4個參數hWnd,wMsg,wParam,lParam,其中,hWnd是對話框的視窗句柄,wMsg表示發生的訊息事件,如這個對話框初始化時Windows會以WM_INITDIALOG為訊息調用,關閉時為WM_CLOSE,按下對話框上的按鈕時為WM_COMMAND等,wParam和lParam是附加的參數,對應不同的訊息對應不同定義,具體可以參考Win32 Programmer's reference。

創建視窗

過程

創建一個視窗的過程如下:
取得程式的實例句柄(hInstance)
註冊視窗類,實際上就是為你的視窗指定處理訊息的過程,定義游標,視窗風格,顏色等參數
創建視窗
顯示視窗
然後進入訊息循環,也就是不停地檢測有無訊息,並把它傳送給視窗進程去處理。

編程要點

創建一個視窗的代碼在不同的程式中實際上是幾乎一模一樣的,所以你編一個新的程式時可以把這一段拷來拷去,稍微修改一下就行,程式的大部分代碼實際上是用在視窗過程中,因為這才是不同程式的不同之處。視窗過程的編程要點如下:
從Windows傳給視窗過程的參數 uMsg 得到訊息類型,並轉到不同的分枝去處理。
對自己已經處理的訊息,返回 Windows 時必須在eax 中返回0。
自己不處理的訊息,必須調用 DefWindowProc 處理,並把返回值傳回Windows,否則,Windows會無法顯示。
uMsg 參數中指定的訊息有280多種,實際上我們需要處理的只有重要的幾種,如Windows在創建的時候會傳送 WM_CREATE 訊息,我們就可以在這時候初始化,分配記憶體等等,而退出時會傳送 WM_CLOSE,我們就可以進行釋放記憶體等清除工作,當Windows上的選單或按鈕被按下時傳送 WM_COMMAND 訊息等等,具體可以參考 Win32 Programmer's Reference。下面,我們來看一個創建視窗的簡單程式。
程式
.386
.model flat, stdcall
option casemap :none ; case sensitive
include windows.inc
include user32.inc
include kernel32.inc
include comctl32.inc
include comdlg32.inc
include gdi32.inc
includelib user32.lib
includelib kernel32.lib
includelib comctl32.lib
includelib comdlg32.lib
includelib gdi32.lib
IDI_MAIN equ 1000 ;icon
IDM_MAIN equ 4000 ;menu
IDM_EXIT equ 4001
.data?
hInstance dd ?
hWinMain dd ?
hMenu dd ?
szBuffer db 256 dup (?)
.data
szClassName db "Windows Template",0
szCaptionMain db '視窗模板',0
.code
start:
call _WinMain
invoke ExitProcess,NULL
_WinMain proc
local @stWcMain:WNDCLASSEX
local @stMsg:MSG
invoke InitCommonControls
invoke GetModuleHandle,NULL
mov hInstance,eax
invoke LoadIcon,hInstance,IDI_MAIN
mov hIcon,eax
invoke LoadMenu,hInstance,IDM_MAIN
mov hMenu,eax
;*************** 註冊視窗類 *****************************************
invoke LoadCursor,0,IDC_ARROW
mov @stWcMain.hCursor,eax
mov @stWcMain.cbSize,sizeof WNDCLASSEX
mov @stWcMain.hIconSm,0
mov @stWcMain.style,CS_HREDRAW or CS_VREDRAW
mov @stWcMain.lpfnWndProc,offset WndMainProc
mov @stWcMain.cbClsExtra,0
mov @stWcMain.cbWndExtra,0
mov eax,hInstance
mov @stWcMain.hInstance,eax
mov @stWcMain.hIcon,0
mov @stWcMain.hbrBackground,COLOR_WINDOW + 1
mov @stWcMain.lpszClassName,offset szClassName
mov @stWcMain.lpszMenuName,0
invoke RegisterClassEx,addr @stWcMain
;*************** 建立輸出視窗 ***************************************
invoke CreateWindowEx,WS_EX_CLIENTEDGE,\
offset szClassName,offset szCaptionMain,\
WS_OVERLAPPEDWINDOW OR WS_VSCROLL OR WS_HSCROLL,\
0,0,550,300,\
NULL,hMenu,hInstance,NULL
invoke ShowWindow,hWinMain,SW_SHOWNORMAL
invoke UpdateWindow,hWinMain
;*************** 訊息循環 *******************************************
.while TRUE
invoke GetMessage,addr @stMsg,NULL,0,0
.break .if eax == 0
invoke TranslateMessage,addr @stMsg
invoke DispatchMessage,addr @stMsg
.endw
ret
_WinMain endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
WndMainProc proc uses ebx edi esi, \
hWnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
mov eax,uMsg
.if eax == WM_CREATE
mov eax,hWnd
mov hWinMain,eax
call _Init
.elseif eax == WM_COMMAND
.if lParam == 0
mov eax,wParam
.if ax == IDM_EXIT
call _Quit
.endif
.endif
.elseif eax == WM_CLOSE
call _Quit
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndMainProc endp
_Init proc
invoke SendMessage,hWinMain,WM_SETICON,ICON_SMALL,hIcon
ret
_Init endp
_Quit proc
invoke DestroyWindow,hWinMain
invoke PostQuitMessage,NULL
ret
_Quit endp
end start
視窗程式的分析
讓我們來簡單分析一下這個程式,首先程式調用 _WinMain,在_WinMain 中定義了兩個局部變數 @stMsg 和 @stWinMain,數據類型分別是 MSG 和 WNDCLASSEX結構,在參考手冊中,可以看到WNDCLASSEX定義了一個視窗的所有參數,如使用的選單、游標、顏色、視窗過程等,接下來的一大堆 mov 指令實際上就是在填寫這個數據結構,填寫完成後,最重要的兩句是 mov @stWcMain.lpfnWndProc,offset WndMainProc 定義了處理訊息的視窗過程, mov @stWcMain.lpszClassName,offset szClassName 定義了你要創建的類的名稱,然後就是使用 RegisterClassEx 註冊這個視窗類,注意,這時候視窗並沒有創建,你只不過是定義好了一個子類,接下去你要用你定義的類去創建一個視窗。也就是使用 CreateWindowEx 函式去創建它。在手冊中,CreateWindowEx 是這樣定義的:
HWND CreateWindowEx(
DWORD dwExStyle, // extended window style
LPCTSTR lpClassName, // pointer to registered class name
LPCTSTR lpWindowName, // pointer to window name
DWORD dwStyle, // window style
int x, // horizontal position of window
int y, // vertical position of window
int nWidth, // window width
int nHeight, // window height
HWND hWndParent, // handle to parent or owner window
HMENU hMenu, // handle to menu, or child-window identifier
HINSTANCE hInstance, // handle to application instance
LPVOID lpParam // pointer to window-creation data );
其中的參數 dwExStyle 是視窗的風格,lpClassName 就是我們自己定義的類的名字。如果大家要創建一個已經定義好的類,如 RichEdit 類等等,只要把 lpClassName 指向 "RichEdit32" 字元串就行了,當然這時就不用 RegisterClass 以及編寫自己的視窗過程了。執行 CreateWindowEx 後,得到一個返回值就是視窗句柄,這個值在以後是要經常用到了,所以要先保存下來。這時視窗並沒有在螢幕上顯示出來,而是處於隱藏狀態,我們要用 ShowWindow 來顯示出視窗並用UpdateWindow 來繪視窗的內容。
視窗顯示出來後,程式就進入一個循環----訊息循環,前面我已經說過,作用是不停地接收 Windows 訊息並傳送給視窗過程去處理。GetMessage 從訊息佇列中取出一條訊息,如果取得的訊息不是 WM_QUIT,那么 GetMessage 返回一個非零值,否則返回零,這時候循環結束,程式執行 ExitProcess退回作業系統。TranslateMessage 將訊息進行一些鍵盤轉換,用於處理一些快捷鍵,DispatchMessage 將處理後的訊息發回 Windows,由Windows調用視窗進程進行處理,當視窗進程處理完返回後,程式才從 DispatchMessage 返回,從而開始下一個 GetMessage 調用。這些函的參數可以參考手冊。
視窗過程的分析
視窗過程有四個參數,hWnd 是本視窗的句柄,和創建視窗時返回的值相同,uMsg 是本次調用的訊息類型,wParam 和lParam是訊息的參數,其含義和數值根據訊息的不同而不同。在本程式中,我們處理 WM_CREATE,WM_COMMAND 和 WM_QUIT 訊息,然後返回0,對不處理的訊息,使用 invoke DefWindowProc,hWnd,uMsg,wParam,lParam 來處理並直接用 ret 將返回值傳回 Windows。在回響 WM_CLOSE 訊息時,我們用 DestroyWindow 清除視窗並用PostQuitMessage 產生一條 WM_QUIT 訊息,從而使程式在 訊息循環調用GetMessage 時返回0,以結束訊息循環並結束程式。

常見套用

如何隱藏/顯示系統列
shell db "Shell_TrayWnd",0 ; 系統列的類名
invoke FindWindow,addr shell,NULL ; 先獲得句柄,之後隱藏.
.if eax != 0
invoke ShowWindow,eax,SW_HIDE ; 用SW_SHOW顯示
.endif
- 如何禁止/允許/顯示/隱藏開始按鈕?
.data?
buffer db 127 dup(?)
.data
shell db "Shell_TrayWnd",0
sbar db "BUTTON",0
child dd ?
slen dd ?
.code
invoke FindWindow,addr shell,NULL ; 獲得狀態欄句柄
mov tray, eax
invoke GetWindow,tray, GW_CHILD ; 獲得狀態欄的子視窗(如果有的話)
mov child, eax
.if child != 0
invoke GetClassName,child,offset buffer, sizeof buffer ;獲得子視窗類名
.if eax > 0
invoke lstrlen, offset buffer ;獲得類名長度
mov slen,eax
invoke CharUpperBuff,offset buffer,slen ;轉為大寫
invoke lstrcmp,addr buffer, addr sbar ;將類名與'BUTTON'比較
.if eax == 0
invoke ShowWindow,child,SW_HIDE ; 隱藏開始按鈕
; invoke ShowWindow,child,SW_SHOW ; 顯示開始按鈕
; invoke EnableWindow,child,FALSE ; 禁止開始按鈕
; invoke EnableWindow,child,TRUE ; 允許開始按鈕
.endif
.endif
.endif
--------------------------------------------------------------------------------
- 如何創建一個真正的"總在最上面"視窗?
invoke SetWindowPos,hWin, HWND_TOPMOST,NULL,NULL,NULL,NULL,SWP_NOACTIVATE or SWP_NOMOVE or SWP_NOSIZE
- 如何創建熱鍵?比如CTRL + ALT + A
.data
hmsg db "HotKey CTRL + ALT + A Works good!",0
hcap db "Hotkey Example",0
.code
.if uMsg == WM_CREATE
invoke RegisterHotKey,hWnd,065h,MOD_CONTROL or MOD_ALT, 041h ; CTRL + ALT + A (041h is 65 - 065h is 101)
.elseif uMsg == WM_HOTKEY
invoke MessageBox,hWin,addr hmsg,addr hcap, MB_OK or MB_ICONINFORMATION
.elseif uMsg == WM_DESTROY
invoke UnregisterHotKey,hWin,065h
invoke PostQuitMessage,NULL
return 0
.endif
-如何獲得Windows目錄和系統目錄?
.data
buffer db 50 dup(?)
hCap db "WindowsDirectory",0
.code
invoke GetWindowsDirectory, addr buffer, sizeof buffer ; 置Windows目錄於緩衝區中
; invoke GetSystemDirectory, addr buffer, sizeof buffer ;置系統目錄於緩衝區中
invoke MessageBox,hWnd, addr buffer, addr hCap, MB_OK or MB_ICONINFORMATION
- 如何從我的程式打開開始選單?
invoke SendMessage,hWnd,WM_SYSCOMMAND,SC_TASKLIST,NULL
- 如何關閉正被激活的程式 ?
.data
fwin dd ?
.code
invoke GetForegroundWindow
mov fwin,eax
invoke SendMessage, fwin, WM_CLOSE,NULL
- 如何去掉視窗標題 ?
invoke GetWindowLong,hWnd,GWL_STYLE ; 獲得當前視窗類
and eax,not WS_CAPTION ; 去掉WS_CAPTION
invoke SetWindowLong,hWnd,GWL_STYLE,eax ; 設定視窗類
- 如何知道視窗是否在系統列中(或可見)?
invoke IsWindowVisible,hWin
.if eax == TRUE
; 視窗可見
.else
; 視窗不可見
.endif
- 如何隱藏一個視窗?
.data
mirc db "mIRC32",0
mhand dd ?
.code
invoke FindWindow,addr mirc, NULL ; 尋找mIRC32
mov mhand,eax
.if mhand != 0 ; 找到?
invoke ShowWindow,mhand,SW_SHOW ; 顯示視窗
; invoke ShowWindow,mhand,SW_HIDE ; 隱藏視窗
.else
; mIRC32未運行...
.endif
- 如何將視窗置於前台?
invoke SetForegroundWindow, mhand
- 如何禁止CTRL+ALT+DEL,ALT+TAB+CTRL+ESC這些鍵?
invoke SystemParametersInfo,SPI_SCREENSAVERRUNNING,1,NULL,NULL
; Windows98 only 1 關閉 0 允許
- 如何確定Windows系統列的自動隱藏特性是否被激活?
.data
AppBar APPBARDATA {} ; {} 指使用默認值... Thanks to TTom
.code
mov AppBar.cbSize, sizeof AppBar
invoke SHAppBarMessage, ABM_GETSTATE, addr AppBar ; ShellApi命令
and eax, ABS_AUTOHIDE
.if eax == TRUE
; 系統列被隱藏
.else
; 系統列未被隱藏
.endif
- 如何使用默認的瀏覽器或郵件程式?
.data
lpPage db http://win32asm,0
lpMail db .tr",0
lpOperation db "open",0
.code
invoke ShellExecute,hWin,addr lpOperation, addr lpPage, NULL, NULL, SW_SHOWNORMAL
invoke ShellExecute,hWin,addr lpOperation, addr lpMail, NULL, NULL, SW_SHOWNORMAL
- 如何用Win32 API顯示網路連線對話框?
include \MASM32\INCLUDE\mpr.inc
includelib \MASM32\LIB\mpr.lib
invoke WNetConnectionDialog,hWnd,RESOURCETYPE_DISK

主要錯誤

1、80連線埠問題
很大一部分都來自連線埠問題,但是一般沒裝IIS的。則不會出現這個問題
2、配置檔案httpd.conf問題
配置檔案中的則是拼寫問題了,建議每次更改前都對httpd.conf 檔案備份。主要檢查 LoadModule php5_module "" 的路徑。包括有無中文字元,空格。以及ErrorLog 路徑,php.ini檔案路徑無效等
3、第三就網上流傳最廣的解答辦法了,直接copy了
這一般是netbios解析失敗造成的。解決的辦法很簡單,步驟如下:
一、 在"網上鄰居"上 點右鍵,選單上選"屬性(R)"。出來"網路連線"視窗,在"本地連線"上點右鍵,選單中點"屬性(R)"。出來"本地連線 屬性"面板,在"此連結使用下列項目"中找到:"Internet 協定 (TCP/IP)",並點擊它。在彈出的"Internet 協定 (TCP/IP) 屬性"面板上,點擊"高級(V)..."按鈕。"高級 TCP/IP 設定"面板上點"WINS"選項標籤,去掉"啟用 LMHOSTS 查詢(L)"前面的勾,點"確定"按鈕。
二、 打開"控制臺",點擊"Windows 防火牆",點擊"高級"選項標籤,選擇"本地連線",並點擊旁邊的"設定(T)..."按鈕。在服務選項中找到"安全 Web 伺服器(HTTPS)",並把它勾選上,點"確定"按鈕。
4、第四相關軟體影響。迅雷開啟時,apache是啟動不了的。可以關掉迅雷再試試
5、可能是php版本不是php5.0以上版。LoadModule phpX_module "" 讀取這個模組是許可權不夠。一般去掉這一行apache 就可以啟動了,但是php 卻沒載入上來。建議使用5.0以上版。
6、打開httpd.conf檔案。搜尋 ServerAdmin 把這行改為"#ServerAdmin" 保存,重啟。
可能還存在各種各樣的問題。
我的解決方式就是第6個。搞的我鬱悶半天。不過還好啟動了
啟動服務錯誤時,如服務日誌沒有記錄。可以使用事件查看器,查看系統的日誌錯誤。cmd 命令:eventvwr.msc 進入。

相關詞條

熱門詞條

聯絡我們