mirror of https://github.com/dahall/Vanara.git
Renamed, made public, documented and hardened
parent
ed1eac8f41
commit
14d863faa5
|
@ -1,86 +0,0 @@
|
|||
using System;
|
||||
using static Vanara.PInvoke.User32;
|
||||
using static Vanara.PInvoke.User32_Gdi;
|
||||
|
||||
namespace Vanara.Windows.Shell
|
||||
{
|
||||
// This class encapsulates the management of a message loop for an application. It supports queing a callback to the application via the
|
||||
// message loop to enable the app to return from a call and continue processing that call later. This behavior is needed when
|
||||
// implementing a shell verb as verbs must not block the caller.
|
||||
//
|
||||
// Notes: The ComObject derived class should call QueueNonBlockingCallback in its invoke function, for example IExecuteCommand::Execute()
|
||||
// or IDropTarget::Drop() passing a method that will complete the initialization work.
|
||||
internal class ComMessageLoop
|
||||
{
|
||||
// This timer is used to exit the message loop if the the application is activated but not invoked. This is needed if there is a
|
||||
// failure when the verb is being invoked due to low resources or some other uncommon reason. Without this the app would not exit in
|
||||
// this case. This timer needs to be canceled once the app learns that it has should remain running.
|
||||
protected const uint cTimeout = 30 * 1000;
|
||||
|
||||
private Action<object> appCallback;
|
||||
private object callbackObj;
|
||||
private IntPtr postTimerId; // timer id used to to queue a callback to the app
|
||||
private IntPtr timeoutTimerId; // timer id used to exit the app if the app is not called back within a certain time
|
||||
|
||||
public ComMessageLoop() => timeoutTimerId = SetTimer(default, default, cTimeout, null);
|
||||
|
||||
public bool Running { get; private set; } = false;
|
||||
|
||||
// Cancel the timeout timer. This should be called when the appliation knows that it wants to keep running, for example when it
|
||||
// recieves the incomming call to invoke the verb, this is done implictly when the app queues a callback.
|
||||
public void CancelTimeout()
|
||||
{
|
||||
if (postTimerId != default)
|
||||
{
|
||||
KillTimer(default, timeoutTimerId);
|
||||
timeoutTimerId = default;
|
||||
}
|
||||
}
|
||||
|
||||
public void QueueAppCallback(Action<object> callback, object tag = null)
|
||||
{
|
||||
// queue a callback on OnAppCallback() by setting a timer of zero seconds
|
||||
appCallback = callback;
|
||||
callbackObj = tag;
|
||||
postTimerId = SetTimer(default, default, 0, null);
|
||||
if (postTimerId != default)
|
||||
CancelTimeout();
|
||||
}
|
||||
|
||||
public void Quit(int exitCode = 0)
|
||||
{
|
||||
CancelTimeout();
|
||||
if (Running)
|
||||
PostQuitMessage(exitCode);
|
||||
}
|
||||
|
||||
public void RunMessageLoop()
|
||||
{
|
||||
const uint WM_TIMER = 0x0113;
|
||||
Running = true;
|
||||
while (GetMessage(out var msg, default, 0, 0))
|
||||
{
|
||||
if (msg.message == WM_TIMER)
|
||||
{
|
||||
KillTimer(default, msg.wParam); // all are one shot timers
|
||||
|
||||
if (msg.wParam == timeoutTimerId)
|
||||
{
|
||||
timeoutTimerId = IntPtr.Zero;
|
||||
}
|
||||
else if (msg.wParam == postTimerId)
|
||||
{
|
||||
postTimerId = IntPtr.Zero;
|
||||
appCallback?.Invoke(callbackObj);
|
||||
appCallback = null;
|
||||
}
|
||||
PostQuitMessage(0);
|
||||
}
|
||||
|
||||
TranslateMessage(msg);
|
||||
DispatchMessage(msg);
|
||||
}
|
||||
Running = false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
using System;
|
||||
using static Vanara.PInvoke.User32;
|
||||
using static Vanara.PInvoke.User32_Gdi;
|
||||
|
||||
namespace Vanara.PInvoke
|
||||
{
|
||||
// This class encapsulates the management of a message loop for an application. It supports queing a callback to the application via the
|
||||
// message loop to enable the app to return from a call and continue processing that call later. This behavior is needed when
|
||||
// implementing a shell verb as verbs must not block the caller.
|
||||
//
|
||||
// Notes: The ComObject derived class should call QueueNonBlockingCallback in its invoke function, for example IExecuteCommand::Execute()
|
||||
// or IDropTarget::Drop() passing a method that will complete the initialization work.
|
||||
public class MessageLoop
|
||||
{
|
||||
private uint callbackMsg;
|
||||
private Action<object> appCallback;
|
||||
private object callbackObj;
|
||||
private IntPtr timeoutTimerId; // timer id used to exit the app if the app is not called back within a certain time
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="MessageLoop"/> class.</summary>
|
||||
public MessageLoop() { }
|
||||
|
||||
/// <summary>Gets a value indicating whether this <see cref="MessageLoop"/> is running.</summary>
|
||||
/// <value><see langword="true" /> if running; otherwise, <see langword="false" />.</value>
|
||||
public virtual bool Running { get; private set; } = false;
|
||||
|
||||
// Cancel the timeout timer. This should be called when the appliation knows that it wants to keep running, for example when it
|
||||
// recieves the incomming call to invoke the verb.
|
||||
public virtual void CancelTimeout()
|
||||
{
|
||||
if (timeoutTimerId == default) return;
|
||||
KillTimer(default, timeoutTimerId);
|
||||
timeoutTimerId = default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queues a one-time callback function via the message loop. This method is not intended to be used simultaneously by multiple callers.
|
||||
/// </summary>
|
||||
/// <param name="callback">The callback delegate method.</param>
|
||||
/// <param name="tag">An optional argument that will be passed to the callback.</param>
|
||||
/// <exception cref="InvalidOperationException">Another callback is currently queued.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="callback"/> cannot be <see langword="null"/>.</exception>
|
||||
public virtual void QueueCallback(Action<object> callback, object tag = null)
|
||||
{
|
||||
if (appCallback != null) throw new InvalidOperationException("Another callback is currently queued.");
|
||||
appCallback = callback ?? throw new ArgumentNullException(nameof(callback));
|
||||
callbackObj = tag;
|
||||
if (callbackMsg == 0)
|
||||
callbackMsg = RegisterWindowMessage(GetType().FullName + "+Callback");
|
||||
PostThreadMessage(Vanara.PInvoke.Kernel32.GetCurrentThreadId(), callbackMsg);
|
||||
}
|
||||
|
||||
/// <summary>Quits the running message loop by calling <see cref="PostQuitMessage"/>.</summary>
|
||||
/// <param name="exitCode">An optional exit code.</param>
|
||||
public virtual void Quit(int exitCode = 0)
|
||||
{
|
||||
CancelTimeout();
|
||||
if (Running)
|
||||
PostQuitMessage(exitCode);
|
||||
}
|
||||
|
||||
/// <summary>Runs the message loop.</summary>
|
||||
/// <param name="timeout">
|
||||
/// The time span after which the message loop will be terminated. If this value equals TimeSpan.Zero or is not specified, the
|
||||
/// message loop will run until the <see cref="Quit"/> method is called or the message loop receives a quit message.
|
||||
/// </param>
|
||||
public virtual void Run(TimeSpan timeout = default)
|
||||
{
|
||||
const uint WM_TIMER = 0x0113;
|
||||
|
||||
if (timeout != TimeSpan.Zero)
|
||||
timeoutTimerId = SetTimer(uElapse: (uint)timeout.TotalMilliseconds);
|
||||
|
||||
Running = true;
|
||||
while (GetMessage(out var msg))
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Message loop: message={msg.message}");
|
||||
if (msg.message == WM_TIMER)
|
||||
{
|
||||
KillTimer(default, msg.wParam);
|
||||
if (msg.wParam == timeoutTimerId)
|
||||
{
|
||||
timeoutTimerId = IntPtr.Zero;
|
||||
PostQuitMessage(0);
|
||||
}
|
||||
}
|
||||
else if (msg.message == callbackMsg)
|
||||
{
|
||||
try { appCallback?.Invoke(callbackObj); } catch { }
|
||||
appCallback = null;
|
||||
callbackObj = null;
|
||||
}
|
||||
TranslateMessage(msg);
|
||||
DispatchMessage(msg);
|
||||
}
|
||||
Running = false;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue