Windows核心编程读书笔记之通过命令行传递句柄
今天继续读《Windows核心编程》,读到了第3.3节——跨进程边界共享内核对象,先来动手写第一个程序,用命令行参数在父子进程之间传递可继承的句柄。这个程序写的很痛苦,因为对API编程还不够熟悉,老是存在各种各样的问题,早晨六点整就爬起来,一直鼓捣到十二点半才算写完,真无语了。
对象句柄是可以继承的,当然前提是在父子进程之间继承。想获得一个可以继承的句柄,应该在使用相应的Create*()函数时,将SECURITY_ATTRIBUTES结构体的bInheritHandle域要设置为TRUE。
创建子进程时将CreateProcess()函数的第五个参数bInheritHandles也设置为TRUE,父进程中所有可以继承的句柄就都会被拷贝到子进程的句柄表中。
虽然可继承的句柄都被复制到子进程中了,但是Windows却并没有告诉子进程——子进程根本不知道自己继承了任何句柄,所以父进程还应该通知子进程,起码得告诉子进程它所继承的句柄的值是多少,否则子进程怎么使用这个句柄呢?书上提供了三种方法,第一种是通过命令行传递,第二种是父进程给子进程发消息,第三种是通过添加一个环境变量,我只写了第一种,剩下两种等过几天再写吧。顺便吐槽几句,这几天很忙啊,明天一门考试,后天一门考试,周六或者周日还得搬校区,估计搬完校区就得去南京实习了,不知假期还有没有时间看完这本书,唉,时间总是不够用,每门考试都只拿出两三个小时来复习,好在都能弄个70来分,及格万岁!
先来介绍一个我自己定义的函数MyAppendAndSetText()
VOID MyAppendAndSetText(HWND hwnd,int nIDDlgItem,LPTSTR OriginalString,LPTSTR AppendString)
{
if (GetDlgItemText(hwnd,nIDDlgItem,OriginalString,BUFFER_SIZE) != 0)
{
//如果不是第一行,则需要换行
_tcscat_s(OriginalString,BUFFER_SIZE/sizeof(TCHAR),_T("\r\n"));
}
_tcscat_s(OriginalString,BUFFER_SIZE/sizeof(TCHAR),AppendString); //note:没考虑缓冲区满的情况
SetDlgItemText(hwnd,nIDDlgItem,OriginalString);
}
因为我将信息都输出到了一个只读的编辑框里,试了好久不知道怎么让新输出信息出现在旧信息的下一行,网上搜了一下,看到很多人说读出旧信息,将新信息添加到后面,然后全部输出,我觉得这样很无耻……但是我却想不出更好的方法,那就这么实现吧,定义了一个全局变量lpBuffer做输出缓冲区指针,在WM_INITDIALOG消息里通过HeapAlloc()函数分配了BUFFER_SIZE字节的内存做输出缓冲区,默认大小是4KB,只要别太疯狂就够用了,程序里没检查,如果不够用了_tcscat_s()函数会送程序上天堂的。
LONG fOk;
TCHAR data[100]; //从注册表中读出的字符串
DWORD dwSize = sizeof(data);
DWORD dwType = REG_SZ;
TCHAR lpHandle[20];//字符串形式的句柄,用于输出
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.bInheritHandle = TRUE; //该句柄可以被继承
sa.lpSecurityDescriptor = NULL;
fOk = RegCreateKeyEx(HKEY_LOCAL_MACHINE,
_T("SOFTWARE\\TENCENT\\QQPlayer"),
0,
NULL,
REG_OPTION_NON_VOLATILE,
KEY_READ,
&sa,
&hKey,
NULL);
if (fOk != ERROR_SUCCESS)
{
MyAppendAndSetText(hwndDlg,IDC_OUTPUT,lpBuffer,_T("打开注册表句柄失败。"));
return TRUE;
}
wsprintf(lpHandle,_T("%s0x%08X"),_T("打开的注册表句柄为"),hKey);
MyAppendAndSetText(hwndDlg,IDC_OUTPUT,lpBuffer,lpHandle);
fOk = RegQueryValueEx(hKey,
_T("ExePath"),
0,
&dwType,
(LPBYTE)data,
&dwSize);
if (fOk != ERROR_SUCCESS)
{
MyAppendAndSetText(hwndDlg,IDC_OUTPUT,lpBuffer,_T("读取注册表值失败。"));
return TRUE;
}
MyAppendAndSetText(hwndDlg,IDC_OUTPUT,lpBuffer,_T("读取注册表值成功。"));
MyAppendAndSetText(hwndDlg,IDC_OUTPUT,lpBuffer,data);
return TRUE;
然后就是上面的代码获得一个句柄,我读取的是QQ影音的程序路径,唯一需要注意的就是sa.bInheritHandle = TRUE这一句,可不要直接传了一个NULL的SECURITY_ATTRIBUTES指针进去了,那样就不能继承了。顺便再吐槽一句,我在RegQueryValueEx()函数的第四个参数这里卡了很久,杯具死了。
STARTUPINFO si;
PROCESS_INFORMATION pi;
TCHAR lpHandle[20];
ZeroMemory(&si,sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi,sizeof(pi));
wsprintf(lpHandle,_T("%X"),hKey); //句柄转换成字符串
if( !CreateProcess(_T("SubProcess.exe "),
lpHandle, //句柄做为参数传递给子进程
NULL,
NULL,
TRUE, //这里也要设置为允许继承
0,
NULL,
NULL,
&si,
&pi))
{
MyAppendAndSetText(hwndDlg,IDC_OUTPUT,lpBuffer,_T("创建子进程失败。"));
return TRUE;
}
MyAppendAndSetText(hwndDlg,IDC_OUTPUT,lpBuffer,_T("创建子进程成功。"));
return TRUE;
上面这个就是先把句柄值转成字符串,然后通过CreateProcess()把它当做命令行参数传递给子进程。
父进程里有用的代码就这点,下面就是子进程里的代码,有用的更少,哈哈。
TCHAR buffer[256];
//分配输出缓冲区
lpBuffer = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,BUFFER_SIZE);
wsprintf(buffer,_T("%s%s"),_T("从命令行接收到的句柄是"),GetCommandLine());
MyAppendAndSetText(hwndDlg,IDC_OUTPUT,lpBuffer,buffer);
_stscanf_s(GetCommandLine(),_T("%x"),&hKey); //从命令行解析出句柄
return TRUE;
先输出,然后解析,保存到全局变量hKey中。然后就是在WM_COMMAND消息里直接用hKey这个句柄去读注册表,代码只是比上面贴的父进程的代码少了一个RegCreateKeyEx(),就不需要贴了,对代码感兴趣的直接下载最下方提供的压缩包吧。
老规矩,贴图增肥一下,每次都只写这么几个字实在让我自己都不好意思。
上图左边那个是父进程的对话框,右边两个是子进程的,可以看到右上的子进程继承的句柄值是0x00000178,右下的子进程继承的则是0x00000184,然后再一想就发现——父进程中存在句柄泄露问题,没错,看一下上面的第二个代码段就发现,我确实没有调用RegCloseKey()。我之所以没撤销该句柄是因为如果在IDC_CREATE_HANDLE消息里撤销了该句柄的话,那么这个句柄就不会被复制到子进程里了。我在IDC_CREATE_PROCESS里创建子进程后再撤销hKey,但是这样也就要求我们必须点一次“创建注册表句柄”就点一次“创建子进程”,否则依旧会泄露一个句柄。由于泄露的并不是很严重,毕竟创建一个子进程才泄露一个句柄,完全可以接受:-( 毕竟想让程序crash的话要泄露十万多个句柄才行,我们肯定创建不了那么多子进程的,所以我就将其放任不管了,如果你知道怎么样才能更好的管理这个句柄,麻烦指点我一下,先谢了。
如果你也愿意写这个程序的话,可以再试一下SetHandleInformation()函数,书中提了一下,我只是随手写了一下,没写到这个程序里。
本文涉及的源代码压缩包下载(VS2005工程): https://files.cnblogs.com/pianoid/TransferHandlesByCommandline.rar
修改记录:修改压缩包名称,唉,我杯具的英语啊,英译中还行,中译英就完蛋喽。