Files
mxaccessgw/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessComServer.cs
T
Joseph Doherty 29399325d5 feat(worker): implement 6 MXAccess COM commands in executor
Wire up the previously-unimplemented Suspend, Activate, AuthenticateUser,
ArchestrAUserToId, AddBufferedItem, and SetBufferedUpdateInterval command
kinds in MxAccessCommandExecutor. These are real COM calls and run on the
STA via the executor.

- IMxAccessServer gains the 6 methods; MxAccessComServer routes them to the
  right interface version (Suspend/Activate -> ILMXProxyServer4 out MxStatus,
  AuthenticateUser -> base ILMXProxyServer, ArchestrAUserToId ->
  ILMXProxyServer2, AddBufferedItem/SetBufferedUpdateInterval ->
  ILMXProxyServer5).
- Suspend/Activate surface the native MxStatus, converted to MxStatusProxy
  via the existing MxStatusProxyConverter.
- AuthenticateUser hands the credential straight to MXAccess and never logs
  it; native HResult failures propagate via the dispatcher.
- MxAccessSession gains matching pass-throughs; AddBufferedItem registers
  the item handle in the handle registry.
- Unit tests (fake IMxAccessServer / fake COM object) cover each arm plus a
  password-non-leak assertion; existing IMxAccessServer fakes updated.

No proto changes (all request/reply messages already exist).
2026-06-15 10:41:22 -04:00

334 lines
9.9 KiB
C#

using System;
using ArchestrA.MxAccess;
namespace ZB.MOM.WW.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 object Suspend(
int serverHandle,
int itemHandle)
{
if (mxAccessComObject is IMxAccessServer typedFake)
{
return typedFake.Suspend(serverHandle, itemHandle);
}
AsProxyServer4().Suspend(serverHandle, itemHandle, out MxStatus status);
return status;
}
/// <inheritdoc />
public object Activate(
int serverHandle,
int itemHandle)
{
if (mxAccessComObject is IMxAccessServer typedFake)
{
return typedFake.Activate(serverHandle, itemHandle);
}
AsProxyServer4().Activate(serverHandle, itemHandle, out MxStatus status);
return status;
}
/// <inheritdoc />
public int AuthenticateUser(
int serverHandle,
string verifyUser,
string verifyUserPassword)
{
if (mxAccessComObject is IMxAccessServer typedFake)
{
return typedFake.AuthenticateUser(serverHandle, verifyUser, verifyUserPassword);
}
return AsProxyServer().AuthenticateUser(serverHandle, verifyUser, verifyUserPassword);
}
/// <inheritdoc />
public int ArchestrAUserToId(
int serverHandle,
string userIdGuid)
{
if (mxAccessComObject is IMxAccessServer typedFake)
{
return typedFake.ArchestrAUserToId(serverHandle, userIdGuid);
}
return AsProxyServer2().ArchestrAUserToId(serverHandle, userIdGuid);
}
/// <inheritdoc />
public int AddBufferedItem(
int serverHandle,
string itemDefinition,
string itemContext)
{
if (mxAccessComObject is IMxAccessServer typedFake)
{
return typedFake.AddBufferedItem(serverHandle, itemDefinition, itemContext);
}
return AsProxyServer5().AddBufferedItem(serverHandle, itemDefinition, itemContext);
}
/// <inheritdoc />
public void SetBufferedUpdateInterval(
int serverHandle,
int updateIntervalMilliseconds)
{
if (mxAccessComObject is IMxAccessServer typedFake)
{
typedFake.SetBufferedUpdateInterval(serverHandle, updateIntervalMilliseconds);
return;
}
AsProxyServer5().SetBufferedUpdateInterval(serverHandle, updateIntervalMilliseconds);
}
/// <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 ILMXProxyServer2 AsProxyServer2()
{
return mxAccessComObject as ILMXProxyServer2
?? throw new InvalidOperationException(
$"MXAccess COM object of type '{mxAccessComObject.GetType().FullName}' does not implement "
+ $"{nameof(ILMXProxyServer2)} 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)}.");
}
private ILMXProxyServer5 AsProxyServer5()
{
return mxAccessComObject as ILMXProxyServer5
?? throw new InvalidOperationException(
$"MXAccess COM object of type '{mxAccessComObject.GetType().FullName}' does not implement "
+ $"{nameof(ILMXProxyServer5)} or {nameof(IMxAccessServer)}.");
}
}