Sharing Kernel Objects Across Process Boundaries

[Previous] [Next]

Frequently, threads running in different processes need to share kernel objects. Here are some of the reasons why:

  • File-mapping objects allow you to share blocks of data between two processes running on a single machine.
  • Mailslots and named pipes allow applications to send blocks of data between processes running on different machines connected to the network.
  • Mutexes, semaphores, and events allow threads in different processes to synchronize their continued execution, as in the case of an application that needs to notify another application when it has completed some task.

Because kernel object handles are process-relative, performing these tasks is difficult. However, Microsoft had several good reasons for designing the handles to be process-relative. The most important reason was robustness. If kernel object handles were system-wide values, one process could easily obtain the handle to an object that another process was using and wreak havoc on that process. Another reason for process-relative handles is security. Kernel objects are protected with security, and a process must request permission to manipulate an object before attempting to manipulate it. The creator of the object can prevent an unauthorized user from touching the object simply by denying access to it.

In the following section, we'll look at the three different mechanisms that allow processes to share kernel objects.

Object Handle Inheritance

Object handle inheritance can be used only when processes have a parent-child relationship. In this scenario, one or more kernel object handles are available to the parent process, and the parent decides to spawn a child process, giving the child access to the parent's kernel objects. For this type of inheritance to work, the parent process must perform several steps.

First, when the parent process creates a kernel object, the parent must indicate to the system that it wants the object's handle to be inheritable. Keep in mind that although kernel object handles are inheritable, kernel objects themselves are not.

To create an inheritable handle, the parent process must allocate and initialize a SECURITY_ATTRIBUTES structure and pass the structure's address to the specific Create function. The following code creates a mutex object and returns an inheritable handle to it:

 SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(sa); sa.lpSecurityDescriptor = NULL; sa.bInheritHandle = TRUE; // Make the returned handle inheritable. HANDLE hMutex = CreateMutex(&sa, FALSE, NULL);  

This code initializes a SECURITY_ATTRIBUTES structure indicating that the object should be created using default security (ignored in Windows 98) and that the returned handle should be inheritable.

Windows 98
Even though Windows 98 does not have complete security support, it does support inheritance; therefore, Windows 98 correctly uses the value of the bInheritHandle member.

Now we come to the flags that are stored in a process's handle table entry. Each handle table entry has a flag bit indicating whether the handle is inheritable. If you pass NULL as the PSECURITY_ATTRIBUTES parameter when you create a kernel object, the handle returned is not inheritable and this bit is zero. Setting the bInheritHandle member to TRUE causes this flag bit to be set to 1.

Imagine a process's handle table that looks like the one shown in Table 3-2.

Table 3-2. A process's handle table containing two valid entries

Index Pointer to Kernel Object Memory Block Access Mask (DWORD of Flag Bits) Flags (DWORD of Flag Bits)
10xF00000000x????????0x00000000
20x00000000(N/A)(N/A)
30xF00000100x????????0x00000001

Table 3-2 indicates that this process has access to two kernel objects (handles 1 and 3). Handle 1 is not inheritable and handle 3 is inheritable.

The next step to perform when using object handle inheritance is for the parent process to spawn the child process. This is done using the CreateProcess function:

 BOOL CreateProcess( PCTSTR pszApplicationName, PTSTR pszCommandLine, PSECURITY_ATTRIBUTES psaProcess, PSECURITY_ATTRIBUTES pszThread, BOOL bInheritHandles, DWORD dwCreationFlags, PVOID pvEnvironment, PCTSTR pszCurrentDirectory, LPSTARTUPINFO pStartupInfo, PPROCESS_INFORMATION pProcessInformation); 

We'll examine this function in detail in the next chapter, but for now I want to draw your attention to the bInheritHandles parameter. Usually, when you spawn a process, you will pass FALSE for this parameter. This value tells the system that you do not want the child process to inherit the inheritable handles that are in the parent process's handle table.

