mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-24 04:00:02 +01:00
make rot
This commit is contained in:
@@ -5,58 +5,92 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using ManagedCommon;
|
||||
|
||||
namespace Awake
|
||||
#nullable enable
|
||||
#pragma warning disable IL2050 // Suppress COM interop trimming warnings for ROT hosting P/Invokes (desktop only scenario)
|
||||
|
||||
namespace ManagedCommon
|
||||
{
|
||||
/// <summary>
|
||||
/// Background ROT host for the automation object. No registry / class factory registration; discovery is by moniker.
|
||||
/// Generic helper to host a single COM-visible automation object in the Running Object Table (ROT)
|
||||
/// without registry/CLSID class factory registration. Used for lightweight cross-process automation.
|
||||
/// Pattern: create instance -> register with moniker -> wait until Stop.
|
||||
/// Threading: spins up a dedicated STA thread so objects needing STA semantics are safe.
|
||||
/// </summary>
|
||||
internal static class ComServerHost
|
||||
public sealed class RotSingletonHost : IDisposable
|
||||
{
|
||||
private const string DefaultMonikerName = "Awake.Automation";
|
||||
private readonly Lock _sync = new();
|
||||
private readonly Func<object> _factory;
|
||||
private readonly string _monikerName;
|
||||
private readonly string _threadName;
|
||||
private readonly ManualResetEvent _shutdown = new(false);
|
||||
|
||||
private static readonly object SyncLock = new();
|
||||
private static readonly ManualResetEvent ShutdownEvent = new(false);
|
||||
private Thread? _thread;
|
||||
private int _rotCookie;
|
||||
private object? _instance; // keep alive
|
||||
private IMoniker? _moniker;
|
||||
private bool _disposed;
|
||||
|
||||
private static Thread? _rotThread;
|
||||
private static int _rotCookie;
|
||||
private static object? _automationInstance; // keep alive
|
||||
private static IMoniker? _moniker;
|
||||
|
||||
public static void StartBackground(string? monikerName = null)
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RotSingletonHost"/> class.
|
||||
/// </summary>
|
||||
/// <param name="monikerName">Moniker name (logical unique id), e.g. "Awake.Automation".</param>
|
||||
/// <param name="factory">Factory that creates the object to expose. Should return a COM-visible object.</param>
|
||||
/// <param name="threadName">Optional thread name for diagnostics.</param>
|
||||
public RotSingletonHost(string monikerName, Func<object> factory, string? threadName = null)
|
||||
{
|
||||
lock (SyncLock)
|
||||
_monikerName = string.IsNullOrWhiteSpace(monikerName) ? throw new ArgumentException("Moniker required", nameof(monikerName)) : monikerName;
|
||||
_factory = factory ?? throw new ArgumentNullException(nameof(factory));
|
||||
_threadName = threadName ?? $"RotHost:{_monikerName}";
|
||||
}
|
||||
|
||||
public bool IsRunning => _thread != null;
|
||||
|
||||
public string MonikerName => _monikerName;
|
||||
|
||||
public void Start()
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
if (_rotThread != null)
|
||||
if (_disposed)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
}
|
||||
|
||||
if (_thread != null)
|
||||
{
|
||||
return; // already running
|
||||
}
|
||||
|
||||
_thread = new Thread(ThreadMain)
|
||||
{
|
||||
IsBackground = true,
|
||||
Name = _threadName,
|
||||
};
|
||||
_thread.SetApartmentState(ApartmentState.STA);
|
||||
_thread.Start();
|
||||
Logger.LogInfo($"ROT host starting for moniker '{_monikerName}'");
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
if (_thread == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string name = string.IsNullOrWhiteSpace(monikerName) ? DefaultMonikerName : monikerName!;
|
||||
_rotThread = new Thread(() => RotThreadMain(name))
|
||||
{
|
||||
Name = "AwakeAutomationRotThread",
|
||||
IsBackground = true,
|
||||
};
|
||||
_rotThread.SetApartmentState(ApartmentState.STA);
|
||||
_rotThread.Start();
|
||||
Logger.LogInfo($"Starting Awake automation ROT host with moniker '{name}'");
|
||||
}
|
||||
}
|
||||
|
||||
public static void Stop()
|
||||
{
|
||||
lock (SyncLock)
|
||||
{
|
||||
ShutdownEvent.Set();
|
||||
_shutdown.Set();
|
||||
}
|
||||
|
||||
_rotThread?.Join(3000);
|
||||
_rotThread = null;
|
||||
_thread?.Join(3000);
|
||||
_thread = null;
|
||||
_shutdown.Reset();
|
||||
}
|
||||
|
||||
private static void RotThreadMain(string monikerName)
|
||||
private void ThreadMain()
|
||||
{
|
||||
int hr = Ole32.CoInitializeEx(IntPtr.Zero, Ole32.CoinitApartmentThreaded);
|
||||
if (hr < 0)
|
||||
@@ -74,18 +108,18 @@ namespace Awake
|
||||
return;
|
||||
}
|
||||
|
||||
hr = Ole32.CreateItemMoniker("!", monikerName, out _moniker);
|
||||
hr = Ole32.CreateItemMoniker("!", _monikerName, out _moniker);
|
||||
if (hr < 0 || _moniker == null)
|
||||
{
|
||||
Logger.LogError($"CreateItemMoniker failed: 0x{hr:X8}");
|
||||
return;
|
||||
}
|
||||
|
||||
_automationInstance = new AwakeAutomation();
|
||||
var unk = Marshal.GetIUnknownForObject(_automationInstance);
|
||||
_instance = _factory();
|
||||
var unk = Marshal.GetIUnknownForObject(_instance);
|
||||
try
|
||||
{
|
||||
hr = rot.Register(0x1 /* ROTFLAGS_REGISTRATIONKEEPSALIVE */, _automationInstance, _moniker, out _rotCookie);
|
||||
hr = rot.Register(0x1 /* ROTFLAGS_REGISTRATIONKEEPSALIVE */, _instance, _moniker, out _rotCookie);
|
||||
if (hr < 0)
|
||||
{
|
||||
Logger.LogError($"IRunningObjectTable.Register failed: 0x{hr:X8}");
|
||||
@@ -97,20 +131,20 @@ namespace Awake
|
||||
Marshal.Release(unk);
|
||||
}
|
||||
|
||||
Logger.LogInfo("Awake automation registered in ROT.");
|
||||
WaitHandle.WaitAny(new WaitHandle[] { ShutdownEvent });
|
||||
Logger.LogInfo($"ROT registered: '{_monikerName}'");
|
||||
WaitHandle.WaitAny(new WaitHandle[] { _shutdown });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Automation ROT exception: {ex}");
|
||||
Logger.LogError($"ROT host exception: {ex}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_rotCookie != 0 && Ole32.GetRunningObjectTable(0, out var rot) == 0 && rot != null)
|
||||
if (_rotCookie != 0 && Ole32.GetRunningObjectTable(0, out var rot2) == 0 && rot2 != null)
|
||||
{
|
||||
rot.Revoke(_rotCookie);
|
||||
rot2.Revoke(_rotCookie);
|
||||
_rotCookie = 0;
|
||||
}
|
||||
}
|
||||
@@ -120,14 +154,26 @@ namespace Awake
|
||||
}
|
||||
|
||||
Ole32.CoUninitialize();
|
||||
Logger.LogInfo("Awake automation ROT host stopped.");
|
||||
Logger.LogInfo($"ROT host stopped: '{_monikerName}'");
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Stop();
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
private static class Ole32
|
||||
{
|
||||
internal const int CoinitApartmentThreaded = 0x2;
|
||||
|
||||
#pragma warning disable IL2050 // Suppress trimming warnings for COM interop P/Invokes; ROT hosting not used in trimmed scenarios.
|
||||
[DllImport("ole32.dll")]
|
||||
internal static extern int CoInitializeEx(IntPtr pvReserved, int dwCoInit);
|
||||
|
||||
@@ -139,6 +185,7 @@ namespace Awake
|
||||
|
||||
[DllImport("ole32.dll")]
|
||||
internal static extern int CreateItemMoniker([MarshalAs(UnmanagedType.LPWStr)] string lpszDelim, [MarshalAs(UnmanagedType.LPWStr)] string lpszItem, out IMoniker? ppmk);
|
||||
#pragma warning restore IL2050
|
||||
}
|
||||
|
||||
[ComImport]
|
||||
@@ -98,8 +98,9 @@ namespace Awake
|
||||
|
||||
// Start background COM automation host (ROT) so any startup path exposes the automation surface.
|
||||
// Uses default moniker; could be extended with a --rotname parameter if needed later.
|
||||
ComServerHost.StartBackground();
|
||||
AppDomain.CurrentDomain.ProcessExit += (_, _) => ComServerHost.Stop();
|
||||
var rotHost = new RotSingletonHost("Awake.Automation", () => new AwakeAutomation(), "AwakeAutomationRotThread");
|
||||
rotHost.Start();
|
||||
AppDomain.CurrentDomain.ProcessExit += (_, _) => rotHost.Stop();
|
||||
|
||||
TaskScheduler.UnobservedTaskException += (sender, args) =>
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user