第3章 内核对象(2)

3.3 跨进程边界共享内核对象

3.3.1 使用对象句柄继承

(1)对象句柄继承,只发生在进程之间有父子关系的时候(即一个进程而另一个进程CreateProcess起来)

(2)内核对象句柄继承的实现

①父进程必须先指出哪些内核对象句柄是可继承(注意不是内核对象本身的继承,而是内核对象的句柄继承),父进程在创建内核对象时要将SECURITY_ATTRIBUTES的bInheritHandle字段设为TRUE,表示可继承。这时句柄表中相应的记录项的标志位被设为1,否则为0。

②父进程调用CreateProcess创建子进程,其中的bInheritHandles设为TRUE,表示希望子进程继承父进程句柄表的中“可继承句柄”。这时新创建的子进程不会立即执行,而是先遍历父进程的句柄表,并将每一个有效的“可继承句柄”完整地复制到子进程的句柄表,并且复制项的位置与父进程的位置完全一致。(即两句柄的索引值一样,如索引值为3,则子进程句柄表中也有3个记录项,其中第3个就是父进程可继承句柄,其余两个被标记为不可用,见课本44表3-3),并递增内核对象的使用计数

③如果子进程再调用CreateProcess生成自己的子进程(父进程的孙进程),并且bInheritHandles也设为TRUE,则孙进程也会继承这个内核对象,并在孙进程的句柄表中,继承的对象句柄具有相同的句柄值、访问掩码及标志。

④为了让子进程得到这个内核对象的句柄值,可通过命令行参数或通过向父进程环境块添加一个环境变量(环境变量也是会被继承的)或其他进程间通信技述,将句柄值从父进程传递给子进程。

⑤为了销毁内核对象,父子乃至孙进程都要调用CloseHandle(因为他们使用的是相同的内核对象),只有都CloseHandle,使用计数值才会减为0。

(3)对象句柄的继承只发生在生成子进程的时候,如果父进程后来又创建了一个可继承的内核对象,正在运行的子进程是不会继承这个新的内核对象句柄的。

(4)改变句柄的标志

①使用场景,如父进程的可继承句柄只希望被多个子进程中的其中一个继承。即改变了这句柄标志,只影响之后创建的子进程。

②SetHandleInformation函数——改变句柄的标志


参数


描述


HANDLE hObject


在本进程中要改变标志的内核对象句柄,会影响该进程的子进程


DWORD dwMask


想更改哪个或者哪些标志位

HANDLE_FLAG_INHERIT(1)——是否被继承标志

HANDLE_FLAG_PROTECT_FROM_CLOSE(2)——允许或禁止关闭句柄


DWORD dwFlags


将上述的标志位设成什么样的值

★改变内核对象句柄可继承标志

打开继承:SetHandleInformation(hObj,HANDLE_FLAG_INHERIT,HANDLE_FLAG_INHERIT)

关闭继承:SetHandleInformation(hObj,HANDLE_FLAG_INHERIT,0)

★改变可关闭标志

禁止关闭:SetHandleInformation(hObj, HANDLE_FLAG_PROTECT_FROM_CLOSE,

HANDLE_FLAG_PROTECT_FROM_CLOS)

允许关闭:SetHandleInformation(hObj, HANDLE_FLAG_PROTECT_FROM_CLOSE,0)

这时如果任何一个进程(含父子进程)调用CloseHandle都会引发异常。但要注意在子进程里面如果又将该内核对象的该标志更改为允许关闭的,那该对象照样会被关闭

③获取内核对象有关的标志信息GetHandleInformation,如获取是否可以继承

DWORD dwFlags;

GetHandleInformation(hObj,&dwFlags);

BOOL fHandleIsInheritable =(0!=(dwFlags & HANDLE_FLAG_INHERIT));

3.3.2 为对象命名

3.3.2.1 创建一般的命名对象

