第5章 作业和进程池(2)

5.6 作业对象事件和完成端口

1)将作业对象与完成端口对象关联起来

   JOBOBJECT_ASSOCIATE_COMPLETION_PORT joacp;

   joacp.CompletionKey = hJob1; //可用来标识作业对象任意唯一值,这里取其句柄

   joacp.CompletionPort = hIOCP; //完成端口的句柄

   SetInformationJobObject(hJob,JobObjectAssociateCompletionPortInformation,

           &joacp,sizeof(joacp));

 2)创建线程,将完成端口对象作为参数传入线程函数。并GetQueuedCompletionStatus来等待作业对象的通知事件。

参数

描述

hIOCP

要获取事件的完成端口对象的句柄

pNumBytesTransferred

等待的事件ID

【与作业对象有关的事件】

JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT:作业对象中活动进程数达到上限时通知

JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO:作业对象中当前没有活动进程时通知

JOB_OBJECT_MSG_END_OF_JOB_TIME:作业对象耗尽指定的时间片时通知。但其中的进程不会自动终止。可以设置一个新的时间限额以允许继续,或调用TerminateJobObject来终止进程。

JOB_OBJECT_MSG_JOB_MEMORY_LIMIT:作业对象耗尽指定的内存时通知,同时给出进程ID

【与进程有关的事件】

JOB_OBJECT_MSG_NEW_PROCESS:新进程加入作业对象时通知,并给出进程ID

JOB_OBJECT_MSG_EXIT_PROCESS:进程正常退出时通知,并给出进程ID

JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS:进程异常退出时通知,并给出进程ID

JOB_OBJECT_MSG_END_OF_PROCESS_TIME:进程耗尽时间片时通知,进程将终止,并给出进程ID

JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT:进程消耗内存数量达到上限时通知,同时给出进程ID

pCompletionKey

指定触发这个事件的作业对象的句柄(可以将一个完成端口对象与多个作业对象进行绑定)

pOverlapped

在作业事件中,该值表示哪个进程ID发生的事件。

dwMilliseconds

用于指定调用者等待完成端口的时间

   注意:

①作业对象的状态变为己触发是在分配作业的时间到期时,而不是所有进程都结束时。

②默认下,当作业时间到期时,它的所有进程都会自动终止,所以也就不会投递JOB_OBJECT_MSG_END_OF_JOB_TIME。如果只想发送该通知给应用程序,而让应用程序自行来杀死进程,可以做如下设置:

  //创建结构体,并将JOb结束时要采取的措施填入该结构体

  JOBOBJECT_END_OF_JOB_TIME_INFORMATION joeojti;

  joeojti.EndOfJobTimeAction = JOB_OBJECT_POST_AT_END_OF_JOB; //投递通知,而不是“杀死”进程。创建作业时,默认值为JOB_OBJECT_TERMINATE_AT_END_OF_JOB;

 

 //告诉作业对象当作业时间到期时,要采取的措施

 SetInformationJobObject(hJob,JobObjectEndOfJobTimeInformation,

&joeojti,sizeof(joeojti));

JobIOCP】利用完成端口实现进程管理

 

#include <windows.h>
#include <locale.h>
#include <tchar.h>
#include <strsafe.h>

HANDLE g_hJob = NULL;
HANDLE g_hIOCP = NULL;

DWORD WINAPI IOCPThread(LPVOID lpParam);

__inline VOID GetAppPath(LPTSTR pszBuffer)
{
    DWORD dwLen = 0;  //pszbuffer中接收到的字符个数
    //获取当前应用程序全路径(含文件名)
    dwLen = GetModuleFileName(NULL, pszBuffer, MAX_PATH);
    if (0 == dwLen) 
        return;
    //去件文件及扩展名,只保留路径(路径的最后面含""字符)
    for (DWORD i = dwLen; i > 0; i--){
        if ('\'==pszBuffer[i]){
            pszBuffer[i + 1] = '';
            break;
        }
    }

}

int _tmain()
{
    _tsetlocale(LC_ALL, _T("chs"));

    //创建一个默认的安全属性结构(不继承)
    SECURITY_ATTRIBUTES  sa = { sizeof(SECURITY_ATTRIBUTES) };
    sa.bInheritHandle = FALSE;

    //创建匿名的Job对象
    g_hJob = CreateJobObject(&sa, NULL);

    //指定不显示异常关闭对话框,即静默方式运行本程序
    JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = {};
    //jeli.BasicLimitInformation.PerProcessUserTimeLimit.QuadPart = 100 * 10000i64;
    jeli.BasicLimitInformation.LimitFlags =JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION;
    SetInformationJobObject(g_hJob, JobObjectExtendedLimitInformation,
                            &jeli, sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));

    //为作业对象设置一些限制(经测试,以下的基本限制要在扩展限制设置完以后或,才能设置//也可以直接在扩展限制里面直接设置!)
    JOBOBJECT_BASIC_LIMIT_INFORMATION jbli = {0};
    //限制进程的用户时间最大值,单位是100纳秒,本例中设置为100ms
    jbli.PerProcessUserTimeLimit.QuadPart = 100 * 10000i64; //该进程能够获得的CPU执行时间最多为100ms
    jbli.PerJobUserTimeLimit.QuadPart = 350 * 10000i64;     //整个作业的时间片为350ms(指CPU执行时间)
    //限制最大工作集为256K
    //jbli.MaximumWorkingSetSize = 256 * 1024;
    //jbli.MinimumWorkingSetSize = 4 * 1024;   //这也是页面的大小(4K)

    jbli.LimitFlags = JOB_OBJECT_LIMIT_PROCESS_TIME | JOB_OBJECT_LIMIT_JOB_TIME; /*| JOB_OBJECT_LIMIT_WORKINGSET*/;
    SetInformationJobObject(g_hJob, JobObjectBasicLimitInformation,
                            &jbli, sizeof(JOBOBJECT_BASIC_LIMIT_INFORMATION));

    //创建完成端口对象
    /*
    HANDLE CreateIoCompletionPort (
    HANDLE FileHandle,              // 有效的文件句柄或INVALID_HANDLE_VALUE
    HANDLE ExistingCompletionPort,  // 已经存在的完成端口。如果为NULL,则为新建一个IOCP
    ULONG_PTR CompletionKey,        // completion key是传送给处理函数的参数
    DWORD NumberOfConcurrentThreads // number of threads to execute concurrently
    );
    */
    g_hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, NULL, 1); //1个线程的

    //将Job对象与完成端口对象绑定
    JOBOBJECT_ASSOCIATE_COMPLETION_PORT jobiocp;
    jobiocp.CompletionKey = g_hJob;
    jobiocp.CompletionPort = g_hIOCP;
    SetInformationJobObject(g_hJob, JobObjectAssociateCompletionPortInformation,
                            &jobiocp, sizeof(jobiocp));

    //启动监视Job事件的IOCP线程(1个线程)
    HANDLE hIOCPThread = CreateThread(NULL, 0,(LPTHREAD_START_ROUTINE)IOCPThread
                                      , (LPVOID)g_hIOCP, 0, NULL);


    TCHAR pAppPath[MAX_PATH] = {};
    GetAppPath(pAppPath);
    StringCchCat(pAppPath, MAX_PATH, _T("ErrorShow.exe"));//用课本的ErrorShow程序演示
    
    const int iProcessNums = 3;
    STARTUPINFO si = { sizeof(si) };
    PROCESS_INFORMATION piArray[iProcessNums] = { { 0 }, { 0 }, { 0 } };
    HANDLE h[iProcessNums];
    //以暂停主线程方式创建一个独立的进程(可以改为循环创建多个)
    //注意加上CREATE_BREAKAWAY_FROM_JOB标志
    for (int i = 0; i < iProcessNums; i++)
    {
        CreateProcess(pAppPath, NULL, &sa, &sa, FALSE,
                      CREATE_SUSPENDED | CREATE_BREAKAWAY_FROM_JOB, NULL, NULL, &si, &piArray[i]);
        //将进程与Job对象绑定
        AssignProcessToJobObject(g_hJob, piArray[i].hProcess);
        ResumeThread(piArray[i].hThread); //恢复新进程的主线程
        h[i] = piArray[i].hProcess;

        //查询一些作业对象的统计信息,本例中查询基本统计信息和IO统计信息
        JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION jobioinfo;
        DWORD dwNeedLen = 0;
        QueryInformationJobObject(g_hJob, JobObjectBasicAndIoAccountingInformation,
                                  &jobioinfo, sizeof(jobioinfo), &dwNeedLen);
    }
    //等进程退出
    WaitForMultipleObjects(iProcessNums, h, TRUE, INFINITE);
    for (int i = 0; i < iProcessNums;i++)
    {
        CloseHandle(piArray[i].hProcess);
        CloseHandle(piArray[i].hThread);
    }


    //向IOCPThread函数发送一个退出的消息,指定dwNumberOfBytesTransferred为0
    //该值直接传递给GetQueuedCompletionStatus函数中对应的参数,用来表示事件ID
    PostQueuedCompletionStatus(g_hIOCP,0,(ULONG_PTR)g_hJob,NULL);
    //等待线程退出
    WaitForSingleObject(hIOCPThread,INFINITE);
    CloseHandle(hIOCPThread);

    //关闭作业对象等
    CloseHandle(g_hJob);
    CloseHandle(g_hIOCP);
    _tsystem(TEXT("PAUSE"));
    return 0;
}