If you pass TRUE for this parameter, however, the child will inherit the parent's inheritable handle values. When you pass TRUE, the operating system creates the new child process but does not allow the child process to begin executing its code right away. Of course, the system creates a new, empty process handle table for the child process—just as it would for any new process. But because you passed TRUE to CreateProcess's bInheritHandles parameter, the system does one more thing: it walks the parent process's handle table, and for each entry it finds that contains a valid inheritable handle, the system copies the entry exactly into the child process's handle table. The entry is copied to the exact same position in the child process's handle table as in the parent's handle table. This fact is important because it means that the handle value that identifies a kernel object is identical in both the parent and the child processes.

In addition to copying the handle table entry, the system increments the usage count of the kernel object because two processes are now using the object. For the kernel object to be destroyed, both the parent process and the child process must either call CloseHandle on the object or terminate. The child does not have to terminate first—but neither does the parent. In fact, the parent process can close its handle to the object immediately after the CreateProcess function returns without affecting the child's ability to manipulate the object.

Table 3-3 shows the child process's handle table immediately before the process is allowed to begin execution. You can see that entries 1 and 2 are not initialized and are therefore invalid handles for the child process to use. However, index 3 does identify a kernel object. In fact, it identifies the kernel object at address 0xF0000010, the same object as in the parent process's handle table. The access mask is identical to the mask in the parent, and the flags are also identical. This means that if the child process were to spawn its own child process (a grandchild process of the parent), this grandchild process would also inherit this kernel object handle with the same handle value, same access, and same flags, and the usage count on the object would again be incremented.

Table 3-3. A child process's handle table after inheriting the parent process's inheritable handle

Index Pointer to Kernel Object Memory Block Access Mask (DWORD of Flag Bits) Flags (DWORD of Flag Bits)
10x00000000(N/A)(N/A)
20x00000000(N/A)(N/A)
30xF00000100x????????0x00000001

Be aware that object handle inheritance applies only at the time the child process is spawned. If the parent process were to create any new kernel objects with inheritable handles, an already-running child process would not inherit these new handles.

Object handle inheritance has one very strange characteristic: when you use it, the child has no idea that it has inherited any handles. Kernel object handle inheritance is useful only when the child process documents the fact that it expects to be given access to a kernel object when spawned from another process. Usually, the parent and child applications are written by the same company; however, a different company can write the child application if that company documents what the child application expects.

By far the most common way for a child process to determine the handle value of the kernel object that it's expecting is to have the handle value passed as a command-line argument to the child process. The child process's initialization code parses the command line (usually by calling sscanf) and extracts the handle value. Once the child has the handle value, it has unlimited access to the object. Note that the only reason handle inheritance works is because the handle value of the shared kernel object is identical in both the parent process and the child process; this is why the parent process is able to pass the handle value as a command-line argument.

Of course, you can use other forms of interprocess communication to transfer an inherited kernel object handle value from the parent process into the child process. One technique is for the parent to wait for the child to complete initialization (using the WaitForInputIdle function discussed in Chapter 9); then the parent can send or post a message to a window created by a thread in the child process.

Another technique is for the parent process to add an environment variable to its environment block. The variable's name would be something that the child process knows to look for, and the variable's value would be the handle value of the kernel object to be inherited. Then when the parent spawns the child process, the child process inherits the parent's environment variables and can easily call GetEnvironmentVariable to obtain the inherited object's handle value. This approach is excellent if the child process is going to spawn another child process, because the environment variables can be inherited again.

Changing a Handle's Flags

Occasionally, you might encounter a situation in which a parent process creates a kernel object retrieving an inheritable handle and then spawns two child processes. The parent process wants only one child to inherit the kernel object handle. In other words, you might at times want to control which child processes inherit kernel object handles. To alter the inheritance flag of a kernel object handle, you can call the SetHandleInformation function:

 BOOL SetHandleInformation( HANDLE hObject, DWORD dwMask, DWORD dwFlags); 

As you can see, this function takes three parameters. The first, hObject, identifies a valid handle. The second parameter, dwMask, tells the function which flag or flags you want to change. Currently, two flags are associated with each handle:

 #define HANDLE_FLAG_INHERIT 0x00000001 #define HANDLE_FLAG_PROTECT_FROM_CLOSE 0x00000002 

