Sunday, October 25, 2009

7.1 GDI Objects, Handles, and Handle Table




< BACK  NEXT >

[oR]

7.1
GDI OBJECTS, HANDLES, AND HANDLE TABLE


The Win32 API uses dozens of different kinds of objects�file objects, synchronization objects, GDI objects�although the API is not based on any modern object-oriented language. In fact, the Windows 1.0 API was released in 1985, when C++ had only 500 users, long before object-oriented languages become popular during the 1990s. An object in an object-oriented language is defined as an instance of a class, which defines the object's data structure and behavior through class member variables and class member functions. Special member functions called constructors initialize new objects for the class, which will finally be deinitialized by another special member function called a destructor. So an object, in an object-oriented language, has a data structure and a set of class member functions that operate on it. The definition of a class determines how many member variables and functions are publicly accessible, protected, or private. This serves the important purpose of information hiding.



Although the Win32 API is not an object-oriented API, it faces the same problems that object-oriented programming languages are trying to solve�namely, information hiding and abstract data type.



The Win32 API tries to hide objects more than an object-oriented language does. For a Win32 API object, the application program does not know the size of the object or where it's allocated. The Win32 API provides special functions to create objects of different kinds, which is different from the use of constructors in the C++ language. Creating an object in C++ is a two-step process: First, storage for the object needs to be allocated; second, a constructor is called to initialize the object. So a user application using the object knows exactly how much space the object occupies and where it's located. A Win32 function to create a Win32 object hides away the object's size and lo cation. Furthermore, the application does not get back a pointer to an object; it merely gets back a handle to the object. A handle in the Win32 API is a value that can be mapped to a Win32 object in a one-to-one mapping, known only to the operating system.



Abstract data type is achieved in C++ through abstract class, concrete class, and virtual functions. An abstract class defines the generic behavior of a class through virtual functions. Several concrete classes could implement the same abstract class by providing implementation of its virtual functions. Objects of the concrete classes can be treated as objects of the abstract class, with function calls dispatched through a virtual function table.



Win32 uses quite a few abstract object types, if you look carefully. The file object type is an abstract object type, which has several concrete object types. CreateFile can be used to create files, pipes, mailslots, communication ports, consoles, directories, and devices, all returning the same type of handle. If you trace into WriteFile, you will find the final operation is handled by different routines in different parts of the OS and even in vendor-provided device drivers, much as virtual functions are used in C++. In the GDI domain, device context can be seen as an abstract object type. Creating or retrieving a printer device context, display device context, memory device context, or meta file device context�all return the same type of device context handle. But clearly, generic drawing calls through the handle are handled by different routines provided by GDI or graphics device drivers through a function pointer table in the physical device structure.



The Win32 API does have good reason to claim it's object based. Now let's take a detailed look at how Windows NT/2000 stores GDI objects, and how GDI object handles are mapped to objects. This serves as a concrete example of how the Win32 GDI could be implemented, which of course is not the only possible implementation.



GDI Object Storage


A C++ object is normally stored on a stack, a heap, or wherever defined by an application through a customized new operator. Basically, it is normally in user mode address space, unless we are talking about a kernel mode device driver.



The Windows NT/2000 graphics system is divided into three parts: the user mode client DLL GDI32.DLL, the kernel mode graphics engine WIN32K.SYS, and various device drivers which are also normally in kernel mode. The mixed user and kernel mode implementation makes GDI object storage design a little more fun.



A GDI object stored entirely in user mode address space can be accessed only when the process creating it is active, because different processes have different user mode address spaces. Once the process is switched, the address which stores the object is no longer valid. If this approach is used, GDI operations in the kernel mode graphics engine can be carried out only in the context of the calling process. This clearly interferes too much with the smooth task switching required for high-performance multitasking.



On the other hand, a GDI object stored fully in kernel mode address space can be accessed only in kernel mode address space. Simple operations like setting an attribute in a device context will need a system service call to switch the processor into kernel mode, execute a few lines of assembly code, and then switch back to user mode.



In Windows NT/2000, a GDI object normally has two portions of storage�a user mode object and a kernel mode object. The user mode object provides quick access, while the kernel mode object provides process-independent information storage. Some kernel mode objects, like the kernel mode device context, have copies of their user mode objects, which are synchronized through some mechanism.



For most GDI objects, a fixed-size data structure is kept by GDI to maintain GDI's internal representation of it. For these GDI objects, memory usage is not a big concern. For GDI region objects and device-dependent bitmap objects, the size of the data kept by GDI can grow to a significant amount. The kernel mode data structure for the GDI data structure is stored in a so-called paged pool. According to the Windows NT/2000 design, the kernel paged pool has a 192-MB size limitation on a single-user system. Given that GDI's internal memory pool is limited and shared by all applications on a system, the application should take care not to create large amounts of GDI regions and device-dependent bitmaps.




