句柄与指针的区别

********句柄与指针的区别(一)********
  句柄其实就是指针,但是他和指针最大的不同是:给你一个指针,你可以通过这个指针做任何事情,也许是好事,也许是通过这个指针破坏内存,干一些捣乱的事情。这个我想大家都会碰到过,因为乱用指针导致程序崩溃
    句柄就没有这个缺点,通过句柄,你只能干一些windows让你干的事情(调用一些api函数等等),没有了指针的坏处。

    句柄是一些表的索引也就是指向指针的指针,句柄和指针都是地址,句柄是Windows编程的一个关键性的概念,编写Windows应用程序总是要和各种句柄打交道。
    所谓句柄,就是一个唯一的数,用以标识许多不同的对象类型,如窗口、菜单、内存、画笔、画刷等。在Win32里,句柄是指向一个“无类型对象”(void*)的指针,也就是一个4字节长的数据。
    无论它的本质是什么,句柄并不是一个真正意义上的指针。从构造上看,句柄是一个指针,尽管它没有指向用于存储某个对象的内存位置。事实上,句柄指向一个包含了对该对象进行的引用的位置。
    句柄的声明是这样的:
    typedef void *HANDLE
    由于Windows是一个多任务操作系统,它可以同时运行多个程序或一个程序的多个副本。这些运行的程序称为一个实例。为了对同一程序的多个副本进行管理,Windows引入了实例句柄。Windows为每个应用程序建立一张表,实例句柄就好象是这张表的一个索引。
    不同在于:
      1、句柄所指的可以是一个很复杂的结构,并且很有可以是与系统有关的,比如说上面所说的线程的句柄,它指向的就是一个类或者结构,他和系统有很密切的关系,当一个线程由于不可预料的原因,而终止时在系统就可以回它所占用的资料,如CPU,内存等等,反过来想可以知道,这个句柄中的某一些项,是与系统进行交互的。由于Windows系统,是一个多任务的系统,它随时都可能要分配内存,回收内存,重组内存。
      2、指针它也可以指向一个复杂的结构,但是通常是用户定义的,所以的必需的工作都要用户完成,特别是在删除的时候。但在VC++6.0中也有一些指针,它们都是处理一些小问题才用的,如最常见的字符的指针,它也是要用户处理的如果你动态分配了内存;但是Cstring 就不要用户处理了,它其实是VC++中的一个类,所以的操作都由成员函数完成,产生(分配)由构造函数,删除(回收)由析构函数完成。


获得窗口句柄三种方法

.HWND FindWindow(LPCTSTR lpClassName, LPCTSTR lpWindowName)

HWND FindWindowEx(HWND hwndParent, HWND hwndChildAfter,LPCTSTR lpClassName, LPCTSTR lpWindowName)

2.HWND WindowFromPoint(POINT& Point)//获得当前鼠标光标位置的窗口HWND

3.BOOL CALLBACK EnumChildProc(HWND hwnd,LPARAM lParam)

BOOL CALLBACK EnumChildWindows(HWND hWndParent, WNDENUMPROC lpEnumFunc,LPARAM lParam)
BOOL CALLBACK EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam)
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)

指针 句柄之间的转换

a.由指针获得句柄
CWnd * pWnd;
CWnd HWnd;
HWnd = pWnd->GetSafeHWnd();

b.由句柄得到指针:
CWnd* pWnd=FromeHandle(hMyHandle);
pWnd->SetWindowText("Hello World!");
or CWnd* pWnd; pWnd->Attach(hMyHandle);

MFC类中有的还提供了标准方法,比如Window 句柄:
static CWnd* PASCAL FromHandle( HWND hWnd );
HWND GetSafeHwnd( ) const;

对于位图:
static CBitmap* PASCAL FromHandle( HBITMAP hBitmap );
static CGdiObject* PASCAL FromHandle( HGDIOBJ hObject );
HGDIOBJ GetSafeHandle( ) const;

    有人说句并就是一个标示,一个ID号,是错误的。一个ID号可以包括多个资源,比如说单文档中的IDR_MAINFRAME,一般是指在硬盘上的资源。但是当把硬盘上的资源调入内存以后,将有一个句柄指向它,但是句柄只能指向一个资源。而且句柄知道所指的内存有多大。还有指针,指针指向地址,它不知道分配的内存有多大。
    但是如果你定义一个句柄,然后在VC里面右击鼠标,选择"go to definition of HANDLE,你会发现它的本质就是一个指针,但是它的作用不同于指针。

    句柄是个指针,指向一块内存,但至于这块内存跟句柄所标识的对象是怎么联系起来的,调用者不需要清楚,调用者只需要知道,这个句柄联系着一个win32对象。
    句柄是物理地址,可以跨进程传递,例如,HANDLE ha进程A的一个窗口,你可以在进程B中利用一个跟ha相等的值(相等就是说它们强制转成int32的值相等)初始化一个句柄,利用这个句柄你可以对进程 A的那个对象进行操作,例如MoveWindow,ShowWindow等。
    句柄包含了一些引用计数之类的东西,所以我的上一点说的给句柄赋值是不安全的,Windows API提供了一些函数,可以对句柄进行操作。

句柄就是受限的指针。
    它是由操作系统管理的,你不能通过它存取操作系统创建的数据结构。

    操作系统在创建一个对象(如GDI, FILE)等的时候,它会为这个对象CONTEXT保留一块数据结构,然后把它放在一张全局表中。。句柄就是这块数据结构在表中的索引

    指针对应着一个数据在内存中的地址,得到了指针就可以自由地修改该数据。Windows并不希望一般程序修改其内部数据结构,因为这样太不安全。所以 Windows给每个使用GlobalAlloc等函数声明的内存区域指定一个句柄(本质上仍是一个指针,但不要直接操作它),平时你只是在调用API函数时利用这个句柄来说明要操作哪段内存。当你需要对某个内存进行直接操作时,可以使用GlobalLock锁住这段内存并获得指针来直接进行操作。
    lshgao的意见:
    句柄是指针的“指针”,使用句柄主要是为了利于windows在进程内存地址空间移动分配的内存块,以防止进程的内存空间被撕的四分五裂而存在过多的碎片。
    
    阿城的意见:
    句柄是一些表的索引也就是指向指针的指针。间接的引用对象,windows可以修改对象的"物理"地址和
    描述器的值,但是句柄的值是不变的。
    
    刘志用的意见:
    句柄和指针都是地址,不同在于:
    1,句柄所指的可以是一个很复杂的结构,并且很有可以是与系统有关的,比如说上面所说的线程的句柄,它指向的就是一个很类或者结构,他和系统有很密切的关系,当一个线程由于不可预料的原因,而终止时在系统就可以回它所占用的资料,如CPU,内存等等,反过来想可以知道,这个句柄中的某一些项,是与系统进行交互的。由于Windows系统,是一个多任务的系统,它随时都可能要分配内存,回收内存,重组内存。
    2,指针它也可以指向一个复杂的结构,但是通常是用户定义的,所以的必需的工作都要用户完成,特别是在删除的时候。
    但在VC++6.0中也有一些指针,它们都是处理一些小问题才用的,如最常见的字符的指针,它也是要用户处理的如果你动态分配了内存;但是Cstring 就不要用户处理了,它其实是VC++中的一个类,所以的操作都由成员函数完成,产生(分配)由构造函数,删除(回收)由析构函数完成。
    
    zjf问:
    你好,我在学习用vc++6.0编译多线程程序中遇到了很多句柄,但是不明白他的具体作用以及如何使用句柄,希望您能给我举几个具体实例,不甚感激!
    比如说: HANDLE hThread,它是怎样具体使用的?
    答:你使用CreateThead后函数会返回一个句柄,它代表这个线程。你可能会调用SetThreadPriority去修改线程的优先级,使用 ResumeThread去重新开始一个线程的运行,在调用这些函数时你都需要告诉系统你到底要操作哪个线程,而刚才返回的句柄派上用处了,这些函数的第一个参数就是线程的句柄。
    看VC中总是出现这个句柄的概念,以前一直以为就是指指针,但是越看越觉得不是这么简单,于是本着有问题百度一下的原则,看到如下解释,很是经典:

    csdn上有人说过:牧童遥指杏花村。
    牧童的手为指针,杏花村的牌子为句柄,杏花村酒店为对象的实例.

    句柄就是烤叉,用烤炉烤过鸭,鸡,牛,羊,狗么?
    炉子里的东西是看不见,摸不到的,但你能用叉子去控制,至于叉子上的是什么,你放进去前应该记住。呵呵

    句柄有时是指针,有时是索引,但他绝对是一把钥匙,内核句柄110的钥匙,GDI句柄是您的钥匙,只对您有效。

    单从概念上讲,句柄指一个对象的标识,而指针是一个对象的首地址。从实际处理的角度讲,即可以把句柄定义为指针,又可以把它定义为同类对象数组的索引,这两种处理方法都有优缺点,至于选用哪种方式,完全应该看实际需要,这可以说是一种程序设计上的技巧。那种单纯认为句柄是指针或索引的想法都是机械的、不确切的。其实,在Windows中类似的处理是很多的、很灵活的。再具个相似的例子:

    我们知道,在Windows中有个函数叫做CallWindowProc。
    故名思义,它的作用就是向指定的窗口过程传递一个消息。你也许会想,既然我已经有了窗口过程的指针,为什么我不可以直接通过这个指针调用该函数(这是C语言的内建功能)?事实上,在Win16中确实可以这么做,因为GetWindowLong返回的确实是该函数的指针。但在Win32 下,GetWindowLong返回的并不是该函数的指针,而是一个包含函数指针的数据结构的指针(MSDN上说返回的是一个窗口函数地址或它的句柄,就是指的这种情况)。该数据结构是可变的,但只要你使用CallWindowProc来调用的话是不会出错的。这里我们又看到使用句柄处理带来的好处。(补充说明一点:微软在这里之所以这么处理,是为了解决16位/32位以及ANSI/UNICODE的转化问题)

    看来,句柄很多时候是一个用于描述和标记一个资源的数据结构的指针,而不是资源本身的指针,句柄中可能包含资源的指针,但是根多时候不仅仅是这样。