(1)创建内核对象的函数一般最后一个参数是pszName,当为NULL表示创建匿名内核对象。否则创建一个以pszName参数命名的对象(但Windows内部并没有办法保证该名称是唯一的,即使内核对象的类型不同也不行),如果创建同名对象,会返回NULL,用GetLastError得到ERROR_INVALID_HANDLE,但这错误代码说明不了什么问题。

(2)利用对象命名来共享内核对象时,两个进程不一定是父子关系、内核对象句柄也可以不是可继承的

①以创建互斥量内核对象为例来分析

进程A:HANDLE hMutexProcessA = CreateMutex(NULL,FALSE,TEXT("JeffMutex"));

进程B:HANDLE hMutexProcessB = CreateMutex(NULL,FALSE,TEXT("JeffMutex"));

②系统首先检查"JeffMutex"内核对象是否存在,然后检查对象类型是否相同,接着进行访问权限的检查。如果都通过,就在进程B的句柄表中找到一个空白记录项,让其指向现有的内核对象,这时进程B得到的对象与进程A实际上是同一个对象,实际上的真正的创建,只是将使用计数增加1。(注意两个对象的句柄值可能不一样这与句柄继承机制是不同的!)

③进程B创建内核对象时,如果指定名称的对象确实存在,则其CreateMutex函数中的安全属性信息和第2个参数将被忽略

④要判断是新创建一个内核对象还是打开一个现有的,可在CreateMutex之后,调用GetLastError函数,当错误代码为ERROR_ALREADY_EXISTS时,表示打开一个现有的。

⑤为了实现内核对象的共享,也可以考虑用Open*函数,找到指定名称的对象时,会在自己所在的进程句柄表中增加一个相应的记录项,并使该对象的使用计数递增。该函数与Create*主要区别在于,如果对象不存在,Create*会创建它,而Open*不会,只是简单地以调用失败而告终。还有一个不同,Open*还可以指定打开的内核对象是否可继承

⑥因Windows内部没办法保证名称的唯一性,建议用GUID来作对象的名称,以达到对象名称不重复。(方法是VS→工具→创建GUID)

【Singleton1程序】利用互斥量对象实现

#include <stdio.h>
#include <windows.h>

int main()
{

    //利用GUID生成唯一的锁名,防止内核对象重名
    HANDLE h = CreateMutex(NULL, FALSE,
            TEXT("{349210D3-EF54-4EC9-8313-9F47435D785D}"));

    if (GetLastError()== ERROR_ALREADY_EXISTS)
    {
        CloseHandle(h);
        return 0;
    }

    printf("单例程序正在运行中...\n");
    //为了演示,这里暂停一下cmd输出窗口
    system("pause");

    CloseHandle(h);
    return 0;

}

3.3.2.2 终端服务命名空间