The GDI Object Table


GDI objects are stored in a systemwide fixed-size object table, or call it an object handle table. We have several things to explain here. First, the GDI object table is a fixed-size table, not a dynamically growing array as you might have expected. This means simplicity and efficiency, but also limitation. As of now, Windows NT/2000 allows 16,384 GDI handles. Second, the GDI object table is shared by all the processes and system DLLs in the system. Your desktop, browser, word processor, and DirectX game all compete for the same pool of GDI handles. In Windows 2000, DirectX uses a separate object handle table.



The GDI handle table is stored in kernel mode address space so that the graphics engine can easily access it. Through some magic, a read-only view of the table is created in user mode address space for every process that uses GDI.



NOTE




On a terminal server, each session has its own copy of the Windows graphics engine and the window manager (WIN32K.SYS), so there are multiple GDI object tables on the system.









Each entry in the GDI handle table is a 16-byte structure, as shown here:





typedef struct
{
void * pKernel;
unsigned short nPid;
unsigned short nCount;
unsigned short nUnique;
unsigned short nType;
void * pUser;
} GdiTableEntry;


So a GDI object has a pointer to its kernel mode object, a pointer to its user mode object, a process id, a count of some kind, a uniqueness value, and a type identifier. What a nice, elegant design!



The nPid field stores the process identifier of the process creating the object [value returned by GetCurrentProcessId()]. Every GDI object created by a user process is tagged with the process's id, to prevent the cross-process usage of GDI handles. When you pass a GDI object handle to GDI, it can easily validate whether the object is created by the current process. Using a GDI object created by a foreign process results in failure, except for stock objects. Stock objects, like black pen, white brush, and system font, are GDI objects that are preselected into a new device context. It would be wasteful for each process to create its own copy of all the stock objects. Instead, stock objects are created once in the system and have 0 as their process id. Your task manager will tell you that zero happens to be the process id for the system idle process. GDI object handles with zero as their Pid can be used in all processes.



The nCount could serve as some sort of select count, or reference count, to prevent the deletion of objects in use, or to assure that certain objects can be selected only once. Unfortunately, the nCount field is used only in a limited way. The user application still needs to take care of when an object can be deleted.



The uUnique field may be the more interesting field here. GDI objects are stored in the same table. It could happen that an object is created, used, and then deleted, so the slot occupied by it is freed. Later on, another object is created in the same slot. Now some buggy code still holds a handle to the first object and dares to use it; what's going to happen? On Windows NT/2000, the buggy call is almost guaranteed to fail, because of this uniqueness field. The nUnique field is divided into an 8-bit recycle count and an 8-bit type code. The recyle count starts with zero, incremented every time a GDI object is created in this slot. There is another scheme in the GDI handle manager that ensures that slots in the table are used evenly. So the old handle can pass the uniqueness check only if the number of objects created on the same slot is a multiple of 256 and if the object types are the same.



The nType field is the internal type GDI keeps for each GDI object, which can be translated to the GDI object type returned by GetObjectType.




The GDI Object Handle


My dictionary defines a handle to be the part of a door, window, etc., by which it can be opened. MSDN defines a handle to be a variable that identifies an object, an indirect reference to an operating-system resource. A programmer nowadays should be able to live with this kind of abstract definition and write wonderful programs with it. But someone chasing a thunking or subclassing defect between 16-bit and 32-bit code on Windows NT/2000, or someone writing a low-level tool, would prefer a more concrete breakdown of every bit of every handle in the Win32 API.



A GDI object handle in Windows NT/2000 is made up of two 16-bit values, its uniqueness value and its index to the GDI handle table.



What's the significance of this design? Easy mapping from handle to GDI object, excellent protection, and limited GDI handles.



To map a GDI handle to an entry in the GDI object table, just mask out the lower 16 bits of the handle, multiply it by 16, and add the starting address of the GDI object table. The GDI object table starting address is kept in global variables in both WIN32K.SYS and GDI32.DLL.



For protection, we've talked about the effectiveness of the uniqueness value. The uniqueness value is also part of a GDI object handle. The uniqueness value in a GDI handle must match the uniqueness value stored in a GDI object table entry before GDI allows accessing of the handle. Once GDI gets the GDI object table entry, the process identifier stored in it provides a double check. An invalid handle, dead handle, or a handle from a foreign process can be easily singled out.



In the current implementation, the number of GDI object handles on a system (or per session on a Windows terminal server) is limited to 16,384 objects. This can easily be expanded to 65,536 handles, because within a GDI handle, the index part takes 16 bits instead of 12.



