From 37c943fe4a825d9882e1faa2e217f8494a4bcdee Mon Sep 17 00:00:00 2001 From: David Hall Date: Wed, 17 Jul 2019 15:27:05 -0600 Subject: [PATCH] Completed unit tests and fixes for threadpoolapiset.h --- PInvoke/Kernel32/ThreadPoolApiSet.cs | 756 +++++++++++++++++++-- UnitTests/PInvoke/Kernel32/Kernel32.csproj | 1 + .../PInvoke/Kernel32/ThreadPoolApiSetTests.cs | 152 +++++ 3 files changed, 838 insertions(+), 71 deletions(-) create mode 100644 UnitTests/PInvoke/Kernel32/ThreadPoolApiSetTests.cs diff --git a/PInvoke/Kernel32/ThreadPoolApiSet.cs b/PInvoke/Kernel32/ThreadPoolApiSet.cs index 0b13f146..821cc1ec 100644 --- a/PInvoke/Kernel32/ThreadPoolApiSet.cs +++ b/PInvoke/Kernel32/ThreadPoolApiSet.cs @@ -13,40 +13,7 @@ namespace Vanara.PInvoke /// Optional application-defined data specified during creation of the object. /// Optional application-defined data specified using CloseThreadpoolCleanupGroupMembers. [UnmanagedFunctionPointer(CallingConvention.Winapi)] - public delegate void PTP_CLEANUP_GROUP_CANCEL_CALLBACK(IntPtr ObjectContext, IntPtr CleanupContext); - - /// Applications implement this callback if they call the TrySubmitThreadpoolCallback function to start a worker thread. - /// - /// A TP_CALLBACK_INSTANCE structure that defines the callback instance. Applications do not modify the members of this structure. - /// - /// The application-defined data. - [UnmanagedFunctionPointer(CallingConvention.Winapi)] - public delegate void PTP_SIMPLE_CALLBACK(PTP_CALLBACK_INSTANCE Instance, IntPtr Context); - - /// - /// Applications implement this callback if they call the SetThreadpoolTimer function to start a worker thread for the timer object. - /// - /// - /// A TP_CALLBACK_INSTANCE structure that defines the callback instance. Applications do not modify the members of this structure. - /// - /// The application-defined data. - /// A TP_TIMER structure that defines the timer object that generated the callback. - [UnmanagedFunctionPointer(CallingConvention.Winapi)] - public delegate void PTP_TIMER_CALLBACK(PTP_CALLBACK_INSTANCE Instance, IntPtr Context, PTP_TIMER Timer); - - /// - /// Applications implement this callback if they call the SetThreadpoolWait function to start a worker thread for the wait object. - /// - /// - /// A TP_CALLBACK_INSTANCE structure that defines the callback instance. Applications do not modify the members of this structure. - /// - /// The application-defined data. - /// A TP_WAIT structure that defines the wait object that generated the callback. - /// - /// The result of the wait operation. This parameter can be one of the following values from WaitForMultipleObjects: WAIT_OBJECT_0, WAIT_TIMEOUT - /// - [UnmanagedFunctionPointer(CallingConvention.Winapi)] - public delegate void PTP_WAIT_CALLBACK(PTP_CALLBACK_INSTANCE Instance, IntPtr Context, PTP_WAIT Wait, uint WaitResult); + public delegate void CleanupGroupCancelCallback(IntPtr ObjectContext, IntPtr CleanupContext); /// /// Applications implement this callback if they call the StartThreadpoolIo function to start a worker thread for the I/O completion object. @@ -66,9 +33,42 @@ namespace Vanara.PInvoke /// The number of bytes transferred during the I/O operation that has completed. /// A TP_IO structure that defines the I/O completion object that generated the callback. [UnmanagedFunctionPointer(CallingConvention.Winapi)] - public delegate void PTP_WIN32_IO_CALLBACK(PTP_CALLBACK_INSTANCE Instance, IntPtr Context, IntPtr Overlapped, uint IoResult, + public delegate void IoCompletionCallback(PTP_CALLBACK_INSTANCE Instance, IntPtr Context, IntPtr Overlapped, uint IoResult, UIntPtr NumberOfBytesTransferred, PTP_IO Io); + /// Applications implement this callback if they call the TrySubmitThreadpoolCallback function to start a worker thread. + /// + /// A TP_CALLBACK_INSTANCE structure that defines the callback instance. Applications do not modify the members of this structure. + /// + /// The application-defined data. + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + public delegate void SimpleCallback(PTP_CALLBACK_INSTANCE Instance, IntPtr Context); + + /// + /// Applications implement this callback if they call the SetThreadpoolTimer function to start a worker thread for the timer object. + /// + /// + /// A TP_CALLBACK_INSTANCE structure that defines the callback instance. Applications do not modify the members of this structure. + /// + /// The application-defined data. + /// A TP_TIMER structure that defines the timer object that generated the callback. + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + public delegate void TimerCallback(PTP_CALLBACK_INSTANCE Instance, IntPtr Context, PTP_TIMER Timer); + + /// + /// Applications implement this callback if they call the SetThreadpoolWait function to start a worker thread for the wait object. + /// + /// + /// A TP_CALLBACK_INSTANCE structure that defines the callback instance. Applications do not modify the members of this structure. + /// + /// The application-defined data. + /// A TP_WAIT structure that defines the wait object that generated the callback. + /// + /// The result of the wait operation. This parameter can be one of the following values from WaitForMultipleObjects: WAIT_OBJECT_0, WAIT_TIMEOUT + /// + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + public delegate void WaitCallback(PTP_CALLBACK_INSTANCE Instance, IntPtr Context, PTP_WAIT Wait, uint WaitResult); + /// /// Applications implement this callback if they call the SubmitThreadpoolWork function to start a worker thread for the work object. /// @@ -78,7 +78,7 @@ namespace Vanara.PInvoke /// The application-defined data. /// A TP_WORK structure that defines the work object that generated the callback. [UnmanagedFunctionPointer(CallingConvention.Winapi)] - public delegate void PTP_WORK_CALLBACK(PTP_CALLBACK_INSTANCE Instance, IntPtr Context, PTP_WORK Work); + public delegate void WorkCallback(PTP_CALLBACK_INSTANCE Instance, IntPtr Context, PTP_WORK Work); [Flags] public enum TP_CALLBACK_ENV_FLAGS @@ -175,7 +175,7 @@ namespace Vanara.PInvoke // PVOID pvCleanupContext); https://msdn.microsoft.com/en-us/library/windows/desktop/ms682036(v=vs.85).aspx [DllImport(Lib.Kernel32, SetLastError = false, ExactSpelling = true)] [PInvokeData("WinBase.h", MSDNShortId = "ms682036")] - public static extern void CloseThreadpoolCleanupGroupMembers(PTP_CLEANUP_GROUP ptpcg, [MarshalAs(UnmanagedType.Bool)] bool fCancelPendingCallbacks, IntPtr pvCleanupContext); + public static extern void CloseThreadpoolCleanupGroupMembers(PTP_CLEANUP_GROUP ptpcg, [MarshalAs(UnmanagedType.Bool)] bool fCancelPendingCallbacks, [Optional] IntPtr pvCleanupContext); /// Releases the specified I/O completion object. /// @@ -224,12 +224,28 @@ namespace Vanara.PInvoke /// If the function succeeds, it returns a TP_POOL structure representing the newly allocated thread pool. Applications do not /// modify the members of this structure. /// - /// If function fails, it returns NULL. To retrieve extended error information, call GetLastError. + /// If function fails, it returns NULL. To retrieve extended error information, call GetLastError. /// - // PTP_POOL WINAPI CreateThreadpool( _Reserved_ PVOID reserved); https://msdn.microsoft.com/en-us/library/windows/desktop/ms682456(v=vs.85).aspx + /// + /// + /// After creating the new thread pool, you should call SetThreadpoolThreadMaximum to specify the maximum number of threads that the + /// pool can allocate and SetThreadpoolThreadMinimum to specify the minimum number of threads available in the pool. + /// + /// + /// To use the pool, you must associate the pool with a callback environment. To create the callback environment, call + /// InitializeThreadpoolEnvironment. Then, call SetThreadpoolCallbackPool to associate the pool with the callback environment. + /// + /// To release the thread pool, call CloseThreadpool. + /// To compile an application that uses this function, define _WIN32_WINNT as 0x0600 or higher. + /// Examples + /// For an example, see Using the Thread Pool Functions. + /// + // https://docs.microsoft.com/en-us/windows/win32/api/threadpoolapiset/nf-threadpoolapiset-createthreadpool PTP_POOL + // CreateThreadpool( PVOID reserved ); [DllImport(Lib.Kernel32, SetLastError = true, ExactSpelling = true)] - [PInvokeData("WinBase.h", MSDNShortId = "ms682456")] - public static extern PTP_POOL CreateThreadpool(IntPtr reserved = default); + [PInvokeData("threadpoolapiset.h", MSDNShortId = "cc00d7bf-ac52-44ff-a6a8-76c8eaace5e6")] + // public static extern PTP_POOL CreateThreadpool(IntPtr reserved); + public static extern SafePTP_POOL CreateThreadpool(IntPtr reserved = default); /// Creates a cleanup group that applications can use to track one or more thread pool callbacks. /// @@ -242,7 +258,7 @@ namespace Vanara.PInvoke // PTP_CLEANUP_GROUP WINAPI CreateThreadpoolCleanupGroup(void); https://msdn.microsoft.com/en-us/library/windows/desktop/ms682462(v=vs.85).aspx [DllImport(Lib.Kernel32, SetLastError = true, ExactSpelling = true)] [PInvokeData("WinBase.h", MSDNShortId = "ms682462")] - public static extern PTP_CLEANUP_GROUP CreateThreadpoolCleanupGroup(); + public static extern SafePTP_CLEANUP_GROUP CreateThreadpoolCleanupGroup(); /// Creates a new I/O completion object. /// The file handle to bind to this I/O completion object. @@ -264,11 +280,11 @@ namespace Vanara.PInvoke /// /// If the function fails, it returns NULL. To retrieve extended error information, call GetLastError. /// - // PTP_IO WINAPI CreateThreadpoolIo( _In_ HANDLE fl, _In_ PTP_WIN32_IO_CALLBACK pfnio, _Inout_opt_ PVOID pv, _In_opt_ + // PTP_IO WINAPI CreateThreadpoolIo( _In_ HANDLE fl, _In_ IoCompletionCallback pfnio, _Inout_opt_ PVOID pv, _In_opt_ // PTP_CALLBACK_ENVIRON pcbe); https://msdn.microsoft.com/en-us/library/windows/desktop/ms682464(v=vs.85).aspx [DllImport(Lib.Kernel32, SetLastError = true, ExactSpelling = true)] [PInvokeData("WinBase.h", MSDNShortId = "ms682464")] - public static extern PTP_IO CreateThreadpoolIo(HFILE fl, PTP_WIN32_IO_CALLBACK pfnio, IntPtr pv, PTP_CALLBACK_ENVIRON pcbe); + public static extern SafePTP_IO CreateThreadpoolIo(HFILE fl, IoCompletionCallback pfnio, IntPtr pv, PTP_CALLBACK_ENVIRON pcbe); /// Creates a new timer object. /// The callback function to call each time the timer object expires. For details, see TimerCallback. @@ -287,10 +303,10 @@ namespace Vanara.PInvoke /// /// If the function fails, it returns NULL. To retrieve extended error information, call GetLastError. /// - // PTP_TIMER WINAPI CreateThreadpoolTimer( _In_ PTP_TIMER_CALLBACK pfnti, _Inout_opt_ PVOID pv, _In_opt_ PTP_CALLBACK_ENVIRON pcbe); https://msdn.microsoft.com/en-us/library/windows/desktop/ms682466(v=vs.85).aspx + // PTP_TIMER WINAPI CreateThreadpoolTimer( _In_ TimerCallback pfnti, _Inout_opt_ PVOID pv, _In_opt_ PTP_CALLBACK_ENVIRON pcbe); https://msdn.microsoft.com/en-us/library/windows/desktop/ms682466(v=vs.85).aspx [DllImport(Lib.Kernel32, SetLastError = true, ExactSpelling = true)] [PInvokeData("WinBase.h", MSDNShortId = "ms682466")] - public static extern PTP_TIMER CreateThreadpoolTimer(PTP_TIMER_CALLBACK pfnti, IntPtr pv, PTP_CALLBACK_ENVIRON pcbe); + public static extern SafePTP_TIMER CreateThreadpoolTimer(TimerCallback pfnti, [Optional] IntPtr pv, [Optional] PTP_CALLBACK_ENVIRON pcbe); /// Creates a new wait object. /// The callback function to call when the wait completes or times out. For details, see WaitCallback. @@ -309,10 +325,10 @@ namespace Vanara.PInvoke /// /// If the function fails, it returns NULL. To retrieve extended error information, call GetLastError. /// - // PTP_WAIT WINAPI CreateThreadpoolWait( _In_ PTP_WAIT_CALLBACK pfnwa, _Inout_opt_ PVOID pv, _In_opt_ PTP_CALLBACK_ENVIRON pcbe); https://msdn.microsoft.com/en-us/library/windows/desktop/ms682474(v=vs.85).aspx + // PTP_WAIT WINAPI CreateThreadpoolWait( _In_ WaitCallback pfnwa, _Inout_opt_ PVOID pv, _In_opt_ PTP_CALLBACK_ENVIRON pcbe); https://msdn.microsoft.com/en-us/library/windows/desktop/ms682474(v=vs.85).aspx [DllImport(Lib.Kernel32, SetLastError = true, ExactSpelling = true)] [PInvokeData("WinBase.h", MSDNShortId = "ms682474")] - public static extern PTP_WAIT CreateThreadpoolWait(PTP_WAIT_CALLBACK pfnwa, IntPtr pv, PTP_CALLBACK_ENVIRON pcbe); + public static extern SafePTP_WAIT CreateThreadpoolWait(WaitCallback pfnwa, [Optional] IntPtr pv, [Optional] PTP_CALLBACK_ENVIRON pcbe); /// Creates a new work object. /// @@ -334,10 +350,10 @@ namespace Vanara.PInvoke /// /// If the function fails, it returns NULL. To retrieve extended error information, call GetLastError. /// - // PTP_WORK WINAPI CreateThreadpoolWork( _In_ PTP_WORK_CALLBACK pfnwk, _Inout_opt_ PVOID pv, _In_opt_ PTP_CALLBACK_ENVIRON pcbe); https://msdn.microsoft.com/en-us/library/windows/desktop/ms682478(v=vs.85).aspx + // PTP_WORK WINAPI CreateThreadpoolWork( _In_ WorkCallback pfnwk, _Inout_opt_ PVOID pv, _In_opt_ PTP_CALLBACK_ENVIRON pcbe); https://msdn.microsoft.com/en-us/library/windows/desktop/ms682478(v=vs.85).aspx [DllImport(Lib.Kernel32, SetLastError = true, ExactSpelling = true)] [PInvokeData("WinBase.h", MSDNShortId = "ms682478")] - public static extern PTP_WORK CreateThreadpoolWork(PTP_WORK_CALLBACK pfnwk, IntPtr pv, PTP_CALLBACK_ENVIRON pcbe); + public static extern SafePTP_WORK CreateThreadpoolWork(WorkCallback pfnwk, [Optional] IntPtr pv, [Optional] PTP_CALLBACK_ENVIRON pcbe); /// /// Removes the association between the currently executing callback function and the object that initiated the callback. The current @@ -457,7 +473,7 @@ namespace Vanara.PInvoke // VOID SetThreadpoolCallbackCleanupGroup( _Inout_ PTP_CALLBACK_ENVIRON pcbe, _In_ PTP_CLEANUP_GROUP ptpcg, _In_opt_ // PTP_CLEANUP_GROUP_CANCEL_CALLBACK pfng); https://msdn.microsoft.com/en-us/library/windows/desktop/ms686255(v=vs.85).aspx [PInvokeData("WinBase.h", MSDNShortId = "ms686255")] - public static void SetThreadpoolCallbackCleanupGroup(this PTP_CALLBACK_ENVIRON pcbe, PTP_CLEANUP_GROUP ptpcg, PTP_CLEANUP_GROUP_CANCEL_CALLBACK pfng) + public static void SetThreadpoolCallbackCleanupGroup(this PTP_CALLBACK_ENVIRON pcbe, PTP_CLEANUP_GROUP ptpcg, [Optional] CleanupGroupCancelCallback pfng) { pcbe.CleanupGroup = ptpcg; pcbe.CleanupGroupCancelCallback = pfng; @@ -621,7 +637,7 @@ namespace Vanara.PInvoke // msWindowLength); https://msdn.microsoft.com/en-us/library/windows/desktop/ms686271(v=vs.85).aspx [DllImport(Lib.Kernel32, SetLastError = false, ExactSpelling = true)] [PInvokeData("WinBase.h", MSDNShortId = "ms686271")] - public static extern void SetThreadpoolTimer(PTP_TIMER pti, in FILETIME pftDueTime, uint msPeriod, uint msWindowLength); + public static extern void SetThreadpoolTimer(PTP_TIMER pti, in FILETIME pftDueTime, [Optional] uint msPeriod, [Optional] uint msWindowLength); /// /// Sets the timer object—, replacing the previous timer, if any. A worker thread calls the timer object's callback after the @@ -659,7 +675,7 @@ namespace Vanara.PInvoke [DllImport(Lib.Kernel32, SetLastError = false, ExactSpelling = true)] [PInvokeData("Threadpoolapiset.h", MSDNShortId = "dn894018")] [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool SetThreadpoolTimerEx(PTP_TIMER pti, in FILETIME pftDueTime, uint msPeriod, uint msWindowLength); + public static extern bool SetThreadpoolTimerEx(PTP_TIMER pti, in FILETIME pftDueTime, [Optional] uint msPeriod, [Optional] uint msWindowLength); /// /// Sets the wait object—replacing the previous wait object, if any. A worker thread calls the wait object's callback function after @@ -694,6 +710,72 @@ namespace Vanara.PInvoke [PInvokeData("WinBase.h", MSDNShortId = "ms686273")] public static extern void SetThreadpoolWait(PTP_WAIT pwa, SafeEventHandle h, in FILETIME pftTimeout); + /// + /// Sets the wait object—replacing the previous wait object, if any. A worker thread calls the wait object's callback function after + /// the handle becomes signaled or after the specified timeout expires. + /// + /// + /// A pointer to a TP_WAIT structure that defines the wait object. The CreateThreadpoolWait function returns this structure. + /// + /// + /// A handle. + /// + /// If this parameter is NULL, the wait object will cease to queue new callbacks (but callbacks already queued will still occur). + /// + /// If this parameter is not NULL, it must refer to a valid waitable object. + /// + /// If this handle is closed while the wait is still pending, the function's behavior is undefined. If the wait is still pending and + /// the handle must be closed, use CloseThreadpoolWait to cancel the wait and then close the handle. + /// + /// + /// + /// + /// A pointer to a FILETIME structure that specifies the absolute or relative time at which the wait operation should time + /// out. If this parameter points to a positive value, it indicates the absolute time since January 1, 1601 (UTC), in 100-nanosecond + /// intervals. If this parameter points to a negative value, it indicates the amount of time to wait relative to the current time. + /// For more information about time values, see File Times. + /// + /// If this parameter points to 0, the wait times out immediately. If this parameter is NULL, the wait will not time out. + /// + /// This function does not return a value. + // VOID WINAPI SetThreadpoolWait( _Inout_ PTP_WAIT pwa, _In_opt_ HANDLE h, _In_opt_ PFILETIME pftTimeout); https://msdn.microsoft.com/en-us/library/windows/desktop/ms686273(v=vs.85).aspx + [DllImport(Lib.Kernel32, SetLastError = false, ExactSpelling = true)] + [PInvokeData("WinBase.h", MSDNShortId = "ms686273")] + public static extern void SetThreadpoolWait(PTP_WAIT pwa, SafeEventHandle h, [Optional] IntPtr pftTimeout); + + /// + /// Sets the wait object—replacing the previous wait object, if any. A worker thread calls the wait object's callback function after + /// the handle becomes signaled or after the specified timeout expires. + /// + /// + /// A pointer to a TP_WAIT structure that defines the wait object. The CreateThreadpoolWait function returns this structure. + /// + /// + /// A handle. + /// + /// If this parameter is NULL, the wait object will cease to queue new callbacks (but callbacks already queued will still occur). + /// + /// If this parameter is not NULL, it must refer to a valid waitable object. + /// + /// If this handle is closed while the wait is still pending, the function's behavior is undefined. If the wait is still pending and + /// the handle must be closed, use CloseThreadpoolWait to cancel the wait and then close the handle. + /// + /// + /// + /// + /// A pointer to a FILETIME structure that specifies the absolute or relative time at which the wait operation should time + /// out. If this parameter points to a positive value, it indicates the absolute time since January 1, 1601 (UTC), in 100-nanosecond + /// intervals. If this parameter points to a negative value, it indicates the amount of time to wait relative to the current time. + /// For more information about time values, see File Times. + /// + /// If this parameter points to 0, the wait times out immediately. If this parameter is NULL, the wait will not time out. + /// + /// This function does not return a value. + // VOID WINAPI SetThreadpoolWait( _Inout_ PTP_WAIT pwa, _In_opt_ HANDLE h, _In_opt_ PFILETIME pftTimeout); https://msdn.microsoft.com/en-us/library/windows/desktop/ms686273(v=vs.85).aspx + [DllImport(Lib.Kernel32, SetLastError = false, ExactSpelling = true)] + [PInvokeData("WinBase.h", MSDNShortId = "ms686273")] + public static extern void SetThreadpoolWait(PTP_WAIT pwa, [Optional] IntPtr h, [Optional] IntPtr pftTimeout); + /// /// Sets the wait object—replacing the previous wait object, if any. A worker thread calls the wait object's callback function after /// the handle becomes signaled or after the specified timeout expires. @@ -770,11 +852,11 @@ namespace Vanara.PInvoke /// If the function succeeds, it returns TRUE. /// If the function fails, it returns FALSE. To retrieve extended error information, call GetLastError. /// - // BOOL WINAPI TrySubmitThreadpoolCallback( _In_ PTP_SIMPLE_CALLBACK pfns, _Inout_opt_ PVOID pv, _In_opt_ PTP_CALLBACK_ENVIRON pcbe); https://msdn.microsoft.com/en-us/library/windows/desktop/ms686862(v=vs.85).aspx + // BOOL WINAPI TrySubmitThreadpoolCallback( _In_ SimpleCallback pfns, _Inout_opt_ PVOID pv, _In_opt_ PTP_CALLBACK_ENVIRON pcbe); https://msdn.microsoft.com/en-us/library/windows/desktop/ms686862(v=vs.85).aspx [DllImport(Lib.Kernel32, SetLastError = true, ExactSpelling = true)] [PInvokeData("WinBase.h", MSDNShortId = "ms686862")] [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool TrySubmitThreadpoolCallback(PTP_SIMPLE_CALLBACK pfns, IntPtr pv, PTP_CALLBACK_ENVIRON pcbe); + public static extern bool TrySubmitThreadpoolCallback(SimpleCallback pfns, [Optional] IntPtr pv, [Optional] PTP_CALLBACK_ENVIRON pcbe); /// /// Waits for outstanding I/O completion callbacks to complete and optionally cancels pending callbacks that have not yet started to execute. @@ -828,46 +910,367 @@ namespace Vanara.PInvoke [PInvokeData("WinBase.h", MSDNShortId = "ms687053")] public static extern void WaitForThreadpoolWorkCallbacks(PTP_WORK pwk, [MarshalAs(UnmanagedType.Bool)] bool fCancelPendingCallbacks); + /// Creates a new timer object. + /// The callback function to call each time the timer object expires. For details, see TimerCallback. + /// Optional application-defined data to pass to the callback function. + /// + /// + /// A TP_CALLBACK_ENVIRON structure that defines the environment in which to execute the callback. The + /// InitializeThreadpoolEnvironment function returns this structure. + /// + /// If this parameter is NULL, the callback executes in the default callback environment. For more information, see InitializeThreadpoolEnvironment. + /// + /// + /// + /// If the function succeeds, it returns a TP_TIMER structure that defines the timer object. Applications do not modify the + /// members of this structure. + /// + /// If the function fails, it returns NULL. To retrieve extended error information, call GetLastError. + /// + // PTP_TIMER WINAPI CreateThreadpoolTimer( _In_ TimerCallback pfnti, _Inout_opt_ PVOID pv, _In_opt_ PTP_CALLBACK_ENVIRON pcbe); https://msdn.microsoft.com/en-us/library/windows/desktop/ms682466(v=vs.85).aspx + [DllImport(Lib.Kernel32, SetLastError = true, EntryPoint = "CreateThreadpoolTimer")] + [PInvokeData("WinBase.h", MSDNShortId = "ms682466")] + internal static extern PTP_TIMER InternalCreateThreadpoolTimer(TimerCallback pfnti, IntPtr pv, PTP_CALLBACK_ENVIRON pcbe); + + /// Creates a new wait object. + /// The callback function to call when the wait completes or times out. For details, see WaitCallback. + /// Optional application-defined data to pass to the callback function. + /// + /// + /// A TP_CALLBACK_ENVIRON structure that defines the environment in which to execute the callback. The + /// InitializeThreadpoolEnvironment function returns this structure. + /// + /// If this parameter is NULL, the callback executes in the default callback environment. For more information, see InitializeThreadpoolEnvironment. + /// + /// + /// + /// If the function succeeds, it returns a TP_WAIT structure that defines the wait object. Applications do not modify the + /// members of this structure. + /// + /// If the function fails, it returns NULL. To retrieve extended error information, call GetLastError. + /// + // PTP_WAIT WINAPI CreateThreadpoolWait( _In_ WaitCallback pfnwa, _Inout_opt_ PVOID pv, _In_opt_ PTP_CALLBACK_ENVIRON pcbe); https://msdn.microsoft.com/en-us/library/windows/desktop/ms682474(v=vs.85).aspx + [DllImport(Lib.Kernel32, SetLastError = true, EntryPoint = "CreateThreadpoolWait")] + [PInvokeData("WinBase.h", MSDNShortId = "ms682474")] + internal static extern PTP_WAIT InternalCreateThreadpoolWait(WaitCallback pfnwa, IntPtr pv, PTP_CALLBACK_ENVIRON pcbe); + + /// Creates a new work object. + /// + /// The callback function. A worker thread calls this callback each time you call SubmitThreadpoolWork to post the work + /// object. For details, see WorkCallback. + /// + /// Optional application-defined data to pass to the callback function. + /// + /// + /// A TP_CALLBACK_ENVIRON structure that defines the environment in which to execute the callback. The + /// InitializeThreadpoolEnvironment function returns this structure. + /// + /// If this parameter is NULL, the callback executes in the default callback environment. For more information, see InitializeThreadpoolEnvironment. + /// + /// + /// + /// If the function succeeds, it returns a TP_WORK structure that defines the work object. Applications do not modify the + /// members of this structure. + /// + /// If the function fails, it returns NULL. To retrieve extended error information, call GetLastError. + /// + // PTP_WORK WINAPI CreateThreadpoolWork( _In_ WorkCallback pfnwk, _Inout_opt_ PVOID pv, _In_opt_ PTP_CALLBACK_ENVIRON pcbe); https://msdn.microsoft.com/en-us/library/windows/desktop/ms682478(v=vs.85).aspx + [DllImport(Lib.Kernel32, SetLastError = true, EntryPoint = "CreateThreadpoolWork")] + [PInvokeData("WinBase.h", MSDNShortId = "ms682478")] + internal static extern PTP_WORK InternalCreateThreadpoolWork(WorkCallback pfnwk, IntPtr pv, PTP_CALLBACK_ENVIRON pcbe); + [StructLayout(LayoutKind.Sequential)] public struct PTP_CALLBACK_INSTANCE { private readonly IntPtr handle; } + /// Provides a handle to a pool cleanup group. [StructLayout(LayoutKind.Sequential)] - public struct PTP_CLEANUP_GROUP + public struct PTP_CLEANUP_GROUP : IHandle { - private readonly IntPtr handle; + private IntPtr handle; + + /// Initializes a new instance of the struct. + /// An object that represents the pre-existing handle to use. + public PTP_CLEANUP_GROUP(IntPtr preexistingHandle) => handle = preexistingHandle; + + /// Returns an invalid handle by instantiating a object with . + public static PTP_CLEANUP_GROUP NULL => new PTP_CLEANUP_GROUP(IntPtr.Zero); + + /// Gets a value indicating whether this instance is a null handle. + public bool IsNull => handle == IntPtr.Zero; + + /// Performs an explicit conversion from to . + /// The handle. + /// The result of the conversion. + public static explicit operator IntPtr(PTP_CLEANUP_GROUP h) => h.handle; + + /// Performs an implicit conversion from to . + /// The pointer to a handle. + /// The result of the conversion. + public static implicit operator PTP_CLEANUP_GROUP(IntPtr h) => new PTP_CLEANUP_GROUP(h); + + /// Implements the operator !=. + /// The first handle. + /// The second handle. + /// The result of the operator. + public static bool operator !=(PTP_CLEANUP_GROUP h1, PTP_CLEANUP_GROUP h2) => !(h1 == h2); + + /// Implements the operator ==. + /// The first handle. + /// The second handle. + /// The result of the operator. + public static bool operator ==(PTP_CLEANUP_GROUP h1, PTP_CLEANUP_GROUP h2) => h1.Equals(h2); + + /// + public override bool Equals(object obj) => obj is PTP_CLEANUP_GROUP h ? handle == h.handle : false; + + /// + public override int GetHashCode() => handle.GetHashCode(); + + /// + public IntPtr DangerousGetHandle() => handle; } + /// Provides a handle to a threadpool IO. [StructLayout(LayoutKind.Sequential)] - public struct PTP_IO + public struct PTP_IO : IHandle { - private readonly IntPtr handle; + private IntPtr handle; + + /// Initializes a new instance of the struct. + /// An object that represents the pre-existing handle to use. + public PTP_IO(IntPtr preexistingHandle) => handle = preexistingHandle; + + /// Returns an invalid handle by instantiating a object with . + public static PTP_IO NULL => new PTP_IO(IntPtr.Zero); + + /// Gets a value indicating whether this instance is a null handle. + public bool IsNull => handle == IntPtr.Zero; + + /// Performs an explicit conversion from to . + /// The handle. + /// The result of the conversion. + public static explicit operator IntPtr(PTP_IO h) => h.handle; + + /// Performs an implicit conversion from to . + /// The pointer to a handle. + /// The result of the conversion. + public static implicit operator PTP_IO(IntPtr h) => new PTP_IO(h); + + /// Implements the operator !=. + /// The first handle. + /// The second handle. + /// The result of the operator. + public static bool operator !=(PTP_IO h1, PTP_IO h2) => !(h1 == h2); + + /// Implements the operator ==. + /// The first handle. + /// The second handle. + /// The result of the operator. + public static bool operator ==(PTP_IO h1, PTP_IO h2) => h1.Equals(h2); + + /// + public override bool Equals(object obj) => obj is PTP_IO h ? handle == h.handle : false; + + /// + public override int GetHashCode() => handle.GetHashCode(); + + /// + public IntPtr DangerousGetHandle() => handle; } + /// Provides a handle to a thread pool. [StructLayout(LayoutKind.Sequential)] - public struct PTP_POOL + public struct PTP_POOL : IHandle { - private readonly IntPtr handle; + private IntPtr handle; + + /// Initializes a new instance of the struct. + /// An object that represents the pre-existing handle to use. + public PTP_POOL(IntPtr preexistingHandle) => handle = preexistingHandle; + + /// Returns an invalid handle by instantiating a object with . + public static PTP_POOL NULL => new PTP_POOL(IntPtr.Zero); + + /// Gets a value indicating whether this instance is a null handle. + public bool IsNull => handle == IntPtr.Zero; + + /// Performs an explicit conversion from to . + /// The handle. + /// The result of the conversion. + public static explicit operator IntPtr(PTP_POOL h) => h.handle; + + /// Performs an implicit conversion from to . + /// The pointer to a handle. + /// The result of the conversion. + public static implicit operator PTP_POOL(IntPtr h) => new PTP_POOL(h); + + /// Implements the operator !=. + /// The first handle. + /// The second handle. + /// The result of the operator. + public static bool operator !=(PTP_POOL h1, PTP_POOL h2) => !(h1 == h2); + + /// Implements the operator ==. + /// The first handle. + /// The second handle. + /// The result of the operator. + public static bool operator ==(PTP_POOL h1, PTP_POOL h2) => h1.Equals(h2); + + /// + public override bool Equals(object obj) => obj is PTP_POOL h ? handle == h.handle : false; + + /// + public override int GetHashCode() => handle.GetHashCode(); + + /// + public IntPtr DangerousGetHandle() => handle; } + /// Provides a handle to a threadpool timer. [StructLayout(LayoutKind.Sequential)] - public struct PTP_TIMER + public struct PTP_TIMER : IHandle { - private readonly IntPtr handle; + private IntPtr handle; + + /// Initializes a new instance of the struct. + /// An object that represents the pre-existing handle to use. + public PTP_TIMER(IntPtr preexistingHandle) => handle = preexistingHandle; + + /// Returns an invalid handle by instantiating a object with . + public static PTP_TIMER NULL => new PTP_TIMER(IntPtr.Zero); + + /// Gets a value indicating whether this instance is a null handle. + public bool IsNull => handle == IntPtr.Zero; + + /// Performs an explicit conversion from to . + /// The handle. + /// The result of the conversion. + public static explicit operator IntPtr(PTP_TIMER h) => h.handle; + + /// Performs an implicit conversion from to . + /// The pointer to a handle. + /// The result of the conversion. + public static implicit operator PTP_TIMER(IntPtr h) => new PTP_TIMER(h); + + /// Implements the operator !=. + /// The first handle. + /// The second handle. + /// The result of the operator. + public static bool operator !=(PTP_TIMER h1, PTP_TIMER h2) => !(h1 == h2); + + /// Implements the operator ==. + /// The first handle. + /// The second handle. + /// The result of the operator. + public static bool operator ==(PTP_TIMER h1, PTP_TIMER h2) => h1.Equals(h2); + + /// + public override bool Equals(object obj) => obj is PTP_TIMER h ? handle == h.handle : false; + + /// + public override int GetHashCode() => handle.GetHashCode(); + + /// + public IntPtr DangerousGetHandle() => handle; } + /// Provides a handle to a threadpool wait. [StructLayout(LayoutKind.Sequential)] - public struct PTP_WAIT + public struct PTP_WAIT : IHandle { - private readonly IntPtr handle; + private IntPtr handle; + + /// Initializes a new instance of the struct. + /// An object that represents the pre-existing handle to use. + public PTP_WAIT(IntPtr preexistingHandle) => handle = preexistingHandle; + + /// Returns an invalid handle by instantiating a object with . + public static PTP_WAIT NULL => new PTP_WAIT(IntPtr.Zero); + + /// Gets a value indicating whether this instance is a null handle. + public bool IsNull => handle == IntPtr.Zero; + + /// Performs an explicit conversion from to . + /// The handle. + /// The result of the conversion. + public static explicit operator IntPtr(PTP_WAIT h) => h.handle; + + /// Performs an implicit conversion from to . + /// The pointer to a handle. + /// The result of the conversion. + public static implicit operator PTP_WAIT(IntPtr h) => new PTP_WAIT(h); + + /// Implements the operator !=. + /// The first handle. + /// The second handle. + /// The result of the operator. + public static bool operator !=(PTP_WAIT h1, PTP_WAIT h2) => !(h1 == h2); + + /// Implements the operator ==. + /// The first handle. + /// The second handle. + /// The result of the operator. + public static bool operator ==(PTP_WAIT h1, PTP_WAIT h2) => h1.Equals(h2); + + /// + public override bool Equals(object obj) => obj is PTP_WAIT h ? handle == h.handle : false; + + /// + public override int GetHashCode() => handle.GetHashCode(); + + /// + public IntPtr DangerousGetHandle() => handle; } + /// Provides a handle to a threadpool work. [StructLayout(LayoutKind.Sequential)] - public struct PTP_WORK + public struct PTP_WORK : IHandle { - private readonly IntPtr handle; + private IntPtr handle; + + /// Initializes a new instance of the struct. + /// An object that represents the pre-existing handle to use. + public PTP_WORK(IntPtr preexistingHandle) => handle = preexistingHandle; + + /// Returns an invalid handle by instantiating a object with . + public static PTP_WORK NULL => new PTP_WORK(IntPtr.Zero); + + /// Gets a value indicating whether this instance is a null handle. + public bool IsNull => handle == IntPtr.Zero; + + /// Performs an explicit conversion from to . + /// The handle. + /// The result of the conversion. + public static explicit operator IntPtr(PTP_WORK h) => h.handle; + + /// Performs an implicit conversion from to . + /// The pointer to a handle. + /// The result of the conversion. + public static implicit operator PTP_WORK(IntPtr h) => new PTP_WORK(h); + + /// Implements the operator !=. + /// The first handle. + /// The second handle. + /// The result of the operator. + public static bool operator !=(PTP_WORK h1, PTP_WORK h2) => !(h1 == h2); + + /// Implements the operator ==. + /// The first handle. + /// The second handle. + /// The result of the operator. + public static bool operator ==(PTP_WORK h1, PTP_WORK h2) => h1.Equals(h2); + + /// + public override bool Equals(object obj) => obj is PTP_WORK h ? handle == h.handle : false; + + /// + public override int GetHashCode() => handle.GetHashCode(); + + /// + public IntPtr DangerousGetHandle() => handle; } /// Used to set the stack reserve and commit sizes for new threads in a thread pool. @@ -882,7 +1285,8 @@ namespace Vanara.PInvoke public SizeT StackCommit; } - /// Defines a callback environment.` + /// Defines a callback environment. + /// ` [PInvokeData("threadpoolapiset.h")] [StructLayout(LayoutKind.Sequential)] public class PTP_CALLBACK_ENVIRON @@ -890,10 +1294,10 @@ namespace Vanara.PInvoke internal uint Version; internal PTP_POOL Pool; internal PTP_CLEANUP_GROUP CleanupGroup; - internal PTP_CLEANUP_GROUP_CANCEL_CALLBACK CleanupGroupCancelCallback; + internal CleanupGroupCancelCallback CleanupGroupCancelCallback; internal HINSTANCE RaceDll; internal HACTCTX _ActivationContext; - internal PTP_SIMPLE_CALLBACK _FinalizationCallback; + internal SimpleCallback _FinalizationCallback; internal TP_CALLBACK_ENV_FLAGS Flags; internal TP_CALLBACK_PRIORITY CallbackPriority; internal uint Size; @@ -911,11 +1315,221 @@ namespace Vanara.PInvoke /// Indicates a function to call when the callback environment is finalized. /// Pointer to a TP_SIMPLE_CALLBACK structure indicating a function to call when the callback environment is finalized. - public PTP_SIMPLE_CALLBACK FinalizationCallback { set => _FinalizationCallback = value; } + public SimpleCallback FinalizationCallback { set => _FinalizationCallback = value; } /// Assigns an activation context to the callback environment. /// Pointer to an _ACTIVATION_CONTEXT structure. public HACTCTX ActivationContext { set => _ActivationContext = value; } } + + /// Provides a for that is disposed using . + public class SafePTP_CLEANUP_GROUP : SafeHANDLE + { + /// Initializes a new instance of the class and assigns an existing handle. + /// An object that represents the pre-existing handle to use. + /// + /// to reliably release the handle during the finalization phase; otherwise, (not recommended). + /// + public SafePTP_CLEANUP_GROUP(IntPtr preexistingHandle, bool ownsHandle = true) : base(preexistingHandle, ownsHandle) { } + + /// Initializes a new instance of the class. + private SafePTP_CLEANUP_GROUP() : base() { } + + /// Gets or sets a value indicating whether to call CloseThreadpoolCleanupGroupMembers on disposal. + public bool AutoCloseMembers { get; set; } + + /// Performs an implicit conversion from to . + /// The safe handle instance. + /// The result of the conversion. + public static implicit operator PTP_CLEANUP_GROUP(SafePTP_CLEANUP_GROUP h) => h.handle; + + /// + /// Releases the members of this cleanup group, waits for all callback functions to complete, and optionally cancels any + /// outstanding callback functions. + /// + /// + /// If this parameter is TRUE, the function cancels outstanding callbacks that have not yet started. If this parameter is FALSE, + /// the function waits for outstanding callback functions to complete. + /// + /// + /// The application-defined data to pass to the application's cleanup group callback function. You can specify the callback + /// function when you call SetThreadpoolCallbackCleanupGroup. + /// + /// This function does not return a value. + public void CloseMembers(bool fCancelPendingCallbacks, [Optional] IntPtr pvCleanupContext) => CloseThreadpoolCleanupGroupMembers(handle, fCancelPendingCallbacks, pvCleanupContext); + + /// Creates a new timer object. + /// The callback function to call each time the timer object expires. For details, see TimerCallback. + /// Optional application-defined data to pass to the callback function. + /// + /// + /// A TP_CALLBACK_ENVIRON structure that defines the environment in which to execute the callback. The + /// InitializeThreadpoolEnvironment function returns this structure. + /// + /// If this parameter is NULL, the callback executes in the default callback environment. For more information, see InitializeThreadpoolEnvironment. + /// + /// + /// + /// If the function succeeds, it returns a TP_TIMER structure that defines the timer object. Applications do not modify + /// the members of this structure. + /// + /// If the function fails, it returns NULL. To retrieve extended error information, call GetLastError. + /// + public PTP_TIMER CreateTimer(TimerCallback pfnti, IntPtr pv = default, PTP_CALLBACK_ENVIRON pcbe = null) => InternalCreateThreadpoolTimer(pfnti, pv, pcbe); + + /// Creates a new wait object. + /// The callback function to call when the wait completes or times out. For details, see WaitCallback. + /// Optional application-defined data to pass to the callback function. + /// + /// + /// A TP_CALLBACK_ENVIRON structure that defines the environment in which to execute the callback. The + /// InitializeThreadpoolEnvironment function returns this structure. + /// + /// If this parameter is NULL, the callback executes in the default callback environment. For more information, see InitializeThreadpoolEnvironment. + /// + /// + /// + /// If the function succeeds, it returns a TP_WAIT structure that defines the wait object. Applications do not modify the + /// members of this structure. + /// + /// If the function fails, it returns NULL. To retrieve extended error information, call GetLastError. + /// + public PTP_WAIT CreateWait(WaitCallback pfnwa, IntPtr pv = default, PTP_CALLBACK_ENVIRON pcbe = null) => InternalCreateThreadpoolWait(pfnwa, pv, pcbe); + + /// Creates a new work object. + /// + /// The callback function. A worker thread calls this callback each time you call SubmitThreadpoolWork to post the work + /// object. For details, see WorkCallback. + /// + /// Optional application-defined data to pass to the callback function. + /// + /// + /// A TP_CALLBACK_ENVIRON structure that defines the environment in which to execute the callback. The + /// InitializeThreadpoolEnvironment function returns this structure. + /// + /// If this parameter is NULL, the callback executes in the default callback environment. For more information, see InitializeThreadpoolEnvironment. + /// + /// + /// + /// If the function succeeds, it returns a TP_WORK structure that defines the work object. Applications do not modify the + /// members of this structure. + /// + /// If the function fails, it returns NULL. To retrieve extended error information, call GetLastError. + /// + public PTP_WORK CreateWork(WorkCallback pfnwk, IntPtr pv = default, PTP_CALLBACK_ENVIRON pcbe = null) => InternalCreateThreadpoolWork(pfnwk, pv, pcbe); + + /// + protected override bool InternalReleaseHandle() { if (AutoCloseMembers) CloseMembers(false); CloseThreadpoolCleanupGroup(handle); return true; } + } + + /// Provides a for that is disposed using . + public class SafePTP_IO : SafeHANDLE + { + /// Initializes a new instance of the class and assigns an existing handle. + /// An object that represents the pre-existing handle to use. + /// + /// to reliably release the handle during the finalization phase; otherwise, (not recommended). + /// + public SafePTP_IO(IntPtr preexistingHandle, bool ownsHandle = true) : base(preexistingHandle, ownsHandle) { } + + /// Initializes a new instance of the class. + private SafePTP_IO() : base() { } + + /// Performs an implicit conversion from to . + /// The safe handle instance. + /// The result of the conversion. + public static implicit operator PTP_IO(SafePTP_IO h) => h.handle; + + /// + protected override bool InternalReleaseHandle() { CloseThreadpoolIo(handle); return true; } + } + + /// Provides a for that is disposed using . + public class SafePTP_POOL : SafeHANDLE + { + /// Initializes a new instance of the class and assigns an existing handle. + /// An object that represents the pre-existing handle to use. + /// + /// to reliably release the handle during the finalization phase; otherwise, (not recommended). + /// + public SafePTP_POOL(IntPtr preexistingHandle, bool ownsHandle = true) : base(preexistingHandle, ownsHandle) { } + + /// Initializes a new instance of the class. + private SafePTP_POOL() : base() { } + + /// Performs an implicit conversion from to . + /// The safe handle instance. + /// The result of the conversion. + public static implicit operator PTP_POOL(SafePTP_POOL h) => h.handle; + + /// + protected override bool InternalReleaseHandle() { CloseThreadpool(handle); return true; } + } + + /// Provides a for that is disposed using . + public class SafePTP_TIMER : SafeHANDLE + { + /// Initializes a new instance of the class and assigns an existing handle. + /// An object that represents the pre-existing handle to use. + /// + /// to reliably release the handle during the finalization phase; otherwise, (not recommended). + /// + public SafePTP_TIMER(IntPtr preexistingHandle, bool ownsHandle = true) : base(preexistingHandle, ownsHandle) { } + + /// Initializes a new instance of the class. + private SafePTP_TIMER() : base() { } + + /// Performs an implicit conversion from to . + /// The safe handle instance. + /// The result of the conversion. + public static implicit operator PTP_TIMER(SafePTP_TIMER h) => h.handle; + + /// + protected override bool InternalReleaseHandle() { CloseThreadpoolTimer(handle); return true; } + } + + /// Provides a for that is disposed using . + public class SafePTP_WAIT : SafeHANDLE + { + /// Initializes a new instance of the class and assigns an existing handle. + /// An object that represents the pre-existing handle to use. + /// + /// to reliably release the handle during the finalization phase; otherwise, (not recommended). + /// + public SafePTP_WAIT(IntPtr preexistingHandle, bool ownsHandle = true) : base(preexistingHandle, ownsHandle) { } + + /// Initializes a new instance of the class. + private SafePTP_WAIT() : base() { } + + /// Performs an implicit conversion from to . + /// The safe handle instance. + /// The result of the conversion. + public static implicit operator PTP_WAIT(SafePTP_WAIT h) => h.handle; + + /// + protected override bool InternalReleaseHandle() { CloseThreadpoolWait(handle); return true; } + } + + /// Provides a for that is disposed using . + public class SafePTP_WORK : SafeHANDLE + { + /// Initializes a new instance of the class and assigns an existing handle. + /// An object that represents the pre-existing handle to use. + /// + /// to reliably release the handle during the finalization phase; otherwise, (not recommended). + /// + public SafePTP_WORK(IntPtr preexistingHandle, bool ownsHandle = true) : base(preexistingHandle, ownsHandle) { } + + /// Initializes a new instance of the class. + private SafePTP_WORK() : base() { } + + /// Performs an implicit conversion from to . + /// The safe handle instance. + /// The result of the conversion. + public static implicit operator PTP_WORK(SafePTP_WORK h) => h.handle; + + /// + protected override bool InternalReleaseHandle() { CloseThreadpoolWork(handle); return true; } + } } } \ No newline at end of file diff --git a/UnitTests/PInvoke/Kernel32/Kernel32.csproj b/UnitTests/PInvoke/Kernel32/Kernel32.csproj index 5f786b81..f9c265e9 100644 --- a/UnitTests/PInvoke/Kernel32/Kernel32.csproj +++ b/UnitTests/PInvoke/Kernel32/Kernel32.csproj @@ -48,6 +48,7 @@ + diff --git a/UnitTests/PInvoke/Kernel32/ThreadPoolApiSetTests.cs b/UnitTests/PInvoke/Kernel32/ThreadPoolApiSetTests.cs new file mode 100644 index 00000000..b4da02ce --- /dev/null +++ b/UnitTests/PInvoke/Kernel32/ThreadPoolApiSetTests.cs @@ -0,0 +1,152 @@ +using NUnit.Framework; +using System; +using System.Diagnostics; +using Vanara.Extensions; +using static Vanara.PInvoke.Kernel32; + +namespace Vanara.PInvoke.Tests +{ + [TestFixture] + public class ThreadPoolApiSetTests + { + [Test] + public void QuerySetThreadpoolStackInformationTest() + { + using (var pool = CreateThreadpool()) + { + Assert.That(pool, ResultIs.ValidHandle); + Assert.That(QueryThreadpoolStackInformation(pool, out var si), ResultIs.Successful); + Assert.That(si.StackReserve.Value, Is.Not.Zero); + Assert.That(SetThreadpoolStackInformation(pool, si), ResultIs.Successful); + } + } + + [Test] + public void ThreadpoolIoWorkTimerTest() + { + InitializeThreadpoolEnvironment(out var CallBackEnviron); + + // Create a custom, dedicated thread pool. + using (var pool = CreateThreadpool()) + { + Assert.That(pool, ResultIs.ValidHandle); + + // The thread pool is made persistent simply by setting both the minimum and maximum threads to 1. + SetThreadpoolThreadMaximum(pool, 1); + Assert.That(SetThreadpoolThreadMinimum(pool, 1), ResultIs.Successful); + + // Create a cleanup group for this thread pool. + using (var cleanupgroup = CreateThreadpoolCleanupGroup()) + { + Assert.That(cleanupgroup, ResultIs.ValidHandle); + cleanupgroup.AutoCloseMembers = true; + + // Associate the callback environment with our thread pool. + CallBackEnviron.SetThreadpoolCallbackPool(pool); + + // Associate the cleanup group with our thread pool. Objects created with the same callback environment as the cleanup + // group become members of the cleanup group. + CallBackEnviron.SetThreadpoolCallbackCleanupGroup(cleanupgroup, null); + + // Create work with the callback environment. + var work = cleanupgroup.CreateWork(MyWorkCallback, default, CallBackEnviron); + Assert.That(work, ResultIs.ValidHandle); + + // Submit the work to the pool. Because this was a pre-allocated work item (using CreateThreadpoolWork), it is guaranteed + // to execute. + SubmitThreadpoolWork(work); + + // Create a timer with the same callback environment. + var timer = cleanupgroup.CreateTimer(MyTimerCallback, default, CallBackEnviron); + Assert.That(timer, ResultIs.ValidHandle); + + // Set the timer to fire in one second. + var FileDueTime = TimeSpan.FromSeconds(-1).ToFileTimeStruct(); + Assert.That(SetThreadpoolTimerEx(timer, FileDueTime, 0, 0), Is.False); + Assert.That(IsThreadpoolTimerSet(timer), Is.True); + + using (var hFile = CreateFile(@"C:\Temp\help.ico", FileAccess.FILE_GENERIC_READ, System.IO.FileShare.Read, null, System.IO.FileMode.Open, FileFlagsAndAttributes.FILE_FLAG_OVERLAPPED)) + using (var io = CreateThreadpoolIo(hFile, MyIoCallback, default, CallBackEnviron)) + { + Assert.That(io, ResultIs.ValidHandle); + StartThreadpoolIo(io); + WaitForThreadpoolIoCallbacks(io, true); + } + + // Delay for the timer to be fired + Sleep(1500); + } + } + + void MyIoCallback(PTP_CALLBACK_INSTANCE Instance, IntPtr Context, IntPtr Overlapped, uint IoResult, UIntPtr NumberOfBytesTransferred, PTP_IO Io) + { + Debug.Write("MyIoCallback: I/O has fired.\n"); + CancelThreadpoolIo(Io); + } + + // Thread pool timer callback function template + void MyTimerCallback(PTP_CALLBACK_INSTANCE a, IntPtr b, PTP_TIMER c) => Debug.Write("MyTimerCallback: timer has fired.\n"); + + // This is the thread pool work callback function. + void MyWorkCallback(PTP_CALLBACK_INSTANCE a, IntPtr b, PTP_WORK c) + { + CallbackMayRunLong(a); + Debug.Write("MyWorkCallback: Task performed.\n"); + } + } + + [Test] + public void ThreadpoolWaitTest() + { + SafeEventHandle retEvent; + // Create an auto-reset event. + using (var hEvent = CreateEvent(null, false, false, null)) + using (retEvent = CreateEvent(null, false, false, null)) + { + Assert.That(hEvent, ResultIs.ValidHandle); + + using (var Wait = CreateThreadpoolWait(MyWaitCallback)) + { + Assert.That(Wait, ResultIs.ValidHandle); + + // Need to re-register the event with the wait object each time before signaling the event to trigger the wait callback. + for (var i = 0; i < 5; i++) + { + SetThreadpoolWait(Wait, hEvent); + + SetEvent(hEvent); + + // Delay for the waiter thread to act if necessary. + Sleep(500); + + // Block here until the callback function is done executing. + WaitForThreadpoolWaitCallbacks(Wait, false); + + // Ensure that callback return event is signaled. + WaitForSingleObject(retEvent, INFINITE); + } + + SetThreadpoolWait(Wait); + } + } + + // Thread pool wait callback function template + void MyWaitCallback(PTP_CALLBACK_INSTANCE a, IntPtr b, PTP_WAIT c, uint d) + { + Debug.Write("MyWaitCallback: wait is over.\n"); + Sleep(200); + SetEventWhenCallbackReturns(a, retEvent); + } + } + + [Test] + public void TrySubmitThreadpoolCallbackTest() + { + Assert.That(TrySubmitThreadpoolCallback((i, c) => + { + Debug.WriteLine("SimpleCallback from TrySubmitThreadpoolCallback"); + DisassociateCurrentThreadFromCallback(i); + }), Is.True); ; + } + } +} \ No newline at end of file