【實驗報告】Windows核心程式設計——程序和執行緒程式設計
實驗環境
-
作業系統:Windows 10;
-
程式碼編寫執行環境:Visual Studio 2019。
實驗目的
-
完成一個 Windows 視窗應用程式,熟悉 Windows 視窗應用結構,訊息驅動;
-
掌握
WNDCLASSEX
結構體、CreateWindows
等 API 函式、圖示、游標。瞭解 GDI 中 DC 概念,以及繪圖函式; -
掌握程序和執行緒的概念、程序的狀態;
-
掌握執行緒同步技術,包括執行緒使用者模式下同步,例如臨界區(關鍵段)執行緒同步,以及採用核心物件模式執行緒同步,例如事件,互斥量,訊號量;
-
熟悉程序和執行緒API函式程式設計,要求能夠完成程序建立,以及應用執行緒相關函式完成多執行緒程式設計,並完成多執行緒同步;
-
熟悉 Process Explorer 軟體和 Spy++ 軟體,使用這兩款軟體工具對核心物件、程序和執行緒相關資訊進行檢視。
實驗內容
-
使用 Process Explorer 和 Spy++,並學會使用軟體檢視核心物件,理解核心物件、程序和執行緒概念。
-
- 建立一個視窗應用程式,完成自定義圖示(最小化圖示和小圖示),以及視窗游標,視窗標題名字;
- 採用自創畫筆和畫刷,繪製一個多邊形、長方形、橢圓、直線、弧線,以及餅狀圖等圖形;
- 按下鍵盤上某鍵,呼叫
CreateProcess
建立子程序,開啟記事本程式,向記事本中輸出子程序和執行緒 ID,然後顯示出來; - 在視窗應用程式點選滑鼠左鍵,分別在記事本和應用程式視窗輸出滑鼠左鍵的座標,並在記事本和視窗應用程式以滑鼠左鍵座標點為圓心,輸出半徑為100的圓,並用自創畫筆和畫刷填充;
- 在選單項新建選單子項 “退出記事本”,退出記事本,呼叫結束程序函式
TerminateProcess
結束記事本程序。
-
程式建立三個執行緒。一個主執行緒,主執行緒建立兩個附加執行緒,採用事件或者其他執行緒同步機制實現執行緒同步:
- 第一個附加執行緒功能為:螢幕輸入字串,寫入檔案,字串寫入完,通知第二個附加執行緒;
- 第二個附加執行緒功能為:讀取檔案中字串,查詢一個單詞,若找到了該單詞,通知主執行緒把找到的單詞顯示出來;
- 若附加執行緒退出時,還未查詢到此單詞,則通知主執行緒,打印出未找到此單詞。
實驗步驟
用 Process Explorer 軟體檢視程序相關限制資訊:
使用 Visual Studio Tools 的 Spy++ 檢視視窗相關資訊:
建立一個視窗應用程式,完成自定義圖示(最小化圖示和小圖示),以及視窗游標,視窗標題名字:
// 使用自定義圖示
wndclass.hIcon = ::LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));
// 使用自定義的游標
wndclass.hCursor = ::LoadCursor(hInstance, MAKEINTRESOURCE(IDC_CURSOR1));
// 自定義類的小圖示
wndclass.hIconSm = ::LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));
注意:此處的自定義圖示需要自行從網上下載,並自行匯入 Visual Studio 的專案檔案中,具體操作請查詢其他參考資料。
採用自創畫筆和畫刷,繪製一個多邊形、長方形、橢圓、直線、弧線、以及餅狀圖等圖形:
hdc = ::BeginPaint(hWnd, &ps);
::GetClientRect(hWnd, &rt);
hBrush = (HBRUSH)CreateHatchBrush(HS_DIAGCROSS, RGB(255, 0, 0));
cxClient = rt.right - rt.left;
cyClient = rt.bottom - rt.top;
::SelectObject(hdc, hBrush);
// 畫多邊形
::MoveToEx(hdc, 10, 10, NULL);
::LineTo(hdc, 200, 10);
::MoveToEx(hdc, 200, 10, NULL);
::LineTo(hdc, 105, 200);
::MoveToEx(hdc, 105, 200, NULL);
::LineTo(hdc, 10, 10);
// 畫長方形
::Rectangle(hdc, 205, 10, 400, 200);
// 畫橢圓
::Ellipse(hdc, 405, 50, 600, 150);
// 畫直線
::MoveToEx(hdc, 0, 205, NULL);
::LineTo(hdc, cxClient, 205);
// 畫弧線
::Arc(hdc, 605, 10, 800, 200, 605, 100, 800, 100);
// 畫餅狀圖(扇形)
::Pie(hdc, 50, 250, 250, 450, 70, 300, 250, 250);
::DeleteObject(hBrush);
按下鍵盤上某鍵,呼叫 CreateProcess 建立子程序,開啟記事本程式,向記事本中輸出子程序和執行緒 ID,然後顯示出來:
// 當鍵盤按下,新建記事本程序,輸出程序ID
case WM_CHAR:
{
BOOL bRet = ::CreateProcess(
NULL,
szCommandLine,
NULL,
NULL,
FALSE,
CREATE_NEW_CONSOLE,
NULL,
NULL,
&startupInfo,
&processInformation
);
char szPoint[1000] = {};
wsprintf(szPoint, "新程序的程序ID號:%d, 新程序的主執行緒ID號:%d", processInformation.dwProcessId, processInformation.dwThreadId);
if (bRet)
{
Sleep(1000);
HWND hWnd = ::FindWindow("Notepad", NULL);
if(hWnd != NULL)
{
HDC hdc;
hdc = GetDC(hWnd);
HPEN hPen;
hPen = ::CreatePen(PS_DASH, 1, RGB(255, 0, 255));
::SelectObject(hdc, hPen);
HFONT hFont;
hFont = ::CreateFont(
20,
10,
0,
0,
FW_NORMAL,
0,
1,
0,
GB2312_CHARSET,
OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY,
DEFAULT_PITCH | FF_DONTCARE,
"宋體"
);
::SelectObject(hdc, hFont);
::SetTextColor(hdc, RGB(255, 0, 255));
::SetBkColor(hdc, RGB(0, 0, 0));
::TextOut(hdc, 100, 100, szPoint, lstrlen(szPoint));
}
else
{
::MessageBox(NULL, "開啟視窗失敗", "提示", MB_OK);
}
}
hpr = processInformation.hProcess;
::CloseHandle(processInformation.hThread);
}
break;
在視窗應用程式點選滑鼠左鍵,分別在記事本和應用程式視窗輸出滑鼠左鍵的座標,並在記事本和視窗應用程式以滑鼠左鍵座標點為圓心,輸出半徑為 100 的圓,並用自創畫筆和畫刷填充:
// 當滑鼠點選,輸出指標座標
case WM_LBUTTONDOWN:
{
px = LOWORD(lParam);
py = HIWORD(lParam);
flag = 1;
HWND hwnd = ::FindWindow("Notepad", NULL);
if (hwnd != NULL)
{
HDC hdc;
hdc = GetDC(hwnd);
HPEN hPen;
hPen = ::CreatePen(PS_DASH, 1, RGB(0, 0, 0));
::SelectObject(hdc, hPen);
// 畫圓
::Ellipse(hdc, px - 100, py - 100, px + 100, py + 100);
char szUnicode[100];
wsprintf(szUnicode, "x = %d, y = %d", px, py);
HFONT hFont;
hFont = ::CreateFont(
20,
10,
0,
0,
FW_NORMAL,
0,
1,
0,
GB2312_CHARSET,
OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY,
DEFAULT_PITCH | FF_DONTCARE,
"宋體");
::SelectObject(hdc, hFont);
::SetTextColor(hdc, RGB(255, 0, 0));
::SetBkColor(hdc, RGB(0, 0, 0));
::TextOut(hdc, 300, 100, szUnicode, lstrlen(szUnicode));
}
else
{
::MessageBox(NULL, "開啟視窗失敗", "提示", MB_OK);
}
InvalidateRect(hWnd, NULL, FALSE);
}
break;
在選單項新建選單子項 “退出記事本”,退出記事本,呼叫結束程序函式 TerminateProcess
結束記事本程序:
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
// 分析選單選擇
switch (wmId)
{
case IDM_EXIT:
{
::DestroyWindow(hWnd);
break;
}
case IDM_NOTEPADEXIT:
{
if (hpr != NULL)
{
::TerminateProcess(hpr, 4);
}
break;
}
defult:
return ::DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
程式建立三個執行緒。一個主執行緒,主執行緒建立兩個附加執行緒,採用事件或者其他執行緒同步機制實現執行緒同步。
第一個附加執行緒功能為:螢幕輸入字串,寫入檔案,字串寫入完,通知第二個附加執行緒:
// 附加執行緒1:往檔案中寫入字串
DWORD WINAPI WriteProc(LPVOID lpParam)
{
DWORD dw;
dw = WaitForSingleObject(hWriteEvent[0], INFINITE);
ofstream outfile;
cout << "Input String:\n";
scanf("%[^\n]", str);
getchar();
outfile.open("exp2.txt", ios::out | ios::trunc);
outfile << str;
outfile.close();
SetEvent(hWriteEvent[1]);
return 0;
}
第二個附加執行緒功能為:讀取檔案中字串,查詢一個單詞,若找到了該單詞,通知主執行緒把找到的單詞顯示出來:
// 附加執行緒2:從檔案中讀取字串,並匹配要查詢的單詞
DWORD WINAPI Read1Proc(LPVOID lpParam)
{
DWORD dw;
dw = WaitForSingleObject(hWriteEvent[1], INFINITE);
char words[256][10];
ifstream infile;
char data[256];
int flag = 0;
infile.open("exp2.txt");
while (!infile.eof())
{
infile.getline(data, 256);
}
int k = 0;
int j = 0;
for (int i = 0; i < strlen(data); i++)
{
if (data[i] == ' ')
{
words[j++][k] = '\0';
k = 0;
continue;
}
words[j][k++] = data[i];
}
words[j][k] = '\0';
cout << "Input a word want to find:\n";
scanf("%[^\n]", word);
getchar();
for (int i = 0; i <= j; i++)
{
if (strcmp(word, words[i]) == 0)
{
flag = 1;
break;
}
}
if (flag == 0)
{
::SetEvent(hReadEvent[0]);
}
else
{
::SetEvent(hReadEvent[1]);
}
infile.close();
return 0;
}
若附加執行緒退出時,還未查詢到此單詞,則通知主執行緒,打印出未找到此單詞:
if (WaitForMultipleObjects(2, hReadEvent, FALSE, INFINITE) == 1)
{
cout << "The word you want to search has been found: " << word << endl;
}
else
{
cout << "The word can't be found.";
}