句柄和指针的区别

本文详细阐述了Windows系统中句柄与指针的概念及其区别。句柄作为系统资源的标识符,主要用于隐藏系统信息,并且是系统分配给窗口等资源的唯一标识;而指针则是指向内存地址的数据类型。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这是初学者最常问及的问题,一些面试官也很喜欢问这个问题 。

当把硬盘上的资源调入内存以后,将有一个句柄指向它,但是句柄只能指向一个资源。而且句柄知道所指的内存有多大。还有指针,指针指向地址,它不知道分配的内存有多大。  

但是如果你定义一个句柄,然后在VC里面右击鼠标,选择"go to definition of handle”,你会发现它的本质就是一个指针,但是它的作用不同于指针。它和通常意义上的指针是有区别的。句柄借用了指针的思想,有它的逻辑特点,但没有它的物理功能。句柄是WINDOWS分配给窗口等资源的唯一标识,是一个整数。

一、书上定义:

<<Microsoft Windows 3 Developer''s Workshop>>(Microsoft Press,by Richard Wilton)

在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)。

<<WINDOWS编程短平快>>(南京大学出版社):

句柄是WONDOWS用来标识被应用程序所建立或使用的对象的唯一整数,WINDOWS使用各种各样的句柄标识诸如应用程序实例,窗口,控制,位图,GDI对象等等。WINDOWS句柄有点象C语言中的文件句柄。

二、MFC源代码

#ifdef STRICT

typedef void *HANDLE;

#define DECLARE_HANDLE(name) struct name##__ { int unused; }; typedef struct name##__ *name

#else

typedef PVOID HANDLE;

#define DECLARE_HANDLE(name) typedef HANDLE name

#endif

DECLARE_HANDLE(HMODULE);

DECLARE_HANDLE(HINSTANCE);

DECLARE_HANDLE(HLOCAL);

DECLARE_HANDLE(HGLOBAL);

DECLARE_HANDLE(HDC);

DECLARE_HANDLE(HRGN);

DECLARE_HANDLE(HWND);

DECLARE_HANDLE(HMENU);

DECLARE_HANDLE(HACCEL);

DECLARE_HANDLE(HTASK);

三、理解:

句柄是一个32位的整数,实际上是windows在内存中维护的一个对象(窗口等)内存物理地址列表的整数索引。因为windows的内存管理经常会将当前空闲对象的内存释放掉,当需要时访问再重新提交到物理存储,所以对象的物理地址是变化的,不允许程序直接通过物理地址来访问对象。程序将想访问的对象的句柄传递给系统,系统根据句柄检索自己维护的对象列表就能知道程序想访问的对象及其物理地址了。

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

   对于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++一样有一个类似于虚函数表的函数指针表,一个变量和函数指针通过这个表形成映射,方便的实现这种虚函数和多态机制,这个变量就是句柄.... 

因此,句柄和指针其实是两个截然不同的概念。windows系统用句并标记系统资源,用句并隐藏系统信息。你只需要知道有这个东西,然后去调用它就行了,它是32bit的uint。指针则标记某个物理内存的地址,是不同的概念。

指针对应着一个数据在内存中的地址,得到了指针就可以自由地修改该数据。Windows并不希望一般程序修改其内部数据结构,因为这样太不安全。所以Windows给每个使用GlobalAlloc等函数声明的内存区域指定一个句柄(本质上仍是一个指针,但不要直接操作它),平时你只是在调用API函数时利用这个句柄来说明要操作哪段内存。当你需要对某个内存进行直接操作时,可以使用GlobalLock锁住这段内存并获得指针来直接进行操作。

句柄是指针的“指针”,使用句柄主要是为了利于windows在进程内存地址空间移动分配的内存块,以防止进程的内存空间被撕的四分五裂而存在过多的碎片。

句柄是一些表的索引也就是指向指针的指针。间接的引用对象,windows可以修改对象的"物理"地址和 描述器的值,但是句柄的值是不变的。

句柄可以在获得窗口的时候使用,指针可以进行调用窗口,两个使用的地方不一样.一个括号外,一个括号内.

 

从窗口指针获取窗口句柄:GetSafeHwnd();

从窗口句柄获取临时窗口指针:FromHandle();

从窗口句柄获取永久窗口指针: FromHandlePermanent();

其实两者被没有关系,实际上是MFC在创建窗口的时候用钩子函数沟住HCBT_CREATEWND消息,

然后通过CWnd::Attach()函数把二者捆绑在一起。

以后就可以用GetSafeHwnd(),FromHandle(),FromHandlePermanent()这三个函数可以互相得到了。

MFC之所以要这样做,主要是为了使原来的SDK面向过程的编程遍成面向对象的编程,所有的MFC的窗口都共用一窗口过程函数,在窗口过程函数里,通过窗口句柄(HWND)找到窗口对象指针(CWnd *)从而把消息分发到窗口对象中,这样以后就可以在窗口类中实行消息响应编程处理了。

附注一:获得窗口句柄三种方法

1.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;

<think>我们正在讨论虚幻引擎中的句柄指针。用户之前的问题是关于句柄指针的一般区别,现在特别询问在虚幻引擎中的情况。我需要结合虚幻引擎的特性来回答。 首先回顾一下句柄指针的基本概念: - 指针:直接存储内存地址,用于访问对象。 - 句柄:通常是一个抽象标识(如整数),通过系统(如引擎或操作系统)映射到实际资源。 在虚幻引擎中,这两种机制都存在,但使用场景目的不同。 参考引用内容: 引用[1]提到了UFUNCTION宏,这是用于将C++函数暴露给蓝图系统的。虽然与句柄指针没有直接关系,但说明虚幻引擎有自己的一套抽象机制,其中可能涉及句柄的使用。 引用[2]介绍了纹理空间着色,其中提到了纹理映射,这可能会使用到句柄(例如纹理句柄)来管理纹理资源。 引用[3]提到了多线程环境下的竞争问题,这提醒我们在使用指针时需要注意线程安全,而句柄系统可能由引擎内部管理,提供一定程度的线程安全。 在虚幻引擎中,句柄指针的差异关联: 1. **指针(Pointer)**: - 虚幻引擎中大量使用C++对象,因此指针是直接操作对象的常用方式。 - 例如,`AActor*`指向一个Actor对象。 - 但是,直接使用裸指针存在风险,比如对象被销毁后成为野指针。 2. **句柄(Handle)**: - 虚幻引擎中,句柄通常用于管理那些需要安全引用的资源,例如: - **TWeakObjectPtr**:虽然名为指针,但实际上是一种句柄。它是对UObject的弱引用,不会阻止垃圾回收,当对象被销毁时,它会自动置空。内部实现可能使用一个句柄(例如索引到对象表的整数)而不是直接指针。 - **FDelegateHandle**:用于管理委托(delegate)的注册,以便后续可以取消注册。 - **资源句柄**:如纹理、网格等资源,引擎可能使用句柄来引用,而不是直接使用指针,以便于资源管理跨平台安全。 - 句柄的优势在于:当资源被卸载或对象被销毁时,句柄可以安全地检测到,避免访问无效内存。 3. **智能指针**: - 虚幻引擎也有自己的智能指针系统(如TSharedPtr, TSharedRef, TWeakPtr),这些可以看作是指针的封装,提供引用计数自动管理。它们与句柄有相似之处(安全引用),但实现上还是基于指针(只不过加了控制块)。 - 但是,对于UObject(继承自UObject的对象),引擎使用自己的垃圾回收机制,因此通常使用裸指针或TWeakObjectPtr,而不使用智能指针。 4. **关联**: - 句柄内部通常包含一个指向资源管理系统的索引或键,通过这个索引,系统可以找到实际的资源指针。因此,句柄可以看作是指针的间接层。 - 例如,`TWeakObjectPtr`内部存储了一个`FObjectHandle`,它由两部分组成:一个对象索引一个唯一标识符(用来检查对象是否已被替换)。当需要访问对象时,通过全局对象表将句柄解析为指针。 5. **差异**: - **生命周期管理**:指针直接指向内存,而句柄由引擎的资源管理系统维护,可以安全地处理对象销毁。 - **安全性**:句柄更安全,特别是在多线程环境下(如引用[3]提到的竞争问题)异步加载时,因为句柄可以验证有效性。 - **性能**:指针访问更快,因为直接访问内存;句柄需要间接查找(但引擎做了优化,如缓存指针,减少查找开销)。 6. **虚幻引擎中的具体例子**: - **TWeakObjectPtr**:用于安全的弱引用。使用示例: ```cpp TWeakObjectPtr<AMyActor> WeakActorPtr = MyActor; // 在另一个地方使用 if (WeakActorPtr.IsValid()) { AMyActor* ActorPtr = WeakActorPtr.Get(); } ``` - **FTextureResource**:纹理资源可能通过句柄引用,但通常我们使用`UTexture*`。在底层渲染代码中,可能会使用句柄(如RHI纹理句柄)来代表平台相关的纹理资源。 7. **为什么使用句柄**: - 避免野指针:当对象被垃圾回收后,句柄可以自动失效。 - 支持资源热重载:句柄可以保持不变,而实际资源指针改变(重新加载后)。 - 跨模块边界:句柄可以安全地传递,而指针可能因为模块卸载(如动态加载的模块)而无效。 总结: | 特性 | 指针(如AActor*) | 句柄(如TWeakObjectPtr) | |--------------|----------------------------------|-----------------------------------| | 直接内存访问 | 是 | 否(通过Get()获取指针,可能无效) | | 安全性 | 低(可能野指针) | 高(自动失效) | | 性能 | 高(直接访问) | 较低(需要检查有效性) | | 使用场景 | 同一作用域内,对象生命周期确定 | 跨函数、跨线程、不确定生命周期 | | 管理方式 | 无(或手动管理) | 引擎内部资源管理系统 | 在虚幻引擎中,推荐的做法是: - 对于UObject派生类,使用`TWeakObjectPtr`或`TObjectPtr`(UE5引入的增强指针)来避免野指针。 - 对于非UObject对象,可以使用智能指针(如`TSharedPtr`)或句柄。 注意:在UE中,句柄通常不是整数,而是一个类(如`TWeakObjectPtr`),它封装了查找验证逻辑。 相关问题: 1. 在虚幻引擎中,TWeakObjectPtr是如何实现安全引用的? 2. 虚幻引擎的垃圾回收机制如何与句柄交互? 3. 在渲染系统中,句柄是如何管理GPU资源的? 4. 如何避免在多线程环境下使用指针导致的竞争条件? 以上问题可以引导用户进一步理解虚幻引擎中的资源管理。</think>在虚幻引擎(Unreal Engine)中,句柄(Handle)指针(Pointer)都是管理资源的机制,但设计目的实现方式有显著差异。以下是核心区别关联: --- ### **一、核心区别** | **特性** | **指针(Pointer)** | **句柄(Handle)** | |------------------|---------------------------------------------|------------------------------------------------| | **本质** | 直接内存地址(如 `AActor* ActorPtr`) | 资源的抽象标识符(如 `FDelegateHandle`) | | **资源管理** | 需手动管理生命周期(易内存泄漏) | 由引擎自动管理(如垃圾回收/资源池)[^3] | | **安全性** | 高风险(野指针、多线程竞争)[^3] | 高安全(自动失效检测,如 `TWeakObjectPtr`) | | **稳定性** | 对象移动/销毁后失效 | 对象销毁后句柄自动标记无效(`IsValid()`检查) | | **使用场景** | 高频操作(如游戏逻辑循环) | 跨系统边界(如事件委托、异步加载) | | **虚幻实现** | 原生C++指针(`UObject*`) | 引擎封装类型(`FHandle`、`TWeakPtr`等) | > **示例对比** > ```cpp > // 指针:直接访问对象(风险高) > AActor* ActorPtr = GetActor(); > ActorPtr->Destroy(); > > // 句柄:安全访问(引擎管理) > FDelegateHandle EventHandle = OnEvent.Add(...); > if (EventHandle.IsValid()) OnEvent.Remove(EventHandle); // 安全移除 > ``` --- ### **二、核心关联** 1. **间接访问资源** 两者均提供对资源(如 `UObject`、纹理、委托)的间接访问,避免直接暴露底层实现[^1]。 2. **资源标识作用** - 指针:标识对象内存地址(如 `UTexture* TexturePtr`) - 句柄:标识资源在引擎内部的逻辑ID(如渲染资源句柄 `FRHITextureHandle`)[^2] 3. **相互转换机制** 虚幻提供工具类实现安全转换: ```cpp // 将句柄转为指针(需有效性检查) UObject* ObjPtr = WeakObjectPtr.Get(); // 使用 TWeakObjectPtr ``` --- ### **三、虚幻引擎中的典型应用** #### **1. 指针的核心场景** - **游戏对象操作** ```cpp APawn* PlayerPawn = GetPlayerPawn(); // 直接操作Pawn对象 ``` - **性能敏感逻辑** 在 `Tick()` 循环中直接访问组件指针,避免句柄的间接开销。 #### **2. 句柄的核心场景** - **事件委托系统** ```cpp FDelegateHandle Handle = OnActorSpawn.AddLambda([](){...}); // 委托句柄 ``` - **弱引用管理** ```cpp TWeakObjectPtr<AActor> WeakActorPtr = ActorPtr; // 自动失效检测 ``` - **渲染资源管理** 纹理空间着色中,通过句柄解耦资源访问(如 `FRHITextureHandle`)[^2] ![](https://docs.unrealengine.com/5.3/Images/rendering/texture-space-shading/TextureSpaceShadingOverview.webp) *纹理空间着色通过句柄分离可见性采样(屏幕空间)着色(纹理空间)[^2]* - **异步资源加载** ```cpp FStreamableHandle AssetHandle = LoadAssetAsync(...); // 异步加载句柄 ``` --- ### **四、设计选择原因** 1. **句柄的优势** - **线程安全**:避免多线程竞争(如 `Stop()` 与 `Run()` 的竞争)[^3] - **生命周期安全**:对象销毁后句柄自动失效(如 `TWeakPtr`) - **跨系统兼容**:统一管理不同资源(如委托、GPU资源、异步任务) 2. **指针的适用场景** - 需要直接内存操作的高性能场景(如物理模拟) - 对象生命周期完全可控的局部逻辑(如临时计算) --- ### **五、总结** | **维度** | 指针(Pointer) | 句柄(Handle) | |----------------|-------------------------------------|-------------------------------------| | **设计目标** | 最大化性能 | 最大化安全性与稳定性 | | **使用风险** | 内存泄漏/野指针 | API误用(如未移除委托句柄) | | **最佳实践** | 局部作用域、高频逻辑 | 跨系统通信、资源异步操作 | | **演进趋势** | 逐渐被 `TObjectPtr`(UE5)增强 | 仍是引擎核心机制(委托/弱引用) | > **黄金法则**: > - 优先使用句柄管理**跨系统**资源(委托、异步加载、弱引用) > - 仅在**性能关键路径**且**生命周期可控**时使用裸指针
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值