User applications should not be designed to rely on knowledge of the GDI handle format and how the GDI object table is organized. This changes from one operating system to another and may change again in future releases. But Windows NT/2000 does provide more protections and restrictions on GDI handles, which also allows better debugging of code. Make sure that you don't share GDI handles across a process boundary and that you test your application on all current operating systems.



For more information on the internal GDI data structure behind the GDI handles, refer to Chapter 3 of this book.




The GDI Object API


GDI objects are divided into several major categories. Logical brushes, logical pens, logical fonts, logical palettes, regions, device-dependent bitmaps, DIB sections, enhanced metafiles, and device contexts are common types of GDI objects. Each normally has several dedicated routines to create a new GDI object of that type. Once a GDI object is created, GDI returns a GDI object handle to the application. A number of GDI functions operate on generic GDI object handles.





HGDIOBJ SelectObject(HDC hDC, HGDIOBJ hgdiobj);
BOOL DeleteObject(HGDIOBJ hObject);
DWORD GetObjectType(HGDIOBJ h);
int GetObject(HGDIOBJ hgdiObj, int cbBuffer, LPVOID lpvObject);


Several types of GDI objects can be attributes of a device context: brushes, pens, fonts, palettes, device-dependent bitmaps, and DIB sections. SelectObject is used to attach one of them to a device context object. The function returns the previous GDI handle of the same type as a return value. To deselect a GDI object from a device context, call SelectObject again with the value returned from the first selection. The logical palette has a special routine for selecting it into a device context, SelectPalette.



When a GDI object is no longer required, it needs to be deleted using Delete Object. Before deleting a GDI object, the application needs to make sure it's not selected into any device context.



Windows 95/98/Me and Windows NT/2000 have slightly different implementations of the GDI object deletion. On Windows 95/98/Me, DeleteObject does not delete an object if it's still selected into a device context. This can cause potential GDI object leakage. On Windows NT/2000, DeleteObject deletes a GDI object even if it is still selected into a device context. Further drawing using the already deleted GDI object will fail, which makes it easier for programmers to notice the problem.



If a program is developed and tests fine on a Windows NT/2000 machine, but fails on a Windows 95/98/Me machine, one possible reason is that it has an improper GDI object deletion problem. The class KGDIObject is a simple wrapper around GDI object selection, deselection, and deletion.





class KGDIObject
{
HGDIOBJ m_hOld;
HDC m_hDC;

public:

HGDIOBJ m_hObj;

KGDIObject(HDC hDC, HGDIOBJ hObj)
{
m_hDC = hDC;
m_hObj = hObj;
m_hOld = SelectObject(hDC, hObj);

assert(m_hDC);
assert(m_hObj);
assert(m_hOld);
}

~KGDIObject()
{
HGDIOBJ h = SelectObject(m_hDC, m_hOld);
assert(h==j);

DeleteObject(m_hObj);
// assert(GetObjectType(m_hObj)==0);
}
};


The KGDIObject has three member variables: for the GDI object handle to be selected, for a device context handle, and for a handle to the original object in that device context. Its constructor handles the selection of a GDI object into a device context. Three assertions are added to make sure that the parameters are right and that selection succeeds. The destructor of the class deselects the GDI object and then deletes it. The first assertion in it makes sure that deselection occurs as expected. The second assertion makes sure the GDI object is really deleted. The second assertion is commented out, because GDI sometimes caches delete the objects to speed up the creation of new objects of the same types.



NOTE




MFC wraps GDI's SelectObject function to return a pointer to an instance of MFC's GDI object class CGdi Object. But the result could be a temporary pointer if MFC is unable to map a GDI object handle to an MFC object pointer. If an application is relying on the temporary pointer to deselect an object when it's not used, it could already be changed by another SelectObject call�a possible cause for a GDI object leak.









Here is a simple example of using the KGDIObject class:





void OnDraw(HDC hDC)
{
KGDIObject blue(hDC, CreateSolidBrush(RGB(0, 0, 0xFF));
Rectangle(hDC, 0, 0, 100, 100);
}


As a last line of defense for a healthy system, when a process finishes, all GDI objects created by that process are automatically deleted, apparently using the process identifier attached to each GDI object. Still, applications should be very careful in deleting GDI objects properly, instead of relying on the system to clean up the mess when processes terminate. Constant GDI resource leakage could quickly stop the whole system from functioning properly.



Given a GDI object handle, GetObjectType returns an integer number representing the type of GDI object it is. For example, OBJ_DC will be returned for a device context object, OBJ_BRUSH will be returned for a logical brush object, OBJ_ BITMAP will be returned for either a device-dependent bitmap or a DIB section. If zero is returned, the handle passed in is either an invalid GDI object handle, or a handle belonging to another process.



GetObject can be used to query for the original structure used in creating a GDI object for several types of GDI objects. It fills in a BITMAP structure for a device-dependent bitmap, a DIBSECTION structure for a DIB section, an EXTLOGPEN structure for an extended pen, a LOGPEN structure for a pen, a LOGBRUSH structure for a brush, a LOGFONT structure for a font, and a WORD for a palette. To call GetObject, pass a GDI object handle, the expected size of the structure, and a pointer to a data block large enough to hold the structure. If the caller is not sure about the number of bytes needed to hold the structure, passing a NULL pointer to Get Object lets it return the number of bytes needed.



GetObject can be used to distinguish between device-dependent bitmaps and DIB sections, which make no difference to GetObjectType. Just call GetObject with a DIBSECTION structure; if the call succeeds, the GDI object is a DIB section.



Details of each type of GDI object will be discussed as their services are needed.




GDI Object Leakage Detection


When symptoms of GDI object leakage appear�for example, when painting can't be completed or the system user interface display is not drawn properly�it's quite hard to find which application is causing the problem, or exactly what is leaked, and where the leak occurs.



The only powerful tool to pinpoint GDI object leakage and other types of memory leakage and resource leakage seems to be Numega's BoundsChecker. Bounds Checker works by overwriting almost all Win32 API functions, recording, validating, and analyzing parameters and function return results. For example, if all GDI object-creation calls and object-deletion calls are logged, a tool like BoundsChecker can compare object creation and object deletion before process termination and find all GDI object leakage together with the caller's exact location. Chapter 4 of this book describes several tools for spying on GDI API calls, system service calls, and DDI calls. It's possible to extend these tools to detect GDI object leakage.



Based on the understanding of GDI internal data structure, it's not hard to write a program to monitor the usage of GDI objects for all the processes running on a Windows NT/2000 system. Such a monitoring program can indicate GDI resources in the system, although it does not have enough information to pinpoint the exact location of leaking. Figure 7-1 illustrates the �GDIObj� program developed for this chapter. The program enumerates objects in the GDI object table on a periodic basis, sorts them according to their process identifiers and object types, and displays in a list view.




Figure 7-1. GDI objects monitoring program.


As shown in Figure 7-1, MS Developer Studio is using 466 GDI objects, 2.8% of the total available GDI object handles. If the number of GDI handles used by an application keeps increasing over time, it's a clear sign of GDI object leakage. For a monitored process, �GDIObj� displays the total number of GDI objects and breaks it down into several major categories. If there is a leak, it gives you an indication of the types of GDI objects actually being leaked.



Figure 7-1 shows that the first process (with PID 0) has 66 unclassified objects. These objects are mainly physical font objects used by GDI. It also shows that a Win32 process with a graphics user interface uses at least four GDI objects: two device contexts, one bitmap, and one brush. These objects may be created during the initialization of either the USER32.DLL or GDI32.DLL. This implementation limits the number of processes you can run on a single system to 4,096, because there are only 16,384 handles available.



Windows 2000 provides a new function for applications to query GDI and USER resource usage.





DWORD GetGuiResources(HANDLE hProcess, DWORD uiFlags);


Function GetGuiResources can be used to query the number of GDI objects or USER objects used by an application. The first parameter to GetGuiResources is the process handle for the process you're interested in. The second parameter can either be GR_GDIOBJECTS or GR_USEROBJECTS. The function returns the current number of objects used.



Function GetGuiResources offer an official way to tell if a piece of code has possible resource leakage. For example, you can call it before and after a drawing routine and compare the function return values to see if it creates any new objects. Although the difference in the return results does not always mean resource leakage, because of possible object caching by application and GDI, continuous increase in object usage is a clear indication of object leakage.



Here is a simple wrapper class and its sample usage in handling the WM_PAINT message:





class KGUIResource
{
int m_gdi, m_user;

public:
KGUIResource()
{
m_gdi = GetGuiResources(GetCurrentProcess(), GR_GDIOBJECTS);
m_user = GetGuiResources(GetCurrentProcess(), GR_USEROBJECTS);
}

~KGUIResource()
{
int gdi = GetGuiResources(GetCurrentProcess(), GR_GDIOBJECTS);
int user = GetGuiResources(GetCurrentProcess(), GR_USEROBJECTS);

if ( (m_gdi==gdi) && (m_user==user) )
return;

char temp[64];
wsprintf(temp, "ResourceDifference: gdi(%d->%d) user(%d->%d)\n",
m_gdi, gdi, m_user, user);
OutputDebugString(temp);
}
};
case WM_PAINT:
{
KGUIResource res;

PAINTSTRUCT ps;
HDC hDC = BeginPaint(m_hWnd, &ps);

OnDraw(hDC, &ps.rcPaint);

EndPaint(m_hWnd, &ps);
}






< BACK  NEXT >





No comments:

Post a Comment