/*
* Process Hacker -
* fast resource lock
*
* Copyright (C) 2009 wj32
*
* This file is part of Process Hacker.
*
* Process Hacker is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Process Hacker is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Process Hacker. If not, see .
*/
#define DEFER_EVENT_CREATION
//#define ENABLE_STATISTICS
//#define RIGOROUS_CHECKS
using System;
using System.Runtime.InteropServices;
using System.Security;
using System.Threading;
namespace GMap.NET.Internals
{
#if !MONO && !PocketPC
///
/// Provides a fast resource (reader-writer) lock.
///
///
/// There are three types of acquire methods in this lock:
///
/// Normal methods (AcquireExclusive, AcquireShared) are preferred
/// for general purpose use.
/// Busy wait methods (SpinAcquireExclusive, SpinAcquireShared) are
/// preferred if very little time is spent while the lock is acquired.
/// However, these do not give exclusive acquires precedence over
/// shared acquires.
/// Try methods (TryAcquireExclusive, TryAcquireShared) can be used to
/// quickly test if the lock is available.
///
/// Note that all three types of functions can be used concurrently
/// in the same class instance.
///
internal sealed class FastResourceLock : IDisposable
{
// Details
//
// Resource lock value width: 32 bits.
// Lock owned (either exclusive or shared): L (1 bit).
// Exclusive waking: W (1 bit).
// Shared owners count: SC (10 bits).
// Shared waiters count: SW (10 bits).
// Exclusive waiters count: EW (10 bits).
//
// Acquire exclusive:
// {L=0,W=0,SC=0,SW,EW=0} -> {L=1,W=0,SC=0,SW,EW=0}
// {L=0,W=1,SC=0,SW,EW} or {L=1,W,SC,SW,EW} ->
// {L,W,SC,SW,EW+1},
// wait on event,
// {L=0,W=1,SC=0,SW,EW} -> {L=1,W=0,SC=0,SW,EW}
//
// Acquire shared:
// {L=0,W=0,SC=0,SW,EW=0} -> {L=1,W=0,SC=1,SW,EW=0}
// {L=1,W=0,SC>0,SW,EW=0} -> {L=1,W=0,SC+1,SW,EW=0}
// {L=1,W=0,SC=0,SW,EW=0} or {L,W=1,SC,SW,EW} or
// {L,W,SC,SW,EW>0} -> {L,W,SC,SW+1,EW},
// wait on event,
// retry.
//
// Release exclusive:
// {L=1,W=0,SC=0,SW,EW>0} ->
// {L=0,W=1,SC=0,SW,EW-1},
// release one exclusive waiter.
// {L=1,W=0,SC=0,SW,EW=0} ->
// {L=0,W=0,SC=0,SW=0,EW=0},
// release all shared waiters.
//
// Note that we never do a direct acquire when W=1
// (i.e. L=0 if W=1), so here we don't have to check
// the value of W.
//
// Release shared:
// {L=1,W=0,SC>1,SW,EW} -> {L=1,W=0,SC-1,SW,EW}
// {L=1,W=0,SC=1,SW,EW=0} -> {L=0,W=0,SC=0,SW,EW=0}
// {L=1,W=0,SC=1,SW,EW>0} ->
// {L=0,W=1,SC=0,SW,EW-1},
// release one exclusive waiter.
//
// Again, we don't need to check the value of W.
//
// Convert exclusive to shared:
// {L=1,W=0,SC=0,SW,EW} ->
// {L=1,W=0,SC=1,SW=0,EW},
// release all shared waiters.
//
// Convert shared to exclusive:
// {L=1,W=0,SC=1,SW,EW} ->
// {L=1,W=0,SC=0,SW,EW}
//
/* */
// Note: I have included many small optimizations in the code
// because of the CLR's dumbass JIT compiler.
#region Constants
// Lock owned: 1 bit.
private const int LockOwned = 0x1;
// Exclusive waking: 1 bit.
private const int LockExclusiveWaking = 0x2;
// Shared owners count: 10 bits.
private const int LockSharedOwnersShift = 2;
private const int LockSharedOwnersMask = 0x3ff;
private const int LockSharedOwnersIncrement = 0x4;
// Shared waiters count: 10 bits.
private const int LockSharedWaitersShift = 12;
private const int LockSharedWaitersMask = 0x3ff;
private const int LockSharedWaitersIncrement = 0x1000;
// Exclusive waiters count: 10 bits.
private const int LockExclusiveWaitersShift = 22;
private const int LockExclusiveWaitersMask = 0x3ff;
private const int LockExclusiveWaitersIncrement = 0x400000;
private const int ExclusiveMask = LockExclusiveWaking | (LockExclusiveWaitersMask << LockExclusiveWaitersShift);
#endregion
public struct Statistics
{
///
/// The number of times the lock has been acquired in exclusive mode.
///
public int AcqExcl;
///
/// The number of times the lock has been acquired in shared mode.
///
public int AcqShrd;
///
/// The number of times either the fast path was retried due to the
/// spin count or the exclusive waiter went to sleep.
///
///
/// This number is usually much higher than AcqExcl, and indicates
/// a good spin count if AcqExclSlp is very small.
///
public int AcqExclCont;
///
/// The number of times either the fast path was retried due to the
/// spin count or the shared waiter went to sleep.
///
///
/// This number is usually much higher than AcqShrd, and indicates
/// a good spin count if AcqShrdSlp is very small.
///
public int AcqShrdCont;
///
/// The number of times exclusive waiters have gone to sleep.
///
///
/// If this number is high and not much time is spent in the
/// lock, consider increasing the spin count.
///
public int AcqExclSlp;
///
/// The number of times shared waiters have gone to sleep.
///
///
/// If this number is high and not much time is spent in the
/// lock, consider increasing the spin count.
///
public int AcqShrdSlp;
///
/// The highest number of exclusive waiters at any one time.
///
public int PeakExclWtrsCount;
///
/// The highest number of shared waiters at any one time.
///
public int PeakShrdWtrsCount;
}
// The number of times to spin before going to sleep.
private static readonly int SpinCount = NativeMethods.SpinCount;
private int _value;
private IntPtr _sharedWakeEvent;
private IntPtr _exclusiveWakeEvent;
#if ENABLE_STATISTICS
private int _acqExclCount = 0;
private int _acqShrdCount = 0;
private int _acqExclContCount = 0;
private int _acqShrdContCount = 0;
private int _acqExclSlpCount = 0;
private int _acqShrdSlpCount = 0;
private int _peakExclWtrsCount = 0;
private int _peakShrdWtrsCount = 0;
#endif
///
/// Creates a FastResourceLock.
///
public FastResourceLock()
{
_value = 0;
#if !DEFER_EVENT_CREATION
_sharedWakeEvent = NativeMethods.CreateSemaphore(IntPtr.Zero, 0, int.MaxValue, null);
_exclusiveWakeEvent = NativeMethods.CreateSemaphore(IntPtr.Zero, 0, int.MaxValue, null);
#endif
}
~FastResourceLock()
{
this.Dispose(false);
}
private void Dispose(bool disposing)
{
if(_sharedWakeEvent != IntPtr.Zero)
{
NativeMethods.CloseHandle(_sharedWakeEvent);
_sharedWakeEvent = IntPtr.Zero;
}
if(_exclusiveWakeEvent != IntPtr.Zero)
{
NativeMethods.CloseHandle(_exclusiveWakeEvent);
_exclusiveWakeEvent = IntPtr.Zero;
}
}
///
/// Disposes resources associated with the FastResourceLock.
///
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Gets the number of exclusive waiters.
///
public int ExclusiveWaiters
{
get
{
return (_value >> LockExclusiveWaitersShift) & LockExclusiveWaitersMask;
}
}
///
/// Gets whether the lock is owned in either
/// exclusive or shared mode.
///
public bool Owned
{
get
{
return (_value & LockOwned) != 0;
}
}
///
/// Gets the number of shared owners.
///
public int SharedOwners
{
get
{
return (_value >> LockSharedOwnersShift) & LockSharedOwnersMask;
}
}
///
/// Gets the number of shared waiters.
///
public int SharedWaiters
{
get
{
return (_value >> LockSharedWaitersShift) & LockSharedWaitersMask;
}
}
///
/// Acquires the lock in exclusive mode, blocking
/// if necessary.
///
///
/// Exclusive acquires are given precedence over shared
/// acquires.
///
public void AcquireExclusive()
{
int value;
int i = 0;
#if ENABLE_STATISTICS
Interlocked.Increment(ref _acqExclCount);
#endif
while(true)
{
value = _value;
// Case 1: lock not owned AND an exclusive waiter is not waking up.
// Here we don't have to check if there are exclusive waiters, because
// if there are the lock would be owned, and we are checking that anyway.
if((value & (LockOwned | LockExclusiveWaking)) == 0)
{
#if RIGOROUS_CHECKS
System.Diagnostics.Trace.Assert(((value >> LockSharedOwnersShift) & LockSharedOwnersMask) == 0);
System.Diagnostics.Trace.Assert(((value >> LockExclusiveWaitersShift) & LockExclusiveWaitersMask) == 0);
#endif
if(Interlocked.CompareExchange(
ref _value,
value + LockOwned,
value
) == value)
break;
}
// Case 2: lock owned OR lock not owned and an exclusive waiter is waking up.
// The second case means an exclusive waiter has just been woken up and is
// going to acquire the lock. We have to go to sleep to make sure we don't
// steal the lock.
else if(i >= SpinCount)
{
#if DEFER_EVENT_CREATION
// This call must go *before* the next operation. Otherwise,
// we will have a race condition between potential releasers
// and us.
this.EnsureEventCreated(ref _exclusiveWakeEvent);
#endif
if(Interlocked.CompareExchange(
ref _value,
value + LockExclusiveWaitersIncrement,
value
) == value)
{
#if ENABLE_STATISTICS
Interlocked.Increment(ref _acqExclSlpCount);
int exclWtrsCount = (value >> LockExclusiveWaitersShift) & LockExclusiveWaitersMask;
Interlocked2.Set(
ref _peakExclWtrsCount,
(p) => p < exclWtrsCount,
(p) => exclWtrsCount
);
#endif
// Go to sleep.
if(NativeMethods.WaitForSingleObject(
_exclusiveWakeEvent,
Timeout.Infinite
) != NativeMethods.WaitObject0)
UtilsBreak("Utils.MsgFailedToWaitIndefinitely");
// Acquire the lock.
// At this point *no one* should be able to steal the lock from us.
do
{
value = _value;
#if RIGOROUS_CHECKS
System.Diagnostics.Trace.Assert((value & LockOwned) == 0);
System.Diagnostics.Trace.Assert((value & LockExclusiveWaking) != 0);
#endif
} while(Interlocked.CompareExchange(
ref _value,
value + LockOwned - LockExclusiveWaking,
value
) != value);
break;
}
}
#if ENABLE_STATISTICS
Interlocked.Increment(ref _acqExclContCount);
#endif
i++;
}
}
///
/// Acquires the lock in shared mode, blocking
/// if necessary.
///
///
/// Exclusive acquires are given precedence over shared
/// acquires.
///
public void AcquireShared()
{
int value;
int i = 0;
#if ENABLE_STATISTICS
Interlocked.Increment(ref _acqShrdCount);
#endif
while(true)
{
value = _value;
// Case 1: lock not owned AND no exclusive waiter is waking up AND
// there are no shared owners AND there are no exclusive waiters
if((value & (
LockOwned |
(LockSharedOwnersMask << LockSharedOwnersShift) |
ExclusiveMask
)) == 0)
{
if(Interlocked.CompareExchange(
ref _value,
value + LockOwned + LockSharedOwnersIncrement,
value
) == value)
break;
}
// Case 2: lock is owned AND no exclusive waiter is waking up AND
// there are shared owners AND there are no exclusive waiters
else if(
(value & LockOwned) != 0 &&
((value >> LockSharedOwnersShift) & LockSharedOwnersMask) != 0 &&
(value & ExclusiveMask) == 0
)
{
if(Interlocked.CompareExchange(
ref _value,
value + LockSharedOwnersIncrement,
value
) == value)
break;
}
// Other cases.
else if(i >= SpinCount)
{
#if DEFER_EVENT_CREATION
this.EnsureEventCreated(ref _sharedWakeEvent);
#endif
if(Interlocked.CompareExchange(
ref _value,
value + LockSharedWaitersIncrement,
value
) == value)
{
#if ENABLE_STATISTICS
Interlocked.Increment(ref _acqShrdSlpCount);
int shrdWtrsCount = (value >> LockSharedWaitersShift) & LockSharedWaitersMask;
Interlocked2.Set(
ref _peakShrdWtrsCount,
(p) => p < shrdWtrsCount,
(p) => shrdWtrsCount
);
#endif
// Go to sleep.
if(NativeMethods.WaitForSingleObject(
_sharedWakeEvent,
Timeout.Infinite
) != NativeMethods.WaitObject0)
UtilsBreak("Utils.MsgFailedToWaitIndefinitely");
// Go back and try again.
continue;
}
}
#if ENABLE_STATISTICS
Interlocked.Increment(ref _acqShrdContCount);
#endif
i++;
}
}
public static void UtilsBreak(string logMessage)
{
System.Diagnostics.Debugger.Log(0, "Error", logMessage);
System.Diagnostics.Debugger.Break();
}
///
/// Converts the ownership mode from exclusive to shared.
///
///
/// Exclusive acquires are not given a chance to acquire
/// the lock before this function does - as a result,
/// this function will never block.
///
public void ConvertExclusiveToShared()
{
int value;
int sharedWaiters;
while(true)
{
value = _value;
#if RIGOROUS_CHECKS
System.Diagnostics.Trace.Assert((value & LockOwned) != 0);
System.Diagnostics.Trace.Assert((value & LockExclusiveWaking) == 0);
System.Diagnostics.Trace.Assert(((value >> LockSharedOwnersShift) & LockSharedOwnersMask) == 0);
#endif
sharedWaiters = (value >> LockSharedWaitersShift) & LockSharedWaitersMask;
if(Interlocked.CompareExchange(
ref _value,
(value + LockSharedOwnersIncrement) & ~(LockSharedWaitersMask << LockSharedWaitersShift),
value
) == value)
{
if(sharedWaiters != 0)
NativeMethods.ReleaseSemaphore(_sharedWakeEvent, sharedWaiters, IntPtr.Zero);
break;
}
}
}
#if DEFER_EVENT_CREATION
///
/// Checks if the specified event has been created, and
/// if not, creates it.
///
/// A reference to the event handle.
private void EnsureEventCreated(ref IntPtr handle)
{
IntPtr eventHandle;
if(Thread.VolatileRead(ref handle) != IntPtr.Zero)
return;
eventHandle = NativeMethods.CreateSemaphore(IntPtr.Zero, 0, int.MaxValue, null);
if(Interlocked.CompareExchange(ref handle, eventHandle, IntPtr.Zero) != IntPtr.Zero)
NativeMethods.CloseHandle(eventHandle);
}
#endif
///
/// Gets statistics information for the lock.
///
/// A structure containing statistics.
public Statistics GetStatistics()
{
#if ENABLE_STATISTICS
return new Statistics()
{
AcqExcl = _acqExclCount,
AcqShrd = _acqShrdCount,
AcqExclCont = _acqExclContCount,
AcqShrdCont = _acqShrdContCount,
AcqExclSlp = _acqExclSlpCount,
AcqShrdSlp = _acqShrdSlpCount,
PeakExclWtrsCount = _peakExclWtrsCount,
PeakShrdWtrsCount = _peakShrdWtrsCount
};
#else
return new Statistics();
#endif
}
///
/// Releases the lock in exclusive mode.
///
public void ReleaseExclusive()
{
int value;
while(true)
{
value = _value;
#if RIGOROUS_CHECKS
System.Diagnostics.Trace.Assert((value & LockOwned) != 0);
System.Diagnostics.Trace.Assert((value & LockExclusiveWaking) == 0);
System.Diagnostics.Trace.Assert(((value >> LockSharedOwnersShift) & LockSharedOwnersMask) == 0);
#endif
// Case 1: if we have exclusive waiters, release one.
if(((value >> LockExclusiveWaitersShift) & LockExclusiveWaitersMask) != 0)
{
if(Interlocked.CompareExchange(
ref _value,
value - LockOwned + LockExclusiveWaking - LockExclusiveWaitersIncrement,
value
) == value)
{
NativeMethods.ReleaseSemaphore(_exclusiveWakeEvent, 1, IntPtr.Zero);
break;
}
}
// Case 2: if we have shared waiters, release all of them.
else
{
int sharedWaiters;
sharedWaiters = (value >> LockSharedWaitersShift) & LockSharedWaitersMask;
if(Interlocked.CompareExchange(
ref _value,
value & ~(LockOwned | (LockSharedWaitersMask << LockSharedWaitersShift)),
value
) == value)
{
if(sharedWaiters != 0)
NativeMethods.ReleaseSemaphore(_sharedWakeEvent, sharedWaiters, IntPtr.Zero);
break;
}
}
}
}
///
/// Releases the lock in shared mode.
///
public void ReleaseShared()
{
int value;
int sharedOwners;
while(true)
{
value = _value;
#if RIGOROUS_CHECKS
System.Diagnostics.Trace.Assert((value & LockOwned) != 0);
System.Diagnostics.Trace.Assert((value & LockExclusiveWaking) == 0);
System.Diagnostics.Trace.Assert(((value >> LockSharedOwnersShift) & LockSharedOwnersMask) != 0);
#endif
sharedOwners = (value >> LockSharedOwnersShift) & LockSharedOwnersMask;
// Case 1: there are multiple shared owners.
if(sharedOwners > 1)
{
if(Interlocked.CompareExchange(
ref _value,
value - LockSharedOwnersIncrement,
value
) == value)
break;
}
// Case 2: we are the last shared owner AND there are exclusive waiters.
else if(((value >> LockExclusiveWaitersShift) & LockExclusiveWaitersMask) != 0)
{
if(Interlocked.CompareExchange(
ref _value,
value - LockOwned + LockExclusiveWaking - LockSharedOwnersIncrement - LockExclusiveWaitersIncrement,
value
) == value)
{
NativeMethods.ReleaseSemaphore(_exclusiveWakeEvent, 1, IntPtr.Zero);
break;
}
}
// Case 3: we are the last shared owner AND there are no exclusive waiters.
else
{
if(Interlocked.CompareExchange(
ref _value,
value - LockOwned - LockSharedOwnersIncrement,
value
) == value)
break;
}
}
}
///
/// Acquires the lock in exclusive mode, busy waiting
/// if necessary.
///
///
/// Exclusive acquires are *not* given precedence over shared
/// acquires for busy wait methods.
///
public void SpinAcquireExclusive()
{
int value;
while(true)
{
value = _value;
if((value & (LockOwned | LockExclusiveWaking)) == 0)
{
if(Interlocked.CompareExchange(
ref _value,
value + LockOwned,
value
) == value)
break;
}
if(NativeMethods.SpinEnabled)
Thread.SpinWait(8);
else
Thread.Sleep(0);
}
}
///
/// Acquires the lock in shared mode, busy waiting
/// if necessary.
///
///
/// Exclusive acquires are *not* given precedence over shared
/// acquires for busy wait methods.
///
public void SpinAcquireShared()
{
int value;
while(true)
{
value = _value;
if((value & ExclusiveMask) == 0)
{
if((value & LockOwned) == 0)
{
if(Interlocked.CompareExchange(
ref _value,
value + LockOwned + LockSharedOwnersIncrement,
value
) == value)
break;
}
else if(((value >> LockSharedOwnersShift) & LockSharedOwnersMask) != 0)
{
if(Interlocked.CompareExchange(
ref _value,
value + LockSharedOwnersIncrement,
value
) == value)
break;
}
}
if(NativeMethods.SpinEnabled)
Thread.SpinWait(8);
else
Thread.Sleep(0);
}
}
///
/// Converts the ownership mode from shared to exclusive,
/// busy waiting if necessary.
///
public void SpinConvertSharedToExclusive()
{
int value;
while(true)
{
value = _value;
// Can't convert if there are other shared owners.
if(((value >> LockSharedOwnersShift) & LockSharedOwnersMask) == 1)
{
if(Interlocked.CompareExchange(
ref _value,
value - LockSharedOwnersIncrement,
value
) == value)
break;
}
if(NativeMethods.SpinEnabled)
Thread.SpinWait(8);
else
Thread.Sleep(0);
}
}
///
/// Attempts to acquire the lock in exclusive mode.
///
/// Whether the lock was acquired.
public bool TryAcquireExclusive()
{
int value;
value = _value;
if((value & (LockOwned | LockExclusiveWaking)) != 0)
return false;
return Interlocked.CompareExchange(
ref _value,
value + LockOwned,
value
) == value;
}
///
/// Attempts to acquire the lock in shared mode.
///
/// Whether the lock was acquired.
public bool TryAcquireShared()
{
int value;
value = _value;
if((value & ExclusiveMask) != 0)
return false;
if((value & LockOwned) == 0)
{
return Interlocked.CompareExchange(
ref _value,
value + LockOwned + LockSharedOwnersIncrement,
value
) == value;
}
else if(((value >> LockSharedOwnersShift) & LockSharedOwnersMask) != 0)
{
return Interlocked.CompareExchange(
ref _value,
value + LockSharedOwnersIncrement,
value
) == value;
}
else
{
return false;
}
}
///
/// Attempts to convert the ownership mode from shared
/// to exclusive.
///
/// Whether the lock was converted.
public bool TryConvertSharedToExclusive()
{
int value;
while(true)
{
value = _value;
#if RIGOROUS_CHECKS
System.Diagnostics.Trace.Assert((value & LockOwned) != 0);
System.Diagnostics.Trace.Assert((value & LockExclusiveWaking) == 0);
System.Diagnostics.Trace.Assert(((value >> LockSharedOwnersShift) & LockSharedOwnersMask) != 0);
#endif
// Can't convert if there are other shared owners.
if(((value >> LockSharedOwnersShift) & LockSharedOwnersMask) != 1)
return false;
if(Interlocked.CompareExchange(
ref _value,
value - LockSharedOwnersIncrement,
value
) == value)
return true;
}
}
}
[SuppressUnmanagedCodeSecurity]
internal class NativeMethods
{
public const int WaitObject0 = 0x0;
public const int WaitAbandoned = 0x80;
public const int WaitTimeout = 0x102;
public const int WaitFailed = -1;
public static readonly int SpinCount = Environment.ProcessorCount != 1 ? 4000 : 0;
public static readonly bool SpinEnabled = Environment.ProcessorCount != 1;
// We need to import some stuff. We can't use
// ProcessHacker.Native because it depends on this library.
[DllImport("kernel32.dll")]
public static extern bool CloseHandle(
[In] IntPtr Handle
);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr CreateEvent(
[In] [Optional] IntPtr EventAttributes,
[In] bool ManualReset,
[In] bool InitialState,
[In] [Optional] string Name
);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr CreateSemaphore(
[In] [Optional] IntPtr SemaphoreAttributes,
[In] int InitialCount,
[In] int MaximumCount,
[In] [Optional] string Name
);
[DllImport("kernel32.dll")]
public static extern bool ReleaseSemaphore(
[In] IntPtr SemaphoreHandle,
[In] int ReleaseCount,
[In] IntPtr PreviousCount // out int
);
[DllImport("kernel32.dll")]
public static extern bool ResetEvent(
[In] IntPtr EventHandle
);
[DllImport("kernel32.dll")]
public static extern bool SetEvent(
[In] IntPtr EventHandle
);
[DllImport("kernel32.dll")]
public static extern int WaitForSingleObject(
[In] IntPtr Handle,
[In] int Milliseconds
);
[DllImport("ntdll.dll")]
public static extern int NtCreateKeyedEvent(
[Out] out IntPtr KeyedEventHandle,
[In] int DesiredAccess,
[In] [Optional] IntPtr ObjectAttributes,
[In] int Flags
);
[DllImport("ntdll.dll")]
public static extern int NtReleaseKeyedEvent(
[In] IntPtr KeyedEventHandle,
[In] IntPtr KeyValue,
[In] bool Alertable,
[In] [Optional] IntPtr Timeout
);
[DllImport("ntdll.dll")]
public static extern int NtWaitForKeyedEvent(
[In] IntPtr KeyedEventHandle,
[In] IntPtr KeyValue,
[In] bool Alertable,
[In] [Optional] IntPtr Timeout
);
}
#endif
}