2019-01-31 13:33:27 -05:00
|
|
|
|
using System;
|
2019-03-07 13:43:18 -05:00
|
|
|
|
using static Vanara.PInvoke.Kernel32;
|
2019-01-31 13:33:27 -05:00
|
|
|
|
using static Vanara.PInvoke.User32;
|
|
|
|
|
|
|
|
|
|
namespace Vanara.PInvoke
|
|
|
|
|
{
|
2019-02-01 17:12:08 -05:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// <para>
|
|
|
|
|
/// This class encapsulates the management of a message loop for an application. It supports queuing 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.
|
|
|
|
|
/// </para>
|
|
|
|
|
/// <note type="note">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.</note>
|
|
|
|
|
/// </summary>
|
2019-01-31 13:33:27 -05:00
|
|
|
|
public class MessageLoop
|
|
|
|
|
{
|
2019-02-02 15:35:57 -05:00
|
|
|
|
private readonly uint curThreadId;
|
2019-01-31 13:33:27 -05:00
|
|
|
|
private Action<object> appCallback;
|
2019-02-01 13:39:42 -05:00
|
|
|
|
private uint callbackMsg;
|
2019-01-31 13:33:27 -05:00
|
|
|
|
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>
|
2019-03-07 13:43:18 -05:00
|
|
|
|
public MessageLoop() => curThreadId = GetCurrentThreadId();
|
2019-01-31 13:33:27 -05:00
|
|
|
|
|
2019-02-02 15:35:57 -05:00
|
|
|
|
/// <summary>Occurs when a new message is available.</summary>
|
2019-10-03 14:55:32 -04:00
|
|
|
|
public event EventHandler<MessageEventArgs> ProcessMessage;
|
2019-02-02 15:35:57 -05:00
|
|
|
|
|
2019-01-31 13:33:27 -05:00
|
|
|
|
/// <summary>Gets a value indicating whether this <see cref="MessageLoop"/> is running.</summary>
|
2019-02-02 15:35:57 -05:00
|
|
|
|
/// <value><see langword="true"/> if running; otherwise, <see langword="false"/>.</value>
|
2019-01-31 13:33:27 -05:00
|
|
|
|
public virtual bool Running { get; private set; } = false;
|
|
|
|
|
|
2019-02-01 17:12:08 -05:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Cancel the timeout timer. This should be called when the application knows that it wants to keep running, for example when it
|
|
|
|
|
/// receives the incoming call to invoke the verb.
|
|
|
|
|
/// </summary>
|
2019-01-31 13:33:27 -05:00
|
|
|
|
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");
|
2019-02-01 13:39:42 -05:00
|
|
|
|
PostThreadMessage(curThreadId, callbackMsg);
|
2019-01-31 13:33:27 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <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}");
|
2019-02-02 15:35:57 -05:00
|
|
|
|
try { ProcessMessage?.Invoke(this, new MessageEventArgs(msg)); } catch { }
|
2019-01-31 13:33:27 -05:00
|
|
|
|
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;
|
|
|
|
|
}
|
2019-02-02 15:35:57 -05:00
|
|
|
|
|
|
|
|
|
/// <summary>Holds a copy of the MSG instance retrieved by GetMessage.</summary>
|
|
|
|
|
/// <seealso cref="System.EventArgs"/>
|
|
|
|
|
public class MessageEventArgs : EventArgs
|
|
|
|
|
{
|
|
|
|
|
internal MessageEventArgs(MSG msg) => MSG = msg;
|
|
|
|
|
|
|
|
|
|
/// <summary>Gets or sets the MSG.</summary>
|
|
|
|
|
/// <value>The MSG.</value>
|
|
|
|
|
public MSG MSG { get; set; }
|
|
|
|
|
}
|
2019-01-31 13:33:27 -05:00
|
|
|
|
}
|
|
|
|
|
}
|