*********句柄与指针的区别(二)********

句柄vs指针 
    句柄是一种指向指针的指针。我们知道,所谓指针是一种内存地址。应用程序启动后,组成这个程序的各对象是住留在内存的。如果简单地理解,似乎我们只要获知这个内存的首地址,那么就可以随时用这个地址访问对象。但是,如果您真的这样认为,那么您就大错特错了。我们知道,Windows是一个以虚拟内存为基础的操作系统。在这种系统环境下,Windows内存管理器经常在内存中来回移动对象,依此来满足各种应用程序的内存需要。对象被移动意味着它的地址变化了。如果地址总是如此变化,我们该到哪里去找该对象呢?为了解决这个问题,Windows操作系统为各应用程序腾出一些内存储地址,用来专门登记各应用对象在内存中的地址变化,而这个地址(存储单元的位置)本身是不变的。Windows内存管理器在移动对象在内存中的位置后,把对象新的地址告知这个句柄地址来保存。这样我们只需记住这个句柄地址就可以间接地知道对象具体在内存中的哪个位置。这个地址是在对象装载(Load)时由系统分配给的,当系统卸载时 (Unload)又释放给系统。句柄地址(稳定)→记载着对象在内存中的地址→对象在内存中的地址(不稳定)→实际对象。但是,必须注意的是程序每次从新启动,系统不能保证分配给这个程序的句柄还是原来的那个句柄,而且绝大多数情况的确不一样的。假如我们把进入电影院看电影看成是一个应用程序的启动运行,那么系统给应用程序分配的句柄总是不一样,这和每次电影院售给我们的门票总是不同的一个座位是一样的道理。

 

