---
title : 06_windows視窗
---
# Windows視窗
By Cheng-Yen, Tsai & Wei-Liang, Liu
Date : 2021-09-24
---
## 壹、前言
介面程式是勒索程式重要的一環,視窗可作為與勒索者之橋梁,其中的每個文字、按鈕、編輯框、圖片等等,都是需要透過程式設計的編寫。雖然稱為視窗,但卻不是只有視窗的功能,例如木馬程式的鍵盤側錄器,或是畫面上看到的計時器、外部網站等等都是要用到視窗程式提供的函數。

---
## 貳、Windows視窗程式基礎架構
不同於一般主控台應用程式,windows程式的架構我認為較複雜。
1. 首先是程式進入點,傳統C/C++程式進入點為main(),而windows的視窗程式進入點為wWinMain(),在visual studio 2019可以直接建立windws視窗程式專案。這裡可以放相關的運算程式,視窗執行會一起進行運算。
```cpp=
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: 在此放置程式碼。
// 將全域字串初始化
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_WINDOWSPROJECT, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// 執行應用程式初始化:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WINDOWSPROJECT));
MSG msg;
// 主訊息迴圈:
while (GetMessage(&msg, nullptr, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int) msg.wParam;
}
```
2. 專案建立後會看到wWinMain(),裡面可以寫一些程式,而這些程式會在視窗出現之前執行。在主程式下面有MyRegisterClass,中文翻譯是註冊視窗類別,但是我覺得有點難懂,其實就是把這個window的外觀、名稱等等設定好,基本上不會去動這個函數。
```cpp=
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINDOWSPROJECT));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_WINDOWSPROJECT);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassExW(&wcex);
}
```
3. 在設定好視窗後,下面是InitInstance(),用來產生主視窗,,並顯示我們自己更動好的結果。
```cpp=
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // 將執行個體控制代碼儲存在全域變數中
HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
```
4. WndProc()是主要更動的地方,而這裡是一種事件導向的架構。一般的程式都是從主程式、副程式這樣一直執行下去,但是視窗程式不行,因為要考慮到鍵盤的輸入,滑鼠點擊滾動等等動作,所以要使用事件導向的方式進行。
事件導向的函數通常是Call Back型態,是指能藉由參數通往另一個函式的函式,所以裡面可以看到是使用switch來判斷要跳往哪個動作。
```cpp=
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
// 剖析功能表選取項目:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// TODO: 在此新增任何使用 hdc 的繪圖程式碼...
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
```
## 視窗相關函數
1. **新增視窗** **:** CreateWindow(), CreateWindowEx()
針對視窗創建有兩種方式,一種是CreateWindow(),是創建主要視窗的函數,另一種是CreateWindowEx(),是用來創建主視窗內物件的函數,可以理解成在主視窗內,像是按鈕、下拉選單等等都是一個子視窗,主視窗的呼叫就是透過CreateWindow()產生出來的。CreateWindowEx()僅比CreateWindow()多一個參數,其第一個參數表示擴充樣式,其餘皆相同。
CreateWindowEx()的參數如下:
* **dwExStyle** => 擴充視窗樣式,通常不常使用,可填入0。
* **lpClassName** => 窗口的類別名稱,以0字元為結尾的字串,也可是RegisterClass或RegisterClassEx產生的ATOM。
* **lpWindowName** => 視窗上的標題,是以0為結尾的字串。
* **dwStyle** => window之樣式選擇。常用到的有:
| 名稱 | 解釋 |代碼
| -------- | -------- | -------- |
| WS_VSCROLL | 增加後右側會有scroll bar | 0x00200000L |
| WS_VISIBLE | 這個物件被初始化成可以看見的 | 0x10000000L |
| WS_TABSTOP | 加入這個style可以讓用戶按下tab鍵後跳至另一個物件 | 0x00010000L |
| WS_CHILD | 代表這個視窗是子視窗 | 0x40000000L |
* **x** => 視窗的x座標,或是輸入CW_USEDEFAULT設為預設值。
* **y** => 視窗的y座標,或是輸入CW_USEDEFAULT設為預設值。
* **Width** => 視窗的寬度,或是輸入CW_USEDEFAULT設為預設值。
* **nHeight** => 視窗的長度,或是輸入CW_USEDEFAULT設為預設值。
* **hWndParent** => 產生視窗程式之視窗handle。
* **hMenu** => 可為選單handle,或是用以指定的子視窗標識(為一整數值),該值由視窗樣式來決定。如果是menu,可放入NULL。
* **Instance** => 產生視窗程序的handle,通常放入全域變數hInst。
* **ipParam** => 從CreateWindow產生視窗時,產生WM_CREATE訊息,而參數指向CREATESTRUCT結構。若產生的是MDI客戶視窗,則指向CLIENTCREATESTRUCT結構。
回傳值為新視窗的handle,失敗則回傳NULL,可由GetLastError取得錯誤碼。
2. 傳送訊息:SendMessage()
許多元件裡都需有文字,像是按鈕、編輯框中都需要文字,決定傳達文字與字型就是要用到SendMessage()。以下四個為該函示之參數:
* **hWnd** => 接收訊息視窗的handle,若放入HWND_BROADCAST,則表示廣播給所有最頂端的視窗。
* **Msg** => 要傳送之訊息,可分為系統定義與應用程式定義訊息。系統定義表示window本身的訊息,例如WM_PAINT、progress bar、combobox等,若要設定字型,要填入WM_SETFONT。
* **wParam** => 訊息所附加的資訊。已設定字型來說,則放CreateFont產生的handle,或是NULL代表預設字型
* **IParam** => 訊息所附加的資訊。