(1)终端服务(Terminal Services),其工作原理是客户机和服务器通过TCP/IP协议和标准的局域网构架联系。通过客户端终端,客户机的鼠标、键盘的输入传递到终端服务器上,再把服务器上的显示传递回客户端。客户端不需要具有计算能力,至多只需提供一定的缓存能力。众多的客户端可以同时登录到服务器上,仿佛同时在服务器上工作一样,它们之间作为不同的会话连接是互相独立的。此外,远程桌面(Remote Desktop)和快速用户切换也是利用终端服务会话来实现的。(快速用户切换的方法:按Win+L或当登录两用户后,在任务管理器→用户→选择相应的用户,然后右键,连接

(2)全局命名空间和会话私有的命名空间

全局命名空间的内核对象供所有客户端会话共享,在全局空间中创建内核对象要在名称前加“Global\”为前缀,如

HANDLE  h = CreateEvent(NULL,FALSE,FALSE,TEXT("Global\\JeffEvent"))

会话私有的命名空间(默认),名称前加“Local\”前缀或省略前缀

HANDLE  h = CreateEvent(NULL,FALSE,FALSE,TEXT("Local\\JeffEvent"))

HANDLE  h = CreateEvent(NULL,FALSE,FALSE,TEXT("JeffEvent"))//同上

【TerminalService】终端服务命名空间中内核对象的测试程序

登录Windows帐号A,运行本程序两个实例,测试结果

保持帐号A的程序仍在运行中,再登录Windows帐号B,运行本程序两个实例,测试结果

/*-----------------------------------------------------------------------------------
   TerminalService程序需要用登录不同的Windows帐号同时运行,才能看出效果!
   建议程序测试流程:
   1、先登录Windows帐号A,运行两个本程序实例
   2、保持上述两个实例仍在运行中,再登录Windows帐号B,再运行两个实例
-----------------------------------------------------------------------------------*/
#include <stdio.h>
#include <windows.h>

int main()
{
    //先显示进程ID号和所在的会话ID
    DWORD processID = GetCurrentProcessId();
    DWORD sessionID;

    if (ProcessIdToSessionId(processID, &sessionID))
    {
        wprintf(TEXT("Process ‘%u‘ runs in Terminal Services session ‘%u‘\n"),
            processID, sessionID);

        //测试,尝试在全局命名空间中创建内核对象,实验在不同帐户下同时运行该程序时,
        //第2个启动的程序会提示内核对象己存在的错误
        HANDLE hGlobalMutex = CreateMutex(NULL, FALSE, TEXT("Global\\MyMutex"));

        if (hGlobalMutex == NULL || ERROR_ALREADY_EXISTS == GetLastError())
            printf("错误提示:全局命名空间己经存在名称为“MyMutex”的内核对象!\n");
        else
            printf("在全局命名空间中成功创建名称为“MyMutex”内核对象!\n");

        //在局部命名空间中创建内核对象,在不同帐户下同时运行该程序,可以创建同名的内核对象
        HANDLE hLocalMutex = CreateMutex(NULL, FALSE, TEXT("Local\\MyMutex"));
        //HANDLE hLocalMutex = CreateMutex(NULL, FALSE, TEXT("MyLocalMutex")); //同上
        if (hLocalMutex == NULL || ERROR_ALREADY_EXISTS == GetLastError())
            printf("错误提示:会话(SessionID=%u)命名空间中己存在名称为“MyMutex”同名内核对象!\n",sessionID);
        else
            printf("在会话(sessionID=%u)的命名空间中成功创建名称为“MyMutex”的内核对象!\n",sessionID);

        system("pause"); //这里必须暂时,否则当CloseHandle后内核对象会被释放

        CloseHandle(hGlobalMutex);
        CloseHandle(hLocalMutex);

    }
    else
    {
        wprintf(TEXT("Unable to get Terminal Service session ID for process:‘%u‘\n"), processID);
    }

    system("pause");

    return 0;

}

3.3.2.3 专有命名空间

(1)DoS攻击:如果恶意程序先于Singleton1程序建立同名的互斥量对象,该程序无法启动,很容易被劫持,会错误地以为它自己的另一个实例正在运行,这就是DoS攻击机制。显然未命名的内核对象不会遭受DoS攻击。

(2)利用边界描述符创建私有命名空间自身名称加以保护,从而达到保护内核对象的目的。

专有命名空间类似于在内核对象的名称之前加一个目录名称,但这个命名空间没有父目录,也没有名称,所以显示出来的前缀为"\..\锁名 ",从而不会暴露专有命名空间的名称,即减少名称冲突,又可免遭劫持能更好的防范名称被劫持。

(3)创建专有命名空间

①创建边界描述符CreateBoundaryDescriptor:用于定义那些在命名空间中要被隔离的对象的边界。


参数


描述


LPCTSTR Name


边界描述符的名称


ULONG Flags


这个参数是为以后保留的。目前没什么用,可以为之传入0


返回值


注意这里的返回值虽然是HANDLE类型的,但并不是一个内核对象句柄,而是指针,指向一个用户模式的结构,该结构包含了边界的定义。删除里用DeleteBoundaryDescriptor。

②将边界描述符与本地管理员组的安全描述符关联起来

A、创建一个SID(安全描述符):CreateWellKnownSid


参数


描述


WELL_KNOWN_SID_TYPE WellKnownSidType


SID类型

WinBuiltinAdministratorsSid:表示管理员账户组

WinWorldSid:表示所有账户


PSID DomainSid


指向创建了SID的域的指针,为NULL时表示使用本地计算机


PSID pSid,


指向要返回的SID的存储地址,为传出的参数


DWORD *cbSid


指向存储pSid的大小的地址

B、将SID与边界描述符关联起来AddSIDToBoundaryDescriptor,用来决定谁能进入边界并创建命名空间


参数


描述


HANDLE *BoundaryDescriptor


创建边界描述符返回的句柄


PSID RequiredSid


指向SID结构体的指针

③创建或打开专有命名空间的名称

A、创建专有命名空间CreatePrivateNamespace


参数


描述


SECURITY_ATTRIBUTES* sa


传给Windows使用,用于允许或禁止应用程序通过OpenPrivateNamespace访问专有命名空间来打开或创建内核对象。为谁能“打开命名空间”设置一个筛选层。


LPVOID lpBoundaryDescriptor


指向边界描述符的指针


LPCTSTR  lpAliasPrefix


用于创建内核对象的字符串前缀的别名

★将一个字符串格式 安全描述符 转换为一个有效的、 功能的安全描述符

ConvertStringSecurityDescriptorToSecurityDescriptor


参数


描述


LPCTSTR  StringSecurityDescriptor


指向一个空结尾的字符串包含要转换的字符串格式安全描述符的指针


DWORD  StringSDRevision


指定 StringSecurityDescriptor 字符串的修订级别。 当前,此值必须 SDDL_REVISION_1


PSECURITY_DESCRIPTOR SecurityDescriptor


指向一个变量,接收转换后的安全描述符的指针的指针。要释放返回的缓冲区,调用 LocalFree 函数


PULONG

SecurityDescriptorSize


指向一个变量,接收以字节为单位的转换后的安全描述符的指针的大小。此参数可以是NULL


返回值


如果该函数成功,返回值是,则返回非零值。

如果函数失败,返回值是零。 若要获取扩展的错误的信息,请调用 GetLastError 。 GetLastError 可能会返回以下错误代码之一。

ERROR_INVALID_PARAMETER:参数不是有效的

ERROR_UNKNOWN_REVISION:SDDL 修订级别无效

ERROR_NONE_MAPPED:一个 安全标识符 (SID 输入的安全描述符字符串中) 找不到一个帐户查找操作

★打开己有的专有命名空间:OpenPrivateNamespace

★关闭专有命名空间:ClosePrivateNamespace

④利用专有命名空间名称创建内核对象

【Singleton2程序】

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

#include "..\..\CommonFiles\CmnHdr.h"
#include <tchar.h>
#include <strsafe.h>
#include <sddl.h>
#include "resource.h"
/////////////////////////////////////////////////////////////////////////////////////////

//主对话框
HWND g_hDlg;

//互斥对象、边界描述符和命名空间——用来检测前一个实例是否正在运行
HANDLE g_hSingleton = NULL;
HANDLE g_hBoundary = NULL;
HANDLE g_hNameSpace = NULL;

//跟踪命令空间是否被创建或被打开
BOOL g_bNamesapceOpened = FALSE;

//专有命名空间和边界描述符的名称
PCTSTR g_szBoundary = TEXT("3-Boundary");
PCTSTR g_szNameSpace = TEXT("3-Namespace");

#define DETAILS_CTRL  GetDlgItem(g_hDlg,IDC_EDIT_DETAILS)

/////////////////////////////////////////////////////////////////////////////////////////
//增加字符串到编辑框控件中
/* VA_LIST的用法:
*(1)首先在函数里定义一个VA_LIST型的变量,这个变量是指向参数的指针;
*(2)然后用VA_START宏初始化变量刚定义的VA_LIST变量;
*(3)然后用VA_ARG返回可变的参数,VA_ARG的第二个参数是你要返回的参数的
*      类型(如果函数有多个可变参数的,依次调用VA_ARG获取各个参数);
*(4)最后用VA_END宏结束可变参数的获取。 */
void AddText(PCTSTR pszFormat, ...)
{
    va_list argList;
    va_start(argList, pszFormat);
    TCHAR sz[20 * 1024];

    Edit_GetText(DETAILS_CTRL, sz, _countof(sz));
    //strchr函数原型:extern char *strchr(const char *s,char c);查找字符串s中首次出现字符c的位置
    //strchr在字符串str中查找字符ch第一次出现的位置,找到后返回一个指向该位置的指针。如果该字符不
    //存在于字符串中,则返回一个NULL指针
    //本例先找到编辑框字符串最后的位置,用strchr函数查找,然后用_vstprintfs_s格式化缓冲区
    _vstprintf_s(_tcschr(sz, TEXT(‘\0‘)), _countof(sz) - _tcslen(sz), pszFormat, argList);

    Edit_SetText(DETAILS_CTRL, sz);
    va_end(argList);
}

/////////////////////////////////////////////////////////////////////////////////////////
void CheckInstance()
{
    //创建边界描述符
    //参数Name:边界描述符名称;Flags:保留字,没用,填0
    //返回值:虽为句柄,但实际上是个指针,不能CloseHandle,要用DeleteBoundaryDescriptor删除
    g_hBoundary = CreateBoundaryDescriptor(g_szBoundary, 0);

    //创建与本地管理员组相关的用户SID
    BYTE LocalAdminSID[SECURITY_MAX_SID_SIZE];
    PSID pLocalAdminSID = &LocalAdminSID;
    DWORD cbSID = sizeof(LocalAdminSID);

    //CreateWellKnownSid——创建与本地管理员组相关的用户SID
    //第1个参数: WinBuiltinAdministratorsSid表示管理员账户组,WinWorldSid:表示所有账户
    //第2个参数:指向创建了SID的域的指针,为NULL时表示使用本地计算机
    //第3个参数:指向要返回的SID的存储地址,为传出的参数
    //第4个参数:指向要返回的SID的存储地址,为传出的参数
    if (!CreateWellKnownSid(
                         WinBuiltinAdministratorsSid,NULL,pLocalAdminSID,&cbSID ))
    {
        AddText(TEXT("添加安全描述符到边界描述符失败:%u\r\n"), GetLastError());
        return;
    }

    //将SID与边界描述符关联起来AddSIDToBoundaryDescriptor,用来决定谁能进入边界并创建命名空间
    //只有管理员身份运行的应用程序能获得该命名空间中的内核对象
    if (!AddSIDToBoundaryDescriptor(&g_hBoundary, pLocalAdminSID)){
        AddText(TEXT("添加安全描述符到边界描述符失败:%u\r\n"), GetLastError());
        return;
    }

    //创建本地管理员专有的命名空间
    SECURITY_ATTRIBUTES sa;
    sa.nLength = sizeof(sa);
    sa.bInheritHandle = FALSE;
    //以下这个函数在sddl.h文件中,用来将一个字符串格式 安全描述符 转换为一个有效的、 功能
    //的安全描述符。
    //第1个参数:指向一个空结尾的字符串包含要转换的字符串格式安全描述符的指针
    //           "D:"表示DACL,
    //           (A;;GA;;;BA)表示允许完全控制管理员帐号(Allow full control to Adminstrators)
    /*
         // Define the SDDL for the DACL. This example sets
         // the following access:
         //     Built-in guests are denied all access.
         //     Anonymous logon is denied all access.
         //     Authenticated users are allowed read/write/execute access.
         //     Administrators are allowed full control.
         // Modify these values as needed to generate the proper
         // DACL for your application.
         TCHAR * szSD = TEXT("D:")       // Discretionary ACL
            TEXT("(D;OICI;GA;;BG)")      // Deny access to built-in guests
            TEXT("(D;OICI;GA;;;AN)")     // Deny access to anonymous logon
            TEXT("(A;OICI;GRGWGX;;;AU)") // Allow read/write/execute to authenticated users
            TEXT("(A;OICI;GA;;;BA)");    // Allow full control to administrators
    */
    //第2个参数:指定 StringSecurityDescriptor 字符串的修订级别。 当前,此值必须 SDDL_REVISION_1
    //第3个参数:指向一个变量,接收转换后的安全描述符的指针的指针。要释放返回的缓冲区,调用 LocalFree 函数
    //第4个参数:指向一个变量,接收该的大小以字节为单位的转换后的安全描述符的指针。此参数可以是NULL
    if (!ConvertStringSecurityDescriptorToSecurityDescriptor(
        TEXT("D:(A;;GA;;;BA)"),SDDL_REVISION_1,&sa.lpSecurityDescriptor,NULL))
    {
        AddText(TEXT("安全描述符创建失败:%u\r\n"), GetLastError());
        return;
    }

    g_hNameSpace = CreatePrivateNamespace(&sa, g_hBoundary, g_szNameSpace);
    LocalFree(sa.lpSecurityDescriptor);

    //检查专有命名空间创建是否成功
    DWORD dwLastError = GetLastError();

    //创建失败
    if (g_hNameSpace == NULL)
    {
        //如果被拒绝,则直接返回,这段代码必须在本地管理员账户下运行
        if (dwLastError == ERROR_ACCESS_DENIED){
            AddText(TEXT("创建命名空间时被拒绝。\r\n"));
            AddText(TEXT("    必须以管理员身份运行。\r\n\r\n"));
            return;
        }else{
            //如果另一个实例己经创建了该命名空间,就打开它
            if (dwLastError == ERROR_ALREADY_EXISTS)
            {
                AddText(TEXT("创建专有命名空间失败:%u\r\n"), dwLastError);
                g_hNameSpace = OpenPrivateNamespace(g_hBoundary, g_szNameSpace);
                if (g_hNameSpace == NULL){
                    AddText(TEXT("    并且打开命名空间失败:%u\r\n"), GetLastError());
                    return;
                }else{
                    g_bNamesapceOpened = TRUE;
                    AddText(TEXT("    但是打开命名空间成功。\r\n\r\n"));
                }
            }else{
                AddText(TEXT("发生了未知错误:%u\r\n\r\n"),dwLastError);
                return;
            }
        }
    }

    //尝试创建一个基于命名空间的具名互斥对象
    TCHAR szMutexName[64];
    StringCchPrintf(szMutexName, _countof(szMutexName), TEXT("%s\\%s"),
                         g_szNameSpace, TEXT("Singleton"));

    g_hSingleton = CreateMutex(NULL, FALSE, szMutexName);
    if (GetLastError()==ERROR_ALREADY_EXISTS)
    {
        //己经存在另一个实例对象
        AddText(TEXT("单一实例应用程序的另一个实例正在运行:\r\n"));
        AddText(TEXT("-->不能访问应用程序功能。\r\n"));
    }
    else
    {
        AddText(TEXT("单一实例应用程序的第一个实例:\r\n"));
        AddText(TEXT("-->现在访问应用程序功能。\r\n"));
    }
}

/////////////////////////////////////////////////////////////////////////////////////////
void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
{
    switch (id)
    {
    case IDOK:
    case IDCANCEL:
        //用户单击退出按钮或按ESC键
        EndDialog(hwnd, id);
        break;
    }
}

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

    g_hDlg = hwnd;

    //检测是否有另一个实例正在运行
    CheckInstance();
    return TRUE;
}

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

