1. 程式人生 > 其它 >【實驗報告】Windows核心程式設計——程序和執行緒程式設計

【實驗報告】Windows核心程式設計——程序和執行緒程式設計

實驗環境

  1. 作業系統:Windows 10;

  2. 程式碼編寫執行環境:Visual Studio 2019。

實驗目的

  1. 完成一個 Windows 視窗應用程式,熟悉 Windows 視窗應用結構,訊息驅動;

  2. 掌握 WNDCLASSEX 結構體、CreateWindows 等 API 函式、圖示、游標。瞭解 GDI 中 DC 概念,以及繪圖函式;

  3. 掌握程序和執行緒的概念、程序的狀態;

  4. 掌握執行緒同步技術,包括執行緒使用者模式下同步,例如臨界區(關鍵段)執行緒同步,以及採用核心物件模式執行緒同步,例如事件,互斥量,訊號量;

  5. 熟悉程序和執行緒API函式程式設計,要求能夠完成程序建立,以及應用執行緒相關函式完成多執行緒程式設計,並完成多執行緒同步;

  6. 熟悉 Process Explorer 軟體和 Spy++ 軟體,使用這兩款軟體工具對核心物件、程序和執行緒相關資訊進行檢視。

實驗內容

  1. 使用 Process Explorer 和 Spy++,並學會使用軟體檢視核心物件,理解核心物件、程序和執行緒概念。

    1. 建立一個視窗應用程式,完成自定義圖示(最小化圖示和小圖示),以及視窗游標,視窗標題名字;
    2. 採用自創畫筆和畫刷,繪製一個多邊形、長方形、橢圓、直線、弧線,以及餅狀圖等圖形;
    3. 按下鍵盤上某鍵,呼叫 CreateProcess 建立子程序,開啟記事本程式,向記事本中輸出子程序和執行緒 ID,然後顯示出來;
    4. 在視窗應用程式點選滑鼠左鍵,分別在記事本和應用程式視窗輸出滑鼠左鍵的座標,並在記事本和視窗應用程式以滑鼠左鍵座標點為圓心,輸出半徑為100的圓,並用自創畫筆和畫刷填充;
    5. 在選單項新建選單子項 “退出記事本”,退出記事本,呼叫結束程序函式 TerminateProcess 結束記事本程序。
  2. 程式建立三個執行緒。一個主執行緒,主執行緒建立兩個附加執行緒,採用事件或者其他執行緒同步機制實現執行緒同步:

    1. 第一個附加執行緒功能為:螢幕輸入字串,寫入檔案,字串寫入完,通知第二個附加執行緒;
    2. 第二個附加執行緒功能為:讀取檔案中字串,查詢一個單詞,若找到了該單詞,通知主執行緒把找到的單詞顯示出來;
    3. 若附加執行緒退出時,還未查詢到此單詞,則通知主執行緒,打印出未找到此單詞。

實驗步驟

用 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.";
}

執行截圖