You can bitwise OR both of these flags together if you want to change both of the object's flags simultaneously. SetHandleInformation's third parameter, dwFlags, indicates what you want to set the flags to. For example, to turn on the inheritance flag for a kernel object handle, do the following:

 SetHandleInformation(hobj, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); 

To turn off this flag, do this:

 SetHandleInformation(hobj, HANDLE_FLAG_INHERIT, 0); 

The HANDLE_FLAG_PROTECT_FROM_CLOSE flag tells the system that this handle should not be allowed to close:

 SetHandleInformation(hobj, HANDLE_FLAG_PROTECT_FROM_CLOSE, HANDLE_FLAG_PROTECT_FROM_CLOSE); CloseHandle(hobj); // Exception is raised 

If a thread attempts to close a protected handle, CloseHandle raises an exception. You rarely want to protect a handle from being closed. However, this flag might be useful if you had a process that spawned a child that in turn spawned a grandchild process. The parent process might be expecting the grandchild to inherit the object handle given to the immediate child. It is possible, however, that the immediate child might close the handle before spawning the grandchild. If this were to happen, the parent might not be able to communicate with the grandchild because the grandchild did not inherit the kernel object. By marking the handle as "protected from close," the grandchild will inherit the object.

This approach has one flaw, however: the immediate child process might call the following code to turn off the HANDLE_FLAG_PROTECT_ FROM_CLOSE flag and then close the handle.

 SetHandleInformation(hobj, HANDLE_FLAG_PROTECT_FROM_CLOSE, 0); CloseHandle(hobj); 

The parent process is gambling that the child process will not execute this code. Of course, the parent is also gambling that the child process will spawn the grandchild, so this bet is not that risky.

For the sake of completeness, I'll also mention the GetHandleInformation function:

 BOOL GetHandleInformation( HANDLE hObj, PDWORD pdwFlags); 

This function returns the current flag settings for the specified handle in the DWORD pointed to by pdwFlags. To see if a handle is inheritable, do the following:

 DWORD dwFlags; GetHandleInformation(hObj, &dwFlags); BOOL fHandleIsInheritable = (0 != (dwFlags & HANDLE_FLAG_INHERIT)); 

Named Objects

The second method available for sharing kernel objects across process boundaries is to name the objects. Many—though not all—kernel objects can be named. For example, all of the following functions create named kernel objects:

 HANDLE CreateMutex( PSECURITY_ATTRIBUTES psa, BOOL bInitialOwner, PCTSTR pszName); HANDLE CreateEvent( PSECURITY_ATTRIBUTES psa, BOOL bManualReset, BOOL bInitialState, PCTSTR pszName); HANDLE CreateSemaphore( PSECURITY_ATTRIBUTES psa, LONG lInitialCount, LONG lMaximumCount, PCTSTR pszName); HANDLE CreateWaitableTimer( PSECURITY_ATTRIBUTES psa, BOOL bManualReset, PCTSTR pszName); HANDLE CreateFileMapping( HANDLE hFile, PSECURITY_ATTRIBUTES psa, DWORD flProtect, DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow, PCTSTR pszName); HANDLE CreateJobObject( PSECURITY_ATTRIBUTES psa, PCTSTR pszName); 

All of these functions have a common last parameter, pszName. When you pass NULL for this parameter, you are indicating to the system that you want to create an unnamed (anonymous) kernel object. When you create an unnamed object, you can share the object across processes by using either inheritance (as discussed in the previous section) or DuplicateHandle (discussed in the next section). To share an object by name, you must give the object a name.