/////////////////////////////////////////////////////////////////////////////////////////
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPTSTR lpCmdLine, int nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    //显示主窗口
    DialogBox(hInstance, MAKEINTRESOURCE(IDD_SINGLETON), NULL, Dlg_Proc);
}

//resource.h

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ 生成的包含文件。
// 供 Singleton2.rc 使用
//
#define IDD_SINGLETON                   101
#define IDI_SINGLETON                   102
#define IDC_EDIT_DETAILS                1001

// 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         1002
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif

//Singleton2.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\0"
END

2 TEXTINCLUDE
BEGIN
    "#include ""winres.h""\r\n"
    "\0"
END

3 TEXTINCLUDE
BEGIN
    "\r\n"
    "\0"
END

#endif    // APSTUDIO_INVOKED

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

IDD_SINGLETON DIALOGEX 0, 0, 251, 161
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_MINIMIZEBOX | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
EXSTYLE WS_EX_APPWINDOW
CAPTION "单一实例"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
    DEFPUSHBUTTON   "退出",IDOK,197,136,40,14
    EDITTEXT        IDC_EDIT_DETAILS,13,13,225,117,ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_READONLY
END

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

#ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO
BEGIN
    IDD_SINGLETON, DIALOG
    BEGIN
        LEFTMARGIN, 7
        RIGHTMARGIN, 244
        TOPMARGIN, 7
        BOTTOMMARGIN, 154
    END
