diff --git a/PInvoke/Kernel32/SynchApi.cs b/PInvoke/Kernel32/SynchApi.cs index 8e0a98da..7f26969b 100644 --- a/PInvoke/Kernel32/SynchApi.cs +++ b/PInvoke/Kernel32/SynchApi.cs @@ -1,5 +1,7 @@ using Microsoft.Win32.SafeHandles; using System; +using System.Collections.Generic; +using System.Linq; using System.Runtime.InteropServices; using Vanara.Extensions; using Vanara.InteropServices; @@ -9,6 +11,8 @@ namespace Vanara.PInvoke { public static partial class Kernel32 { + public const int INIT_ONCE_CTX_RESERVED_BITS = 2; + // PINIT_ONCE_FN PTIMERAPCROUTINE /// @@ -28,9 +32,9 @@ namespace Vanara.PInvoke /// additional error information, call SetLastError before returning FALSE. /// /// - [UnmanagedFunctionPointer(CallingConvention.StdCall)] + [UnmanagedFunctionPointer(CallingConvention.Winapi)] [return: MarshalAs(UnmanagedType.Bool)] - public delegate bool PINIT_ONCE_FN(ref INIT_ONCE InitOnce, IntPtr Parameter, out IntPtr Context); + public delegate bool InitOnceCallback(ref INIT_ONCE InitOnce, IntPtr Parameter, out IntPtr Context); /// /// An application-defined timer completion routine. Specify this address when calling the SetWaitableTimer function. The @@ -47,8 +51,8 @@ namespace Vanara.PInvoke /// The high-order portion of the UTC-based time at which the timer was signaled. This value corresponds to the dwHighDateTime member /// of the FILETIME structure. /// - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void PTIMERAPCROUTINE([In] IntPtr lpArgToCompletionRoutine, uint dwTimerLowValue, uint dwTimerHighValue); + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + public delegate void TimerAPCProc([In] IntPtr lpArgToCompletionRoutine, uint dwTimerLowValue, uint dwTimerHighValue); /// Used by . [Flags] @@ -171,6 +175,54 @@ namespace Vanara.PInvoke SYNCHRONIZATION_BARRIER_FLAGS_NO_DELETE = 0x04, } + /// Synchronization object access flags. + [PInvokeData("synchapi.h")] + [Flags] + public enum SynchronizationObjectAccess : uint + { + /// Modify state access, which is required for the SetEvent, ResetEvent and PulseEvent functions. + EVENT_MODIFY_STATE = 0x0002, + + /// + /// All possible access rights for an event object. Use this right only if your application requires access beyond that granted + /// by the standard access rights and EVENT_MODIFY_STATE. Using this access right increases the possibility that your application + /// must be run by an Administrator. + /// + EVENT_ALL_ACCESS = ACCESS_MASK.STANDARD_RIGHTS_REQUIRED | ACCESS_MASK.SYNCHRONIZE | 0x3, + + /// Reserved for future use. + MUTEX_QUERY_STATE = 0x0001, + + /// + /// All possible access rights for a mutex object. Use this right only if your application requires access beyond that granted by + /// the standard access rights. Using this access right increases the possibility that your application must be run by an Administrator. + /// + MUTEX_ALL_ACCESS = ACCESS_MASK.STANDARD_RIGHTS_REQUIRED | ACCESS_MASK.SYNCHRONIZE | MUTEX_QUERY_STATE, + + /// Modify state access, which is required for the ReleaseSemaphore function. + SEMAPHORE_MODIFY_STATE = 0x0002, + + /// + /// All possible access rights for a semaphore object. Use this right only if your application requires access beyond that + /// granted by the standard access rights and SEMAPHORE_MODIFY_STATE. Using this access right increases the possibility that your + /// application must be run by an Administrator. + /// + SEMAPHORE_ALL_ACCESS = ACCESS_MASK.STANDARD_RIGHTS_REQUIRED | ACCESS_MASK.SYNCHRONIZE | 0x3, + + /// Reserved for future use. + TIMER_QUERY_STATE = 0x0001, + + /// Modify state access, which is required for the SetWaitableTimer and CancelWaitableTimer functions. + TIMER_MODIFY_STATE = 0x0002, + + /// + /// All possible access rights for a waitable timer object. Use this right only if your application requires access beyond that + /// granted by the standard access rights and TIMER_MODIFY_STATE. Using this access right increases the possibility that your + /// application must be run by an Administrator. + /// + TIMER_ALL_ACCESS = ACCESS_MASK.STANDARD_RIGHTS_REQUIRED | ACCESS_MASK.SYNCHRONIZE | TIMER_QUERY_STATE | TIMER_MODIFY_STATE, + } + /// Values returned by . public enum WAIT_STATUS : uint { @@ -200,7 +252,7 @@ namespace Vanara.PInvoke // VOID WINAPI AcquireSRWLockExclusive( _Inout_ PSRWLOCK SRWLock); https://msdn.microsoft.com/en-us/library/windows/desktop/ms681930(v=vs.85).aspx [DllImport(Lib.Kernel32, SetLastError = false, ExactSpelling = true)] [PInvokeData("WinBase.h", MSDNShortId = "ms681930")] - public static extern void AcquireSRWLockExclusive(out SRWLOCK SRWLock); + public static extern void AcquireSRWLockExclusive(ref SRWLOCK SRWLock); /// Acquires a slim reader/writer (SRW) lock in shared mode. /// A pointer to the SRW lock. @@ -208,7 +260,7 @@ namespace Vanara.PInvoke // VOID WINAPI AcquireSRWLockShared( _Inout_ PSRWLOCK SRWLock); https://msdn.microsoft.com/en-us/library/windows/desktop/ms681934(v=vs.85).aspx [DllImport(Lib.Kernel32, SetLastError = false, ExactSpelling = true)] [PInvokeData("WinBase.h", MSDNShortId = "ms681934")] - public static extern void AcquireSRWLockShared(out SRWLOCK SRWLock); + public static extern void AcquireSRWLockShared(ref SRWLOCK SRWLock); /// Sets the specified waitable timer to the inactive state. /// @@ -348,7 +400,7 @@ namespace Vanara.PInvoke // DWORD dwDesiredAccess); https://msdn.microsoft.com/en-us/library/windows/desktop/ms682400(v=vs.85).aspx [DllImport(Lib.Kernel32, SetLastError = true, CharSet = CharSet.Auto)] [PInvokeData("WinBase.h", MSDNShortId = "ms682400")] - public static extern SafeEventHandle CreateEventEx([In, Optional] SECURITY_ATTRIBUTES lpEventAttributes, [In, Optional] string lpName, CREATE_EVENT_FLAGS dwFlags, uint dwDesiredAccess); + public static extern SafeEventHandle CreateEventEx([In, Optional] SECURITY_ATTRIBUTES lpEventAttributes, [In, Optional] string lpName, CREATE_EVENT_FLAGS dwFlags, ACCESS_MASK dwDesiredAccess); /// /// Creates or opens a named or unnamed mutex object. @@ -464,7 +516,7 @@ namespace Vanara.PInvoke // DWORD dwDesiredAccess); https://msdn.microsoft.com/en-us/library/windows/desktop/ms682418(v=vs.85).aspx [DllImport(Lib.Kernel32, SetLastError = true, CharSet = CharSet.Auto)] [PInvokeData("WinBase.h", MSDNShortId = "ms682418")] - public static extern SafeMutexHandle CreateMutexEx([In, Optional] SECURITY_ATTRIBUTES lpMutexAttributes, [In, Optional] string lpName, CREATE_MUTEX_FLAGS dwFlags, uint dwDesiredAccess); + public static extern SafeMutexHandle CreateMutexEx([In, Optional] SECURITY_ATTRIBUTES lpMutexAttributes, [In, Optional] string lpName, CREATE_MUTEX_FLAGS dwFlags, ACCESS_MASK dwDesiredAccess); /// /// Creates or opens a named or unnamed semaphore object. @@ -578,7 +630,7 @@ namespace Vanara.PInvoke // lMaximumCount, _In_opt_ LPCTSTR lpName, _Reserved_ DWORD dwFlags, _In_ DWORD dwDesiredAccess); https://msdn.microsoft.com/en-us/library/windows/desktop/ms682446(v=vs.85).aspx [DllImport(Lib.Kernel32, SetLastError = true, CharSet = CharSet.Auto)] [PInvokeData("WinBase.h", MSDNShortId = "ms682446")] - public static extern SafeSemaphoreHandle CreateSemaphoreEx([In, Optional] SECURITY_ATTRIBUTES lpSemaphoreAttributes, int lInitialCount, int lMaximumCount, [In, Optional] string lpName, [Optional] uint dwFlags, uint dwDesiredAccess); + public static extern SafeSemaphoreHandle CreateSemaphoreEx([In, Optional] SECURITY_ATTRIBUTES lpSemaphoreAttributes, int lInitialCount, int lMaximumCount, [In, Optional] string lpName, [Optional] uint dwFlags, ACCESS_MASK dwDesiredAccess); /// /// Creates or opens a waitable timer object. @@ -682,7 +734,7 @@ namespace Vanara.PInvoke // dwFlags, _In_ DWORD dwDesiredAccess); https://msdn.microsoft.com/en-us/library/windows/desktop/ms682494(v=vs.85).aspx [DllImport(Lib.Kernel32, SetLastError = true, CharSet = CharSet.Auto)] [PInvokeData("WinBase.h", MSDNShortId = "ms682494")] - public static extern SafeWaitableTimerHandle CreateWaitableTimerEx([In, Optional] SECURITY_ATTRIBUTES lpTimerAttributes, [In, Optional] string lpTimerName, CREATE_WAITABLE_TIMER_FLAG dwFlags, uint dwDesiredAccess); + public static extern SafeWaitableTimerHandle CreateWaitableTimerEx([In, Optional] SECURITY_ATTRIBUTES lpTimerAttributes, [In, Optional] string lpTimerName, CREATE_WAITABLE_TIMER_FLAG dwFlags, ACCESS_MASK dwDesiredAccess); /// Releases all resources used by an unowned critical section object. /// @@ -844,7 +896,6 @@ namespace Vanara.PInvoke /// /// /// This parameter can be 0 or the following value. - /// /// /// /// Value @@ -855,15 +906,53 @@ namespace Vanara.PInvoke /// The critical section is created without debug information. /// /// - /// /// /// /// If the function succeeds, the return value is nonzero. - /// If the function fails, the return value is zero (0). To get extended error information, call GetLastError. + /// If the function fails, the return value is zero (0). To get extended error information, call GetLastError. /// - // BOOL WINAPI InitializeCriticalSectionEx( _Out_ LPCRITICAL_SECTION lpCriticalSection, _In_ DWORD dwSpinCount, _In_ DWORD Flags); https://msdn.microsoft.com/en-us/library/windows/desktop/ms683477(v=vs.85).aspx + /// + /// + /// The threads of a single process can use a critical section object for mutual-exclusion synchronization. There is no guarantee + /// about the order that threads obtain ownership of the critical section, however, the system is fair to all threads. + /// + /// + /// The process is responsible for allocating the memory used by a critical section object, which it can do by declaring a variable + /// of type CRITICAL_SECTION. Before using a critical section, some thread of the process must initialize the object. You can + /// subsequently modify the spin count by calling the SetCriticalSectionSpinCount function. + /// + /// + /// After a critical section object is initialized, the threads of the process can specify the object in the EnterCriticalSection, + /// TryEnterCriticalSection, or LeaveCriticalSection function to provide mutually exclusive access to a shared resource. For similar + /// synchronization between the threads of different processes, use a mutex object. + /// + /// + /// A critical section object cannot be moved or copied. The process must also not modify the object, but must treat it as logically + /// opaque. Use only the critical section functions to manage critical section objects. When you have finished using the critical + /// section, call the DeleteCriticalSection function. + /// + /// + /// A critical section object must be deleted before it can be reinitialized. Initializing a critical section that is already + /// initialized results in undefined behavior. + /// + /// + /// The spin count is useful for critical sections of short duration that can experience high levels of contention. Consider a + /// worst-case scenario, in which an application on an SMP system has two or three threads constantly allocating and releasing memory + /// from the heap. The application serializes the heap with a critical section. In the worst-case scenario, contention for the + /// critical section is constant, and each thread makes an processing-intensive call to the WaitForSingleObject function. However, if + /// the spin count is set properly, the calling thread does not immediately call WaitForSingleObject when contention occurs. + /// Instead, the calling thread can acquire ownership of the critical section if it is released during the spin operation. + /// + /// + /// You can improve performance significantly by choosing a small spin count for a critical section of short duration. The heap + /// manager uses a spin count of roughly 4000 for its per-heap critical sections. This gives great performance and scalability in + /// almost all worst-case scenarios. + /// + /// + // https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-initializecriticalsectionex + // BOOL InitializeCriticalSectionEx( LPCRITICAL_SECTION lpCriticalSection, DWORD dwSpinCount, DWORD Flags ); [DllImport(Lib.Kernel32, SetLastError = true, ExactSpelling = true)] - [PInvokeData("WinBase.h", MSDNShortId = "ms683477")] + [PInvokeData("synchapi.h", MSDNShortId = "da84b187-0eb7-4363-8e68-8a525586d7d9")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool InitializeCriticalSectionEx(out CRITICAL_SECTION lpCriticalSection, uint dwSpinCount, CRITICAL_SECTION_FLAGS Flags); @@ -1020,7 +1109,7 @@ namespace Vanara.PInvoke [DllImport(Lib.Kernel32, SetLastError = true, ExactSpelling = true)] [PInvokeData("WinBase.h", MSDNShortId = "ms683493")] [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool InitOnceExecuteOnce(ref INIT_ONCE InitOnce, PINIT_ONCE_FN InitFn, IntPtr Parameter, out IntPtr Context); + public static extern bool InitOnceExecuteOnce(ref INIT_ONCE InitOnce, InitOnceCallback InitFn, [Optional] IntPtr Parameter, out IntPtr Context); /// Initializes a one-time initialization structure. /// A pointer to the one-time initialization structure. @@ -1046,9 +1135,8 @@ namespace Vanara.PInvoke /// The value to compare to Destination. /// The function returns the initial value of the Destination parameter. // LONG __cdecl InterlockedCompareExchange( _Inout_ LONG volatile *Destination, _In_ LONG Exchange, _In_ LONG Comparand); https://msdn.microsoft.com/en-us/library/windows/desktop/ms683560(v=vs.85).aspx - [DllImport(Lib.Kernel32, SetLastError = false, ExactSpelling = true)] [PInvokeData("WinBase.h", MSDNShortId = "ms683560")] - public static extern int InterlockedCompareExchange(ref int Destination, int Exchange, int Comparand); + public static int InterlockedCompareExchange(ref int Destination, int Exchange, int Comparand) => System.Threading.Interlocked.CompareExchange(ref Destination, Exchange, Comparand); /// /// Decrements (decreases by one) the value of the specified 32-bit variable as an atomic operation. @@ -1057,9 +1145,8 @@ namespace Vanara.PInvoke /// A pointer to the variable to be decremented. /// The function returns the resulting decremented value. // LONG __cdecl InterlockedDecrement( _Inout_ LONG volatile *Addend); https://msdn.microsoft.com/en-us/library/windows/desktop/ms683580(v=vs.85).aspx - [DllImport(Lib.Kernel32, SetLastError = false, ExactSpelling = true)] [PInvokeData("WinBase.h", MSDNShortId = "ms683580")] - public static extern int InterlockedDecrement(ref int Addend); + public static int InterlockedDecrement(ref int Addend) => System.Threading.Interlocked.Decrement(ref Addend); /// /// Sets a 32-bit variable to the specified value as an atomic operation. @@ -1073,9 +1160,8 @@ namespace Vanara.PInvoke /// The value to be exchanged with the value pointed to by Target. /// The function returns the initial value of the Target parameter. // LONG __cdecl InterlockedExchange( _Inout_ LONG volatile *Target, _In_ LONG Value); https://msdn.microsoft.com/en-us/library/windows/desktop/ms683590(v=vs.85).aspx - [DllImport(Lib.Kernel32, SetLastError = false, ExactSpelling = true)] [PInvokeData("WinBase.h", MSDNShortId = "ms683590")] - public static extern int InterlockedExchange(ref int Target, int Value); + public static int InterlockedExchange(ref int Target, int Value) => System.Threading.Interlocked.Exchange(ref Target, Value); /// /// Performs an atomic addition of two 32-bit values. @@ -1085,9 +1171,8 @@ namespace Vanara.PInvoke /// The value to be added to the variable pointed to by the Addend parameter. /// The function returns the initial value of the Addend parameter. // LONG __cdecl InterlockedExchangeAdd( _Inout_ LONG volatile *Addend, _In_ LONG Value); https://msdn.microsoft.com/en-us/library/windows/desktop/ms683597(v=vs.85).aspx - [DllImport(Lib.Kernel32, SetLastError = false, ExactSpelling = true)] [PInvokeData("WinBase.h", MSDNShortId = "ms683597")] - public static extern int InterlockedExchangeAdd(ref int Addend, int Value); + public static int InterlockedExchangeAdd(ref int Addend, int Value) => throw new NotImplementedException("Machine level export not found in Kernel32.dll or .NET assemblies."); /// /// Increments (increases by one) the value of the specified 32-bit variable as an atomic operation. @@ -1096,9 +1181,8 @@ namespace Vanara.PInvoke /// A pointer to the variable to be incremented. /// The function returns the resulting incremented value. // LONG __cdecl InterlockedIncrement( _Inout_ LONG volatile *Addend); https://msdn.microsoft.com/en-us/library/windows/desktop/ms683614(v=vs.85).aspx - [DllImport(Lib.Kernel32, SetLastError = false, ExactSpelling = true)] [PInvokeData("WinBase.h", MSDNShortId = "ms683614")] - public static extern int InterlockedIncrement(ref int Addend); + public static int InterlockedIncrement(ref int Addend) => System.Threading.Interlocked.Increment(ref Addend); /// Releases ownership of the specified critical section object. /// A pointer to the critical section object. @@ -1407,8 +1491,8 @@ namespace Vanara.PInvoke [DllImport(Lib.Kernel32, SetLastError = true, ExactSpelling = true)] [PInvokeData("WinBase.h", MSDNShortId = "ms686289")] [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool SetWaitableTimer([In] SafeWaitableTimerHandle hTimer, in FILETIME pDueTime, int lPeriod, PTIMERAPCROUTINE pfnCompletionRoutine, - [In] IntPtr lpArgToCompletionRoutine, [MarshalAs(UnmanagedType.Bool)] bool fResume); + public static extern bool SetWaitableTimer([In] SafeWaitableTimerHandle hTimer, in FILETIME pDueTime, [Optional] int lPeriod, [Optional] TimerAPCProc pfnCompletionRoutine, + [In, Optional] IntPtr lpArgToCompletionRoutine, [MarshalAs(UnmanagedType.Bool)] bool fResume = false); /// /// Activates the specified waitable timer and provides context information for the timer. When the due time arrives, the timer is @@ -1449,8 +1533,50 @@ namespace Vanara.PInvoke [DllImport(Lib.Kernel32, SetLastError = true, ExactSpelling = true)] [PInvokeData("WinBase.h", MSDNShortId = "dd405521")] [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool SetWaitableTimerEx([In] SafeWaitableTimerHandle hTimer, in FILETIME lpDueTime, int lPeriod, PTIMERAPCROUTINE pfnCompletionRoutine, - [In] IntPtr lpArgToCompletionRoutine, in REASON_CONTEXT WakeContext, uint TolerableDelay); + public static extern bool SetWaitableTimerEx([In] SafeWaitableTimerHandle hTimer, in FILETIME lpDueTime, [Optional] int lPeriod, [Optional] TimerAPCProc pfnCompletionRoutine, + [In, Optional] IntPtr lpArgToCompletionRoutine, [In] REASON_CONTEXT WakeContext, uint TolerableDelay); + + /// + /// Activates the specified waitable timer and provides context information for the timer. When the due time arrives, the timer is + /// signaled and the thread that set the timer calls the optional completion routine. + /// + /// + /// A handle to the timer object. The CreateWaitableTimer or OpenWaitableTimer function returns this handle. + /// + /// The handle must have the TIMER_MODIFY_STATE access right. For more information, see Synchronization Object Security and + /// Access Rights. + /// + /// + /// + /// The time after which the state of the timer is to be set to signaled, in 100 nanosecond intervals. Use the format described by + /// the FILETIME structure. Positive values indicate absolute time. Be sure to use a UTC-based absolute time, as the system + /// uses UTC-based time internally. Negative values indicate relative time. The actual timer accuracy depends on the capability of + /// your hardware. For more information about UTC-based time, see System Time. + /// + /// + /// The period of the timer, in milliseconds. If lPeriod is zero, the timer is signaled once. If lPeriod is greater than zero, the + /// timer is periodic. A periodic timer automatically reactivates each time the period elapses, until the timer is canceled using the + /// CancelWaitableTimer function or reset using SetWaitableTimerEx. If lPeriod is less than zero, the function fails. + /// + /// + /// A pointer to an optional completion routine. The completion routine is application-defined function of type + /// PTIMERAPCROUTINE to be executed when the timer is signaled. For more information on the timer callback function, see + /// TimerAPCProc. For more information about APCs and thread pool threads, see Remarks. + /// + /// A pointer to a structure that is passed to the completion routine. + /// Pointer to a REASON_CONTEXT structure that contains context information for the timer. + /// The tolerable delay for expiration time, in milliseconds. + /// + /// If the function succeeds, the return value is nonzero. + /// If the function fails, the return value is zero. To get extended error information, call GetLastError. + /// + // BOOL WINAPI SetWaitableTimerEx( _In_ HANDLE hTimer, _In_ const LARGE_INTEGER *lpDueTime, _In_ LONG lPeriod, _In_ PTIMERAPCROUTINE + // pfnCompletionRoutine, _In_ LPVOID lpArgToCompletionRoutine, _In_ PREASON_CONTEXT WakeContext, _In_ ULONG TolerableDelay); https://msdn.microsoft.com/en-us/library/windows/desktop/dd405521(v=vs.85).aspx + [DllImport(Lib.Kernel32, SetLastError = true, ExactSpelling = true)] + [PInvokeData("WinBase.h", MSDNShortId = "dd405521")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool SetWaitableTimerEx([In] SafeWaitableTimerHandle hTimer, in FILETIME lpDueTime, [Optional] int lPeriod, [Optional] TimerAPCProc pfnCompletionRoutine, + [In, Optional] IntPtr lpArgToCompletionRoutine, [Optional] IntPtr WakeContext, uint TolerableDelay); /// Signals one object and waits on another object as a single operation. /// @@ -1641,7 +1767,7 @@ namespace Vanara.PInvoke // DWORD WINAPI SleepEx( _In_ DWORD dwMilliseconds, _In_ BOOL bAlertable); https://msdn.microsoft.com/en-us/library/windows/desktop/ms686307(v=vs.85).aspx [DllImport(Lib.Kernel32, SetLastError = false, ExactSpelling = true)] [PInvokeData("WinBase.h", MSDNShortId = "ms686307")] - public static extern WAIT_STATUS SleepEx(uint dwMilliseconds, [MarshalAs(UnmanagedType.Bool)] bool bAlertable); + public static extern uint SleepEx(uint dwMilliseconds, [MarshalAs(UnmanagedType.Bool)] bool bAlertable); /// /// Attempts to acquire a slim reader/writer (SRW) lock in exclusive mode. If the call is successful, the calling thread takes @@ -1691,7 +1817,7 @@ namespace Vanara.PInvoke public static extern bool TryEnterCriticalSection(ref CRITICAL_SECTION lpCriticalSection); /// - /// Cancels a registered wait operation issued by the RegisterWaitForSingleObject function. + /// Cancels a registered wait operation issued by the function. /// To use a completion event, call the UnregisterWaitEx function. /// /// The wait handle. This handle is returned by the RegisterWaitForSingleObject function. @@ -1771,8 +1897,11 @@ namespace Vanara.PInvoke // DWORD WINAPI WaitForMultipleObjects( _In_ DWORD nCount, _In_ const HANDLE *lpHandles, _In_ BOOL bWaitAll, _In_ DWORD // dwMilliseconds); https://msdn.microsoft.com/en-us/library/windows/desktop/ms687025(v=vs.85).aspx [PInvokeData("WinBase.h", MSDNShortId = "ms687025")] - public static WAIT_STATUS WaitForMultipleObjects(ISyncHandle[] lpHandles, bool bWaitAll, uint dwMilliseconds) => - WaitForMultipleObjects((uint)(lpHandles?.Length ?? 0), lpHandles == null ? null : Array.ConvertAll(lpHandles, i => i.DangerousGetHandle()), bWaitAll, dwMilliseconds); + public static WAIT_STATUS WaitForMultipleObjects(IEnumerable lpHandles, bool bWaitAll, uint dwMilliseconds) where T : ISyncHandle + { + var h = lpHandles?.Select(i => i.DangerousGetHandle()).ToArray(); + return WaitForMultipleObjects((uint)(h?.Length ?? 0), h, bWaitAll, dwMilliseconds); + } /// /// Waits until one or all of the specified objects are in the signaled state, an I/O completion routine or asynchronous procedure @@ -1860,8 +1989,11 @@ namespace Vanara.PInvoke // DWORD WINAPI WaitForMultipleObjectsEx( _In_ DWORD nCount, _In_ const HANDLE *lpHandles, _In_ BOOL bWaitAll, _In_ DWORD // dwMilliseconds, _In_ BOOL bAlertable); https://msdn.microsoft.com/en-us/library/windows/desktop/ms687028(v=vs.85).aspx [PInvokeData("WinBase.h", MSDNShortId = "ms687028")] - public static WAIT_STATUS WaitForMultipleObjectsEx(ISyncHandle[] lpHandles, bool bWaitAll, uint dwMilliseconds, bool bAlertable) => - WaitForMultipleObjectsEx((uint)(lpHandles?.Length ?? 0), lpHandles == null ? null : Array.ConvertAll(lpHandles, i => i.DangerousGetHandle()), bWaitAll, dwMilliseconds, bAlertable); + public static WAIT_STATUS WaitForMultipleObjectsEx(IEnumerable lpHandles, bool bWaitAll, uint dwMilliseconds, bool bAlertable) where T : ISyncHandle + { + var h = lpHandles?.Select(i => i.DangerousGetHandle()).ToArray(); + return WaitForMultipleObjectsEx((uint)(h?.Length ?? 0), h, bWaitAll, dwMilliseconds, bAlertable); + } /// /// Waits until the specified object is in the signaled state or the time-out interval elapses. @@ -2017,16 +2149,16 @@ namespace Vanara.PInvoke /// // BOOL WINAPI WaitOnAddress( _In_ VOID volatile *Address, _In_ PVOID CompareAddress, _In_ SIZE_T AddressSize, _In_opt_ DWORD // dwMilliseconds); https://msdn.microsoft.com/en-us/library/windows/desktop/hh706898(v=vs.85).aspx - [DllImport(Lib.Kernel32, SetLastError = true, ExactSpelling = true)] + [DllImport(Lib.KernelBase, SetLastError = true, ExactSpelling = true)] [PInvokeData("SynchAPI.h", MSDNShortId = "hh706898")] [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool WaitOnAddress(IntPtr Address, IntPtr CompareAddress, SizeT AddressSize, uint dwMilliseconds); + public static extern unsafe bool WaitOnAddress(void* Address, void* CompareAddress, SizeT AddressSize, uint dwMilliseconds); /// Wake all threads waiting on the specified condition variable. /// A pointer to the condition variable. /// This function does not return a value. // VOID WINAPI WakeAllConditionVariable( _Inout_ PCONDITION_VARIABLE ConditionVariable); https://msdn.microsoft.com/en-us/library/windows/desktop/ms687076(v=vs.85).aspx - [DllImport(Lib.Kernel32, SetLastError = false, ExactSpelling = true)] + [DllImport(Lib.KernelBase, SetLastError = false, ExactSpelling = true)] [PInvokeData("WinBase.h", MSDNShortId = "ms687076")] public static extern void WakeAllConditionVariable(ref CONDITION_VARIABLE ConditionVariable); @@ -2037,9 +2169,9 @@ namespace Vanara.PInvoke /// /// This function does not return a value. // VOID WINAPI WakeByAddressAll( _In_ PVOID Address); https://msdn.microsoft.com/en-us/library/windows/desktop/hh706899(v=vs.85).aspx - [DllImport(Lib.Kernel32, SetLastError = false, ExactSpelling = true)] + [DllImport(Lib.KernelBase, SetLastError = false, ExactSpelling = true)] [PInvokeData("SynchAPI.h", MSDNShortId = "hh706899")] - public static extern void WakeByAddressAll(IntPtr Address); + public static extern unsafe void WakeByAddressAll(void* Address); /// Wakes one thread that is waiting for the value of an address to change. /// @@ -2048,15 +2180,15 @@ namespace Vanara.PInvoke /// /// This function does not return a value. // VOID WINAPI WakeByAddressSingle( _In_ PVOID Address); https://msdn.microsoft.com/en-us/library/windows/desktop/hh706900(v=vs.85).aspx - [DllImport(Lib.Kernel32, SetLastError = false, ExactSpelling = true)] + [DllImport(Lib.KernelBase, SetLastError = false, ExactSpelling = true)] [PInvokeData("SynchAPI.h", MSDNShortId = "hh706900")] - public static extern void WakeByAddressSingle(IntPtr Address); + public static extern unsafe void WakeByAddressSingle(void* Address); /// Wake a single thread waiting on the specified condition variable. /// A pointer to the condition variable. /// This function does not return a value. // VOID WINAPI WakeConditionVariable( _Inout_ PCONDITION_VARIABLE ConditionVariable); https://msdn.microsoft.com/en-us/library/windows/desktop/ms687080(v=vs.85).aspx - [DllImport(Lib.Kernel32, SetLastError = false, ExactSpelling = true)] + [DllImport(Lib.KernelBase, SetLastError = false, ExactSpelling = true)] [PInvokeData("WinBase.h", MSDNShortId = "ms687080")] public static extern void WakeConditionVariable(ref CONDITION_VARIABLE ConditionVariable); @@ -2112,7 +2244,7 @@ namespace Vanara.PInvoke // LocalizedReasonId; ULONG ReasonStringCount; LPWSTR *ReasonStrings; } Detailed; LPWSTR SimpleReasonString; } Reason;} // REASON_CONTEXT, *PREASON_CONTEXT; https://msdn.microsoft.com/en-us/library/windows/desktop/dd405536(v=vs.85).aspx [PInvokeData("MinWinBase.h", MSDNShortId = "dd405536")] - [StructLayout(LayoutKind.Sequential)] + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public class REASON_CONTEXT : IDisposable { /// The version number of the structure. This parameter must be set to DIAGNOSTIC_REASON_VERSION. @@ -2144,7 +2276,7 @@ namespace Vanara.PInvoke private DETAIL _reason; /// A structure that identifies a localizable string resource to describe the reason for the power request. - [StructLayout(LayoutKind.Sequential)] + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct DETAIL { /// The module that contains the string resource. @@ -2164,7 +2296,7 @@ namespace Vanara.PInvoke { Version = DIAGNOSTIC_REASON_VERSION.DIAGNOSTIC_REASON_VERSION; Flags = DIAGNOSTIC_REASON.DIAGNOSTIC_REASON_SIMPLE_STRING; - _reason.LocalizedReasonModule = Marshal.StringToHGlobalAuto(reason); + _reason.LocalizedReasonModule = Marshal.StringToHGlobalUni(reason); } public REASON_CONTEXT(HINSTANCE localizedReasonModule, uint reasonId, string[] substituionValues = null) @@ -2174,7 +2306,7 @@ namespace Vanara.PInvoke _reason.LocalizedReasonModule = (IntPtr)localizedReasonModule; _reason.LocalizedReasonId = reasonId; _reason.ReasonStringCount = (uint)(substituionValues?.Length ?? 0); - _reason.ReasonStrings = substituionValues?.MarshalToPtr(Marshal.AllocHGlobal, out var _) ?? IntPtr.Zero; + _reason.ReasonStrings = substituionValues?.MarshalToPtr(StringListPackMethod.Concatenated, Marshal.AllocHGlobal, out var _, CharSet.Unicode) ?? IntPtr.Zero; } void IDisposable.Dispose() @@ -2197,7 +2329,12 @@ namespace Vanara.PInvoke [StructLayout(LayoutKind.Sequential)] public struct SYNCHRONIZATION_BARRIER { - private readonly IntPtr ptr; + private uint Reserved1; + private uint Reserved2; + private IntPtr Reserved3_1; + private IntPtr Reserved3_2; + private uint Reserved4; + private uint Reserved5; } /// Provides a to an event that is automatically disposed using CloseHandle. diff --git a/UnitTests/PInvoke/Kernel32/Kernel32.csproj b/UnitTests/PInvoke/Kernel32/Kernel32.csproj index b024f9fb..c8bec620 100644 --- a/UnitTests/PInvoke/Kernel32/Kernel32.csproj +++ b/UnitTests/PInvoke/Kernel32/Kernel32.csproj @@ -48,6 +48,7 @@ + diff --git a/UnitTests/PInvoke/Kernel32/SynchApiTests.cs b/UnitTests/PInvoke/Kernel32/SynchApiTests.cs new file mode 100644 index 00000000..78c0867e --- /dev/null +++ b/UnitTests/PInvoke/Kernel32/SynchApiTests.cs @@ -0,0 +1,1001 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using Vanara.Extensions; +using static Vanara.PInvoke.Kernel32; + +namespace Vanara.PInvoke.Tests +{ + [TestFixture] + public class SynchApiTests + { + [Test] + public void ConditionVariableTest() + { + const int BUFFER_SIZE = 10; + const int PRODUCER_SLEEP_TIME_MS = 50; + const int CONSUMER_SLEEP_TIME_MS = 200; + + var Buffer = new int[BUFFER_SIZE]; + int LastItemProduced = 0; + uint QueueSize = 0, QueueStartOffset = 0, TotalItemsProduced = 0, TotalItemsConsumed = 0; + var StopRequested = false; + var rand = new Random(); + + InitializeConditionVariable(out var BufferNotEmpty); + InitializeConditionVariable(out var BufferNotFull); + InitializeCriticalSection(out var BufferLock); + + using (SafeHTHREAD hProducer1 = CreateThread(null, 0, ProducerThreadProc, (IntPtr)1L, 0, out _), + hConsumer1 = CreateThread(null, 0, ConsumerThreadProc, (IntPtr)1L, 0, out _), + hConsumer2 = CreateThread(null, 0, ConsumerThreadProc, (IntPtr)2L, 0, out _)) + { + SleepEx(5000, false); + + EnterCriticalSection(ref BufferLock); + StopRequested = true; + LeaveCriticalSection(ref BufferLock); + + WakeAllConditionVariable(ref BufferNotFull); + WakeAllConditionVariable(ref BufferNotEmpty); + + WaitForMultipleObjects(new[] { hProducer1, hConsumer1, hConsumer2 }, true, INFINITE); + + TestContext.WriteLine("TotalItemsProduced: {0}, TotalItemsConsumed: {1}", TotalItemsProduced, TotalItemsConsumed); + } + + uint ProducerThreadProc(IntPtr p) + { + var ProducerId = p.ToInt64(); + + while (true) + { + // Produce a new item. + Sleep((uint)rand.Next(PRODUCER_SLEEP_TIME_MS)); + + var Item = InterlockedIncrement(ref LastItemProduced); + + EnterCriticalSection(ref BufferLock); + + while (QueueSize == BUFFER_SIZE && !StopRequested) + { + // Buffer is full - sleep so consumers can get items. + SleepConditionVariableCS(ref BufferNotFull, ref BufferLock, INFINITE); + } + + if (StopRequested) + { + LeaveCriticalSection(ref BufferLock); + break; + } + + // Insert the item at the end of the queue and increment size. + Buffer[(QueueStartOffset + QueueSize) % BUFFER_SIZE] = Item; + QueueSize++; + TotalItemsProduced++; + + System.Diagnostics.Debug.Write($"Producer {ProducerId}: item {Item}, queue size {QueueSize}\r\n"); + + LeaveCriticalSection(ref BufferLock); + + // If a consumer is waiting, wake it. + WakeConditionVariable(ref BufferNotEmpty); + } + + System.Diagnostics.Debug.Write($"Producer {ProducerId} exiting\r\n"); + return 0; + } + + uint ConsumerThreadProc(IntPtr p) + { + var ConsumerId = p.ToInt64(); + + while (true) + { + EnterCriticalSection(ref BufferLock); + + while (QueueSize == 0 && !StopRequested) + { + // Buffer is empty - sleep so producers can create items. + SleepConditionVariableCS(ref BufferNotEmpty, ref BufferLock, INFINITE); + } + + if (StopRequested && QueueSize == 0) + { + LeaveCriticalSection(ref BufferLock); + break; + } + + // Consume the first available item. + var Item = Buffer[QueueStartOffset]; + + QueueSize--; + QueueStartOffset++; + TotalItemsConsumed++; + + if (QueueStartOffset == BUFFER_SIZE) + { + QueueStartOffset = 0; + } + + System.Diagnostics.Debug.Write($"Consumer {ConsumerId}: item {Item}, queue size {QueueSize}\r\n"); + + LeaveCriticalSection(ref BufferLock); + + // If a producer is waiting, wake it. + + WakeConditionVariable(ref BufferNotFull); + + // Simulate processing of the item. + + Sleep((uint)rand.Next(CONSUMER_SLEEP_TIME_MS)); + } + + System.Diagnostics.Debug.Write($"Consumer {ConsumerId} exiting\r\n"); + return 0; + } + } + + [Test] + public void ConditionVariableTest2() + { + InitializeSRWLock(out var cond_rwlock); + InitializeConditionVariable(out var start_condition); + var wake_all = false; + var cnt = 0; + + try + { + var ghThreads = new SafeHTHREAD[5]; + for (int i = 0; i < ghThreads.Length; i++) + { + ghThreads[i] = CreateThread(null, 0, ThreadProc, default, 0, out _); + Assert.That(ghThreads[i].IsNull, Is.False); + } + + AcquireSRWLockExclusive(ref cond_rwlock); + // set the flag to true, then wake all threads + wake_all = true; + WakeAllConditionVariable(ref start_condition); + + ReleaseSRWLockExclusive(ref cond_rwlock); + + WaitForMultipleObjects(ghThreads, true, INFINITE); + foreach (var t in ghThreads) t.Dispose(); + + Assert.That(cnt, Is.EqualTo(ghThreads.Length)); + } + finally + { + } + + uint ThreadProc(IntPtr _) + { + AcquireSRWLockShared(ref cond_rwlock); + + // main thread sets wake_all to true and calls WakeAllConditionVariable() + // so this thread should start doing the work (?) + while (!wake_all) + SleepConditionVariableSRW(ref start_condition, ref cond_rwlock, INFINITE, CONDITION_VARIABLE_FLAGS.CONDITION_VARIABLE_LOCKMODE_SHARED); + + InterlockedIncrement(ref cnt); + return 0; + } + } + + [Test] + public void CriticalSectionAndSpinCountTest() + { + Assert.That(InitializeCriticalSectionAndSpinCount(out var critSect, 400), ResultIs.Successful); + try + { + using (var hThread = CreateThread(null, 0, ThreadProc, default, 0, out _)) + { + WaitForSingleObject(hThread, INFINITE); + Assert.That(GetExitCodeThread(hThread, out var c), ResultIs.Successful); + Assert.That(c, Is.Zero); + } + } + finally + { + DeleteCriticalSection(ref critSect); + } + + uint ThreadProc(IntPtr _) + { + if (TryEnterCriticalSection(ref critSect)) + { + Sleep(5); + LeaveCriticalSection(ref critSect); + return 0; + } + return 1; + } + } + + [Test] + public void CriticalSectionExTest() + { + Assert.That(InitializeCriticalSectionEx(out var critSect, 400, CRITICAL_SECTION_FLAGS.CRITICAL_SECTION_FLAG_NO_DEBUG_INFO), ResultIs.Successful); + try + { + using (var hThread = CreateThread(null, 0, ThreadProc, default, 0, out _)) + { + WaitForSingleObject(hThread, INFINITE); + Assert.That(GetExitCodeThread(hThread, out var c), ResultIs.Successful); + Assert.That(c, Is.Zero); + } + } + finally + { + DeleteCriticalSection(ref critSect); + } + + uint ThreadProc(IntPtr _) + { + EnterCriticalSection(ref critSect); + Sleep(5); + LeaveCriticalSection(ref critSect); + return 0; + } + } + + [Test] + public void CriticalSectionTest() + { + InitializeCriticalSection(out var critSect); + SetCriticalSectionSpinCount(ref critSect, 400); + try + { + using (var hThread = CreateThread(null, 0, ThreadProc, default, 0, out _)) + { + WaitForSingleObject(hThread, INFINITE); + Assert.That(GetExitCodeThread(hThread, out var c), ResultIs.Successful); + Assert.That(c, Is.Zero); + } + } + finally + { + DeleteCriticalSection(ref critSect); + } + + uint ThreadProc(IntPtr _) + { + EnterCriticalSection(ref critSect); + Sleep(5); + LeaveCriticalSection(ref critSect); + return 0; + } + } + + [Test] + public void EventExTest() + { + const string name = "myEvent"; + const int THREADCOUNT = 4; + + // Create an unnamed waitable timer. + using (var ghWriteEvent = CreateEventEx(null, name, CREATE_EVENT_FLAGS.CREATE_EVENT_MANUAL_RESET, (uint)SynchronizationObjectAccess.EVENT_ALL_ACCESS)) + { + Assert.That(ghWriteEvent.IsNull, Is.False); + + // Create multiple threads to read from the buffer. + var ghThreads = new SafeHTHREAD[THREADCOUNT]; + for (int i = 0; i < THREADCOUNT; i++) + { + ghThreads[i] = CreateThread(null, 0, ThreadProc, default, 0, out _); + Assert.That(ghThreads[i].IsNull, Is.False); + } + + TestContext.Write("Main thread writing to the shared buffer...\n"); + + // Set ghWriteEvent to signaled + if (!SetEvent(ghWriteEvent)) + { + TestContext.Write("SetEvent failed ({0})\n", GetLastError()); + return; + } + + TestContext.Write("Main thread waiting for threads to exit...\n"); + + // The handle for each thread is signaled when the thread is terminated. + switch (WaitForMultipleObjects(ghThreads, true, INFINITE)) + { + // All thread objects were signaled + case WAIT_STATUS.WAIT_OBJECT_0: + TestContext.Write("All threads ended, cleaning up for application exit...\n"); + break; + + // An error occurred + default: + TestContext.Write("WaitForMultipleObjects failed ({0})\n", GetLastError()); + return; + } + + // Close thread handles + foreach (var t in ghThreads) + t.Dispose(); + } + + uint ThreadProc(IntPtr _) + { + var id = GetCurrentThreadId(); + + TestContext.Write("Thread {0} waiting for write event...\n", id); + + using (var hEvent = OpenEvent((uint)SynchronizationObjectAccess.EVENT_ALL_ACCESS, false, name)) + { + switch (WaitForSingleObject(hEvent, INFINITE)) + { + // Event object was signaled + case WAIT_STATUS.WAIT_OBJECT_0: + TestContext.Write("Thread {0} reading from buffer\n", id); + break; + + // An error occurred + default: + TestContext.Write("Wait error ({0})\n", GetLastError()); + return 0; + } + } + + TestContext.Write("Thread {0} exiting\n", id); + return 1; + } + } + + [Test] + public void EventTest() + { + const string name = "myEvent"; + const int THREADCOUNT = 4; + + // Create an unnamed waitable timer. + SafeEventHandle ghWriteEvent; + using (ghWriteEvent = CreateEvent(null, true, false, name)) + { + Assert.That(ghWriteEvent.IsNull, Is.False); + + // Create multiple threads to read from the buffer. + var ghThreads = new SafeHTHREAD[THREADCOUNT]; + for (int i = 0; i < THREADCOUNT; i++) + { + ghThreads[i] = CreateThread(null, 0, ThreadProc, default, 0, out _); + Assert.That(ghThreads[i].IsNull, Is.False); + } + + TestContext.Write("Main thread writing to the shared buffer...\n"); + + // Set ghWriteEvent to signaled + Assert.That(SetEvent(ghWriteEvent), ResultIs.Successful); + + TestContext.Write("Main thread waiting for threads to exit...\n"); + + // The handle for each thread is signaled when the thread is terminated. + switch (WaitForMultipleObjects(ghThreads, true, INFINITE)) + { + // All thread objects were signaled + case WAIT_STATUS.WAIT_OBJECT_0: + TestContext.Write("All threads ended, cleaning up for application exit...\n"); + break; + + // An error occurred + default: + TestContext.Write("WaitForMultipleObjects failed ({0})\n", GetLastError()); + return; + } + + // Close thread handles + foreach (var t in ghThreads) + t.Dispose(); + + Assert.That(ResetEvent(ghWriteEvent), ResultIs.Successful); + Assert.That(PulseEvent(ghWriteEvent), ResultIs.Successful); + } + + uint ThreadProc(IntPtr _) + { + var id = GetCurrentThreadId(); + + TestContext.Write("Thread {0} waiting for write event...\n", id); + + switch (WaitForSingleObject(ghWriteEvent, INFINITE)) + { + // Event object was signaled + case WAIT_STATUS.WAIT_OBJECT_0: + TestContext.Write("Thread {0} reading from buffer\n", id); + break; + + // An error occurred + default: + TestContext.Write("Wait error ({0})\n", GetLastError()); + return 0; + } + + TestContext.Write("Thread {0} exiting\n", id); + return 1; + } + } + + [Test] + public void MutexExTest() + { + const string name = "myMutex"; + const int THREADCOUNT = 4; + + // Create a mutex with no initial owner + using (var ghMutex = CreateMutexEx(null, name, 0, (uint)SynchronizationObjectAccess.MUTEX_ALL_ACCESS)) + { + Assert.That(ghMutex.IsNull, Is.False); + + // Create worker threads + var ghThreads = new SafeHTHREAD[THREADCOUNT]; + for (int i = 0; i < THREADCOUNT; i++) + { + ghThreads[i] = CreateThread(null, 0, ThreadProc, default, 0, out _); + Assert.That(ghThreads[i].IsNull, Is.False); + } + + // Wait for all threads to terminate using plain handles + var hThreads = Array.ConvertAll(ghThreads, h => (HTHREAD)h); + WaitForMultipleObjectsEx(hThreads, true, INFINITE, false); + + // Close thread and mutex handles + foreach (var t in ghThreads) + { + TestContext.WriteLine($"Thread {GetThreadId(t)} completed with code {(GetExitCodeThread(t, out var c) ? c : 0U)}"); + t.Dispose(); + } + } + + uint ThreadProc(IntPtr _) + { + var id = GetCurrentThreadId(); + + // Request ownership of mutex. + using (var hMut = OpenMutex((uint)SynchronizationObjectAccess.MUTEX_ALL_ACCESS, false, name)) + { + if (hMut.IsNull) return (uint)(int)Win32Error.GetLastError(); + for (int i = 0; i < 20; i++) + { + switch (WaitForSingleObject(hMut, INFINITE)) + { + // The thread got ownership of the mutex + case WAIT_STATUS.WAIT_OBJECT_0: + TestContext.Write("Thread {0} writing to database...\n", id); + if (!ReleaseMutex(hMut)) return (uint)(int)Win32Error.GetLastError(); + break; + + // The thread got ownership of an abandoned mutex The database is in an indeterminate state + case WAIT_STATUS.WAIT_ABANDONED: + return 0; + } + } + } + return 1; + } + } + + [Test] + public void MutexTest() + { + const string name = "myMutex"; + const int THREADCOUNT = 4; + + // Create a mutex with no initial owner + using (var ghMutex = CreateMutex(null, false, name)) + { + Assert.That(ghMutex.IsNull, Is.False); + + // Create worker threads + var ghThreads = new SafeHTHREAD[THREADCOUNT]; + for (int i = 0; i < THREADCOUNT; i++) + { + ghThreads[i] = CreateThread(null, 0, ThreadProc, default, 0, out _); + Assert.That(ghThreads[i].IsNull, Is.False); + } + + // Wait for all threads to terminate + WaitForMultipleObjects(ghThreads, true, INFINITE); + + // Close thread and mutex handles + foreach (var t in ghThreads) + { + TestContext.WriteLine($"Thread {GetThreadId(t)} completed with code {(GetExitCodeThread(t, out var c) ? c : 0U)}"); + t.Dispose(); + } + } + + uint ThreadProc(IntPtr _) + { + var id = GetCurrentThreadId(); + + // Request ownership of mutex. + using (var hMut = OpenMutex((uint)SynchronizationObjectAccess.MUTEX_ALL_ACCESS, false, name)) + { + if (hMut.IsNull) return (uint)(int)Win32Error.GetLastError(); + for (int i = 0; i < 20; i++) + { + switch (WaitForSingleObject(hMut, INFINITE)) + { + // The thread got ownership of the mutex + case WAIT_STATUS.WAIT_OBJECT_0: + TestContext.Write("Thread {0} writing to database...\n", id); + if (!ReleaseMutex(hMut)) return (uint)(int)Win32Error.GetLastError(); + break; + + // The thread got ownership of an abandoned mutex The database is in an indeterminate state + case WAIT_STATUS.WAIT_ABANDONED: + return 0; + } + } + } + return 1; + } + } + + [Test] + public void SemaphoreExTest() + { + const string name = "mySema4"; + const int THREADCOUNT = 12; + + // Create a mutex with no initial owner + using (var ghSemaphore = CreateSemaphoreEx(null, 10, 10, name, 0, (uint)SynchronizationObjectAccess.SEMAPHORE_ALL_ACCESS)) + { + Assert.That(ghSemaphore.IsNull, Is.False); + + // Create worker threads + var ghThreads = new SafeHTHREAD[THREADCOUNT]; + for (int i = 0; i < THREADCOUNT; i++) + { + ghThreads[i] = CreateThread(null, 0, ThreadProc, default, 0, out _); + Assert.That(ghThreads[i].IsNull, Is.False); + } + + // Wait for all threads to terminate + WaitForMultipleObjects(ghThreads, true, INFINITE); + + // Close thread and mutex handles + foreach (var t in ghThreads) + { + TestContext.WriteLine($"Thread {GetThreadId(t)} completed with code {(GetExitCodeThread(t, out var c) ? c : 0U)}"); + t.Dispose(); + } + } + + uint ThreadProc(IntPtr _) + { + var id = GetCurrentThreadId(); + + using (var hSem = OpenSemaphore((uint)SynchronizationObjectAccess.SEMAPHORE_ALL_ACCESS, false, name)) + { + if (hSem.IsNull) return (uint)(int)Win32Error.GetLastError(); + var loop = true; + while (loop) + { + // Try to enter the semaphore gate. + switch (WaitForSingleObject(hSem, 0)) + { + // The semaphore object was signaled. + case WAIT_STATUS.WAIT_OBJECT_0: + TestContext.Write("Thread {0} wait succeeded.\n", id); + loop = false; + // Simulate thread spending time on task + Sleep(5); + // Release the semaphore when task is finished + if (!ReleaseSemaphore(hSem, 1, out var _)) return (uint)(int)Win32Error.GetLastError(); + break; + + // The semaphore was nonsignaled, so a time-out occurred. + case WAIT_STATUS.WAIT_TIMEOUT: + break; + } + } + } + return 1; + } + } + + [Test] + public void SemaphoreTest() + { + const string name = "mySema4"; + const int THREADCOUNT = 12; + + // Create a mutex with no initial owner + using (var ghSemaphore = CreateSemaphore(null, 10, 10, name)) + { + Assert.That(ghSemaphore.IsNull, Is.False); + + // Create worker threads + var ghThreads = new SafeHTHREAD[THREADCOUNT]; + for (int i = 0; i < THREADCOUNT; i++) + { + ghThreads[i] = CreateThread(null, 0, ThreadProc, default, 0, out _); + Assert.That(ghThreads[i].IsNull, Is.False); + } + + // Wait for all threads to terminate + WaitForMultipleObjects(ghThreads, true, INFINITE); + + // Close thread and mutex handles + foreach (var t in ghThreads) + { + TestContext.WriteLine($"Thread {GetThreadId(t)} completed with code {(GetExitCodeThread(t, out var c) ? c : 0U)}"); + t.Dispose(); + } + } + + uint ThreadProc(IntPtr _) + { + var id = GetCurrentThreadId(); + + using (var hSem = OpenSemaphore((uint)SynchronizationObjectAccess.SEMAPHORE_ALL_ACCESS, false, name)) + { + if (hSem.IsNull) return (uint)(int)Win32Error.GetLastError(); + var loop = true; + while (loop) + { + // Try to enter the semaphore gate. + switch (WaitForSingleObject(hSem, 0)) + { + // The semaphore object was signaled. + case WAIT_STATUS.WAIT_OBJECT_0: + TestContext.Write("Thread {0} wait succeeded.\n", id); + loop = false; + // Simulate thread spending time on task + Sleep(5); + // Release the semaphore when task is finished + if (!ReleaseSemaphore(hSem, 1, out var _)) return (uint)(int)Win32Error.GetLastError(); + break; + + // The semaphore was nonsignaled, so a time-out occurred. + case WAIT_STATUS.WAIT_TIMEOUT: + break; + } + } + } + return 1; + } + } + + [Test] + public void SRWLockTest() + { + InitializeSRWLock(out var srwlock); + + Assert.That(TryAcquireSRWLockExclusive(ref srwlock), Is.True); + ReleaseSRWLockExclusive(ref srwlock); + + AcquireSRWLockExclusive(ref srwlock); + ReleaseSRWLockExclusive(ref srwlock); + + Assert.That(TryAcquireSRWLockShared(ref srwlock), Is.True); + ReleaseSRWLockShared(ref srwlock); + + AcquireSRWLockShared(ref srwlock); + ReleaseSRWLockShared(ref srwlock); + } + + [Test] + public void SyncBarrierTest() + { + const int MAX_SLEEP_MS = 32; + var dwMinThreads = Environment.ProcessorCount; + var dwMaxThreads = dwMinThreads * 4; + var dwNumLoops = 50U; + SYNCHRONIZATION_BARRIER gBarrier; + var gErrorCount = 0; + SafeEventHandle gStartEvent; + (int threadCount, int trueCount, int falseCount, uint loops, SYNC_BARRIER_FLAGS flags) p; + var rand = new Random(); + + /* Test invalid parameters */ + Assert.That(InitializeSynchronizationBarrier(out _, 0, -1), ResultIs.Failure); + Assert.That(InitializeSynchronizationBarrier(out _, -1, -1), ResultIs.Failure); + Assert.That(InitializeSynchronizationBarrier(out _, 1, -2), ResultIs.Failure); + + /* Functional tests */ + TestSynchBarrierWithFlags(0, dwMaxThreads, dwNumLoops); + TestSynchBarrierWithFlags(SYNC_BARRIER_FLAGS.SYNCHRONIZATION_BARRIER_FLAGS_SPIN_ONLY, dwMinThreads, dwNumLoops); + TestSynchBarrierWithFlags(SYNC_BARRIER_FLAGS.SYNCHRONIZATION_BARRIER_FLAGS_BLOCK_ONLY, dwMaxThreads, dwNumLoops); + + void TestSynchBarrierWithFlags(SYNC_BARRIER_FLAGS dwFlags, int dwThreads, uint dwLoops) + { + p = (0, 0, 0, dwLoops, dwFlags); + + Assert.That(InitializeSynchronizationBarrier(out gBarrier, dwThreads, -1), ResultIs.Successful); + try + { + using (gStartEvent = CreateEvent(null, true, false, null)) + { + Assert.That(gStartEvent.IsNull, Is.False); + + var threads = new SafeHTHREAD[dwThreads]; + int i; + for (i = 0; i < dwThreads; i++) + { + threads[i] = CreateThread(null, 0, test_synch_barrier_thread, default, 0, out _); + Assert.That(threads[i].IsNull, Is.False); + } + + if (!SetEvent(gStartEvent)) + { + TestContext.WriteLine($"SetEvent(gStartEvent) failed with error = {Win32Error.GetLastError()}"); + InterlockedIncrement(ref gErrorCount); + } + + while (i-- > 0) + { + WAIT_STATUS dwStatus; + if (WAIT_STATUS.WAIT_OBJECT_0 != (dwStatus = WaitForSingleObject(threads[i], INFINITE))) + { + TestContext.WriteLine($"WaitForSingleObject(thread[{i}] unexpectedly returned {dwStatus} (error = {Win32Error.GetLastError()})"); + InterlockedIncrement(ref gErrorCount); + } + + threads[i].Dispose(); + } + } + } + finally + { + DeleteSynchronizationBarrier(ref gBarrier); + } + + Assert.That(p.threadCount, Is.EqualTo(dwThreads)); + Assert.That(p.trueCount, Is.EqualTo(dwLoops)); + Assert.That(p.falseCount, Is.EqualTo(dwLoops * (dwThreads - 1))); + } + + uint test_synch_barrier_thread(IntPtr _) + { + InterlockedIncrement(ref p.threadCount); + + /* wait for start event from main */ + if (WaitForSingleObject(gStartEvent, INFINITE) != WAIT_STATUS.WAIT_OBJECT_0) + { + InterlockedIncrement(ref gErrorCount); + return 1; + } + + for (var i = 0; i < p.loops && gErrorCount == 0; i++) + { + /* simulate different execution times before the barrier */ + Sleep((uint)rand.Next(MAX_SLEEP_MS)); + + if (EnterSynchronizationBarrier(ref gBarrier, p.flags)) + InterlockedIncrement(ref p.trueCount); + else + InterlockedIncrement(ref p.falseCount); + } + + return 0; + } + } + + [Test] + public void TimerQueueTest() + { + SafeEventHandle gDoneEvent; + bool? timerOrWaitFired = null; + + // Use an event object to track the TimerRoutine execution + using (gDoneEvent = CreateEvent(null, true, false, null)) + { + Assert.That(gDoneEvent.IsNull, Is.False); + + // Create the timer queue. + using (var hTimerQueue = CreateTimerQueue()) + { + Assert.That(hTimerQueue.IsNull, Is.False); + + // Set a timer to call the timer routine in 4 seconds. + Assert.That(CreateTimerQueueTimer(out var hTimer, hTimerQueue, TimerRoutine, default, 4000, 0, 0), ResultIs.Successful); + + // TODO: Do other useful work here + + // Wait for the timer-queue thread to complete using an event object. The thread will signal the event at that time. + Assert.That(WaitForSingleObject(gDoneEvent, INFINITE), Is.EqualTo(WAIT_STATUS.WAIT_OBJECT_0)); + + Assert.That(timerOrWaitFired.HasValue, Is.True); + Assert.That(timerOrWaitFired.Value, Is.True); + } + } + + void TimerRoutine(IntPtr lpParameter, bool TimerOrWaitFired) + { + timerOrWaitFired = TimerOrWaitFired; + SetEvent(gDoneEvent); + } + } + + [Test] + public void WaitableTimerExTest() + { + // Create an unnamed waitable timer. + using (var hTimer = CreateWaitableTimerEx(null, null, CREATE_WAITABLE_TIMER_FLAG.CREATE_WAITABLE_TIMER_MANUAL_RESET, (uint)SynchronizationObjectAccess.TIMER_ALL_ACCESS)) + { + Assert.That(hTimer.IsNull, Is.False); + + // Set a timer to wait for 2 seconds. + var liDueTime = TimeSpan.FromSeconds(2).ToFileTimeStruct(); + Assert.That(SetWaitableTimerEx(hTimer, liDueTime, 0, null, default, new REASON_CONTEXT("Because"), 50), ResultIs.Successful); + + // Wait for the timer. + Assert.That(WaitForSingleObject(hTimer, INFINITE), Is.EqualTo(WAIT_STATUS.WAIT_OBJECT_0)); + } + } + + [Test] + public void WaitableTimerTest() + { + const string tName = "myTimer"; + + // Create an unnamed waitable timer. + using (var hTimer = CreateWaitableTimer(null, true, tName)) + { + Assert.That(hTimer.IsNull, Is.False); + + new System.Threading.Thread(ThreadProc).Start(); + + // Set a timer to wait for 2 seconds. + var liDueTime = TimeSpan.FromSeconds(2).ToFileTimeStruct(); + Assert.That(SetWaitableTimer(hTimer, liDueTime, 0, null, default, false), ResultIs.Successful); + + // Wait for the timer. + Assert.That(WaitForSingleObject(hTimer, INFINITE), Is.EqualTo(WAIT_STATUS.WAIT_OBJECT_0)); + + // Set a timer to wait for 2 seconds. + Assert.That(SetWaitableTimer(hTimer, liDueTime), ResultIs.Successful); + Assert.That(CancelWaitableTimer(hTimer), ResultIs.Successful); + } + + void ThreadProc() + { + using (var hThrTimer = OpenWaitableTimer((uint)SynchronizationObjectAccess.TIMER_ALL_ACCESS, false, tName)) + { + Assert.That(hThrTimer.IsNull, Is.False); + } + } + } + + [Test] + public void InitOnceSyncTest() + { + const int ctxVal = 1 << INIT_ONCE_CTX_RESERVED_BITS; + SafeEventHandle gStartEvent; + INIT_ONCE gInitOnce = default; + var initCount = 0; + + using (gStartEvent = CreateEvent(null, true, false, null)) + { + Assert.That(gStartEvent.IsNull, Is.False); + + var threads = new SafeHTHREAD[10]; + for (var i = 0; i < threads.Length; i++) + { + threads[i] = CreateThread(null, 0, ThreadProc, default, 0, out _); + Assert.That(threads[i].IsNull, Is.False); + } + + Assert.That(SetEvent(gStartEvent), ResultIs.Successful); + + WaitForMultipleObjects(threads, true, INFINITE); + + Assert.That(threads.Select(t => GetExitCodeThread(t, out var c) ? c : 0), Has.All.EqualTo(ctxVal)); + Assert.That(initCount, Is.EqualTo(1)); + + foreach (var t in threads) t.Dispose(); + } + + uint ThreadProc(IntPtr _) + { + if (WaitForSingleObject(gStartEvent, INFINITE) != WAIT_STATUS.WAIT_OBJECT_0) + return 0; + InitOnceExecuteOnce(ref gInitOnce, InitFunc, default, out var ctx); + return (uint)ctx.ToInt32(); + } + + bool InitFunc(ref INIT_ONCE InitOnce, IntPtr Parameter, out IntPtr Context) + { + //Sleep(500); + Context = (IntPtr)ctxVal; + InterlockedIncrement(ref initCount); + return true; + } + } + + [Test] + public void InitOnceSyncNoCallbackTest() + { + const int ctxVal = 1 << INIT_ONCE_CTX_RESERVED_BITS; + SafeEventHandle gStartEvent; + + InitOnceInitialize(out var gInitOnce); + using (gStartEvent = CreateEvent(null, true, false, null)) + { + Assert.That(gStartEvent.IsNull, Is.False); + + var threads = new SafeHTHREAD[10]; + for (var i = 0; i < threads.Length; i++) + { + threads[i] = CreateThread(null, 0, ThreadProc, default, 0, out _); + Assert.That(threads[i].IsNull, Is.False); + } + + Assert.That(SignalObjectAndWait(gStartEvent, threads[0], INFINITE, false), ResultIs.Value(WAIT_STATUS.WAIT_OBJECT_0)); + + WaitForMultipleObjects(threads, true, INFINITE); + + Assert.That(threads.Select(t => GetExitCodeThread(t, out var c) ? c : 0), Has.All.EqualTo(ctxVal)); + + foreach (var t in threads) t.Dispose(); + } + + uint ThreadProc(IntPtr _) + { + // Start all threads at the same time + if (WaitForSingleObject(gStartEvent, INFINITE) != WAIT_STATUS.WAIT_OBJECT_0) + return 0; + InitOnceBeginInitialize(ref gInitOnce, 0, out var pending, out var ctx); + if (pending) + { + var newctx = (IntPtr)ctxVal; + InitOnceComplete(ref gInitOnce, 0, newctx); + return (uint)newctx.ToInt32(); + } + return (uint)ctx.ToInt32(); + } + } + + [Test] + public void InterlockedCompareExchangeTest() + { + const int dest = 128; + var destVar = dest; + Assert.That(InterlockedCompareExchange(ref destVar, 64, dest), Is.EqualTo(dest)); + Assert.That(destVar, Is.EqualTo(64)); + } + + [Test] + public unsafe void WakeByWaitOnAddressTest() + { + uint g_ulState = 0; + var cnt = 0; + + var threads = new SafeHTHREAD[10]; + for (var i = 0; i < threads.Length; i++) + { + threads[i] = CreateThread(null, 0, ThreadProc, &g_ulState, 0, out _); + Assert.That(threads[i].IsNull, Is.False); + } + + for (var i = threads.Length / 2; i >= 0; i--) + { + g_ulState = (uint)i; + if (g_ulState == 0) + { + WakeByAddressAll(&g_ulState); + break; + } + WakeByAddressSingle(&g_ulState); + } + WaitForMultipleObjects(threads, true, INFINITE); + + foreach (var t in threads) t.Dispose(); + + Assert.That(cnt, Is.EqualTo(threads.Length)); + + unsafe uint ThreadProc(void* pState) + { + uint ulUndesire = 0; + WaitOnAddress(pState, &ulUndesire, sizeof(uint), INFINITE); + InterlockedIncrement(ref cnt); + return 0; + } + } + } +} \ No newline at end of file