If you don't pass NULL for the pszName parameter, you should pass the address of a zero-terminated string name. This name can be up to MAX_PATH (defined as 260) characters long. Unfortunately, Microsoft offers no guidance for assigning names to kernel objects. For example, if you attempt to create an object called "JeffObj," there's no guarantee that an object called "JeffObj" doesn't already exist. To make matters worse, all of these objects share a single name space. Because of this, the following call to CreateSemaphore will always return NULL:

 HANDLE hMutex = CreateMutex(NULL, FALSE, "JeffObj"); HANDLE hSem = CreateSemaphore(NULL, 1, 1, "JeffObj"); DWORD dwErrorCode = GetLastError(); 

If you examine the value of dwErrorCode after executing the code above, you'll see a return code of 6 (ERROR_INVALID_HANDLE). This error code is not very descriptive, but what can you do?

Now that you know how to name an object, let's see how to share objects this way. Let's say that Process A starts up and calls the following function:

 HANDLE hMutexProcessA = CreateMutex(NULL, FALSE, "JeffMutex"); 

This function call creates a brand new mutex kernel object and assigns it the name "JeffMutex". Notice that in Process A's handle, hMutexProcessA is not an inheritable handle—and it doesn't have to be when you're only naming objects.

Some time later, some process spawns Process B. Process B does not have to be a child of Process A; it might be spawned from the Explorer or any other application. The fact that Process B need not be a child of Process A is an advantage of using named objects instead of inheritance. When Process B starts executing, it executes the following code:

 HANDLE hMutexProcessB = CreateMutex(NULL, FALSE, "JeffMutex"); 

When Process B's call to CreateMutex is made, the system first checks to find out whether a kernel object with the name "JeffMutex" already exists. Because an object with this name does exist, the kernel then checks the object type. Since we are attempting to create a mutex and the object with the name "JeffMutex" is also a mutex, the system then makes a security check to see if the caller has full access to the object and if so, the system locates an empty entry in Process B's handle table and initializes the entry to point to the existing kernel object. If the object types don't match or if the caller is denied access, CreateMutex fails (returns NULL).

When Process B's call to CreateMutex is successful, a mutex is not actually created. Instead, Process B is simply assigned a process-relative handle value that identifies the existing mutex object in the kernel. Of course, because a new entry in Process B's handle table references this object, the mutex object's usage count is incremented; the object will not be destroyed until both Process A and Process B have closed their handles to the object. Notice that the handle values in the two processes are most likely going to be different values. This is OK: Process A will use its handle value, and Process B will use its own handle value to manipulate the one mutex kernel object.

