句柄(Handle)是一个32位(4个字节,64位程序中为8字节)的无符号整数,实际上是Windows在内存中维护的一个对象(窗口等)内存物理地址(32或64位)列表的整数索引。因为Windows的内存管理经常会将当前空闲对象的内存释放掉,当需要访问时再重新提交到物理内存,所以对象的物理地址是变化的,不允许程序直接通过物理地址来访问对象。程序将想访问的对象的句柄传递给系统,系统根据句柄检索自己维护的对象列表就能知道程序想访问的对象及共物理地址了。
我们知道,所谓指针是一种内存地址。应用程序启动后,组成这个程序的各对象是驻留在内存的。如果简单地理解,似乎我们只要获知这个内存的首地址,那么就可以随时用这个地址访问对象。但是,Windos是一个以虚拟内存为基础的操作系统。在这种系统环境下,Windows内存管理器经常在内存中来回移动对象,以此来满足各种应用程序的内存需要。对象被移动意味着它的地址变化了。如果内存总是如此变化,我们该到哪里去找该对象呢?为了解决这个问题,Windows系统为进程分配固定的地址(句柄)来存储进程下的数据对象变化后的地址。Windows操作系统为各应用程序腾出一些内存地址,用来专门登记各应用对象在内存中的地址变化,而这个地址本身是不会变的。Windows内存管理器移动对象在内存中的位置后,把对象新的地址告知这个句柄地址来保存。这样我们只需要记住这个句柄地址就可以间接地知道对象具体在内存中的哪个位置。这个地址是在对象装载时由系统分配的,当系统卸载时又释放给系统。但是,必须注意的是,程序每次重新启动,系统不能保证分配给这个程序的句柄还是原来的那个句柄。而且绝大多数情况下的确不一样。假如我们把进入电影院看电影看成是一个应用程序的启动运行,那么系统给应用程序分配的句柄总是不一样,这和每次电影院售给我们的门票总是不同的座位是一样的道理。
所以,对于winows应用程序中的不同对象和同类中的不同的实例,诸如,一个窗口、按钮、图标、滚动条、输出设备、控件或者文件等,数据对象加载进入内存中之后即获得了地址,但Windows并不允许应用程序直接通过这个地址来访问这些对象,而是通过标识或索引指针的句柄来访问相应的对象的信息。
在Windows环境中,句柄是用来标识项目的,这些项目包括:
*.模块(module)
*.任务(task)
*.实例(instance)
*.文件(file)
*.内存块(block of memory)
*.菜单(menu)
*.控制(control)
*.字体(font)
*.资源(resource),包括图标(icon),光标(cursor),字符串(string)等
*.GDI对象(GDI object),包括位图(bitmap),画刷(brush),元文件(metafile),调色板(palette),画笔(pen),区域(region),以及设备描述表(device context)。
(HINSTANCE(实例句柄),HBITMAP(位图句柄),HDC(设备描述表句柄,相应的,CDC是设备描述表类),HICON(图标句柄)等等,这当中还有一个通用的句柄,就是HANDLE。)
上面的这些项目,通过其指针去定位是无法找到或不稳定的,所以要使用这些项目,必须获取其句柄,句柄才是唯一标识稳定的定位。所以,如果想再通俗一定理解项目的指针与句柄,可以想像有一票供展览货品,数量比较多,储存在某一个公司的仓库里,但仓库的位置是有限的,可能储存了很多票这样的货,这些货因为不同的消耗进度,所以数量的增减会不一样,为此,不同票的货需要在不同的位置移动,以确保位置的最大化利用。怎么做呢?仓库的位置编号,也就是内存地址对应的指针,然后每票货都有一个在整个公司范围内唯一的条码号,这就是句柄。这个公司只需要维护一份条码号与指针对应的索引表格就可以了。货物移动时,将目的地的指针更改到条码号。想要找货物时,只需要在索引表格中找到这个条码号,就可以查找到货位的指针及其具体的位置了。这样既可以保证仓库空间的利用,也有方便地找到货位。
句柄是对象生成时系统指定的,是引用指针的一个标识值或ID号,该标识可以被系统重新定位到一个内存地址上。句柄类似指向指针的指针,仅仅是类似,通过句柄可以找到对应的数据,但是不是二级指针。
句柄与普通指针的区别在于,指针包含的是引用对象的内存地址,而句柄则是由系统所管理的引用标识,该标识可以被系统重新定位到一个内存地址上。这种间接访问对象的模式增强了系统对引用对象的控制。
在内存有限的年代,为了使有限的内存资源得到充分利用,有了虚拟内存和虚拟地址的概念或内存管理方式,相应的,也就有了句柄这样一个编程概念。如今,内存容量的增大和虚拟内存算法使得更简单的指针愈加受到青睐,而指向另一指针的那类句柄受到冷落。
指针标记某个物理内存地址,Windows系统用句柄标记系统资源,隐藏系统的信息。
以上是Windows编程对句柄的理解。而在一般的程序设计中,句柄是一种特殊的智能指针 。当一个应用程序要引用其他系统(如数据库、操作系统)所管理的内存块或对象时,就要使用句柄。
MFC Object和Windows Object的关系Windows系统中有许多内核对象(这里的对象不完全等价于"面向对象程序设计"一词中的"对象",虽然实质上还真差不多),比如打开的文件,创建的线程,程序的窗口,等等。这些重要的对象肯定不是4个字节或者8个字节足以完全描述的,他们拥有大量的属性。为了保存这样一个"对象"的状态,往往需要上百甚至上千字节的内存空间,那么怎么在程序间或程序内部的子过程(函数)之间传递这些数据呢?拖着这成百上千的字节拷贝来拷贝去吗?显然会浪费效率。那么怎么办?当然传递这些对象的首地址是一个办法,但这至少有两个缺点:
所以,Windows操作系统就采用进一步的间接:在进程的地址空间中设一张表,表里头专门保存一些编号和由这个编号对应一个地址,而由那个地址去引用实际的对象,这个编号跟那个地址在数值上没有任何规律性的联系,纯粹是个映射而已。
所以,Windows操作系统就采用进一步的间接:在进程的地址空间中设一张表,表里头专门保存一些编号和由这个编号对应一个地址,而由那个地址去引用实际的对象,这个编号跟那个地址在数值上没有任何规律性的联系,纯粹是个映射而已。
在Windows系统中,这个编号就叫做"句柄"。
从广义上,能够从一个数值拎起一大堆数据的东西都可以叫做句柄。句柄的英文是"Handle",本义就是"柄",只是在计算机科学中,被特别地翻译成"句柄",其实还是个"柄"。从一个小东西拎起一大堆东西,这难道不像是个"柄"吗?
Handle本身是一个32或64位的无符号整数,它用来代表一个内核对象。它并不指向实际的内核对象,用户模式下的程序永远不可能获得一个内核对象的实际地址(一般情况下)。那么Handle的意义何在?它实际上是作为一个索引在一个表中查找对应的内核对象的实际地址。那么这个表在哪里呢?每个进程都有这样的一个表,叫句柄表。该表的第一项就是进程自己的句柄,这也是为什么你调用GetCurrentProcess()总是返回0x7FFFFFFF原因。
简单地说,Handle就是一种用来"间接"代表一个内核对象的整数值。你可以在程序中使用handle来代表你想要操作的内核对象。这里的内核对象包括:事件(Event)、线程、进程、Mutex等等。我们最常见的就是文件句柄(file handle)。
另外要注意的是,Handle仅在其所属的进程中才有意义。将一个进程拥有的handle传给另一个进程没有任何意义,如果非要这么做,则需要使用DuplicateHandle()在多个进程间传递Handle。
MFC是微软公司提供的一个类库(class libraries),以C 类的形式封装了Windows API,并且包含一个应用程序框架,以减少应用程序开发人员的工作量。其中包含大量Windows句柄封装类和很多Windows的内建控件和组件的封装类。
所谓Windows Object(Windows对象)是Win32下用句柄表示的Windows操作系统对象;所谓MFC Object (MFC对象)是C 对象,是一个C 类的实例,这里MFC Object是有特定含义的,指封装Windows Object的C Object,并非指任意的C Object。
MFC Object 和Windows Object是不一样的,但两者紧密联系。以窗口对象为例:
一个MFC窗口对象是一个C CWnd类(或派生类)的实例,是程序直接创建的。在程序执行中它随着窗口类构造函数的调用而生成,随着析构函数的调用而消失。而Windows窗口则是Windows系统的一个内部数据结构的实例,由一个“窗口句柄”标识,Windows系统创建它并给它分配系统资源。Windows窗口在MFC窗口对象创建之后,由CWnd类的Create成员函数创建,“窗口句柄”保存在窗口对象的m_hWnd成员变量中。Windows窗口可以被一个程序销毁,也可以被用户的动作销毁。MFC窗口对象和Windows窗口对象的关系如图2-1所示。其他的Windows Object和对应的MFC Object也有类似的关系。
MFC给这些类定义了一个成员变量来保存MFC Object对应的Windows Object的句柄。对于设备描述表CDC类,将保存两个HDC句柄。
MFC Object通过构造函数由程序直接创建;Windows Object由相应的SDK函数创建。
MFC中,使用这些MFC Object,一般分两步:
首先,创建一个MFC Object,或者在STACK中创建,或者在HEAP中创建,这时,MFC Object的句柄实例变量为空,或者说不是一个有效的句柄。
然后,调用MFC Object的成员函数创建相应的Windows Object,MFC的句柄变量存储一个有效句柄。
(句柄经常做为函数返回值或函数参数而存在。)
MFC Object和Windows Object的对应关系:
描述 | Windows句柄 | MFC Object |
窗口 | HWND | CWnd and CWnd-derived classes |
设备上下文 | HDC | CDC and CDC-derived classes |
菜单 | HMENU | CMenu |
笔 | HPEN | CGdiObject类,CPen和CPen-derived classes |
刷子 | HBRUSH | CGdiObject类,CBrush和CBrush-derived classes |
字体 | HFONT | CGdiObject类,CFont和CFont-derived classes |
位图 | HBITMAP | CGdiObject类,CBitmap和CBitmap-derived classes |
调色板 | HPALETTE | CGdiObject类,CPalette和CPalette-derived classes |
区域 | HRGN | CGdiObject类,CRgn和CRgn-derived classes |
图像列表 | HimageLIST | CimageList和CimageList-derived classes |
套接字 | SOCKET | CSocket,CAsynSocket及其派生类 |
可以从一个MFC Object得到对应的Windows Object的句柄;一般使用MFC Object的成员函数GetSafeHandle得到对应的句柄。
可以从一个已存在的Windows Object创建一个对应的MFC Object; 一般使用MFC Object的成员函数Attach或者FromHandle来创建,前者得到一个永久性对象,后者得到的可能是一个临时对象。
从指针到句柄 :
CWnd *pWnd = GetDlgItem(ID_***); // 取得控件的指针,CWnd是窗口类
HWND hwnd = pWnd->GetSafeHwnd(); // 取得控件的句柄
hdc=GetDC(hwnd);
有了DC绰号hdc,就可以画图了:
MoveToEx( hdc,0, y_shift-(draw_yu-y_min)*ry, NULL );
LineTo(hdc,900,y_shift-(draw_yu-y_min)*ry);
CPaintDC dc(this); // device context for painting
//CPaintDC是CDC的一个派生类,只是在OnPaint()函数中使用;
CPen pen1(PS_SOLID, 1, RGB(198, 198, 198));
//把画笔选到设置描述表当中.覆盖默认画笔.
CDC* pDC = pWnd->GetWindowDC(); //取得CDC的指针
//或CDC *pDC = GetDC();
dc.SelectObject(&pen1);
dc.MoveTo(0,455);//移动到该坐标
dc.LineTo(917,455); //画线画到这个点
pDC->TextOut(92,50,"(如:http://www.ifeng.com)");
从句柄到指针 :
CComBox* com; //声明一个CComBox类的指针
HWND hwnd = GetDlgItem(hwnd,IDC_XX)->GetSafeHwnd();
com = (CComBox*)FromHandle(hwnd);
相当于
CWnd* pCtrl = this->FromHandle(::GetDlgItem(hwnd,IDC_XX));
注意这里FromHandle是CWnd的一个静态函数,也就是说只能用在CWnd或者CWnd的子类,如CDIalog,或者你自己的继承自CWnd的类中。
使用控件指针:
CWnd *pWnd = GetDlgItem(ID_***); // 取得控件的指针,CWnd是窗口类
HWND hwnd = pWnd->GetSafeHwnd(); // 取得控件的句柄
CRichEditCtrl* pRichEdit = (CRichEditCtrl*)(CWnd::FromHandle(hwnd));//hwnd是上面两句获得的句柄,转化为对应控件的指针
CString szRichMsg;
szRichMsg.Format("%s",msg.c_str());
CString szUname;
std::string uname=stanza->from().username();
szUname.Format("%s",uname.c_str());
pRichEdit->ReplaceSel((szUname "\t" m_Time) "\n" szRichMsg "\n");//使用控件指针
以下用控件指针也是可以操作控件的:
CEdit *edit1=(CEdit*)GetDlgItem(IDC_EDIT2);
edit1->GetWindowText(m_text);
ID--句柄--指针三者之间的互相转换
3 再说Windows窗口句柄与MFC窗口类id->句柄-----------hWnd = ::GetDlgItem(hParentWnd,id);
id->指针-----------CWnd::GetDlgItem();
句柄->id-----------id = GetWindowLong(hWnd,GWL_ID);
句柄->指针--------CWnd *pWnd=CWnd::FromHandle(hWnd);
指针->ID----------id = GetWindowLong(pWnd->GetSafeHwnd,GWL_ID);
GetDlgCtrlID();
指针->句柄--------hWnd=cWnd.GetSafeHandle() or mywnd->m_hWnd;
HICON->ID--------HICON hIcon = AfxGetApp()->LoadIcon(nIconID);
HICON hIcon = LoadIcon(AfxGetApp()->m_hInstance, MAKEINTRESOURCE(nIconID));
Windows窗口句柄是用来确定Windows窗口的一个值,在win32编程中,都是很常见的。但是MFC为了简化编程就提供了CWnd窗口类将其包装了,然后对API函数封装后,就在创建对象时将窗口句柄关联到对象,然后提供的成员函数就可以省掉窗口句柄参数了。所以,在基于CWnd继承而来的所有类中,都有一个公有的成员变量m_hWnd,这个成员变量就是窗口对象关联的windows窗口句柄。我们在类中可以直接使用这个窗口句柄成员变量。这个窗口对象就是标准的C 对象。
其实MFC窗口类并不神奇,就是包装了一下API而已。m_hWnd的类型就是HWND。因为窗口类都是一个C 对象,而C 对象就是我们平时使用C 时用的类实例化而来的。这么一说,其实窗口类和窗口对象其实就是将窗口句柄作为C 对象的一个成员,然后创建时关联的而已。在CreateWindow函数中,传入了窗口的ID,这样就让窗口类可以获得这个窗口ID的窗口句柄,进而保存到窗口对象成员m_hWnd中。这样就形成了关联。这样就创建了一个窗口对象。
MFC给这些类定义了一个成员变量来保存MFC Object对应的Windows Object的句柄。对于设备描述表CDC类,将保存两个HDC句柄。
CDC类有两个成员变量:m_hDC,m_hAttribDC,它们都是Windows设备描述表句柄。CDC的成员函数作输出操作时,使用m_Hdc;要获取设备描述表的属性时,使用m_hAttribDC。
void CMyView::OnDraw(CDC *pDC)
{
CPen penBlack; //构造MFC CPen对象
if (penBlack.CreatePen(PS_SOLID, RGB(0, 0, 0)))
{
CPen *pOldPen = pDC->SelectObject(&penBlack)); //选进设备表,保存原笔
…
pDC->SelectObject(pOldPen); //恢复原笔
}else
{
…
}
}
pDC->SelectObject(&penBlack)返回了一个CPen *指针,也就是说,它根据原来PEN的句柄创建了一个MFC CPen对象。
-End-