END
#endif    // APSTUDIO_INVOKED

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

// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_SINGLETON           ICON                    "Singleton.ico"
#endif    // 中文(简体,中国) resources
/////////////////////////////////////////////////////////////////////////////

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

/////////////////////////////////////////////////////////////////////////////
#endif    // not APSTUDIO_INVOKED
时间: 2024-10-19 00:33:18

第3章 内核对象(2)的相关文章

Windows核心编程之核心总结(第三章 内核对象)(2018.6.2)

学习目标 第三章内核对象的概念较为抽象,理解起来着实不易,我不断上网找资料和看视频,才基本理解了内核对象的概念和特性,其实整本书给我的感觉就是完整代码太少了,没有多少实践的代码对内容的实现,而且书本给的源码例子,有太多我们不知道的知识,并且这些知识对本章主要内容来说是多余的,所以我们理解起来也非常困难.为了更好的学习这章,我补充了一些辅助性内容.这一章的学习目标:1.Windows会话和安全机制2.什么是内核对象?3.使用计数和安全描述符4.内核对象句柄表5.创建内核对象6.关闭内核对象7.跨进

读书笔记----《windows核心编程》第三章 内核对象1(句柄与安全性)

最近一直没有更新博客,因为一直在想一个问题,内核对象这一章内容很多很重要,自己没有掌握好也没有把握写好这一章,最后还是决定能写多少写多少,一面写一面学,后续学到新的再更新吧; <windows核心编程>提了几种内核对象: 访问令牌对象:与windows的安全性有关,目前不是很懂,了解后再写; 事件对象: Event对象,可跨进程同步; 由CreateEvent创建; 文件对象: File对象,比较常见; 由CreateFile创建; 文件映射对象: 通过文件映射可以方便的操作文件(如同文件数据

第3章 内核对象(1)

3.1 何为内核对象 3.1.1 Windows平台上的3大对象 (1)分类 对象 描述 备注 GUI对象 也叫用户对象,一般是单线程访问,属于线程级的对象,如 加速键表(HACCEL).插入记号(Caret).光标(HCURSOR).桌面(HDESK). 钩子(HHOOK).图标(HICON).菜单(HMENU).窗口(HWND).窗口栈(HWINSTA) 句柄值是系统唯一的,即一个进程可以通过该句柄值对另一个进程中的对象进行操作,如发送消息. GDI对象 如DC.Pen.Font等,一般是单

第9章 用内核对象进行线程同步(2)_可等待计时器(WaitableTimer)

9.4 可等待的计时器内核对象——某个指定的时间或每隔一段时间触发一次 (1)创建可等待计时器:CreateWaitableTimer(使用时应把常量_WIN32_WINNT定义为0x0400) 参数 描述 psa 安全属性(如使用计数.句柄继承等) bManualReset 手动重置计时器还是自动重置计时器. ①当手动计时器被触发,所有正在等待计时器的线程都变可为可调度. ②当自动计时器被触发时,只有一个正在等待计数器的线程变为可调度 pszName 对象的名字 (2)也可以打开一个己经存在的

第9章 用内核对象进行线程同步(3)_信号量(semaphore)、互斥量(mutex)

9.5 信号量内核对象(Semaphore) (1)信号量的组成 ①计数器:该内核对象被使用的次数 ②最大资源数量:标识信号量可以控制的最大资源数量(带符号的32位) ③当前资源数量:标识当前可用资源的数量(带符号的32位) (2)信号量的使用规则 ①如果当前资源计数>0,那么信号量处于触发状态,表示有可用资源. ②如果当前资源计数=0,那么信号量处于未触发状态,表示没有可用资源. ③系统绝不会让当前资源计数变为负数: ④当前资源计数绝对不会大于最大资源计数 (3)信号量的用法 (4)相关函数

第9章 用内核对象进行线程同步(4)_死锁(DeadLock)及其他

9.7 线程同步对象速查表 对象 何时处于未触发状态 何时处于触发状态 成功等待的副作用 进程 进程仍在运行的时候 进程终止的时(ExitProcess.TerminateProcess) 没有 线程 线程仍在运行的时候 线程终止的时候(ExitThread.TermimateThread) 没有 作业 作业尚未超时的时候 作业超时的时候 没有 文件 有待处理的I/O请求的时候 I/O请求完成的时候 没有 控制台输入 没有输入的时候 有输入的时候 没有 文件变更通知 文件没有变更的时候 文件系统

JavaScript进阶 - 第8章 浏览器对象

第8章 浏览器对象 8-1 window对象 window对象是BOM的核心,window对象指当前的浏览器窗口. window对象方法: 注意:在JavaScript基础篇中,已讲解了部分属性,window对象重点讲解计时器. 任务 在右边编辑器script标签内补充代码,弹出对话框"欢迎来到慕课网". 定义一个函数,实现打开一个网页,宽为600,高为400. 当点击"点击我,打开新窗口"按钮时,在打开网页. 如果忘记了,可以查看JavaScript基础篇. 代码

《Linux Device Drivers》第十一章 内核的数据类型——note

简介 由于Linux的多平台特性,任何一个重要的驱动程序都应该是可移植的 与内核代码相关的核心问题是应该能够同时访问已知长度的数据项,并充分利用不同处理器的能力 内核使用的数据类型主要被分成三类 类似int这样的标准C语言类型 类似u32这样的有确定大小的类型 像pid_t这样的用于特定内核对象的类型 本章将讨论在什么情况下使用这三种类型以及如何使用 使用标准C语言类型 当我们需要"两个字节的填充符"或者"用四个字节字符串表示的某个东西"时,我们不能使用标准类型,因

Windows API学习---线程与内核对象的同步

前言 若干种内核对象,包括进程,线程和作业.可以将所有这些内核对象用于同步目的.对于线程同步来说,这些内核对象中的每种对象都可以说是处于已通知或未通知的状态之中.这种状态的切换是由Microsoft为每个对象建立的一套规则来决定的.例如,进程内核对象总是在未通知状态中创建的.当进程终止运行时,操作系统自动使该进程的内核对象处于已通知状态.一旦进程内核对象得到通知,它将永远保持这种状态,它的状态永远不会改为未通知状态. 当进程正在运行的时候,进程内核对象处于未通知状态,当进程终止运行的时候,它就变