Skip to content

Commit

Permalink
Xml comments.
Browse files Browse the repository at this point in the history
  • Loading branch information
mikasoukhov committed Mar 2, 2025
1 parent c4c7f66 commit 7e7f5d8
Show file tree
Hide file tree
Showing 18 changed files with 692 additions and 25 deletions.
39 changes: 39 additions & 0 deletions Interop.Windows/Dde/DdeSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,39 @@
using Ecng.Common;
using Ecng.Serialization;

/// <summary>
/// Represents the settings for DDE (Dynamic Data Exchange) communication.
/// </summary>
[DisplayName("DDE settings")]
public class DdeSettings : Cloneable<DdeSettings>, IPersistable
{
/// <summary>
/// Initializes a new instance of the <see cref="DdeSettings"/> class with default values.
/// </summary>
public DdeSettings()
{
Server = "EXCEL";
Topic = "[Book1.xlsx]Sheet1";
}

/// <summary>
/// Gets or sets the DDE server name.
/// </summary>
[Display(Name = "Server", Description = "DDE server name.", Order = 0)]
public string Server { get; set; }

/// <summary>
/// Gets or sets the DDE topic name (for example, "[Book1.xlsx]Sheet1").
/// </summary>
[Display(Name = "Topic", Description = "Topic name (like [Book1.xlsx].Sheet1).", Order = 1)]
public string Topic { get; set; }

private int _columnOffset;

/// <summary>
/// Gets or sets the column offset from the left top corner.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">Thrown when a negative value is assigned.</exception>
[Display(Name = "Column offset", Description = "Column offset from left top corner.", Order = 2)]
public int ColumnOffset
{
Expand All @@ -39,6 +55,10 @@ public int ColumnOffset

private int _rowOffset;

/// <summary>
/// Gets or sets the row offset from the left top corner.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">Thrown when a negative value is assigned.</exception>
[Display(Name = "Row offset", Description = "Row offset from left top corner.", Order = 2)]
public int RowOffset
{
Expand All @@ -52,19 +72,34 @@ public int RowOffset
}
}

/// <summary>
/// Gets or sets a value indicating whether header names should be displayed.
/// </summary>
[Display(Name = "Headers", Description = "Show headers name.", Order = 2)]
public bool ShowHeaders { get; set; }

/// <summary>
/// Applies the settings from the specified <see cref="DdeSettings"/> instance.
/// </summary>
/// <param name="clone">The instance containing the new settings.</param>
public void Apply(DdeSettings clone)
{
PersistableHelper.Apply(this, clone);
}

/// <summary>
/// Creates a deep copy of the current <see cref="DdeSettings"/> instance.
/// </summary>
/// <returns>A new instance that is a deep copy of this instance.</returns>
public override DdeSettings Clone()
{
return PersistableHelper.Clone(this);
}

/// <summary>
/// Loads the settings from the provided <see cref="SettingsStorage"/>.
/// </summary>
/// <param name="storage">The storage from which to load the settings.</param>
public void Load(SettingsStorage storage)
{
Server = storage.GetValue<string>(nameof(Server));
Expand All @@ -74,6 +109,10 @@ public void Load(SettingsStorage storage)
ShowHeaders = storage.GetValue<bool>(nameof(ShowHeaders));
}

/// <summary>
/// Saves the current settings to the provided <see cref="SettingsStorage"/>.
/// </summary>
/// <param name="storage">The storage to which the settings will be saved.</param>
public void Save(SettingsStorage storage)
{
storage
Expand Down
25 changes: 25 additions & 0 deletions Interop.Windows/Dde/XlsDdeClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,36 @@

using NDde.Client;

/// <summary>
/// A client for interacting with Excel via Dynamic Data Exchange (DDE), providing methods to start, stop, and send data.
/// </summary>
public class XlsDdeClient(DdeSettings settings) : Disposable
{
private DdeClient _client;

/// <summary>
/// Gets a value indicating whether the DDE client is currently started and connected.
/// </summary>
public bool IsStarted => _client != null;

/// <summary>
/// Gets the settings used to configure the DDE client.
/// </summary>
/// <exception cref="ArgumentNullException">Thrown when the settings provided during construction are null.</exception>
public DdeSettings Settings { get; } = settings ?? throw new ArgumentNullException(nameof(settings));

/// <summary>
/// Starts the DDE client and establishes a connection to the specified server and topic.
/// </summary>
public void Start()
{
_client = new DdeClient(Settings.Server, Settings.Topic);
_client.Connect();
}

/// <summary>
/// Stops the DDE client and disconnects from the server if currently connected.
/// </summary>
public void Stop()
{
if (_client.IsConnected)
Expand All @@ -29,6 +45,12 @@ public void Stop()
_client = null;
}

/// <summary>
/// Sends a block of data to Excel via DDE using the specified row and column offsets.
/// </summary>
/// <param name="rows">A list of rows, where each row is a list of cell values to send.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="rows"/> is null.</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="rows"/> is empty.</exception>
public void Poke(IList<IList<object>> rows)
{
if (rows is null)
Expand All @@ -48,6 +70,9 @@ public void Poke(IList<IList<object>> rows)
XlsDdeSerializer.Serialize(rows), 0x0090 | 0x4000, (int)TimeSpan.FromSeconds(10).TotalMilliseconds);
}

/// <summary>
/// Releases managed resources by stopping the DDE client if it is started.
/// </summary>
protected override void DisposeManaged()
{
if (IsStarted)
Expand Down
15 changes: 15 additions & 0 deletions Interop.Windows/Dde/XlsDdeSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@

using Ecng.Common;

/// <summary>
/// Provides methods for serializing and deserializing data to and from a format compatible with Excel DDE.
/// </summary>
static class XlsDdeSerializer
{
private enum DataTypes : short
Expand Down Expand Up @@ -54,6 +57,12 @@ private static DataTypes GetXlsType(object cell)
throw new ArgumentException($"Unknown cell value type '{cell.GetType()}'.", nameof(cell));
}

/// <summary>
/// Serializes a table of data into a byte array compatible with Excel DDE.
/// </summary>
/// <param name="rows">A list of rows, where each row is a list of cell values to serialize.</param>
/// <returns>A byte array representing the serialized data.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when an unsupported cell type is encountered.</exception>
public static byte[] Serialize(IList<IList<object>> rows)
{
var stream = new MemoryStream();
Expand Down Expand Up @@ -101,6 +110,12 @@ public static byte[] Serialize(IList<IList<object>> rows)
return stream.ToArray();
}

/// <summary>
/// Deserializes a byte array from Excel DDE format into a table of data.
/// </summary>
/// <param name="data">The byte array containing the serialized DDE data.</param>
/// <returns>A list of rows, where each row is a list of cell values.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="data"/> is null.</exception>
public static IList<IList<object>> Deserialize(byte[] data)
{
if (data is null)
Expand Down
33 changes: 33 additions & 0 deletions Interop.Windows/Dde/XlsDdeServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,34 @@ namespace Ecng.Interop.Dde

using NDde.Server;

/// <summary>
/// Provides a DDE server for Excel that handles poke requests and advises clients of updated data.
/// </summary>
[CLSCompliant(false)]
public class XlsDdeServer(string service, Action<string, IList<IList<object>>> poke, Action<Exception> error) : DdeServer(service)
{
/// <summary>
/// Private helper class that dispatches events on dedicated threads.
/// </summary>
private class EventDispatcher(Action<Exception> errorHandler) : Disposable
{
private readonly Action<Exception> _errorHandler = errorHandler ?? throw new ArgumentNullException(nameof(errorHandler));
private readonly SynchronizedDictionary<string, BlockingQueue<Action>> _events = [];

/// <summary>
/// Adds an event to be executed.
/// </summary>
/// <param name="evt">The event action to add.</param>
public void Add(Action evt)
{
Add(evt, string.Empty);
}

/// <summary>
/// Adds an event to be executed with a synchronization token.
/// </summary>
/// <param name="evt">The event action to add.</param>
/// <param name="syncToken">The synchronization token to group events.</param>
public virtual void Add(Action evt, string syncToken)
{
if (evt is null)
Expand Down Expand Up @@ -63,6 +78,9 @@ private static BlockingQueue<Action> CreateNewThreadQueuePair(string syncToken)
return queue;
}

/// <summary>
/// Disposes the managed resources by closing all event queues.
/// </summary>
protected override void DisposeManaged()
{
_events.SyncDo(d => d.ForEach(p => p.Value.Close()));
Expand All @@ -76,6 +94,9 @@ protected override void DisposeManaged()
private readonly Action<string, IList<IList<object>>> _poke = poke ?? throw new ArgumentNullException(nameof(poke));
private readonly Action<Exception> _error = error ?? throw new ArgumentNullException(nameof(error));

/// <summary>
/// Starts the DDE server and initializes the timer to advise clients.
/// </summary>
public void Start()
{
Exception error = null;
Expand Down Expand Up @@ -125,6 +146,14 @@ public void Start()
.Interval(TimeSpan.FromSeconds(1));
}

/// <summary>
/// Handles poke requests from DDE conversations.
/// </summary>
/// <param name="conversation">The DDE conversation instance.</param>
/// <param name="item">The item name requested.</param>
/// <param name="data">The data payload in byte array format.</param>
/// <param name="format">The format of the data received.</param>
/// <returns>A <see cref="PokeResult"/> indicating that the poke has been processed.</returns>
protected override PokeResult OnPoke(DdeConversation conversation, string item, byte[] data, int format)
{
_dispather.Add(() =>
Expand All @@ -136,6 +165,10 @@ protected override PokeResult OnPoke(DdeConversation conversation, string item,
return PokeResult.Processed;
}

/// <summary>
/// Releases the unmanaged resources and, optionally, the managed resources.
/// </summary>
/// <param name="disposing">True to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
protected override void Dispose(bool disposing)
{
_dispather.Dispose();
Expand Down
20 changes: 18 additions & 2 deletions Interop.Windows/WinApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,20 @@

using Microsoft.Win32;

///<summary>
///</summary>
/// <summary>
/// Provides Windows API utility methods.
/// </summary>
[CLSCompliant(false)]
public static class WinApi
{
/// <summary>
/// Retrieves the screen boundaries (left, top, width, height) for the screen that contains the specified window handle.
/// </summary>
/// <param name="hwnd">The handle of the window.</param>
/// <param name="left">Output parameter that returns the left coordinate of the screen.</param>
/// <param name="top">Output parameter that returns the top coordinate of the screen.</param>
/// <param name="width">Output parameter that returns the width of the screen.</param>
/// <param name="height">Output parameter that returns the height of the screen.</param>
public static void GetScreenParams(IntPtr hwnd, out int left, out int top, out int width, out int height)
{
var activeScreen = Screen.FromHandle(hwnd);
Expand All @@ -25,6 +34,13 @@ public static void GetScreenParams(IntPtr hwnd, out int left, out int top, out i

private static RegistryKey BaseKey => Registry.CurrentUser;

/// <summary>
/// Updates the auto-run registry entry for a given application.
/// </summary>
/// <param name="appName">The name of the application.</param>
/// <param name="path">The full path to the application executable.</param>
/// <param name="enabled">True to enable auto-run; false to disable it.</param>
/// <exception cref="InvalidOperationException">Thrown when the autorun registry key cannot be found.</exception>
public static void UpdateAutoRun(string appName, string path, bool enabled)
{
using var key = BaseKey.OpenSubKey(_path, true) ?? throw new InvalidOperationException($"autorun not found ({_path})");
Expand Down
13 changes: 12 additions & 1 deletion Interop.Windows/WindowsGrandAccess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@
using Ecng.Common;

// https://stackoverflow.com/a/30687230/8029915

/// <summary>
/// Provides methods to modify access rights for the Windows window station and desktop.
/// </summary>
public static class WindowsGrandAccess
{
private class Token(WindowsGrandAccess.GenericSecurity wsSecurity, WindowsGrandAccess.GenericSecurity dsSecurity, int? oldWindowStationMaskm, int? oldDesktopMask) : Disposable
private class Token(GenericSecurity wsSecurity, WindowsGrandAccess.GenericSecurity dsSecurity, int? oldWindowStationMaskm, int? oldDesktopMask) : Disposable
{
private readonly GenericSecurity _wsSecurity = wsSecurity ?? throw new ArgumentNullException(nameof(wsSecurity));
private readonly GenericSecurity _dsSecurity = dsSecurity ?? throw new ArgumentNullException(nameof(dsSecurity));
Expand All @@ -35,6 +39,13 @@ protected override void DisposeManaged()
private const int _desktopRightsAllAccess = 0x000f01ff;
private const int _windowStationAllAccess = 0x000f037f;

/// <summary>
/// Grants full access rights to the current process window station and thread desktop for the specified user.
/// </summary>
/// <param name="username">The user account to which access rights are to be granted.</param>
/// <returns>
/// An <see cref="IDisposable"/> token that, when disposed, restores the original access rights.
/// </returns>
public static IDisposable GrantAccessToWindowStationAndDesktop(string username)
{
var wsHandle = new NoopSafeHandle(PInvoke.GetProcessWindowStation());
Expand Down
29 changes: 29 additions & 0 deletions Interop.Windows/WindowsThreadingHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,17 @@

using Ecng.Common;

/// <summary>
/// Provides helper methods to run code on threads with specific apartment states.
/// </summary>
public static class WindowsThreadingHelper
{
/// <summary>
/// Sets the apartment state of the specified thread to single-threaded apartment (STA).
/// </summary>
/// <param name="thread">The thread to set the apartment state for.</param>
/// <returns>The same thread with the updated apartment state.</returns>
/// <exception cref="ArgumentNullException">Thrown when the thread is null.</exception>
public static Thread STA(this Thread thread)
{
if (thread is null)
Expand All @@ -16,6 +25,12 @@ public static Thread STA(this Thread thread)
return thread;
}

/// <summary>
/// Sets the apartment state of the specified thread to multi-threaded apartment (MTA).
/// </summary>
/// <param name="thread">The thread to set the apartment state for.</param>
/// <returns>The same thread with the updated apartment state.</returns>
/// <exception cref="ArgumentNullException">Thrown when the thread is null.</exception>
public static Thread MTA(this Thread thread)
{
if (thread is null)
Expand All @@ -25,6 +40,11 @@ public static Thread MTA(this Thread thread)
return thread;
}

/// <summary>
/// Invokes the specified action on a new STA (single-threaded apartment) thread.
/// </summary>
/// <param name="action">The action to invoke.</param>
/// <exception cref="ArgumentNullException">Thrown when the action is null.</exception>
public static void InvokeAsSTA(this Action action)
{
if (action is null)
Expand All @@ -38,6 +58,15 @@ public static void InvokeAsSTA(this Action action)
}

// http://stackoverflow.com/questions/518701/clipboard-gettext-returns-null-empty-string

/// <summary>
/// Invokes the specified function on a new STA (single-threaded apartment) thread and returns a result.
/// </summary>
/// <typeparam name="T">The type of the return value.</typeparam>
/// <param name="func">The function to invoke.</param>
/// <returns>The result returned by the function.</returns>
/// <exception cref="ArgumentNullException">Thrown when the function is null.</exception>
/// <exception cref="Exception">Throws any exception that occurs during the function execution.</exception>
public static T InvokeAsSTA<T>(this Func<T> func)
{
if (func is null)
Expand Down
Loading

0 comments on commit 7e7f5d8

Please sign in to comment.