Files
mxaccessgw/src/MxGateway.Worker/MxAccess/MxAccessComServer.cs
T
Joseph Doherty 06030dd1ef Implement MXAccess write commands in the worker
The .proto contract and MxCommandKind already defined Write, Write2,
WriteSecured, and WriteSecured2, but the worker's MxAccessCommandExecutor
had no case for any of them — every write kind fell through to
CreateInvalidRequestReply ("Unsupported MXAccess command kind Write").

Implement all four:

- VariantConverter.ConvertToComValue projects an MxValue into a
  COM-marshalable object (scalars, arrays, null) — the inverse of the
  existing COM-to-MxValue projection.
- IMxAccessServer / MxAccessComServer gain Write/Write2/WriteSecured/
  WriteSecured2, routed to ILMXProxyServer / ILMXProxyServer4.
- MxAccessSession and MxAccessCommandExecutor add the four write paths,
  following the existing ExecuteAdvise pattern; the reply is a plain OK
  reply and the outcome surfaces later as an OnWriteComplete event.

Verified live: a Write now returns PROTOCOL_STATUS_CODE_OK and produces
an OnWriteComplete event where it previously returned InvalidRequest.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 14:45:35 -04:00

235 lines
6.9 KiB
C#

using System;
using ArchestrA.MxAccess;
namespace MxGateway.Worker.MxAccess;
/// <summary>
/// Adapter exposing MXAccess COM object methods through the <see cref="IMxAccessServer"/>
/// interface.
/// </summary>
/// <remarks>
/// The supplied object must implement the typed MXAccess COM interface contract.
/// In production it is the <c>LMXProxyServerClass</c> RCW, which implements
/// <see cref="ILMXProxyServer"/> / <see cref="ILMXProxyServer3"/> /
/// <see cref="ILMXProxyServer4"/>. Tests substitute a typed fake that
/// implements <see cref="IMxAccessServer"/> directly. The earlier late-bound
/// <c>Type.InvokeMember</c> reflection fallback was removed: it bypassed the
/// typed interface contract, boxed value-type handles on every call, and only
/// ever served test doubles — a typed fake is the supported test seam now.
/// </remarks>
public sealed class MxAccessComServer : IMxAccessServer
{
private readonly object mxAccessComObject;
/// <summary>
/// Initializes the adapter with the MXAccess COM object.
/// </summary>
/// <param name="mxAccessComObject">
/// MXAccess COM object instance. Must implement either the typed
/// <see cref="ILMXProxyServer"/> COM interface family (production) or
/// <see cref="IMxAccessServer"/> directly (test fakes).
/// </param>
public MxAccessComServer(object mxAccessComObject)
{
this.mxAccessComObject = mxAccessComObject ?? throw new ArgumentNullException(nameof(mxAccessComObject));
}
/// <inheritdoc />
public int Register(string clientName)
{
if (mxAccessComObject is IMxAccessServer typedFake)
{
return typedFake.Register(clientName);
}
return AsProxyServer().Register(clientName);
}
/// <inheritdoc />
public void Unregister(int serverHandle)
{
if (mxAccessComObject is IMxAccessServer typedFake)
{
typedFake.Unregister(serverHandle);
return;
}
AsProxyServer().Unregister(serverHandle);
}
/// <inheritdoc />
public int AddItem(
int serverHandle,
string itemDefinition)
{
if (mxAccessComObject is IMxAccessServer typedFake)
{
return typedFake.AddItem(serverHandle, itemDefinition);
}
return AsProxyServer().AddItem(serverHandle, itemDefinition);
}
/// <inheritdoc />
public int AddItem2(
int serverHandle,
string itemDefinition,
string itemContext)
{
if (mxAccessComObject is IMxAccessServer typedFake)
{
return typedFake.AddItem2(serverHandle, itemDefinition, itemContext);
}
return AsProxyServer3().AddItem2(serverHandle, itemDefinition, itemContext);
}
/// <inheritdoc />
public void RemoveItem(
int serverHandle,
int itemHandle)
{
if (mxAccessComObject is IMxAccessServer typedFake)
{
typedFake.RemoveItem(serverHandle, itemHandle);
return;
}
AsProxyServer().RemoveItem(serverHandle, itemHandle);
}
/// <inheritdoc />
public void Advise(
int serverHandle,
int itemHandle)
{
if (mxAccessComObject is IMxAccessServer typedFake)
{
typedFake.Advise(serverHandle, itemHandle);
return;
}
AsProxyServer().Advise(serverHandle, itemHandle);
}
/// <inheritdoc />
public void UnAdvise(
int serverHandle,
int itemHandle)
{
if (mxAccessComObject is IMxAccessServer typedFake)
{
typedFake.UnAdvise(serverHandle, itemHandle);
return;
}
AsProxyServer().UnAdvise(serverHandle, itemHandle);
}
/// <inheritdoc />
public void AdviseSupervisory(
int serverHandle,
int itemHandle)
{
if (mxAccessComObject is IMxAccessServer typedFake)
{
typedFake.AdviseSupervisory(serverHandle, itemHandle);
return;
}
AsProxyServer4().AdviseSupervisory(serverHandle, itemHandle);
}
/// <inheritdoc />
public void Write(
int serverHandle,
int itemHandle,
object? value,
int userId)
{
if (mxAccessComObject is IMxAccessServer typedFake)
{
typedFake.Write(serverHandle, itemHandle, value, userId);
return;
}
AsProxyServer().Write(serverHandle, itemHandle, value!, userId);
}
/// <inheritdoc />
public void Write2(
int serverHandle,
int itemHandle,
object? value,
object? timestamp,
int userId)
{
if (mxAccessComObject is IMxAccessServer typedFake)
{
typedFake.Write2(serverHandle, itemHandle, value, timestamp, userId);
return;
}
AsProxyServer4().Write2(serverHandle, itemHandle, value!, timestamp!, userId);
}
/// <inheritdoc />
public void WriteSecured(
int serverHandle,
int itemHandle,
int currentUserId,
int verifierUserId,
object? value)
{
if (mxAccessComObject is IMxAccessServer typedFake)
{
typedFake.WriteSecured(serverHandle, itemHandle, currentUserId, verifierUserId, value);
return;
}
AsProxyServer().WriteSecured(serverHandle, itemHandle, currentUserId, verifierUserId, value!);
}
/// <inheritdoc />
public void WriteSecured2(
int serverHandle,
int itemHandle,
int currentUserId,
int verifierUserId,
object? value,
object? timestamp)
{
if (mxAccessComObject is IMxAccessServer typedFake)
{
typedFake.WriteSecured2(serverHandle, itemHandle, currentUserId, verifierUserId, value, timestamp);
return;
}
AsProxyServer4().WriteSecured2(serverHandle, itemHandle, currentUserId, verifierUserId, value!, timestamp!);
}
private ILMXProxyServer AsProxyServer()
{
return mxAccessComObject as ILMXProxyServer
?? throw new InvalidOperationException(
$"MXAccess COM object of type '{mxAccessComObject.GetType().FullName}' does not implement "
+ $"{nameof(ILMXProxyServer)} or {nameof(IMxAccessServer)}.");
}
private ILMXProxyServer3 AsProxyServer3()
{
return mxAccessComObject as ILMXProxyServer3
?? throw new InvalidOperationException(
$"MXAccess COM object of type '{mxAccessComObject.GetType().FullName}' does not implement "
+ $"{nameof(ILMXProxyServer3)} or {nameof(IMxAccessServer)}.");
}
private ILMXProxyServer4 AsProxyServer4()
{
return mxAccessComObject as ILMXProxyServer4
?? throw new InvalidOperationException(
$"MXAccess COM object of type '{mxAccessComObject.GetType().FullName}' does not implement "
+ $"{nameof(ILMXProxyServer4)} or {nameof(IMxAccessServer)}.");
}
}