欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

实操指南:在Windows中掌握进程和线程编程技巧

最编程 2024-02-15 18:50:15
...

实验环境

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

运行截图

推荐阅读