using System; using System.Runtime.InteropServices; using Vanara.Extensions; using Vanara.PInvoke; using static Vanara.PInvoke.Ole32; using static Vanara.PInvoke.Shell32; namespace Vanara.Windows.Shell { /// Exposed methods from . [ComVisible(false)] public interface IComObject { /// Creates an uninitialized object. /// /// A reference to the identifier of the interface to be used to communicate with the newly created object. This parameter is /// generally the IID of the initializing interface. /// /// /// The interface pointer requested in . If the object does not support the interface specified in /// , the implementation must return . /// object QueryInterface(in Guid riid); /// Quits the message loop by sending PostQuitMessage. /// The exit code. void QuitMessageLoop(int exitCode = 0); /// Runs the message loop. /// /// 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 method is called or the message loop receives a quit message. /// void Run(TimeSpan timeout = default); } /// /// Base class for all COM objects which handles calling AddRef and Release for the assembly, connection to IClassFactory, implements /// IObjectWithSite, using an internal message loop, and a mechanism to issue a non-blocking call to itself. Once implemented, you only /// need to implement your own interfaces. The IClassFactory implementation can get any derived interfaces through casting for calls to /// its QueryInterface method. If you want more control, override the QueryInterface method in this class. /// /// /// public abstract class ComObject : IComObject, IDisposable, IObjectWithSite { private readonly CLSCTX ctx; private readonly REGCLS use; private bool disposedValue = false; private MessageLoop msgLoop = new(); /// Initializes a new instance of the class. protected ComObject() : this(CLSCTX.CLSCTX_LOCAL_SERVER, REGCLS.REGCLS_MULTIPLEUSE | REGCLS.REGCLS_SUSPENDED) { } /// Initializes a new instance of the class. /// The context within which the COM object is to be run. /// Indicates how connections are made to the class object. protected ComObject(CLSCTX classContext, REGCLS classUse) { ctx = classContext; use = classUse; CoAddRefServerProcess(); } /// Gets or sets the site exposed by . /// The site object. public virtual object Site { get; set; } /// /// Cancels the timeout specified in the method. 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. /// public void CancelTimeout() => msgLoop.CancelTimeout(); /// Creates an uninitialized object. /// /// A reference to the identifier of the interface to be used to communicate with the newly created object. This parameter is /// generally the IID of the initializing interface. /// /// /// The interface pointer requested in . If the object does not support the interface specified in /// , the implementation must return . /// public virtual object QueryInterface(in Guid riid) => ShellUtil.QueryInterface(this, riid); /// /// Queues a non-blocking callback. This is useful in situations where a method cannot block an implemented method but further /// processing is needed. For example, IDropTarget::DragDrop and IExecuteCommand::Execute. /// /// The callback method. /// An optional object that will be passed to the callback. public void QueueNonBlockingCallback(Action callback, [Optional] object tag) => msgLoop.QueueCallback(callback, tag); /// Quits the message loop by sending PostQuitMessage. /// The exit code. public void QuitMessageLoop(int exitCode = 0) => msgLoop.Quit(exitCode); /// Runs the message loop. /// /// 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 method is called or the message loop receives a quit message. /// public void Run(TimeSpan timeout = default) { if (msgLoop.Running) return; using (var cf = new ComClassFactory(this, ctx, use)) { if (use.IsFlagSet(REGCLS.REGCLS_SUSPENDED)) cf.Resume(); msgLoop.Run(timeout); } System.Diagnostics.Debug.WriteLine("ComObject.Run ended."); } /// void IDisposable.Dispose() => Dispose(true); /// HRESULT IObjectWithSite.GetSite(in Guid riid, out object ppvSite) { ppvSite = null; return Site is null ? HRESULT.E_FAIL : ShellUtil.QueryInterface(Site, riid, out ppvSite); } /// HRESULT IObjectWithSite.SetSite([In, MarshalAs(UnmanagedType.IUnknown)] object pUnkSite) { Site = pUnkSite; return HRESULT.S_OK; } /// Releases the unmanaged resources used by this object, and optionally releases the managed resources. /// /// to release both managed and unmanaged resources; to release only unmanaged resources. /// protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { QuitMessageLoop(); CoReleaseServerProcess(); } disposedValue = true; } } } }