using System;
using System.Runtime.InteropServices;
namespace Vanara.PInvoke;
/// Functions from Avrt.dll.
public static partial class Avrt
{
private const string Lib_Avrt = "Avrt.dll";
/// The relative thread priority of this thread to other threads performing a similar task.
[PInvokeData("avrt.h", MSDNShortId = "NF:avrt.AvSetMmThreadPriority")]
public enum AVRT_PRIORITY
{
/// very low
AVRT_PRIORITY_VERYLOW = -2,
/// low
AVRT_PRIORITY_LOW,
/// normal
AVRT_PRIORITY_NORMAL,
/// high
AVRT_PRIORITY_HIGH,
/// critical
AVRT_PRIORITY_CRITICAL
}
/// Retrieves the system responsiveness setting used by the multimedia class scheduler service.
///
/// A handle to the task. This handle is returned by the AvSetMmThreadCharacteristics or AvSetMmMaxThreadCharacteristics function.
///
/// The system responsiveness value. This value can range from 10 to 100 percent.
///
/// 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.
///
// https://learn.microsoft.com/en-us/windows/win32/api/avrt/nf-avrt-avquerysystemresponsiveness AVRTAPI BOOL AvQuerySystemResponsiveness(
// [in] HAVRT AvrtHandle, [out] PULONG SystemResponsivenessValue );
[PInvokeData("avrt.h", MSDNShortId = "NF:avrt.AvQuerySystemResponsiveness")]
[DllImport(Lib_Avrt, SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool AvQuerySystemResponsiveness([In] HAVRT AvrtHandle, out uint SystemResponsivenessValue);
/// Indicates that a thread is no longer performing work associated with the specified task.
///
/// A handle to the task. This handle is returned by the AvSetMmThreadCharacteristics or AvSetMmMaxThreadCharacteristics function.
///
///
/// 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.
///
///
/// This function must be called from the same thread that called the AvSetMmThreadCharacteristics or AvSetMmMaxThreadCharacteristics
/// function to create the handle. Otherwise, the function will fail.
///
// https://learn.microsoft.com/en-us/windows/win32/api/avrt/nf-avrt-avrevertmmthreadcharacteristics AVRTAPI BOOL
// AvRevertMmThreadCharacteristics( [in] HAVRT AvrtHandle );
[PInvokeData("avrt.h", MSDNShortId = "NF:avrt.AvRevertMmThreadCharacteristics")]
[DllImport(Lib_Avrt, SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool AvRevertMmThreadCharacteristics([In] HAVRT AvrtHandle);
///
/// Creates a thread ordering group.
///
/// To prevent the server thread for the thread ordering group from being starved if higher priority client threads are running, use the
/// AvRtCreateThreadOrderingGroupEx function.
///
///
/// A pointer to a context handle.
///
///
/// A pointer to a value, in 100-nanosecond increments, that specifies the period for the thread ordering group. Each thread in the
/// thread ordering group runs one time during this period. If all threads complete their execution before a period ends, all threads
/// wait until the remainder of the period elapses before any are executed again.
///
///
/// The possible values for this parameter depend on the platform, but this parameter can be as low as 500 microseconds or as high as
/// 0x1FFFFFFFFFFFFFFF. If this parameter is less than 500 microseconds, then it is set to 500 microseconds. If this parameter is greater
/// than the maximum, then it is set to 0x1FFFFFFFFFFFFFFF.
///
///
///
///
/// A pointer to the unique identifier for the thread ordering group to be created. If this value is not unique to the thread ordering
/// service, the function fails.
///
/// If the identifier is GUID_NULL on input, the thread ordering service generates and returns a unique identifier.
///
///
/// A pointer to a time-out value. All threads within the group should complete their execution within Period plus Timeout.
///
/// If a thread fails to complete its processing within the period plus this time-out interval, it is removed from the thread ordering
/// group. If the parent fails to complete its processing within the period plus the time-out interval, the thread ordering group is destroyed.
///
///
/// The possible values for this parameter depend on the platform, but can be as low as 500 microseconds or as high as
/// 0x1FFFFFFFFFFFFFFF. If this parameter is less than 500 microseconds, then it is set to 500 microseconds. If this parameter is greater
/// than the maximum, then it is set to 0x1FFFFFFFFFFFFFFF.
///
/// If this parameter is NULL or 0, the default is five times the value of Period.
///
/// If this parameter is THREAD_ORDER_GROUP_INFINITE_TIMEOUT, the group is created with an infinite time-out interval. This can be useful
/// for debugging purposes.
///
///
///
/// 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.
/// If a thread ordering group with the specified identifier already exists, the function fails and sets the last error to ERROR_ALREADY_EXISTS.
///
///
///
/// The calling thread is considered to be the parent thread. Each thread ordering group has one parent thread. Each parent thread can
/// have zero or more predecessor threads and zero or more successor threads. A client thread can join a thread ordering group and
/// specify whether it is a predecessor or successor using the AvRtJoinThreadOrderingGroup function.
///
///
/// The parent thread encloses the code to be executed during each period within a loop that is controlled by the
/// AvRtWaitOnThreadOrderingGroup function.
///
/// To delete the thread ordering group, call the AvRtDeleteThreadOrderingGroup function.
///
/// A thread can create more than one thread ordering group and join more than one thread ordering group. However, a thread cannot join
/// the same thread ordering group more than one time.
///
/// Examples
/// The following snippet creates a thread ordering group.
///
// https://learn.microsoft.com/en-us/windows/win32/api/avrt/nf-avrt-avrtcreatethreadorderinggroup AVRTAPI BOOL
// AvRtCreateThreadOrderingGroup( [out] PHANDLE Context, [in] PLARGE_INTEGER Period, [in, out] GUID *ThreadOrderingGuid, [in, optional]
// PLARGE_INTEGER Timeout );
[PInvokeData("avrt.h", MSDNShortId = "NF:avrt.AvRtCreateThreadOrderingGroup")]
[DllImport(Lib_Avrt, SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool AvRtCreateThreadOrderingGroup(out HANDLE Context, in long Period, ref Guid ThreadOrderingGuid,
[In, Optional] in long Timeout);
/// Creates a thread ordering group and associates the server thread with a task.
/// A pointer to a context handle.
///
///
/// A pointer to a value, in 100-nanosecond increments, that specifies the period for the thread ordering group. Each thread in the
/// thread ordering group runs one time during this period. If all threads complete their execution before a period ends, all threads
/// wait until the remainder of the period elapses before any are executed again.
///
///
/// The possible values for this parameter depend on the platform, but this parameter can be as low as 500 microseconds or as high as
/// 0x1FFFFFFFFFFFFFFF. If this parameter is less than 500 microseconds, then it is set to 500 microseconds. If this parameter is greater
/// than the maximum, then it is set to 0x1FFFFFFFFFFFFFFF.
///
///
///
///
/// A pointer to the unique identifier for the thread ordering group to be created. If this value is not unique to the thread ordering
/// service, the function fails.
///
/// If the identifier is GUID_NULL on input, the thread ordering service generates and returns a unique identifier.
///
///
/// A pointer to a time-out value. All threads within the group should complete their execution within Period plus Timeout.
///
/// If a thread fails to complete its processing within the period plus this time-out interval, it is removed from the thread ordering
/// group. If the parent fails to complete its processing within the period plus the time-out interval, the thread ordering group is destroyed.
///
///
/// The possible values for this parameter depend on the platform, but can be as low as 500 microseconds or as high as
/// 0x1FFFFFFFFFFFFFFF. If this parameter is less than 500 microseconds, then it is set to 500 microseconds. If this parameter is greater
/// than the maximum, then it is set to 0x1FFFFFFFFFFFFFFF.
///
/// If this parameter is NULL or 0, the default is five times the value of Period.
///
/// If this parameter is THREAD_ORDER_GROUP_INFINITE_TIMEOUT, the group is created with an infinite time-out interval. This can be useful
/// for debugging purposes.
///
///
/// The name of the task.
///
/// 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.
/// If a thread ordering group with the specified identifier already exists, the function fails and sets the last error to ERROR_ALREADY_EXISTS.
///
///
///
/// The calling thread is considered to be the parent thread. Each thread ordering group has one parent thread. Each parent thread can
/// have zero or more predecessor threads and zero or more successor threads. A client thread can join a thread ordering group and
/// specify whether it is a predecessor or successor using the AvRtJoinThreadOrderingGroup function.
///
///
/// The parent thread encloses the code to be executed during each period within a loop that is controlled by the
/// AvRtWaitOnThreadOrderingGroup function.
///
/// To delete the thread ordering group, call the AvRtDeleteThreadOrderingGroup function.
///
/// A thread can create more than one thread ordering group and join more than one thread ordering group. However, a thread cannot join
/// the same thread ordering group more than one time.
///
///
/// The parent and client threads of a thread ordering group run at high priorities. However, the server thread that manages the thread
/// ordering group runs at normal priority. Therefore, there can be a delay switching from one client thread to another if there are
/// other high-priority threads running. The TaskName parameter of this function specifies the task to be associated with the
/// server thread.
///
/// Examples
/// The following snippet creates a thread ordering group.
///
/// Note
///
/// The avrt.h header defines AvRtCreateThreadOrderingGroupEx as an alias which automatically selects the ANSI or Unicode version of this
/// function based on the definition of the UNICODE preprocessor constant. Mixing usage of the encoding-neutral alias with code that not
/// encoding-neutral can lead to mismatches that result in compilation or runtime errors. For more information, see Conventions for
/// Function Prototypes.
///
///
///
// https://learn.microsoft.com/en-us/windows/win32/api/avrt/nf-avrt-avrtcreatethreadorderinggroupexa AVRTAPI BOOL
// AvRtCreateThreadOrderingGroupExA( [out] PHANDLE Context, [in] PLARGE_INTEGER Period, [in, out] GUID *ThreadOrderingGuid, [in,
// optional] PLARGE_INTEGER Timeout, [in] LPCSTR TaskName );
[PInvokeData("avrt.h", MSDNShortId = "NF:avrt.AvRtCreateThreadOrderingGroupExA")]
[DllImport(Lib_Avrt, SetLastError = true, CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool AvRtCreateThreadOrderingGroupEx(out HANDLE Context, in long Period, ref Guid ThreadOrderingGuid,
[In, Optional] in long Timeout, [MarshalAs(UnmanagedType.LPTStr)] string TaskName);
///
/// Deletes the specified thread ordering group created by the caller. It cleans up resources for the thread ordering group, including
/// the context information, and returns.
///
///
/// A context handle. This handle is returned by the AvRtCreateThreadOrderingGroup function when creating the group.
///
///
/// 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.
///
///
///
/// This function can only be called successfully by the parent thread for the thread ordering group. If a thread other than the parent
/// thread calls this function, the function fails with a last error code of ERROR_INVALID_FUNCTION.
///
/// If the parent thread times out and attempts to call this function, the function fails with a last error code of ERROR_INVALID_PARAMETER.
/// Examples
/// The following code deletes a thread ordering group.
///
// https://learn.microsoft.com/en-us/windows/win32/api/avrt/nf-avrt-avrtdeletethreadorderinggroup AVRTAPI BOOL
// AvRtDeleteThreadOrderingGroup( [in] HANDLE Context );
[PInvokeData("avrt.h", MSDNShortId = "NF:avrt.AvRtDeleteThreadOrderingGroup")]
[DllImport(Lib_Avrt, SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool AvRtDeleteThreadOrderingGroup([In] HANDLE Context);
/// Joins client threads to a thread ordering group.
/// A pointer to a context handle.
/// A pointer to the unique identifier for the thread ordering group.
///
/// The thread order. If this parameter is TRUE, the thread is a predecessor thread that is scheduled to run before the parent
/// thread. If this parameter is FALSE, the thread is a successor thread that is scheduled to run after the parent thread.
///
///
/// 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.
///
///
///
/// The thread encloses the code to be executed during each period within a loop that is controlled by the AvRtWaitOnThreadOrderingGroup function.
///
///
/// A thread can create more than one thread ordering group and join more than one thread ordering group. However, a thread cannot join
/// the same thread ordering group more than one time.
///
/// The number of threads that can join a group is limited only by available system resources.
///
// https://learn.microsoft.com/en-us/windows/win32/api/avrt/nf-avrt-avrtjointhreadorderinggroup AVRTAPI BOOL AvRtJoinThreadOrderingGroup(
// [out] PHANDLE Context, [in] GUID *ThreadOrderingGuid, [in] BOOL Before );
[PInvokeData("avrt.h", MSDNShortId = "NF:avrt.AvRtJoinThreadOrderingGroup")]
[DllImport(Lib_Avrt, SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool AvRtJoinThreadOrderingGroup(out HANDLE Context, in Guid ThreadOrderingGuid, [MarshalAs(UnmanagedType.Bool)] bool Before);
/// Enables client threads to leave a thread ordering group.
/// A context handle. This handle is returned by the AvRtJoinThreadOrderingGroup function.
///
/// 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.
///
///
/// The parent thread for a thread ordering group should not remove itself from the group.
/// If a thread times out and attempts to call this function, the function fails with a last error code of ERROR_INVALID_PARAMETER.
///
// https://learn.microsoft.com/en-us/windows/win32/api/avrt/nf-avrt-avrtleavethreadorderinggroup AVRTAPI BOOL
// AvRtLeaveThreadOrderingGroup( [in] HANDLE Context );
[PInvokeData("avrt.h", MSDNShortId = "NF:avrt.AvRtLeaveThreadOrderingGroup")]
[DllImport(Lib_Avrt, SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool AvRtLeaveThreadOrderingGroup([In] HANDLE Context);
/// Enables client threads of a thread ordering group to wait until they should execute.
///
/// A context handle. This handle is returned by the AvRtCreateThreadOrderingGroup or AvRtJoinThreadOrderingGroup function.
///
///
/// 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.
///
///
/// When this function returns, the thread should complete its processing for the period and then call the function again.
///
/// If the thread fails to complete its processing during the time-out interval specified by the parent thread when creating the group,
/// it is deleted from the thread ordering group. Therefore, when the thread finishes its processing loop, the next call to
/// AvRtWaitOnThreadOrderingGroup fails and the last error code is set to ERROR_ACCESS_DENIED.
///
/// If the thread ordering group is deleted during the wait, this function eventually times out and return ERROR_ACCESS_DENIED.
/// Examples
///
// https://learn.microsoft.com/en-us/windows/win32/api/avrt/nf-avrt-avrtwaitonthreadorderinggroup AVRTAPI BOOL
// AvRtWaitOnThreadOrderingGroup( [in] HANDLE Context );
[PInvokeData("avrt.h", MSDNShortId = "NF:avrt.AvRtWaitOnThreadOrderingGroup")]
[DllImport(Lib_Avrt, SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool AvRtWaitOnThreadOrderingGroup([In] HANDLE Context);
/// Associates the calling thread with the specified tasks.
///
/// The name of the first task to be performed. This name must match the name of one of the subkeys of the following key
/// HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Multimedia\SystemProfile\Tasks.
///
///
/// The name of the second task to be performed. This name must match the name of one of the subkeys of the following key
/// HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Multimedia\SystemProfile\Tasks.
///
///
/// The unique task identifier. The first time this function is called, this value must be 0 on input. The index value is returned on
/// output and can be used as input in subsequent calls.
///
///
/// If the function succeeds, it returns a handle to the task.
/// If the function fails, it returns 0. To retrieve extended error information, call GetLastError.
/// The following are possible error codes.
///
///
/// Return code
/// Description
///
/// -
/// ERROR_INVALID_TASK_INDEX
/// Either TaskIndex is not 0 on the first call or is not recognized value (on subsequent calls).
///
/// -
/// ERROR_INVALID_TASK_NAME
/// The specified task does not match any of the tasks stored in the registry.
///
/// -
/// ERROR_PRIVILEGE_NOT_HELD
/// The caller does not have sufficient privilege.
///
///
///
///
/// The resulting characteristics of the thread performing the tasks reflect the task with the highest priority.
/// When the task is completed, call the AvRevertMmThreadCharacteristics function.
///
/// Note
///
/// The avrt.h header defines AvSetMmMaxThreadCharacteristics as an alias which automatically selects the ANSI or Unicode version of this
/// function based on the definition of the UNICODE preprocessor constant. Mixing usage of the encoding-neutral alias with code that not
/// encoding-neutral can lead to mismatches that result in compilation or runtime errors. For more information, see Conventions for
/// Function Prototypes.
///
///
///
// https://learn.microsoft.com/en-us/windows/win32/api/avrt/nf-avrt-avsetmmmaxthreadcharacteristicsa AVRTAPI HANDLE
// AvSetMmMaxThreadCharacteristicsA( [in] LPCSTR FirstTask, [in] LPCSTR SecondTask, [in, out] LPDWORD TaskIndex );
[PInvokeData("avrt.h", MSDNShortId = "NF:avrt.AvSetMmMaxThreadCharacteristicsA")]
[DllImport(Lib_Avrt, SetLastError = true, CharSet = CharSet.Auto)]
public static extern SafeHAVRT AvSetMmMaxThreadCharacteristics([MarshalAs(UnmanagedType.LPTStr)] string FirstTask,
[MarshalAs(UnmanagedType.LPTStr)] string SecondTask, ref uint TaskIndex);
/// Associates the calling thread with the specified task.
///
/// The name of the task to be performed. This name must match the name of one of the subkeys of the following key
/// HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Multimedia\SystemProfile\Tasks.
///
///
/// The unique task identifier. The first time this function is called, this value must be 0 on input. The index value is returned on
/// output and can be used as input in subsequent calls.
///
///
/// If the function succeeds, it returns a handle to the task.
/// If the function fails, it returns 0. To retrieve extended error information, call GetLastError.
/// The following are possible error codes.
///
///
/// Return code
/// Description
///
/// -
/// ERROR_INVALID_TASK_INDEX
/// Either TaskIndex is not 0 on the first call or is not recognized value (on subsequent calls).
///
/// -
/// ERROR_INVALID_TASK_NAME
/// The specified task does not match any of the tasks stored in the registry.
///
/// -
/// ERROR_PRIVILEGE_NOT_HELD
/// The caller does not have sufficient privilege.
///
///
///
///
/// When the task is completed, call the AvRevertMmThreadCharacteristics function.
///
/// Note
///
/// The avrt.h header defines AvSetMmThreadCharacteristics as an alias which automatically selects the ANSI or Unicode version of this
/// function based on the definition of the UNICODE preprocessor constant. Mixing usage of the encoding-neutral alias with code that not
/// encoding-neutral can lead to mismatches that result in compilation or runtime errors. For more information, see Conventions for
/// Function Prototypes.
///
///
///
// https://learn.microsoft.com/en-us/windows/win32/api/avrt/nf-avrt-avsetmmthreadcharacteristicsa AVRTAPI HANDLE
// AvSetMmThreadCharacteristicsA( [in] LPCSTR TaskName, [in, out] LPDWORD TaskIndex );
[PInvokeData("avrt.h", MSDNShortId = "NF:avrt.AvSetMmThreadCharacteristicsA")]
[DllImport(Lib_Avrt, SetLastError = true, CharSet = CharSet.Auto)]
public static extern SafeHAVRT AvSetMmThreadCharacteristics([MarshalAs(UnmanagedType.LPTStr)] string TaskName, ref uint TaskIndex);
/// Adjusts the thread priority of the calling thread relative to other threads performing the same task.
///
/// A handle to the task. This handle is returned by the AvSetMmThreadCharacteristics or AvSetMmMaxThreadCharacteristics function.
///
///
///
/// The relative thread priority of this thread to other threads performing a similar task. This parameter can be one of the following values.
///
/// AVRT_PRIORITY_CRITICAL (2)
/// AVRT_PRIORITY_HIGH (1)
/// AVRT_PRIORITY_LOW (-1)
/// AVRT_PRIORITY_NORMAL (0)
///
///
/// 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.
///
// https://learn.microsoft.com/en-us/windows/win32/api/avrt/nf-avrt-avsetmmthreadpriority AVRTAPI BOOL AvSetMmThreadPriority( [in] HANDLE
// AvrtHandle, [in] AVRT_PRIORITY Priority );
[PInvokeData("avrt.h", MSDNShortId = "NF:avrt.AvSetMmThreadPriority")]
[DllImport(Lib_Avrt, SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool AvSetMmThreadPriority([In] HAVRT AvrtHandle, [In] AVRT_PRIORITY Priority);
/// Provides a handle to an AvRt task.
[StructLayout(LayoutKind.Sequential)]
public readonly struct HAVRT : IHandle
{
private readonly IntPtr handle;
/// Initializes a new instance of the struct.
/// An object that represents the pre-existing handle to use.
public HAVRT(IntPtr preexistingHandle) => handle = preexistingHandle;
/// Returns an invalid handle by instantiating a object with .
public static HAVRT NULL => new(IntPtr.Zero);
/// Gets a value indicating whether this instance is a null handle.
public bool IsNull => handle == IntPtr.Zero;
/// Implements the operator !.
/// The handle.
/// The result of the operator.
public static bool operator !(HAVRT h1) => h1.IsNull;
/// Performs an explicit conversion from to .
/// The handle.
/// The result of the conversion.
public static explicit operator IntPtr(HAVRT h) => h.handle;
/// Performs an implicit conversion from to .
/// The pointer to a handle.
/// The result of the conversion.
public static implicit operator HAVRT(IntPtr h) => new(h);
/// Implements the operator !=.
/// The first handle.
/// The second handle.
/// The result of the operator.
public static bool operator !=(HAVRT h1, HAVRT h2) => !(h1 == h2);
/// Implements the operator ==.
/// The first handle.
/// The second handle.
/// The result of the operator.
public static bool operator ==(HAVRT h1, HAVRT h2) => h1.Equals(h2);
///
public override bool Equals(object obj) => obj is HAVRT h && handle == h.handle;
///
public override int GetHashCode() => handle.GetHashCode();
///
public IntPtr DangerousGetHandle() => handle;
}
/// Provides a for that is disposed using .
public class SafeHAVRT : 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 SafeHAVRT(IntPtr preexistingHandle, bool ownsHandle = true) : base(preexistingHandle, ownsHandle) { }
/// Initializes a new instance of the class.
private SafeHAVRT() : base() { }
/// Performs an implicit conversion from to .
/// The safe handle instance.
/// The result of the conversion.
public static implicit operator HAVRT(SafeHAVRT h) => h.handle;
///
protected override bool InternalReleaseHandle() => AvRevertMmThreadCharacteristics((HAVRT)handle);
}
}