A term often used in programming that sometimes leaves new developers flummoxed is the “handle”. Handles are a convenient way to identify complex objects like windows, files, etc. In this blog, we’re going to get a “handle” on handles by explaining what they are, how Legato creates and manages them, and what you need to know to properly use them.
Friday, November 02. 2018
LDC #109: Getting a Handle on Handles
Introduction
Handles are an abstract way to track and access complex objects within a part of a program, server or operating system. There isn’t really any magic behind them. They are simply a number representing something managed by an API.
Within a specific context, a handle will be a unique number. Internally, the code that employs the handle to provide a service will have a table or management scheme to hold information regarding operations associated with an object. For example, if we open a file within an operating system, the file management system will manage internal information about the device location of the file, cluster information, access privileges, current file position, and so on. The handle provides a method for the file system to pick up the file data and satisfy requests concerning the file. If the handle is never closed or destroyed, the API will never release the resources. This is what is known as a ‘leak’, which used to be a real problem for operating systems that resulted from crashed programs or sloppy programming. Fortunately, operating system developers eventually added protections such that each process’ handles are tracked and automatically closed even if you as the programmer don’t close them. This works for the most part and significantly improves system stability. Still, even with this safeguard, you can still leak resources within your program, which can lead to issues both for your code as well as the system.
Handle Management
Within Legato, handles are managed or unmanaged. For managed handles, which are also the majority of handles used by most programs, there is a composite handle manager that tracks each handle and all of its resources. As shown above, an action such as Open, will request a specific resource from a object processor. If that succeeds, it will register a handle and return that value. The value is then used to identify the resource for further operations. Under the hood, most object processors will in turn request system resources and have their own handles to such resources.
By default, when a handle data type is declared, the script engine will automatically close the handle when the variable pool in which it was created is destroyed or released. In each subroutine, local variables have their own variable pool. Thus, when you exit a function, the pool is released, and any variables declared as handle are closed. Each running script engine also has a global variable pool. Similar to a subroutine exiting, when the script engine shuts down, all global handles are closed. Note that when a handle is passed as a function parameter, it is considered unmanaged and not closed when the called function exits. Finally, when a script is shutdown, the handle manager is closed and all resources are released.
The auto close function for variables declared as handle relies on the variable value being intact. If a handle is not closed and the variable is reused, perhaps to open another object, the resource will only be released at the termination of the script engine.
To manually close a handle, the CloseHandle function can be used (shown below). The variable holding the handle does not need to be cleared, although it is good programming practice to do so when variables are reused or global.
Unmanaged handles are typically resources such as windows.
For those handles, the script engine provides no assistance in management. In most cases, an unmanaged handle is not managed by the script engine but rather is expected to be managed by the operating system, other service, and or the script itself. In some cases it may be up to the programmer to exercise good technique when using them. For example, when a window is closed, the operating system will automatically discard all resources associated with a window and the handle will become invalid.
Can Managed Handles Leak?
Absolutely. Consider a variable declared as a global variable, perhaps a Basic File Object. If the variable is set to a file object handle and later reused to open or create another file, the first handle will be lost. It will of course be released when the script terminates, but until then that file may be locked. The same thing can happen as a local variable, but it is less likely as a matter of practice. On the other hand, if a handle variable is reset without closing the associated object, access to that object will be lost. Therefore, it’s always advisable to manage handles correctly, with or without Legato’s help.
Handle Values
So, what is the actual handle value? For Legato, it is the address in memory of the associated object that controls the resource. Since two items cannot generally occupy the same location in memory, each handle is unique to that specific script. In addition, each open handle is placed in the management table to resolve the associated object for use. If the handle is not in the table, the requesting function will fail. Since handle values are unique to a script, you cannot take a handle value from one script and pass it to another script. Managed handle values will always be larger than 0x00010000 and never zero.
Unmanaged handles use different schemes for identification which may vary between different operating system versions. For example, window handles, file handles, and GDI objects will all have a range of values. Over the years I have tried to find an article on the logic for Windows SDK handle values, but, alas, there does not seem to be a comprehensive document describing them.
There are two constants defined for handles: NULL_HANDLE and INVALID_HANDLE_VALUE. For Legato handles, NULL_HANDLE or 0x00000000 simply indicates a variable that is devoid of a handle. Since variables initialize as zero, they are automatically null. The second value, INVALID_HANDLE_VALUE or -1, is used by the operating system. Some handles, such as Windows file handles, can be zero and still be a valid reference to a file. In that case, you should check the handle against INVALID_HANDLE_VALUE to determine if it’s invalid.
Legato has provisions for pseudo handles, that is, handle values that can be used by certain functions to reference specific items that are always open. The most notable is the SDK definition APP_LOG_HANDLE. Using this with the AddMessage function drives output to the application log, which is always open.
Finally, do not save handle values and use them in a different script session since each managed handle value is unique to the script in which it is running. For certain unmanaged handles such as windows, the handle value is global to the system and can be shared. Still, it’s always a good idea to check a handle whose origin isn’t necessarily known to make sure it’s valid.
Multiple Purpose Functions
Many Legato function groups allow for the use of multiple handle types. A perfect example is the AddMessage function. It allows the use of Basic File Objects, Pool Objects, Log Objects, and consoles. This function will also operate without a handle by directing output to the default log. Many functions that act on Edit Objects will also work with Mapped Text Objects. Similarly, Data View functions will work with a Data View Object or a unmanaged window handle for a specific view or tab.
If the handle type is incorrect or stale, the error code 6 (ERROR_INVALID_HANDLE) will be part of the returned formatted error code.
Why a Data Type
While a handle is a number, there really is no point in performing any level of math or logical operation on a handle. So, while the underlying type for a handle in an unsigned integer, and you can perform math on a dword or int, a handle is a handle. Adding 1 to a variable declared as handle will result in a run-time error in Legato. So the handle data type protects the variable from incorrect uses that may have resulted from a brain hiccup.
Legato has special typing matching for the literal values 0 and -1 for setting a handle value. Any other integer will fail with a run-time type mismatch. As such, only API functions can return handle values. To make a handle from an integer or string, the MakeHandle function can be used:
handle = MakeHandle ( string value | int value );
This is useful when a handle is returned from a function as a value in a string array. For example, when running certain menu functions, the window handles for the MDI and edit views are returned as strings as part of the result. Handle values can also be printed to strings, which is again useful for storing and passing to non-Legato functions.
Finally, at some point applications supporting Legato will be 64-bit. When that occurs, handles will automatically change to a wider type. That’s something to consider for later compatibility.
Handle Types
There are two broad types of handles: Legato object handles and external handles. Legato objects include things like Mapped Text, File, SGML or ODBC object handles. We’ve used a great many of these in the past on this blog. External handles are things like window handles.
Legato handles are used to control access to predefined types of objects, such as the Basic File or Mapped Text Objects. These handles are managed by the script engine, which means that they function in defined ways. They are also closed and released by the script engine upon a function’s return or the script’s termination, so an explicit statement to close a Legato handle is not necessary. As I said above, other handles for Windows items or program-defined objects may be employed, but these are not automatically “cleaned up” by the script engine. It is up to the programmer to properly create, maintain, and release non-Legato objects and their handles.
As a point of interest, a managed handle’s type can be retrieved using the GetHandleType function:
dword = GetHandleType ( handle value );
The following table enumerates the handle types for the base engine. Each extension object will have its own types.
Name | Bitwise | Description | |||||
Types | |||||||
SOT_LOCATION_MASK | 0xFF000000 | Location Mask | |||||
SOT_LOCAL | 0x00000000 | Local Object (0x00------) | |||||
(other types defined in SDK) | |||||||
Base Object Types | |||||||
SOT_BASIC_FILE_OBJECT | 0x00000000 | Basic File Object | |||||
SOT_MAPPED_TEXT_OBJECT | 0x00000001 | Mapped Text Object | |||||
SOT_EDIT_OBJECT | 0x00000002 | Edit Object | |||||
SOT_POOL_OBJECT | 0x00000003 | Pool Object | |||||
SOT_LOG_OBJECT | 0x00000004 | Log Object | |||||
SOT_WORD_PARSE_OBJECT | 0x00000005 | Word Parse Object | |||||
SOT_CSV_OBJECT | 0x00000006 | CSV Object | |||||
SOT_MD5_DIGEST_OBJECT | 0x00000007 | MD5 Digest Object | |||||
SOT_FOLDER_ENUM_OBJECT | 0x00000008 | Find File Object | |||||
SOT_FTP_OBJECT | 0x00000009 | FTP Communications Class | |||||
SOT_HTTP_OBJECT | 0x0000000A | HTTP Communications Class | |||||
SOT_ZIP_OBJECT | 0x0000000B | Compression Object | |||||
SOT_DATA_SHEET_OBJECT | 0x0000000C | Data Sheet Object | |||||
SOT_CLIPBOARD_OBJECT | 0x0000000D | Clipboard Object (base) | |||||
SOT_CONSOLE_WINDOW | 0x0000000E | Console (Synthetic, is Window Handle) | |||||
SOT_DATA_OBJECT | 0x0000000F | Data Object |
It may be necessary or desirable to access handles for Windows objects or other components. These might include handles to dialog boxes, files, external libraries, and other items. These are accessible through various Legato functions.
Closing Handles
As mentioned above, managed handles will close automatically in certain circumstances such as when a local variable containing the handle is discarded at the end of the subroutine. To manually close a handle, though, the CloseHandle function can be used:
int = CloseHandle ( handle handle );
The function takes a valid, open handle and performs the necessary cleanup. In the case of a basic file or mapped text object, the file is released and becomes available for use by other processes, users, or elsewhere within the program.
Closing an unmanaged handle does not do anything. However, while the chances are slim, an unmanaged handle value could match a managed handle, so it is not advisable to close unmanaged handles with the CloseHandle function.
There is also a deprecated CloseFile function that maps to essentially the same logic.
Additional Handle Functions
Some objects support the GetName function, which returns the name of the object related to the handle:
string = GetName ( handle handle );
A handle can be tested as a valid managed handle by using the IsValidHandle function:
boolean = IsValidHandle ( handle handle );
There is also a similar function for window handles, the IsWindowHandleValid function:
boolean = IsWindowHandleValid ( handle hWindow );
Window Handles and Views
When dealing with the application desktop and edit windows, window handles are frequently used. Window handles are always unmanaged.
Let’s look at an example of using window handles to access the contents of an edit view:
handle hTextView, hEdit; string s1; int ix, size; int rc; rc = RunMenuFunction("FILE_NEW_HTML"); if (IsError(rc)) { AddMessage("Error %08X on open", rc); exit; } s1 = GetMenuFunctionResponse(); hTextView = MakeHandle(GetParameter(s1, "CodeViewWindow")); if (IsError(hTextView)) { AddMessage("Failed to get Code View handle"); exit; } hEdit = GetEditObject(hTextView); if (IsError(hTextView)) { AddMessage("Failed to get Edit Object handle"); exit; } AddMessage("HTML Code Dump:"); size = GetLineCount(hEdit); while (ix < size) { AddMessage(" %2d : %s", ix, ReadLine(hEdit, ix)); ix++; }
The function runs a menu command, FILE_NEW_HTML, which will create Page View and Code View windows on success. The GetMenuFunctionResponse function returns the response from the action. An example of the returned response string on success:
ClientWindow: 0x0191136C; FirstWindow: 0x01CA0C4E;
PageViewWindow: 0x01CA0C4E; CodeViewWindow: 0x01740DB6
Here is an example of a function returning handle values as part of a string. Next we need to translate the string values into a specific handle:
hTextView = MakeHandle(GetParameter(s1, "CodeViewWindow"));
This compound statement will take the response, locate the “CodeViewWindow” properties, and translate its value into the window handle. We can then access data within the window using the GetEditObject function:
handle = GetEditObject ( handle hwTarget | int index | string name );
Now we have a handle to an Edit Object. We can then just dump out the window.
HTML Code Dump: 0 : <HTML> 1 : <HEAD> 2 : <TITLE></TITLE> 3 : </HEAD> 4 : <BODY STYLE="font: 10pt Times New Roman, Times, Serif"> 5 : 6 : <P STYLE="margin: 0"> </P> 7 : 8 : </BODY> 9 : </HTML>
In the example, there are two handles used: an unmanaged window handle and a managed Edit Object. In addition, the returned response has string versions of three other window handles.
Conclusion
I always like to understand what is going on “under the hood”. I hope this blog helps to uncover some of the mystery behind handles. While writing this blog it gave me an opportunity to perform a little stress testing and also resulted in a few ideas about improving handle management. As a result, the next release of Legato will contain two new handle related functions: EnumerateHandles and GetHandleCount. These should help programmers inspect open handles, improve performance, and track down certain resource leaks. At any rate, understanding handles and how Legato manages them is important to any programmer working in an environment with multiple objects and windows to control and manipulate. Getting a “handle” on the handles is vital in making sure your scripts run smoothly.
Scott Theis is the President of Novaworks and the principal developer of the Legato scripting language. He has extensive expertise with EDGAR, HTML, XBRL, and other programming languages. |
Additional Resources
Legato Script Developers LinkedIn Group
Primer: An Introduction to Legato