Creating, Registering, and Using a Handle-based API Set

Other versions of this page are also available for the following:

Windows Mobile Not SupportedWindows Embedded CE Supported

8/28/2008

A handle server never manages handles directly. The handle server must provide a set of functions and the type of handle to the kernel, and the kernel manages the rest.

Creating and Registering a Handle-based API Set

Before any process can become a handle server, it must create and register a handle-based API set with the following functions:

To create a new handle to an object, the server must call the CreateAPIHandle function. To pass the handle to another process, the server must call the DuplicateHandle function. In addition, the server must use the CloseHandle function to close its own reference to the handle that was created by CreateAPIHandle or pass DUPLICATE_CLOSE_SOURCE to DuplicateHandle. To asynchronously access the handle, the server must call LockAPIHandle and UnlockAPIHandle so that the handle is not closed in the middle of the asynchronous operation.

API Parameter Descriptors

The following table describes the descriptors for API parameters.

Descriptor Description

DW

4-bit scalar.

I_PTR

Pointer to an input-only buffer. The size is in the next parameter.

I_WSTR

Pointer to an input-only Unicode string.

I_ASTR

Pointer to an input-only ASCII string.

O_PTR

Pointer to an output-only buffer. The size is in the next parameter.

O_PDW

Pointer to an output-only buffer, 4 bytes in size.

O_PI64

Pointer to an output-only buffer, 8 bytes in size.

IO_PTR

Pointer to an in/out buffer. The size is in the next parameter.

IO_PDW

Pointer to an in/out buffer, 4 bytes in size.

IO_PI64

Pointer to an in/out buffer, 8 bytes in size.

Note

When implementing a user-mode server, any pointer that can be accessed asynchronously must be described as a DW scalar. This disables the automatic access-checking and marshaling that is done on the parameter. Instead, the user-mode server must call the CeOpenCallerBuffer and CeAllocAsynchronousBuffer helpers to perform the access-checking and marshaling. This ensures that the marshaling remains valid after the API call returns. The API return would otherwise destroy the duplicated or aliased memory.

The following are some ramifications of the signature:

  • Because there is a limit of 13 parameters (FNSIG13), it is possible that you will have to consolidate scalar parameters into a structure that is passed by a pointer, instead of passing a large number of scalar arguments. You should never need to exceed 13 parameters. Only an API that takes 7 or more buffer pointers, and thus needs 7 or more buffer sizes, would exceed 13 parameters.
  • The limit of 6 pointer parameters applies to both fixed sized pointers (O_PDW, IO_PDW, O_PI64, and IO_PI64), pointers of explicitly specified size (I_PTR, IO_PTR, and O_PTR), and string buffers (I_WSTR and I_ASTR).
  • If your API takes buffer parameters with inline pointers, you may find it useful to move those into primary parameters for your API. Then, the automatic parameter marshaling and validation done by the kernel will save you some work.

Using a Direct Method Table

A kernel-mode server can also register a second table of function pointers to be used for internal calls made by other kernel-mode servers. This registration is performed with the RegisterDirectMethods function. If no direct method table is registered, all internal calls are routed through the external table provided to CreateAPISet. If any entry in the direct-call API set is NULL, that API is not callable inside the kernel. If any entry in the external table is NULL, that API is not callable by other processes.

The external function does the following:

  • Access checking, marshaling, and secure-copy operations that must be done on caller pointers.
  • Assigning handles, pseudo-handles, and other resources that are owned by a particular process to the caller process.
  • Any operations involving GetCallerProcess. The external function can call GetCallerProcess, whereas the internal one calls GetCurrentProcess.
  • Calling the internal function, or some other worker, to do the actual work.

The internal function does the following:

  • Everything else, including any privilege checks. Checks should be based on the privilege of the current thread, not the trust level of the caller process.

If it is too complicated to perform all the marshaling or handle management in the external function, you can implement the API by having the external and internal functions call a common worker function, passing a flag to say whether the call originated externally.

Using a Handle-based API Set

The call to CreateAPIHandle associates the handle object with the API set and returns a handle to the object. Thereafter, the handle uniquely identifies pMyObject and the API set used to manipulate pMyObject. Functions in the function table do not receive the handle as a parameter, but receive the object directly. Inside any of these functions, you can use the handle object and it is not destroyed during the function call, unless you destroy it. The calls to CALLMYREAD and CALLMYWRITE are typically exports from Coredll, where an API call to the kernel is made. The kernel then decodes the handle, locks if necessary, and calls the corresponding functions in the function table.

The following code example shows how a handle server creates and registers a handle-based API set:

// Function table of “my handle”
// Index 0 is called when the handle is to be destroyed. Index 1 is for “pre-close”. Called when the last actual reference is closed (but still in use).
const PFNVOID ppfnMethods [] ={ (PFNVOID) MyDestroyObject, (PFNVOID) MyPreCloseHandle, (PFNVOID) MyRead, (PFNVOID) MyWrite};
// The function signature here matches the parameters of the MyRead function. The first parameter of every function in a handle-based API set is the object the handle refers to.
// Signatures of the functions
const ULONGLONG pu64Sigs [] = { FNSIG1 (DW), FNSIG1 (DW), FNSIG3 (DW, O_PTR, DW), FNSIG3 (DW, I_PTR, DW) };
BOOL MyRead (PMYOBJECT pObj, LPBYTE pbuf, DWORD cbSize)
{
}
BOOL MyWrite (PMYOBJECT pObj, const BYTE *pbuf, DWORD cbSize)
{
}
BOOL MyDestroyObject (PMYOBJECT pObj)
{
   LocalFree (pObj);
}
BOOL MyPreCloseHandle (PMYOBJECT pObj)
{
// The primary purpose of the pre-close operation is to force any asynchronous operations on the handle to complete, so that the last references to the handle are cleaned up when the handle is closed. If your API set does not perform any asynchronous operations on the open handles, then you may not need to implement a pre-close operation
}
// Sample program
// ID (Note, 128 APIsets supported)
#define  ID_MYAPI 0x30
// The type cast helps check that callers pass the right arguments. The arguments listed are those that are passed to MyRead, with the exception that the PMYOBJECT is replaced by a handle since callers pass a handle while MyRead receives the object data buffer. ID_MYAPI identifies the API set, while "2" is the index of the MyRead function in the ppfnMethods table.
#define CALLMYREAD(hObject, pReadBuf, cbReadBuf) (*(BOOL (*) (HANDLE, LPBYTE, DWORD) IMPLICIT_CALL(ID_MYAPI, 2))
#define CALLMYWRITE(hObject, pWriteBuf, cbWriteBuf) (*(BOOL (*) (HANDLE, const BYTE*, DWORD) IMPLICIT_CALL(ID_MYAPI, 3))
void HandleSample (void)
{
   // Create the API set.
   HANDLE hAPISet = CreateAPISet (“MINE”, 4, ppfnMethods, ppfnMyWrite);
   // Register the API set.
   if (!hAPISet || !RegisterAPISet (hAPISet, ID_MYAPI | REGISTER_APISET_TYPE))
   {
      // Error
      return;
   }
   // Create my own handle object.
   PMYOBJECT pHandleObject = (PMYOBJECT) LocalAlloc (sizeof (MYOBJECT));
   InitializeMyObject (pHandleObject);
   // Create the handle associated with my handle object.
   HANDLE hMyHandle = CreateAPIHandle (hAPISet, pHandleObject);
   // Prepare a handle to pass to the target application, and close the server's reference to the object.
   HANDLE hAppHandle = DuplicateHandle (hMyHandle, ..., DUPLICATE_CLOSE_SOURCE);
   // Now pass hAppHandle to the application.
}

The following code sample shows code that calls in to the handle server:

BYTE buf[100];
// Call the server to get a handle
HANDLE hObject = ...
// The exact method in the server that opens the handle is not demonstrated here.
// The server could be a driver which opens the handle during a particular IOCTL. It could be a driver or  a user mode process that opens the handle during some other inter-process communication method, like in response to a point-to-point message queue message or window message. It could be a handle open API that is implemented via a non handle-based API set.
// Call the read method.
CALLMYREAD (hObject, buf, 100);
// Call the write method.
CALLMYWRITE (hObject, buf, 100);
// Close the handle.
CloseHandle (hObject); 
// call the ReadMethod
CALLMYREAD (hMyHandle, buf, 100);
// Close the handle, will trigger MyPreCloseHandle and MyCloseHandle.
CloseHandle (hMyHandle);

See Also

Concepts

System Calls
Handle Tables: Windows CE 5.0 vs. Windows Embedded CE 6.0

Other Resources

DuplicateHandle
CloseHandle