指针与句柄的区别

    对于Wind32 API,尽管为每个对象分配了数据块,但是微软不想向用户程序返回指针。对于一个聪明的程序员来说,指针包含了太多的信息。它给出了对象存储的确切位置。指针一般允许对对象的内部表示进行读写操作,而这些内部表示也许正是操作系统想隐瞒的。指针还使越过进程地址空间共享对象变得困难。为了对程序员进一步隐藏信息,Win32对象创建程序实例一般会返回对象句柄。对象可以映射到唯一句柄,句柄也可由映射到唯一的对象。为了保证句柄能够完成信息隐藏的的任务,对象和句柄之间的映射没有任何文档记载,不保证固定不变,而已仅有微软知道这种映射,或者还有少数系统级工具开放商知道。
    对象指针和句柄之间的映射可以由函数Encode和Decode来实现,原型如下:
    HANDLE Encode(void* pObject);
    Void* Decode(HANDLE hObject);
    在极端情况下,句柄可以和对象指针相同,Encode和Decode只需做类型转换,对象和句柄之间的映射主要是全等映射。
    在Win32 API中,实例句柄(HINSTANCE)或者模块句柄(HMODULE)是指向映射到内存的PE文件映像的指针。LockResource用来锁住全局资源句柄得到指针,但实际上它们的值相同。LockResource返回的资源句柄只是伪装后的内存映射资源的指针。
    通常情况下,对象指针和句柄之间的映射是基于表格的映射。操作系统创建表格或者是一级表示保存所有要考虑的对象。需要创建新对象时,首先要在表格中找到空入口。然后就把表示对象的数据添入其中。当对象被删除时,它的数据成员和它在中的入口被释放,以便再利用入口。用这种基于表的对象管理方法,表中的索引可以很好的组成对象的句柄,编码和解码也很简单。
    (在Win32 API中,内核对象是用进程表实现的。为了容纳大量内核对象,每个进程都有自己的内核对象表。NT/2000内核执行体中一部分是对象管理器,它只管理内核对象。对象管理器提供函数ObReferenceObjectByHandle。根据DDK(Driver Develepment Kits)文档,它提供对象指针的解码全过程,如果存取操作被批准,则会返回对象体相应的指针。因此对于一个把对象句柄翻译称为对象指针的解码全程来说,额外的安全检查很重要。www.internals.com上面有个非常好的工具HandleEx,它能够列出Windows NT/2000的内核对象。
    只有句柄是不够的,尽管句柄提供了近乎完美的抽象,信息隐藏和保护,但是它也是程序员遭受挫折的地方。在像Win32 API这样以句柄为中心的API中,微软没有任何文档记载对象的内部表示以及对象是如何管理的,也没有提供参考实现,程序员只有函数原型,微软文档和或多或少基于微软文档的书籍。程序员面临的首要问题包括系统资源。当对象被创建,对象的句柄被返回时,谁都不知道对象用了什么资源,因为对象的内部表示是不知道的。程序员是应该保护该对象还是应该在对象没有用时尽快把它删除呢?GDI支持的三种位图,为了减少系统资源消耗,应该使用哪一种呢?CPU时间时计算机的主要资源。当内部表示对程序员隐藏时,程序员就很难在复杂的算法设计中判断这种操作的复杂性如果你用GDI组成复杂区域,算法的复杂度是O(n)(问题规模n),O( )(问题规模)还是O()。随着程序的隐藏,调试也成问题。程序运行5分钟显示了一些垃圾数据,猜测由资源泄漏,但是泄漏在哪儿?怎么解决?如果是处理系统中几百个应用程序的管理员,当系统资源很少时,如果找出问题?唯一可以用的资源泄漏工具是BoundsChecker,它依赖API窥视技术查出对象创建和删除之间的不匹配之处。最让人受挫的地方可能是程序的兼容性。程序为什么能在Windows95下把GDI对象从一个进程传递到另外一个进程,而 Windows NT/2000不行?为什么Windows95不能处理大的设备无关图?
    以GDI对象为例子,创建了GDI对象,就会得到该对象的句柄。句柄的类型有可能是HPEN,HBRUSH,HFONT或者是HDC中的一种。但最普通的 GDI对象类型是HGDIOBJ,它被定义成为空指针。HPEN的实际编译类型是随着时间宏STRICT的不同而不同。不同GDI句柄的定义模仿了GDI 对象不同类的类层次结构,但是没有真正的类层次结构。GDI对象一般有多个创建函数和一个接受HGDIOBJ的析构函数——DeleteObject。也可以用GetStockObject取得预先创建好的GDI对象句柄,无论GetStockObject调用顺序是如何,它返回的句柄看起来总是常数。甚至当运行一个程序的两个实例时,它在每个进程中返回相同的,唯一解释是对象句柄堆是不变的,系统初始化,堆对象被创建并被所有进程重复使用。尽管 Windows头文件把GDI句柄定义成为指针,但是检查这些句柄的值时,它们根本不像指针。生成几个GDI对象句柄并看一下返回句柄的十六进制显示,就会发现结果从0x01900011变化到0xba040389。如果HGDIOBJ像在Windows头文件里面定义的那样是指针,则前者是指向用户地址空间中未分配的无效指针,而后者是执行内核地址空间。这就暗示GDI句柄不是指针。另一个发现是GetStockObject(BLACK_PEN)和 GetStockObject(NULL_PEN)返回值只相差一,如果句柄真的是指针的话,这不可能是存储内部GDI对象的空间,因此可以肯定的说 GDI对象句柄不是指针。系统GDI句柄数限制为16384个,进程GDI句柄数限制为12000个。这样单独的进程不会搞乱整个GDI系统。但是 Windows 2000第一版没有对每个进程加以限制。现在在同一个系统下运行两个GDIHandles,在每一个进程中调用8192次CreatePen。第一个很好的创建了对象,第二个在7200左右会停止。第二个进程失败后,整个屏幕一团糟,这个试验表示GDI对象同样是从同一个资源池分配的。系统中的进程使用 GDI资源时会互相影响。把8192和7200相加。考虑到GDIHandle属性页面和其它进程的页面使用的GDI对象句柄,可以猜测,GDI句柄数目有系统范围限制:16384。GDI对象存储于系统范围内的固定大小的对象表中,称之为对象句柄表。它是一个大小固定的表,而不是一个会动态增长的数据结构。这就意味着简明和效率。但是     缺点就是前面说的,限制了GDI句柄数:16384个。下面看看HGDIOBJ的构成,Windows NT/2000下,GDI返回的对象句柄是32位值,组成8位十六进制数。如果用GDIHandles创建很多GDI对象,注意到其中显示的双字句柄的低位字,会发现它们都在0x000到0x3FFF之间。低位字在进程中总是唯一的,出了堆对象外,低位字甚至在进程中也是唯一的。句柄的低位有时候按照递增的顺序排列,有时候又递减。在进程间也是这样。例如,某些情况下,CreatePen在低位返回0x03C1,另一个进程中的下一个CreatePen在低位返回0x03C3。对这些现象的解释是HGDIOBJ的低位字是对系统范围的16384个GDI对象所组成的表的索引。再来关注高4位的十六进制数。创建几个画刷,几个画笔,几个字体,DC等。不难看出相同类型的GDI对象句柄有个共同特点:相同类型的对象句柄的第三位和第四位十六进制数几乎都是相同的。画刷句柄的第三位和第四位总是0x90和0x10,画笔总是0x30和0xb0等等。最高位是1(二进制)的对象句柄都是堆对象。因此可以有足够的证据说对象句柄的第三位和第四位十六进制数是对象类型的编码和堆对象标记。在32位GDI句柄值中余下的两个十六进制位暂时还没找到有意义的模式。总结一下,GDI对象句柄由8位位置高位,一位堆对象标记,7位对象类型信息和高四位为0的16位索引组成。因为GDI对象表是由系统中所有过程和系统DLL所共享的,桌面,浏览器,字处理器以及DirectX游戏都在为同一个GDI句柄的储存区而竞争。而在Windows 2000中,DirectX则使用一个独立的对象句柄表。GDI句柄表一般存储在内核模式的地址空间里以使图形引擎能很容易访问它,通过一定技巧,将为每个使用GDI的进程在用户模式存储空间里面建立表的只读视图。在Windows 2000终端服务中,每个对话都有它自己的Windows图形引擎和视窗管理器(WIN32K.SYS)的拷贝,以至于系统中有多个GDI对象表。
    GDI句柄表的每一个入口都是一个16字节的结构体,如下面代码所示: Typedef struct 

    {  

    void*        pKernel;  

    unsigned short nPaid;  

    unsigned short nCount;  

    unsigned short nUnique;  

    unsigned short nType;  

    void*        pUser;  

} GdiTableEntry;  
复制代码可见:GDI对象有一个指向它的内核模式对象的指针,一个指向其用户模式的指针,一个过程ID,一个种类的计数,一个唯一性的标准值以及一个类型标识符。多么完美,优雅的设计!
    尽管Win32 API不是面相对象的API,但它也面临着和面相对象语言一样要解决的问题,即信息的隐藏和抽象数据类型,而且Win32 API比面相对象语言更想隐藏对象。用于创建Win32对象的Win32函数隐藏了该对象的大小和储存位置,而且没有返回指向对象的指针,而是仅仅返回该对象的句柄。Win32 API句柄是Win32对象一一对应的变量,它仅能被操作系统识别。分析一下,你会发现Win32使用了相当多的抽象数据类型,如文件对象,它包括了许多具体的对象类型,我们可以用CreateFile来创建文件,管道,通讯端口,控制台,目录以及设备等,但是这些操作都返回一种类型的句柄。跟踪到 WriterFile中,会发现最后的操作其实是操作系统中不同例程甚至是不同产商提供的设备驱动程序来处理的,这就像是C++的虚函数和多态机制。同样,在GDI域中,设备上下文被当作一个抽象对象类型看待。创建或者检索打印机设备上下文,显示设备上下文,内存设备上下文和图元文件上下文的程序都将返回同类型的设备上下文句柄。显而易见的是,同年国国句柄的类属绘图调用是通过不同的例程来处理的,而这些例程但是由GDI或者图形设备驱动程序通过物理设备结构中的函数指针表来实现的。因此实现这样的机制也会像C++一样有一个类似于虚函数表的函数指针表,一个变量和函数指针通过这个表形成映射,方便的实现这种虚函数和多态机制,这个变量就是句柄....



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值