DWORD  WINAPI IOCPThread(LPVOID lpParam)
{
    ULONG_PTR hJob = NULL;
    HANDLE   hIocp = (HANDLE)lpParam;
    OVERLAPPED* lpOverlapped = NULL;
    BOOL bLoop = TRUE;
    DWORD dwReasonID = 0; //事件ID,参数lpNumberOfBytes
    DWORD dwProcessID = 0;

    while (bLoop)
    {
        if (!GetQueuedCompletionStatus(hIocp, &dwReasonID, 
                (PULONG_PTR)&hJob, &lpOverlapped, INFINITE))
        {
            _tprintf(_T("IOCPThread:GetQueueCompletionStatus调用失败,错误代码:0x%08x
"),
                     GetLastError());
            continue;
        }

        switch (dwReasonID)
        {
            //作业对象中活动进程数达到上限
        case JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT:
            _tprintf(_T("作业对象中活动进程数己达到上限!
"));
            break;
            
            //作业对象中当前没有活动进程
        case JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO:
            _tprintf(_T("作业对象中当前没有活动进程!
"));
            break;

            //作业对象耗尽指定的时间片
        case JOB_OBJECT_MSG_END_OF_JOB_TIME:
            _tprintf(_T("作业对象耗尽指定的时间片!
"));
            break;

            //作业对象耗尽指定的内存
        case JOB_OBJECT_MSG_JOB_MEMORY_LIMIT:
            dwProcessID = (DWORD)lpOverlapped;
            _tprintf(_T("进程[ID:%u]导致作业对象消耗内存达到上限!
"),dwProcessID);
            break;

            //新进程加入作业对象
        case JOB_OBJECT_MSG_NEW_PROCESS:
            dwProcessID = (DWORD)lpOverlapped;
            _tprintf(_T("进程[ID:%u]加入作业对象[h:0x%08X]!
"), dwProcessID,hJob);
            break;

            //进程正常退出
        case JOB_OBJECT_MSG_EXIT_PROCESS:
            {
                dwProcessID = (DWORD)lpOverlapped;
                DWORD dwExitCode = 0;
                //注意进程退出了,但其内核对象并没释放,还可以从内核对象中获取退出码
                HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwProcessID);
                if (NULL != hProcess)
                {
                    GetExitCodeProcess(hProcess, &dwExitCode);
                    CloseHandle(hProcess);
                }
                _tprintf(_T("进程[ID:%u]正常退出,退出码:%u!
"), dwProcessID, dwExitCode);
            }

            break;

            //进程异常退出
        case JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS:
            {
                dwProcessID = (DWORD)lpOverlapped;
                DWORD dwExitCode = 0;
                //注意进程退出了,但其内核对象并没释放,还可以从内核对象中获取退出码
                HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwProcessID);
                if (NULL != hProcess)
                {
                    GetExitCodeProcess(hProcess, &dwExitCode);
                    CloseHandle(hProcess);
                }
                _tprintf(_T("进程[ID:%u]异常退出,退出码:%u!
"), dwProcessID, dwExitCode);
            }
            break;

            //进程耗尽时间片
        case JOB_OBJECT_MSG_END_OF_PROCESS_TIME:
            dwProcessID = (DWORD)lpOverlapped;
            _tprintf(_T("进程[ID:%u]耗尽时间片!
"), dwProcessID);
            break;

            //进程消耗内存数量达到上限
        case JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT:
            dwProcessID = (DWORD)lpOverlapped;
            _tprintf(_T("进程[ID:%u]消耗内存达到上限!
"), dwProcessID);
            break;

        default:
            bLoop = FALSE;
            break;

        }
    }
    _tprintf(_T("ICOP线程(ID:0x%x退出)
"), GetCurrentThreadId());
    return 0;
}

 【JobLab程序】演示作业限制设置

/******************************************************************************
Module:  JobLab.cpp
Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre
******************************************************************************/

#include "..\..\CommonFilesCmnHdr.h"
#include "resource.h"
#include "Job.h"
#include <tchar.h>
#include <strsafe.h>

//////////////////////////////////////////////////////////////////////////
HWND        g_hwnd;       //对话框句柄(被所有线程访问)
HANDLE        g_hIOCP;      //用来接收作业通知的完成端口
HANDLE      g_hThreadIOCP;//完成端口线程句柄
CJob        g_job;        //作业对象句柄

//完成端口的CompletionKey
#define COMPKEY_TERMINATE   ((UINT_PTR)0)
#define COMPKEY_STATUS      ((UINT_PTR)1)
#define COMPKEY_JOBOBJECT   ((UINT_PTR)2)

//////////////////////////////////////////////////////////////////////////
void GetProcessName(DWORD PID, PTSTR szProcessName, size_t cchSize){
    HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, PID);
    if (hProcess == NULL){
        _tcscpy_s(szProcessName, cchSize, TEXT("???"));
        return;
    }

    ////GetModuleFileNameEx函数须包含Psapi.h,返回值0表示调用失败
    //if (GetModuleFileNameEx(hProcess,(HMODULE)0,szProcessName,cchSize)==0){
    //    //当进程加入作业对象时,其地址空间可能并没有完全初始化时,
    //    //这里调用GetModuleFileNameEx可能会失败。
    //    
    //    //调用失败,可能是文件名复杂的路径,诸如:
    //  //DeviceHarddiskVolume1WindowsSystem32
otepad.exe,可以调用
    //    //GetProcessImageFileName来获得这种路径
    //    if (!GetProcessImageFileName(hProcess, szProcessName, cchSize))
    //        _tcscpy_s(szProcessName, cchSize, TEXT("???"));
    //}

    //以上函数调用,可用下列函数来替换,因为下列函数可适用各种情况。
    DWORD dwSize = (DWORD)cchSize;
    QueryFullProcessImageName(hProcess, 0, szProcessName, &dwSize);
    CloseHandle(hProcess);
}

//////////////////////////////////////////////////////////////////////////
DWORD WINAPI JobNotify(PVOID){
    TCHAR sz[3000];
    BOOL fDone = FALSE;
    ULONG_PTR CompKey; //指定触发这个事件的作业对象的句柄
    LPOVERLAPPED po;   //进程ID
    DWORD dwBytesXferred; //等待的事件ID

    while (!fDone){
        GetQueuedCompletionStatus(g_hIOCP, &dwBytesXferred, 
                                     &CompKey, &po, INFINITE);

        //应用程序关闭,退出线程
        fDone = (CompKey == COMPKEY_TERMINATE);

        //lpClassName为 NULL则查找所有标题与第2个参数的匹配的窗口
        HWND hwndLB = FindWindow(NULL, TEXT("Job Lab"));
        hwndLB = GetDlgItem(hwndLB, IDC_STATUS);

        if (CompKey == COMPKEY_JOBOBJECT) {
            _tcscpy_s(sz, _countof(sz), TEXT("--> Notification:"));
            PTSTR psz = sz + _tcslen(sz);
            switch (dwBytesXferred)
            {
                //作业对象中活动进程数达到上限
            case JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT:
                StringCchPrintf(psz, _countof(sz) - _tcslen(sz), 
                                TEXT("作业对象中活动进程数己达到上限!"));
                break;

                //作业对象中当前没有活动进程
            case JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO:
                StringCchPrintf(psz, _countof(sz) - _tcslen(sz),
                                TEXT("作业对象中当前没有活动进程!"));
                break;

                //作业对象耗尽指定的时间片
            case JOB_OBJECT_MSG_END_OF_JOB_TIME:{
                StringCchPrintf(psz, _countof(sz) - _tcslen(sz),
                                TEXT("作业对象耗尽指定的时间片!"));
                }
                break;

                //作业对象耗尽指定的内存
            case JOB_OBJECT_MSG_JOB_MEMORY_LIMIT:{
                    TCHAR szProcessName[MAX_PATH];
                    GetProcessName(PtrToUlong(po), szProcessName, MAX_PATH);//po存在进程ID
                    StringCchPrintf(psz, _countof(sz) - _tcslen(sz),
                                    TEXT("进程 %s(ID=%d)导致作业对象消耗内存达到上限!"), szProcessName,po);
                }
                break;

                //新进程加入作业对象
            case JOB_OBJECT_MSG_NEW_PROCESS:{
                    TCHAR szProcessName[MAX_PATH];
                    GetProcessName(PtrToUlong(po), szProcessName, MAX_PATH);//po存在进程ID
                    StringCchPrintf(psz, _countof(sz) - _tcslen(sz),
                                    TEXT("进程 %s(ID=%d)加入作业对象!"), szProcessName, po);
                }
                break;

                //进程正常退出
            case JOB_OBJECT_MSG_EXIT_PROCESS:{
                    TCHAR szProcessName[MAX_PATH];
                    DWORD dwExitCode = 0;
                    //注意进程退出了,但其内核对象并没释放,还可以从内核对象中获取退出码
                    HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, PtrToUlong(po));
                    if (NULL != hProcess)
                    {
                        GetExitCodeProcess(hProcess, &dwExitCode);
                        CloseHandle(hProcess);
                    }
                    GetProcessName(PtrToUlong(po), szProcessName, MAX_PATH);//po存在进程ID
                    StringCchPrintf(psz, _countof(sz) - _tcslen(sz),
                                    TEXT("进程 %s(ID=%d)正常退出,退出码(%d)!"), szProcessName, po, dwExitCode);
               }
               break;

            //进程异常退出
            case JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS:{                
                    TCHAR szProcessName[MAX_PATH];
                    DWORD dwExitCode = 0;
                    //注意进程退出了,但其内核对象并没释放,还可以从内核对象中获取退出码
                    HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, PtrToUlong(po));
                    if (NULL != hProcess)
                    {
                        GetExitCodeProcess(hProcess, &dwExitCode);
                        CloseHandle(hProcess);
                    }
                    GetProcessName(PtrToUlong(po), szProcessName, MAX_PATH);//po存在进程ID
                    StringCchPrintf(psz, _countof(sz) - _tcslen(sz),
                                    TEXT("进程 %s(ID=%d)异常退出,退出码(%d)!"), szProcessName, po, dwExitCode);
            }
            break;

            //进程耗尽时间片
            case JOB_OBJECT_MSG_END_OF_PROCESS_TIME:{
                    TCHAR szProcessName[MAX_PATH];
                    GetProcessName(PtrToUlong(po), szProcessName, MAX_PATH);//po存在进程ID
                    StringCchPrintf(psz, _countof(sz) - _tcslen(sz),
                                    TEXT("进程 %s(ID=%d)耗尽时间片!"), szProcessName, po);
               }
                break;

                //进程消耗内存数量达到上限
            case JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT:{
                    TCHAR szProcessName[MAX_PATH];
                    GetProcessName(PtrToUlong(po), szProcessName, MAX_PATH);//po存在进程ID
                    StringCchPrintf(psz, _countof(sz) - _tcslen(sz),
                                    TEXT("进程 %s(ID=%d)消耗内存达到上限!"), szProcessName, po);
                }
                break;

            default:
                StringCchPrintf(psz, _countof(sz) - _tcslen(sz),
                                TEXT("未知通知:%d"), dwBytesXferred);
                break;
            }
            ListBox_SetCurSel(hwndLB, ListBox_AddString(hwndLB, sz));
            CompKey = 1; //当作业通知到达时,强迫更新状态
        }

        //更新作业状态
        if (CompKey ==COMPKEY_STATUS)
        {
            static int s_nStatusCount = 0;
            StringCchPrintf(sz, _countof(sz), 
                            TEXT("-->状态更新(%u)"), s_nStatusCount++);
            ListBox_SetCurSel(hwndLB, ListBox_AddString(hwndLB, sz));
            
            //显示作业对象的基本统计信息
            JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION jobai;
            g_job.QueryBasicAccountingInfo(&jobai);

            StringCchPrintf(sz, _countof(sz), 
                            TEXT("总时间:用户=%I64u,内核=%I64u     ")
                            TEXT("Period时间:用户=%I64u,内核=%I64u"),
                            jobai.BasicInfo.TotalUserTime.QuadPart,
                            jobai.BasicInfo.TotalKernelTime.QuadPart);
            ListBox_SetCurSel(hwndLB, ListBox_AddString(hwndLB, sz));

            StringCchPrintf(sz, _countof(sz),
                            TEXT("页面错误=%u,总进程数=%u, ")
                            TEXT("活动进程数=%u,己终止的进程数=%u"),
                            jobai.BasicInfo.TotalPageFaultCount,
                            jobai.BasicInfo.TotalProcesses,
                            jobai.BasicInfo.ActiveProcesses,
                            jobai.BasicInfo.TotalTerminatedProcesses);
            ListBox_SetCurSel(hwndLB, ListBox_AddString(hwndLB, sz));

            //显示I/O统计信息
            StringCchPrintf(sz, _countof(sz),
                            TEXT("读取=%I64u(I64u bytes), ")
                            TEXT("写入=%I64u(I64u bytes),其它=%I64u(I64u bytes)"),
                            jobai.IoInfo.ReadOperationCount,jobai.IoInfo.ReadTransferCount,
                            jobai.IoInfo.WriteOperationCount,jobai.IoInfo.WriteTransferCount,
                            jobai.IoInfo.OtherOperationCount,jobai.IoInfo.OtherTransferCount);
            ListBox_SetCurSel(hwndLB, ListBox_AddString(hwndLB, sz));

            //显示每个进程和作业的内存峰值
            JOBOBJECT_EXTENDED_LIMIT_INFORMATION joeli;
            g_job.QueryExtendedLimitInfo(&joeli);
            StringCchPrintf(sz, _countof(sz),
                            TEXT("己使用内存峰值:进程=%I64u,作业=%I64u"),
                            (__int64)joeli.PeakProcessMemoryUsed,
                            (__int64)joeli.PeakJobMemoryUsed);
            ListBox_SetCurSel(hwndLB, ListBox_AddString(hwndLB, sz));

            //显示进程ID集
            const int iNum = 50;
            DWORD dwNumProcesses = iNum;
            DWORD dwProcessIDList[iNum];
            g_job.QueryBasicProcessIdList(dwNumProcesses, dwProcessIDList, &dwNumProcesses);
            StringCchPrintf(sz, _countof(sz), TEXT("进程ID集:%s"), 
                            (dwNumProcesses == 0)?TEXT("(none)"):TEXT(""));
            ListBox_SetCurSel(hwndLB, ListBox_AddString(hwndLB, sz));

            TCHAR szProcessName[MAX_PATH];
            for (DWORD x = 0; x < dwNumProcesses; x++)
            {
                GetProcessName(dwProcessIDList[x], szProcessName, _countof(szProcessName));
                StringCchPrintf(sz, _countof(sz), TEXT("    %d - %s"),
                                dwProcessIDList[x],szProcessName);
                ListBox_SetCurSel(hwndLB, ListBox_AddString(hwndLB, sz));
            }
        }
    }

    return 0;
}

void Dlg_ApplayLimits(HWND hwnd){
    const int nNanosecondsPerSecond = 1000000000;//1秒等于多少纳秒
    const int nMillisecondsPerSecond = 1000;     //1s=1000ms
    const int nNanosecondsPerMillisecond =       //1ms 等于多少纳秒
               nNanosecondsPerSecond / nMillisecondsPerSecond; 
    
    __int64 q;
    SIZE_T  s;
    DWORD   d;
    BOOL f;

    //设置作业的基本扩展限制
    JOBOBJECT_EXTENDED_LIMIT_INFORMATION joeli = {};
    joeli.BasicLimitInformation.LimitFlags = 0;
    
    //进程用户模式时间限制——f指出转换是否成功
    q = GetDlgItemInt(hwnd, IDC_PERPROCESSUSERTIMELIMIT, &f, FALSE);
    if (f)
    {
        joeli.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_PROCESS_TIME;
        joeli.BasicLimitInformation.PerProcessUserTimeLimit.QuadPart =
            q*nNanosecondsPerMillisecond / 100;  // ns/ms 
    }

    //作业用户模式时间限制——f指出转换是否成功
    q = GetDlgItemInt(hwnd, IDC_PERJOBUSERTIMELIMIT, &f, FALSE);
    if (f)
    {
        joeli.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_JOB_TIME;
        joeli.BasicLimitInformation.PerJobUserTimeLimit.QuadPart =
            q*nNanosecondsPerMillisecond / 100;  // ns/ms 
    }

    //最小工作集
    s = GetDlgItemInt(hwnd, IDC_MINWORKINGSETSIZE, &f, FALSE);
    if (f){
        joeli.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_WORKINGSET;
        joeli.BasicLimitInformation.MinimumWorkingSetSize = s * 1024 * 1024; //(MB)
        s = GetDlgItemInt(hwnd, IDC_MAXWORKINGSETSIZE, &f, FALSE);
        if (f){
            joeli.BasicLimitInformation.MaximumWorkingSetSize = s * 1024 * 1024; //(MB)
        } else{
            joeli.BasicLimitInformation.LimitFlags &= ~JOB_OBJECT_LIMIT_WORKINGSET;
            MessageBox(hwnd,TEXT("最小和最大工作集要同时设置。
"),NULL,MB_OK|MB_ICONERROR);    
        }
    }

    //最大同时活动进程数
    d = GetDlgItemInt(hwnd, IDC_ACTIVEPROCESSLIMIT, &f, TRUE);
    if (f){
        joeli.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_ACTIVE_PROCESS;
        joeli.BasicLimitInformation.ActiveProcessLimit = d;
    }

    //进程亲缘性掩码
    s = GetDlgItemInt(hwnd, IDC_AFFINITYMASK, &f, FALSE);
    if (f){
        joeli.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_AFFINITY;
        joeli.BasicLimitInformation.Affinity = s;
    }

    //作业优先级
    joeli.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_PRIORITY_CLASS;
    switch (ComboBox_GetCurSel(GetDlgItem(hwnd,IDC_PRIORITYCLASS)))
    {
    case 0:
        joeli.BasicLimitInformation.LimitFlags &=
            ~JOB_OBJECT_LIMIT_PRIORITY_CLASS;
        break;

    case 1:
        joeli.BasicLimitInformation.PriorityClass =
            IDLE_PRIORITY_CLASS;
        break;

    case 2:
        joeli.BasicLimitInformation.PriorityClass =
            BELOW_NORMAL_PRIORITY_CLASS;
        break;

    case 3:
        joeli.BasicLimitInformation.PriorityClass =
            NORMAL_PRIORITY_CLASS;
        break;

    case 4:
        joeli.BasicLimitInformation.PriorityClass =
            ABOVE_NORMAL_PRIORITY_CLASS;
        break;

    case 5:
        joeli.BasicLimitInformation.PriorityClass =
            HIGH_PRIORITY_CLASS;
        break;

    case 6:
        joeli.BasicLimitInformation.PriorityClass =
            REALTIME_PRIORITY_CLASS;
        break;
    }

    //SchedulingClass

    int nSchedulingClass =
        ComboBox_GetCurSel(GetDlgItem(hwnd, IDC_SCHEDULINGCLASS));
    if (nSchedulingClass>0){
        joeli.BasicLimitInformation.LimitFlags |=
            JOB_OBJECT_LIMIT_SCHEDULING_CLASS;
        joeli.BasicLimitInformation.SchedulingClass = nSchedulingClass - 1;
    }

    //作业提交的最大物理页面
    s = GetDlgItemInt(hwnd, IDC_MAXCOMMITPERJOB, &f, FALSE);
    if (f){
        joeli.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_JOB_MEMORY;
        joeli.JobMemoryLimit= s * 1024*1024;
    }

    //进程的提交最大物理页面
    s = GetDlgItemInt(hwnd, IDC_MAXCOMMITPERPROCESS, &f, FALSE);
    if (f){
        joeli.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_PROCESS_MEMORY;
        joeli.ProcessMemoryLimit= s * 1024 * 1024;
    }

    if (IsDlgButtonChecked(hwnd, IDC_CHILDPROCESSESCANBREAKAWAYFROMJOB)){
        joeli.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_BREAKAWAY_OK;
    }

    if (IsDlgButtonChecked(hwnd, IDC_CHILDPROCESSESDOBREAKAWAYFROMJOB)){
        joeli.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK;
    }

    if (IsDlgButtonChecked(hwnd, IDC_TERMINATEPROCESSONEXCEPTIONS)){
        joeli.BasicLimitInformation.LimitFlags |= 
                 JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION;
    }

    f = g_job.SetExtendedLimitInfo(&joeli,
          ((joeli.BasicLimitInformation.LimitFlags & JOB_OBJECT_LIMIT_JOB_TIME)
          !=0)?FALSE:
          IsDlgButtonChecked(hwnd,IDC_PRESERVEJOBTIMEWHENAPPLYINGLIMITS));

    chASSERT(f);//调试版,!f弹对话框

    //设置UI限制
    DWORD jobuir = JOB_OBJECT_UILIMIT_NONE;  //0;
    if (IsDlgButtonChecked(hwnd, IDC_RESTRICTACCESSTOOUTSIDEUSEROBJECTS))
        jobuir |= JOB_OBJECT_UILIMIT_HANDLES;

    if (IsDlgButtonChecked(hwnd, IDC_RESTRICTREADINGCLIPBOARD))
        jobuir |= JOB_OBJECT_UILIMIT_READCLIPBOARD;

    if (IsDlgButtonChecked(hwnd, IDC_RESTRICTWRITINGCLIPBOARD))
        jobuir |= JOB_OBJECT_UILIMIT_WRITECLIPBOARD;

    if (IsDlgButtonChecked(hwnd, IDC_RESTRICTEXITWINDOW))
        jobuir |= JOB_OBJECT_UILIMIT_EXITWINDOWS;

    if (IsDlgButtonChecked(hwnd, IDC_RESTRICTCHANGINGSYSTEMPARAMETERS))
        jobuir |= JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS;

    if (IsDlgButtonChecked(hwnd, IDC_RESTRICTDESKTOPS))
        jobuir |= JOB_OBJECT_UILIMIT_DESKTOP;

    if (IsDlgButtonChecked(hwnd, IDC_RESTRICTDISPLAYSETTINGS))
        jobuir |= JOB_OBJECT_UILIMIT_DISPLAYSETTINGS;

    if (IsDlgButtonChecked(hwnd, IDC_RESTRICTGLOBALATOMS))
        jobuir |= JOB_OBJECT_UILIMIT_GLOBALATOMS;

    chVERIFY(g_job.SetBasicUIRestrictions(jobuir));
}

//////////////////////////////////////////////////////////////////////////
BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam)
{
    chSETDLGICONS(hwnd, IDI_JOBLAB);

    //保存窗口句柄,以便在完成端口线程中可以访问到
    g_hwnd = hwnd;

    HWND hwndPriortityClass = GetDlgItem(hwnd, IDC_PRIORITYCLASS);
    ComboBox_AddString(hwndPriortityClass, TEXT("No Limit"));
    ComboBox_AddString(hwndPriortityClass, TEXT("Idle"));
    ComboBox_AddString(hwndPriortityClass, TEXT("Below normal"));
    ComboBox_AddString(hwndPriortityClass, TEXT("Normal"));
    ComboBox_AddString(hwndPriortityClass, TEXT("Above normal"));
    ComboBox_AddString(hwndPriortityClass, TEXT("High"));
    ComboBox_AddString(hwndPriortityClass, TEXT("RealTime"));
    ComboBox_SetCurSel(hwndPriortityClass, 0); //默认选中“No Limit”

    HWND hwndSchedlingClass = GetDlgItem(hwnd, IDC_SCHEDULINGCLASS);
    ComboBox_AddString(hwndSchedlingClass, TEXT("No Limit"));
    for (int n = 0; n <= 9;n++)
    {
        TCHAR szSchedulingClass[2];
        StringCchPrintf(szSchedulingClass, _countof(szSchedulingClass), 
                        TEXT("%u"),n);
        ComboBox_AddString(hwndSchedlingClass, szSchedulingClass);
    }
    ComboBox_SetCurSel(hwndSchedlingClass, 0); //默认选中“No Limit”

    SetTimer(hwnd, 1, 10000, NULL);    //每10秒更新一次作业对象的统计信息
    return TRUE;
}

//////////////////////////////////////////////////////////////////////////
void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtrl, UINT codeNotify)
{
    switch (id)
    {
    case IDCANCEL:
        //用户关闭应用程序并结束作业
        KillTimer(hwnd, 1);
        g_job.Terminate(0);
        EndDialog(hwnd, id);
        break;

    case IDC_PERJOBUSERTIMELIMIT:{
            //根据是否输入作业时间限制,来启用/禁用“调整限制时保留作业时间”复选框
            BOOL f;
            GetDlgItemInt(hwnd, IDC_PERJOBUSERTIMELIMIT, &f, FALSE);
            EnableWindow(GetDlgItem(hwnd, IDC_PRESERVEJOBTIMEWHENAPPLYINGLIMITS), !f);
        }
        break;

    case IDC_APPLYLIMITS:
        Dlg_ApplayLimits(hwnd);
        PostQueuedCompletionStatus(g_hIOCP,0, COMPKEY_STATUS, NULL);
        break;

    case IDC_TERMINATE:
        g_job.Terminate(0);
        PostQueuedCompletionStatus(g_hIOCP, 0, COMPKEY_STATUS, NULL);
        break;

    case IDC_SPAWNCMDINJOB:{
             //创建子进程
            STARTUPINFO si = { sizeof(si) };
            PROCESS_INFORMATION pi;
            TCHAR sz[] = TEXT("CMD");
            CreateProcess(NULL, sz, NULL, NULL, FALSE, 
                          CREATE_SUSPENDED, NULL, NULL, &si, &pi);
            g_job.AssignProcess(pi.hProcess); //这里本身会发出通知
            ResumeThread(pi.hThread);
            CloseHandle(pi.hProcess);
            CloseHandle(pi.hThread);
            //PostQueuedCompletionStatus(g_hIOCP, 0, COMPKEY_STATUS, NULL);
        }

        break;

    case IDC_ASSIGNPROCESSTOJOB:{ //可以从任务管理器中查看某进程PID,并加入到该作业中来
            DWORD dwProcessID = GetDlgItemInt(hwnd, IDC_PROCESSID, NULL, FALSE);
            //ROCESS_SET_QUOTA :使进程可以调用AssignProcessToJobObject加入作业和SetProcessWorkingSetSize设置内存限制
            //PROCESS_TERMINATE:使进程可以调用TerminateProcess来结束程序
            HANDLE hProcess = OpenProcess(PROCESS_SET_QUOTA | PROCESS_TERMINATE, FALSE, dwProcessID);
            if (hProcess != NULL){
                chVERIFY(g_job.AssignProcess(hProcess));
                CloseHandle(hProcess);
            } else MessageBox(hwnd, TEXT("该进程不能加入作业!"), 
                                TEXT("错误提示"), MB_OK | MB_ICONEXCLAMATION);

            PostQueuedCompletionStatus(g_hIOCP, 0, COMPKEY_STATUS, NULL);
        }
        break;
    }
}
//////////////////////////////////////////////////////////////////////////
void WINAPI Dlg_OnTimer(HWND hwnd, UINT id){
    PostQueuedCompletionStatus(g_hIOCP, 0, COMPKEY_STATUS, NULL);
}

//////////////////////////////////////////////////////////////////////////
INT_PTR WINAPI Dlg_Proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Dlg_OnInitDialog);
        chHANDLE_DLGMSG(hwnd, WM_TIMER, Dlg_OnTimer);
        chHANDLE_DLGMSG(hwnd, WM_COMMAND, Dlg_OnCommand);
    }
    return FALSE;
}



//////////////////////////////////////////////////////////////////////////
int WINAPI _tWinMain(HINSTANCE hInstExe, HINSTANCE, PTSTR szCmdLine, int)
{
    //创建完成端口来接收作业通知
    //NumberOfConcurrentThreads表示访问该消息队列的线程数,为0时表示与处理器个数相等
    g_hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);

    //创建1个线程来等待完成端口
    g_hThreadIOCP = chBEGINTHREADEX(NULL, 0, JobNotify, NULL, 0, NULL);

    //创建一个作业对象
    g_job.Create(NULL, TEXT("JobLab"));
    g_job.SetEndOfJobInfo(JOB_OBJECT_POST_AT_END_OF_JOB);
    g_job.AssociateCompletionPort(g_hIOCP, COMPKEY_JOBOBJECT);

    DialogBox(hInstExe, MAKEINTRESOURCE(IDD_JOBLAB), NULL, Dlg_Proc);

    //投递一个“退出”消息给完成端口,以便结束作业对象
    PostQueuedCompletionStatus(g_hIOCP, 0, COMPKEY_TERMINATE, NULL);

    //等待完成端口结束各线程
    WaitForSingleObject(g_hThreadIOCP, INFINITE);

    //清理
    CloseHandle(g_hIOCP);
    CloseHandle(g_hThreadIOCP);
    return (0);
}

//Job.h文件

/******************************************************************************
Module:  Job.h
Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre
******************************************************************************/
#pragma once

//////////////////////////////////////////////////////////////////////////
#include <malloc.h>   //for _alloca

class CJob{
private:
    HANDLE m_hJob;
public:
    CJob(HANDLE hJob = NULL);
    ~CJob();

    operator HANDLE() const { return (m_hJob); } //重载()运算符

    //用于打开或创建一个作业对象
    BOOL Create(PSECURITY_ATTRIBUTES psa = NULL, PCTSTR pszName = NULL);
    BOOL Open(PCTSTR pszName, DWORD dwDesiredAccess, BOOL fInheritHandle = FALSE);

    //用来操作作业对象的函数
    BOOL AssignProcess(HANDLE hProcess);
    BOOL Terminate(UINT uExitCode = 0);

    //用于设置或限制作业对象
    BOOL SetExtendedLimitInfo(PJOBOBJECT_EXTENDED_LIMIT_INFORMATION pjoeli, BOOL fPreserveJobTime = FALSE);
    BOOL SetBasicUIRestrictions(DWORD fdwLimits);
    BOOL GrantUserHandleAccess(HANDLE hUserObj, BOOL fGrant = TRUE); //设置访问作业外用户对象能力
    BOOL SetSecurityLimitInfo(PJOBOBJECT_SECURITY_LIMIT_INFORMATION pjosli);


    //查询作业限制信息
    BOOL QueryExtendedLimitInfo(PJOBOBJECT_EXTENDED_LIMIT_INFORMATION pjoeli);
    BOOL QueryBasicUIRestrictions(PDWORD fdwRestrictions);
    BOOL QuerySecurityLimitInfo(PJOBOBJECT_SECURITY_LIMIT_INFORMATION pjosli);

    //查询作业状态信息
    BOOL QueryBasicAccountingInfo(PJOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION pjobai);
    BOOL QueryBasicProcessIdList(DWORD dwMaxProcesses, PDWORD pdwProcessIdList, 
                                  PDWORD pdwProcessesReturned = NULL);

    //设置或查询作业的通知事件
    BOOL AssociateCompletionPort(HANDLE hIOCP, ULONG_PTR CompKey);
    BOOL QueryAssociatedCompletionPort(PJOBOBJECT_ASSOCIATE_COMPLETION_PORT pjoacp);
    BOOL SetEndOfJobInfo(DWORD fdwEndOfJobInfo = JOB_OBJECT_TERMINATE_AT_END_OF_JOB);
    BOOL QueryEndOfJobTimeInfo(PDWORD pfdwEndOfJobTimeInfo);
};

//////////////////////////////////////////////////////////////////////////
inline CJob::CJob(HANDLE hJob){
    m_hJob = hJob;
}

//////////////////////////////////////////////////////////////////////////
inline CJob::~CJob(){
    if (NULL != m_hJob)
        CloseHandle(m_hJob);
}

//////////////////////////////////////////////////////////////////////////
inline BOOL CJob::Create(PSECURITY_ATTRIBUTES psa, PCTSTR pszName){
    m_hJob = CreateJobObject(psa, pszName);
    return (m_hJob != NULL);
}

//////////////////////////////////////////////////////////////////////////
inline BOOL CJob::Open(PCTSTR pszName, DWORD dwDesiredAccess, BOOL fInheritHandle){
    m_hJob = OpenJobObject(dwDesiredAccess, fInheritHandle, pszName);
}

//////////////////////////////////////////////////////////////////////////
inline BOOL CJob::AssignProcess(HANDLE hProcess){
    return (AssignProcessToJobObject(m_hJob, hProcess));
}

//////////////////////////////////////////////////////////////////////////
inline BOOL CJob::Terminate(UINT uExitCode /* = 0 */){
    return (TerminateJobObject(m_hJob, uExitCode));
}

//////////////////////////////////////////////////////////////////////////
inline BOOL CJob::SetExtendedLimitInfo(
         PJOBOBJECT_EXTENDED_LIMIT_INFORMATION pjoeli, BOOL fPreserveJobTime /* = FALSE */){
    
    if (fPreserveJobTime)
        pjoeli->BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_PRESERVE_JOB_TIME;
    
    //如果要保留作业时间信息,则JOB_OBJECT_LIMIT_JOB_TIME标志必须去掉

    const DWORD fdwFlagTest =
        (JOB_OBJECT_LIMIT_JOB_TIME | JOB_OBJECT_LIMIT_PRESERVE_JOB_TIME);

    if ((pjoeli->BasicLimitInformation.LimitFlags & fdwFlagTest) == fdwFlagTest)
        DebugBreak();    //这两个标志位是互斥的,但现在两者同时被设置了。

    return (SetInformationJobObject(m_hJob, JobObjectExtendedLimitInformation, 
                 pjoeli,sizeof(*pjoeli)));
}

//////////////////////////////////////////////////////////////////////////
inline BOOL CJob::SetBasicUIRestrictions(DWORD fdwLimits){
    JOBOBJECT_BASIC_UI_RESTRICTIONS jobuir = { fdwLimits };
    return (SetInformationJobObject(m_hJob, 
               JobObjectBasicUIRestrictions, &jobuir, sizeof(jobuir)));
}

//////////////////////////////////////////////////////////////////////////
inline BOOL CJob::SetSecurityLimitInfo(PJOBOBJECT_SECURITY_LIMIT_INFORMATION pjosli){
    return (SetInformationJobObject(m_hJob,
               JobObjectSecurityLimitInformation,pjosli, sizeof(*pjosli)));
}

//////////////////////////////////////////////////////////////////////////
inline BOOL CJob::GrantUserHandleAccess(HANDLE hUserObj, BOOL fGrant /* = TRUE */){
    return UserHandleGrantAccess(hUserObj, m_hJob, fGrant);  //API
}

//////////////////////////////////////////////////////////////////////////
inline BOOL CJob::QueryExtendedLimitInfo(PJOBOBJECT_EXTENDED_LIMIT_INFORMATION pjoeli){
    return (QueryInformationJobObject(m_hJob,JobObjectExtendedLimitInformation,
              pjoeli,sizeof(*pjoeli),NULL));
}

//////////////////////////////////////////////////////////////////////////
inline BOOL CJob::QueryBasicUIRestrictions(PDWORD pfdwRestrictions){
    JOBOBJECT_BASIC_UI_RESTRICTIONS jobuir;
    BOOL fOk = QueryInformationJobObject(m_hJob, JobObjectBasicUIRestrictions,
                                         &jobuir, sizeof(jobuir), NULL);
    if (fOk)
        *pfdwRestrictions = jobuir.UIRestrictionsClass;
    return (fOk);
}

//////////////////////////////////////////////////////////////////////////
inline BOOL CJob::QuerySecurityLimitInfo(PJOBOBJECT_SECURITY_LIMIT_INFORMATION pjosli){
    return (QueryInformationJobObject(m_hJob, JobObjectSecurityLimitInformation,
        pjosli, sizeof(*pjosli), NULL));
}

//////////////////////////////////////////////////////////////////////////
inline BOOL CJob::QueryBasicAccountingInfo(PJOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION pjobai){
    return (QueryInformationJobObject(m_hJob,
        JobObjectBasicAndIoAccountingInformation, pjobai, sizeof(*pjobai),NULL));
}

//////////////////////////////////////////////////////////////////////////
inline BOOL CJob::QueryBasicProcessIdList(DWORD dwMaxProcesses, PDWORD pdwProcessIdList, 
                                               PDWORD pdwProcessesReturned /* = NULL */){
    //计算所需的空间大小
    DWORD cb = sizeof(JOBOBJECT_BASIC_PROCESS_ID_LIST) + 
                 (sizeof(DWORD)*(dwMaxProcesses -1));

    //从栈上分配内存(注意,不是堆,所有无需释放)
    PJOBOBJECT_BASIC_PROCESS_ID_LIST pjobpil =
        (PJOBOBJECT_BASIC_PROCESS_ID_LIST)_alloca(cb);

    BOOL fOk = (pjobpil != NULL);
    if (fOk)
    {
        //告知函数,我们分配的最大空间大小
        pjobpil->NumberOfProcessIdsInList = dwMaxProcesses; 

        //请求返回当前进程集ID
        fOk = QueryInformationJobObject(m_hJob,
                        JobObjectBasicProcessIdList,pjobpil,cb,NULL);

        if (fOk)
        {
            //得到信息,并返回给调用者
            if (pdwProcessesReturned != NULL)
                *pdwProcessesReturned = pjobpil->NumberOfProcessIdsInList;

            CopyMemory(pdwProcessIdList, pjobpil->ProcessIdList,
                          sizeof(DWORD)*pjobpil->NumberOfProcessIdsInList);
        }
    }
    return fOk;
}

//////////////////////////////////////////////////////////////////////////
inline BOOL CJob::AssociateCompletionPort(HANDLE hIOCP, ULONG_PTR CompKey){
    JOBOBJECT_ASSOCIATE_COMPLETION_PORT joacp;
    joacp.CompletionPort = hIOCP;
    joacp.CompletionKey = (PVOID)CompKey;

    return (SetInformationJobObject(m_hJob,
          JobObjectAssociateCompletionPortInformation,&joacp,sizeof(joacp)));
}

//////////////////////////////////////////////////////////////////////////
inline BOOL CJob::QueryAssociatedCompletionPort(PJOBOBJECT_ASSOCIATE_COMPLETION_PORT pjoacp){
    return (QueryInformationJobObject(m_hJob,
        JobObjectAssociateCompletionPortInformation,pjoacp,sizeof(*pjoacp),NULL));
}

//////////////////////////////////////////////////////////////////////////
inline BOOL CJob::SetEndOfJobInfo(DWORD fdwEndOfJobInfo /* = JOB_OBJECT_TERMINATE_AT_END_OF_JOB */){
    JOBOBJECT_END_OF_JOB_TIME_INFORMATION joeojti = { fdwEndOfJobInfo };
    return (SetInformationJobObject(m_hJob,
        JobObjectEndOfJobTimeInformation, &joeojti, sizeof(joeojti)));
}

//////////////////////////////////////////////////////////////////////////
inline BOOL CJob::QueryEndOfJobTimeInfo(PDWORD pfdwEndOfJobTimeInfo){
    JOBOBJECT_END_OF_JOB_TIME_INFORMATION joeojti;
    BOOL fOk = QueryInformationJobObject(m_hJob, 
                    JobObjectEndOfJobTimeInformation, &joeojti, sizeof(joeojti), NULL);

    if (fOk)
        *pfdwEndOfJobTimeInfo = joeojti.EndOfJobTimeAction;
    
    return (fOk);
}
///////////////////////////// End of File //////////////////////////////////////////

//resource.h

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ 生成的包含文件。
// 供 05_JobLab.rc 使用
//
#define IDI_JOBLAB                      101
#define IDD_JOBLAB                      102
#define IDC_CHILDPROCESSESCANBREAKAWAYFROMJOB 1001
#define IDC_CHILDPROCESSESDOBREAKAWAYFROMJOB 1002
#define IDC_TERMINATEPROCESSONEXCEPTIONS 1003
#define IDC_PRESERVEJOBTIMEWHENAPPLYINGLIMITS 1004
#define IDC_RESTRICTACCESSTOOUTSIDEUSEROBJECTS 1006
#define IDC_RESTRICTCHANGINGSYSTEMPARAMETERS 1007
#define IDC_RESTRICTREADINGCLIPBOARD    1008
#define IDC_RESTRICTDESKTOPS            1009
#define IDC_RESTRICTWRITINGCLIPBOARD    1010
#define IDC_RESTRICTDISPLAYSETTINGS     1011
#define IDC_RESTRICTEXITWINDOW          1012
#define IDC_RESTRICTGLOBALATOMS         1013
#define IDC_STATUS                      1014
#define IDC_APPLYLIMITS                 1015
#define IDC_PRIORITYCLASS               1016
#define IDC_TERMINATE                   1017
#define IDC_SPAWNCMDINJOB               1018
#define IDC_ASSIGNPROCESSTOJOB          1019
#define IDC_SCHEDULINGCLASS             1020
#define IDC_PERPROCESSUSERTIMELIMIT     1021
#define IDC_PERJOBUSERTIMELIMIT         1022
#define IDC_MINWORKINGSETSIZE           1023
#define IDC_MAXWORKINGSETSIZE           1024
#define IDC_ACTIVEPROCESSLIMIT          1025
#define IDC_AFFINITYMASK                1026
#define IDC_MAXCOMMITPERJOB             1027
#define IDC_MAXCOMMITPERPROCESS         1028
#define IDC_PROCESSID                   1029

// Next default values for new objects
// 
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        103
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1023
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif

//5_JobLab.rc

// Microsoft Visual C++ generated resource script.
//
#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"

/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS

/////////////////////////////////////////////////////////////////////////////
// 中文(简体,中国) resources

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED

#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//

1 TEXTINCLUDE 
BEGIN
    "resource.h"
END

2 TEXTINCLUDE 
BEGIN
    "#include ""winres.h""
"
    ""
END

3 TEXTINCLUDE 
BEGIN
    "
"
    ""
END

#endif    // APSTUDIO_INVOKED


/////////////////////////////////////////////////////////////////////////////
//
// Icon
//

// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_JOBLAB              ICON                    "JobLab.ico"

/////////////////////////////////////////////////////////////////////////////
//
// Dialog
//

IDD_JOBLAB DIALOGEX 0, 0, 305, 270
STYLE DS_SETFONT | DS_FIXEDSYS | DS_CENTER | WS_MINIMIZEBOX | WS_CAPTION | WS_SYSMENU
CAPTION "Job Lab"
FONT 9, "MS Shell Dlg", 400, 0, 0x1
BEGIN
    GROUPBOX        "基本和扩展限制",IDC_STATIC,16,7,274,120
    CONTROL         "进程用户时间上限(ms)",IDC_STATIC,"Static",SS_LEFTNOWORDWRAP | WS_GROUP,22,21,79,8
    EDITTEXT        IDC_PERPROCESSUSERTIMELIMIT,107,18,40,12,ES_RIGHT | ES_AUTOHSCROLL | ES_NUMBER
    LTEXT           "作业时间上限(ms)",IDC_STATIC,153,21,63,8
    EDITTEXT        IDC_PERJOBUSERTIMELIMIT,242,18,40,12,ES_RIGHT | ES_AUTOHSCROLL | ES_NUMBER
    LTEXT           "最小工作集(MB)",IDC_STATIC,22,36,56,8
    EDITTEXT        IDC_MINWORKINGSETSIZE,107,33,40,12,ES_RIGHT | ES_AUTOHSCROLL | ES_NUMBER
    LTEXT           "最大工作集(MB)",IDC_STATIC,153,36,56,8
    EDITTEXT        IDC_MAXWORKINGSETSIZE,242,34,40,12,ES_RIGHT | ES_AUTOHSCROLL | ES_NUMBER
    LTEXT           "同时活动进程数量",IDC_STATIC,22,51,65,8
    EDITTEXT        IDC_ACTIVEPROCESSLIMIT,107,48,40,12,ES_RIGHT | ES_AUTOHSCROLL | ES_NUMBER
    LTEXT           "进程亲缘性掩码(10进制)",IDC_STATIC,153,51,86,8
    EDITTEXT        IDC_AFFINITYMASK,242,50,40,12,ES_RIGHT | ES_AUTOHSCROLL | ES_NUMBER
    LTEXT           "优先级",IDC_STATIC,22,66,25,8
    COMBOBOX        IDC_PRIORITYCLASS,99,63,48,200,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
    LTEXT           "线程调度类型",IDC_STATIC,153,66,49,8
    COMBOBOX        IDC_SCHEDULINGCLASS,234,66,48,200,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
    LTEXT           "作业提交物理页面(MB)",IDC_STATIC,22,81,77,8
    EDITTEXT        IDC_MAXCOMMITPERJOB,107,78,40,12,ES_RIGHT | ES_AUTOHSCROLL | ES_NUMBER
    LTEXT           "进程提交物理页面(MB)",IDC_STATIC,153,81,81,8
    EDITTEXT        IDC_MAXCOMMITPERPROCESS,242,81,40,12,ES_RIGHT | ES_AUTOHSCROLL | ES_NUMBER
    CONTROL         "子进程可以作业中分离出来",IDC_CHILDPROCESSESCANBREAKAWAYFROMJOB,
                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,22,96,112,10
    CONTROL         "将子进程从作业中分离出来",IDC_CHILDPROCESSESDOBREAKAWAYFROMJOB,
                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,153,96,110,10
    CONTROL         "进程遇未知错误终止运行",IDC_TERMINATEPROCESSONEXCEPTIONS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,22,112,104,10
    CONTROL         "调整限制时保留作业时间",IDC_PRESERVEJOBTIMEWHENAPPLYINGLIMITS,
                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,153,112,104,10
    GROUPBOX        "UI访问限制",IDC_STATIC,16,129,276,71
    CONTROL         "禁止访问外部用户对象",IDC_RESTRICTACCESSTOOUTSIDEUSEROBJECTS,
                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,21,142,96,10
    CONTROL         "禁止改变系统参数",IDC_RESTRICTCHANGINGSYSTEMPARAMETERS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,136,142,80,10
    CONTROL         "禁止读取剪贴板",IDC_RESTRICTREADINGCLIPBOARD,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,21,156,72,10
    CONTROL         "禁止创建/切换桌面",IDC_RESTRICTDESKTOPS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,136,156,83,10
    CONTROL         "禁止写入剪贴板",IDC_RESTRICTWRITINGCLIPBOARD,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,21,170,72,10
    CONTROL         "禁止改变显示设置",IDC_RESTRICTDISPLAYSETTINGS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,136,170,80,10
    CONTROL         "禁止调用ExitWindows函数",IDC_RESTRICTEXITWINDOW,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,21,184,105,10
    CONTROL         "禁用全局原语表",IDC_RESTRICTGLOBALATOMS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,136,184,72,10
    LISTBOX         IDC_STATUS,15,203,277,61,NOT LBS_NOTIFY | LBS_NOINTEGRALHEIGHT | LBS_NOSEL | WS_VSCROLL | WS_TABSTOP
    PUSHBUTTON      "应用限制",IDC_APPLYLIMITS,217,136,66,14
    PUSHBUTTON      "终止所有进程",IDC_TERMINATE,217,152,66,14
    PUSHBUTTON      "创建CMD子进程",IDC_SPAWNCMDINJOB,217,168,66,14
    PUSHBUTTON      "PID加入作业",IDC_ASSIGNPROCESSTOJOB,231,184,51,14
    EDITTEXT        IDC_PROCESSID,205,185,25,12,ES_RIGHT | ES_AUTOHSCROLL | ES_NUMBER
END


/////////////////////////////////////////////////////////////////////////////
//
// DESIGNINFO
//

#ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO
BEGIN
    IDD_JOBLAB, DIALOG
    BEGIN
        LEFTMARGIN, 7
        RIGHTMARGIN, 298
        TOPMARGIN, 7
        BOTTOMMARGIN, 267
    END
END
#endif    // APSTUDIO_INVOKED

#endif    // 中文(简体,中国) resources
/////////////////////////////////////////////////////////////////////////////



#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//


/////////////////////////////////////////////////////////////////////////////
#endif    // not APSTUDIO_INVOKED

 

 

原文地址:https://www.cnblogs.com/5iedu/p/4690974.html