NOTE
When you have kernel objects sharing names, be aware of one extremely important detail. When Process B calls CreateMutex, it passes security attribute information and a second parameter to the function. These parameters are ignored if an object with the specified name already exists! An application can determine if it did, in fact, create a new kernel object versus simply opening an existing object by calling GetLastError immediately after the call to the Create* function:

 HANDLE hMutex = CreateMutex(&sa, FALSE, "JeffObj"); if (GetLastError() == ERROR_ALREADY_EXISTS) { // Opened a handle to an existing object. // sa.lpSecurityDescriptor and the second parameter // (FALSE) are ignored. } else { // Created a brand new object. // sa.lpSecurityDescriptor and the second parameter // (FALSE) are used to construct the object. } 

An alternative method exists for sharing objects by name. Instead of calling a Create* function, a process can call one of the Open* functions shown here:

 HANDLE OpenMutex( DWORD dwDesiredAccess, BOOL bInheritHandle, PCTSTR pszName); HANDLE OpenEvent( DWORD dwDesiredAccess, BOOL bInheritHandle, PCTSTR pszName); HANDLE OpenSemaphore( DWORD dwDesiredAccess, BOOL bInheritHandle, PCTSTR pszName); HANDLE OpenWaitableTimer( DWORD dwDesiredAccess, BOOL bInheritHandle, PCTSTR pszName); HANDLE OpenFileMapping( DWORD dwDesiredAccess, BOOL bInheritHandle, PCTSTR pszName); HANDLE OpenJobObject( DWORD dwDesiredAccess, BOOL bInheritHandle, PCTSTR pszName); 

Notice that all of these functions have the same prototype. The last parameter, pszName, indicates the name of a kernel object. You cannot pass NULL for this parameter; you must pass the address of a zero-terminated string. These functions search the single name space of kernel objects attempting to find a match. If no kernel object with the specified name exists, the functions return NULL and GetLastError returns 2 (ERROR_FILE_NOT_FOUND). However, if a kernel object with the specified name does exist, and if it is the same type of object, the system then checks to see if the requested access (via the dwDesiredAccess parameter) is allowed; if it is, the calling process's handle table is updated and the object's usage count is incremented. The returned handle will be inheritable if you pass TRUE for the bInheritHandle parameter.

The main difference between calling a Create* function versus calling an Open* function is that if the object doesn't already exist, the Create* function will create it, whereas the Open* function will simply fail.

As I mentioned earlier, Microsoft offers no real guidelines on how to create unique object names. In other words, it would be a problem if a user attempted to run two programs from different companies and each program attempted to create an object called "MyObject". For uniqueness, I recommend that you create a GUID and use the string representation of the GUID for your object names.

Named objects are commonly used to prevent multiple instances of an application from running. To do this, simply call a Create* function in your main or WinMain function to create a named object (it doesn't matter what type of object you create). When the Create* function returns, call GetLastError. If GetLastError returns ERROR_ALREADY_EXISTS, another instance of your application is running and the new instance can exit. Here's some code that illustrates this:

 int WINAPI WinMain(HINSTANCE hinstExe, HINSTANCE, PSTR pszCmdLine, int nCmdShow) { HANDLE h = CreateMutex(NULL, FALSE, "{FA531CC1-0497-11d3-A180-00105A276C3E}"); if (GetLastError() == ERROR_ALREADY_EXISTS) { // There is already an instance of this application running. return(0); } // This is the first instance of this application running.  // Before exiting, close the object. CloseHandle(h); return(0); } 

Terminal Server Name Spaces

Note that Terminal Server changes the above scenario a little bit. A Terminal Server machine will have multiple name spaces for kernel objects. There is one global name space, which is used by kernel objects that are meant to be accessible by any and all client sessions. This name space is mostly used by services. In addition, each client session has its own name space. This keeps two or more sessions that are running the same application from trampling over each other—one session cannot access another session's objects even though the objects share the same name. On a machine without Terminal Server, services and applications share the same kernel object name space as described above; this is not true on a Terminal Server machine.

A service's named kernel objects always go in the global name space. By default, in Terminal Server, an application's named kernel object goes in the session's name space. However, it is possible to force the named object to go into the global name space by prefixing the name with "Global\", as in the example below:

 HANDLE h = CreateEvent(NULL, FALSE, FALSE, "Global\\MyName"); 

You can also explicitly state that you want a kernel object to go in the session's name space by prefixing the name with "Local\", as in

 HANDLE h = CreateEvent(NULL, FALSE, FALSE, "Local\\MyName"); 

Microsoft considers Global and Local to be reserved keywords that you should not use in object names except to force a particular name space. Microsoft also considers Session to be a reserved keyword, although it currently has no meaning. Note that all of these reserved keywords are case-sensitive. Finally, these keywords are ignored if the host machine is not running Terminal Server.

Duplicating Object Handles

The last technique for sharing kernel objects across process boundaries requires the use of the DuplicateHandle function:

 BOOL DuplicateHandle( HANDLE hSourceProcessHandle, HANDLE hSourceHandle, HANDLE hTargetProcessHandle, PHANDLE phTargetHandle, DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwOptions); 

Simply stated, this function takes an entry in one process's handle table and makes a copy of the entry into another process's handle table. DuplicateHandle takes several parameters but is actually quite straightforward. The most general usage of the DuplicateHandle function involves three different processes that are running in the system.

When you call DuplicateHandle, the first and third parameters—hSourceProcessHandle and hTargetProcessHandle—are kernel object handles. The handles themselves must be relative to the process that is calling the DuplicateHandle function. In addition, these two parameters must identify process kernel objects; the function fails if you pass handles to any other type of kernel object. We'll discuss process kernel objects in more detail in Chapter 4; for now, all you need to know is that a process kernel object is created whenever a new process is invoked in the system.

The second parameter, hSourceHandle, is a handle to any type of kernel object. However, the handle value is not relative to the process that calls DuplicateHandle. Instead, this handle must be relative to the process identified by the hSourceProcessHandle handle. The fourth parameter, phTargetHandle, is the address of a HANDLE variable that will receive the index of the entry that gets the copy of the source's handle information. The handle value that comes back is relative to the process identified by hTargetProcessHandle.

DuplicateHandle's last three parameters allow you to indicate the value of the access mask and the inheritance flag that should be used in the target's entry for this kernel object handle. The dwOptions parameter can be 0 (zero) or any combination of the following two flags: DUPLICATE_SAME_ACCESS and DUPLICATE_CLOSE_SOURCE.

Specifying DUPLICATE_SAME_ACCESS tells DuplicateHandle that you want the target's handle to have the same access mask as the source process's handle. Using this flag causes DuplicateHandle to ignore its dwDesiredAccess parameter.

Specifying DUPLICATE_CLOSE_SOURCE has the effect of closing the handle in the source process. This flag makes it easy for one process to hand a kernel object over to another process. When this flag is used, the usage count of the kernel object is not affected.

I'll use an example to show you how DuplicateHandle works. For this demonstration, Process S is the source process that currently has access to some kernel object and Process T is the target process that will gain access to this kernel object. Process C is the catalyst process that will execute the call to DuplicateHandle.

Process C's handle table (Table 3-4) contains two handle values, 1 and 2. Handle value 1 identifies Process S's process kernel object, and handle value 2 identifies Process T's process kernel object.

Table 3-4. Process C's handle table

Index Pointer to Kernel Object Memory Block Access Mask (DWORD of Flag Bits) Flags (DWORD of Flag Bits)
10xF0000000 (Process S's kernel object)0x????????0x00000000
20xF0000010 (Process T's kernel object)0x???????? 0x00000000

Table 3-5 is Process S's handle table, which contains a single entry with a handle value of 2. This handle can identify any type of kernel object—it doesn't have to be a process kernel object.

Table 3-5. Process S's handle table

Index Pointer to Kernel Object Memory Block Access Mask (DWORD of Flag Bits) Flags (DWORD of Flag Bits)
10x00000000(N/A)(N/A)
20xF0000020 (any kernel object) 0x????????0x00000000

Table 3-6 shows what Process T's handle table contains before Process C calls the DuplicateHandle function. As you can see, Process T's handle table contains only a single entry with a handle value of 2; handle entry 1 is currently unused.

Table 3-6. Process T's handle table before calling DuplicateHandle

Index Pointer to Kernel Object Memory Block Access Mask (DWORD of Flag Bits) Flags (DWORD of Flag Bits)
10x00000000(N/A)(N/A)
20xF0000030 (any kernel object) 0x???????? 0x00000000

If Process C now calls DuplicateHandle using the following code, only Process T's handle table has changed, as shown in Table 3-7.

 DuplicateHandle(1, 2, 2, &hObj, 0, TRUE, DUPLICATE_SAME_ACCESS); 

Table 3-7. Process T's handle table after calling DuplicateHandle

Index Pointer to Kernel Object Memory Block Access Mask (DWORD of Flag Bits) Flags (DWORD of Flag Bits)
10xF00000200x????????0x00000001
20xF0000030 (any kernel object)0x????????0x00000000

The second entry in Process S's handle table has been copied to the first entry in Process T's handle table. DuplicateHandle has also filled in Process C's hObj variable with a value of 1, which is the index in process T's handle table where the new entry was placed.

Because the DUPLICATE_SAME_ACCESS flag was passed to DuplicateHandle, the access mask for this handle in Process T's table is identical to the access mask in Process S's table entry. Also, passing the DUPLICATE_SAME_ACCESS flag causes DuplicateHandle to ignore its dwDesiredAccess parameter. Finally, notice that the inheritance bit flag has been turned on because TRUE was passed for DuplicateHandle's bInheritHandle parameter.

Obviously, you would never call DuplicateHandle passing in hard-coded numeric values as I have done in this example. I have used hard-coded numbers only to demonstrate how the function operates. In real applications, you would have the various handle values in variables and you would pass the variables as arguments to the function.

Like inheritance, one of the odd things about the DuplicateHandle function is that the target process is not given any notification that a new kernel object is now accessible to it. So, Process C must somehow notify Process T that it now has access to a kernel object and must use some form of interprocess communication to pass the handle value in hObj to Process T. Obviously, using a command-line argument or changing Process T's environment variables is out of the question since the process is already up and running. A window message or some other IPC mechanism must be used.

What I have just explained is the most general usage of DuplicateHandle. As you can see, it is a very flexible function. However, it is rarely used with the involvement of three different processes (partly because it is unlikely that Process C would know the handle value of an object in use by Process S). Usually, DuplicateHandle is called when only two processes are involved. Imagine a situation in which one process has access to an object that another process wants access to, or a case in which one process wants to give access to a kernel object to another process. For example, let's say that Process S has access to a kernel object and wants to give Process T access to this object. To do this, you would call DuplicateHandle as follows:

 // All of the following code is executed by Process S. // Create a mutex object accessible by Process S. HANDLE hObjProcessS = CreateMutex(NULL, FALSE, NULL); // Open a handle to Process T's kernel object. HANDLE hProcessT = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessIdT); HANDLE hObjProcessT; // An uninitialized handle relative to Process T. // Give Process T access to our mutex object. DuplicateHandle(GetCurrentProcess(), hObjProcessS, hProcessT, &hObjProcessT, 0, FALSE, DUPLICATE_SAME_ACCESS); // Use some IPC mechanism to get the handle // value in hObjProcessS into Process T.  // We no longer need to communicate with Process T. CloseHandle(hProcessT);  // When Process S no longer needs to use the mutex, it should close it. CloseHandle(hObjProcessS); 

The call to GetCurrentProcess returns a pseudo-handle that always identifies the calling process—Process S in this example. Once DuplicateHandle returns, hObjProcessT is a handle relative to Process T that identifies the same object that hObjProcessS's handle does when referenced by code in Process S. Process S should never execute the following code:

// Process S should never attempt to close the // duplicated handle. CloseHandle(hObjProcessT); 

If Process S were to execute this code, the call might or might not fail. The call would succeed if Process S happened to have access to a kernel object with the same handle value as hObjProcessT. This call would have the effect of closing some object so that Process S no longer had access to it, which would certainly cause the application to behave undesirably (to put it nicely).

Here is another way to use DuplicateHandle: Suppose that a process has read and write access to a file-mapping object. At some point a function is called that is supposed to access the file-mapping object by reading it. To make our application more robust, we can use DuplicateHandle to create a new handle for the existing object and ensure that this new handle has read-only access on it. We would then pass this read-only handle to the function; this way, the code in the function would never be able to accidentally write to the file-mapping object. The following code illustrates this example:

 int WINAPI WinMain(HINSTANCE hinstExe, HINSTANCE, LPSTR szCmdLine, int nCmdShow) { // Create a file-mapping object; the handle has read/write access. HANDLE hFileMapRW = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 10240, NULL); // Create another handle to the file-mapping object; // the handle has read-only access. HANDLE hFileMapRO; DuplicateHandle(GetCurrentProcess(), hFileMapRW, GetCurrentProcess(),       &hFileMapRO, FILE_MAP_READ, FALSE, 0); // Call the function that should only read from the file mapping. ReadFromTheFileMapping(hFileMapRO); // Close the read-only file-mapping object. CloseHandle(hFileMapRO); // We can still read/write the file-mapping object using hFileMapRW.         // When the main code doesn t access the file mapping anymore, // close it. CloseHandle(hFileMapRW); } 


Programming Applications for Microsoft Windows
Programming Applications for Microsoft Windows (Microsoft Programming Series)
ISBN: 1572319968
EAN: 2147483647
Year: 1999
Pages: 193

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net