335 lines
9.4 KiB
C#
335 lines
9.4 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Runtime.InteropServices;
|
|
using Google.Protobuf.WellKnownTypes;
|
|
using MxGateway.Contracts.Proto;
|
|
|
|
namespace MxGateway.Worker.MxAccess;
|
|
|
|
public sealed class MxAccessSession : IDisposable
|
|
{
|
|
private readonly object mxAccessComObject;
|
|
private readonly IMxAccessServer mxAccessServer;
|
|
private readonly IMxAccessEventSink eventSink;
|
|
private readonly MxAccessHandleRegistry handleRegistry;
|
|
private bool disposed;
|
|
|
|
private MxAccessSession(
|
|
object mxAccessComObject,
|
|
IMxAccessServer mxAccessServer,
|
|
IMxAccessEventSink eventSink,
|
|
MxAccessHandleRegistry handleRegistry,
|
|
int creationThreadId)
|
|
{
|
|
this.mxAccessComObject = mxAccessComObject ?? throw new ArgumentNullException(nameof(mxAccessComObject));
|
|
this.mxAccessServer = mxAccessServer ?? throw new ArgumentNullException(nameof(mxAccessServer));
|
|
this.eventSink = eventSink ?? throw new ArgumentNullException(nameof(eventSink));
|
|
this.handleRegistry = handleRegistry ?? throw new ArgumentNullException(nameof(handleRegistry));
|
|
CreationThreadId = creationThreadId;
|
|
}
|
|
|
|
public int CreationThreadId { get; }
|
|
|
|
public MxAccessHandleRegistry HandleRegistry => handleRegistry;
|
|
|
|
public WorkerReady CreateWorkerReady(int workerProcessId)
|
|
{
|
|
return new WorkerReady
|
|
{
|
|
WorkerProcessId = workerProcessId,
|
|
MxaccessProgid = MxAccessInteropInfo.ProgId,
|
|
MxaccessClsid = MxAccessInteropInfo.Clsid,
|
|
ReadyTimestamp = Timestamp.FromDateTime(DateTime.UtcNow),
|
|
};
|
|
}
|
|
|
|
public static MxAccessSession Create(
|
|
IMxAccessComObjectFactory factory,
|
|
IMxAccessEventSink eventSink,
|
|
string sessionId)
|
|
{
|
|
if (factory is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(factory));
|
|
}
|
|
|
|
if (eventSink is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(eventSink));
|
|
}
|
|
|
|
object? mxAccessComObject = null;
|
|
|
|
try
|
|
{
|
|
mxAccessComObject = factory.Create();
|
|
if (mxAccessComObject is null)
|
|
{
|
|
throw new InvalidOperationException("MXAccess COM factory returned null.");
|
|
}
|
|
|
|
eventSink.Attach(mxAccessComObject, sessionId);
|
|
|
|
return new MxAccessSession(
|
|
mxAccessComObject,
|
|
new MxAccessComServer(mxAccessComObject),
|
|
eventSink,
|
|
new MxAccessHandleRegistry(),
|
|
Environment.CurrentManagedThreadId);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
eventSink.Detach();
|
|
|
|
if (mxAccessComObject is not null && Marshal.IsComObject(mxAccessComObject))
|
|
{
|
|
Marshal.FinalReleaseComObject(mxAccessComObject);
|
|
}
|
|
|
|
throw MxAccessCreationException.From(exception);
|
|
}
|
|
}
|
|
|
|
public int Register(string clientName)
|
|
{
|
|
ThrowIfDisposed();
|
|
|
|
int serverHandle = mxAccessServer.Register(clientName);
|
|
handleRegistry.RegisterServerHandle(serverHandle, clientName);
|
|
|
|
return serverHandle;
|
|
}
|
|
|
|
public void Unregister(int serverHandle)
|
|
{
|
|
ThrowIfDisposed();
|
|
|
|
mxAccessServer.Unregister(serverHandle);
|
|
handleRegistry.UnregisterServerHandle(serverHandle);
|
|
}
|
|
|
|
public int AddItem(
|
|
int serverHandle,
|
|
string itemDefinition)
|
|
{
|
|
ThrowIfDisposed();
|
|
|
|
int itemHandle = mxAccessServer.AddItem(serverHandle, itemDefinition);
|
|
handleRegistry.RegisterItemHandle(
|
|
serverHandle,
|
|
itemHandle,
|
|
itemDefinition,
|
|
string.Empty,
|
|
hasItemContext: false);
|
|
|
|
return itemHandle;
|
|
}
|
|
|
|
public int AddItem2(
|
|
int serverHandle,
|
|
string itemDefinition,
|
|
string itemContext)
|
|
{
|
|
ThrowIfDisposed();
|
|
|
|
int itemHandle = mxAccessServer.AddItem2(serverHandle, itemDefinition, itemContext);
|
|
handleRegistry.RegisterItemHandle(
|
|
serverHandle,
|
|
itemHandle,
|
|
itemDefinition,
|
|
itemContext,
|
|
hasItemContext: true);
|
|
|
|
return itemHandle;
|
|
}
|
|
|
|
public void RemoveItem(
|
|
int serverHandle,
|
|
int itemHandle)
|
|
{
|
|
ThrowIfDisposed();
|
|
|
|
mxAccessServer.RemoveItem(serverHandle, itemHandle);
|
|
handleRegistry.RemoveItemHandle(serverHandle, itemHandle);
|
|
}
|
|
|
|
public void Advise(
|
|
int serverHandle,
|
|
int itemHandle)
|
|
{
|
|
ThrowIfDisposed();
|
|
|
|
mxAccessServer.Advise(serverHandle, itemHandle);
|
|
handleRegistry.RegisterAdviceHandle(
|
|
serverHandle,
|
|
itemHandle,
|
|
MxAccessAdviceKind.Plain);
|
|
}
|
|
|
|
public void UnAdvise(
|
|
int serverHandle,
|
|
int itemHandle)
|
|
{
|
|
ThrowIfDisposed();
|
|
|
|
mxAccessServer.UnAdvise(serverHandle, itemHandle);
|
|
handleRegistry.RemoveAdviceHandles(serverHandle, itemHandle);
|
|
}
|
|
|
|
public void AdviseSupervisory(
|
|
int serverHandle,
|
|
int itemHandle)
|
|
{
|
|
ThrowIfDisposed();
|
|
|
|
mxAccessServer.AdviseSupervisory(serverHandle, itemHandle);
|
|
handleRegistry.RegisterAdviceHandle(
|
|
serverHandle,
|
|
itemHandle,
|
|
MxAccessAdviceKind.Supervisory);
|
|
}
|
|
|
|
public MxAccessShutdownResult ShutdownGracefully()
|
|
{
|
|
if (disposed)
|
|
{
|
|
return new MxAccessShutdownResult(Array.Empty<MxAccessShutdownFailure>());
|
|
}
|
|
|
|
List<MxAccessShutdownFailure> failures = new();
|
|
|
|
CleanupAdviceHandles(failures);
|
|
CleanupItemHandles(failures);
|
|
CleanupServerHandles(failures);
|
|
DisposeCore(failures);
|
|
|
|
return new MxAccessShutdownResult(failures);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (disposed)
|
|
{
|
|
return;
|
|
}
|
|
|
|
DisposeCore(failures: null);
|
|
}
|
|
|
|
private void CleanupAdviceHandles(ICollection<MxAccessShutdownFailure> failures)
|
|
{
|
|
HashSet<long> cleanedPairs = new();
|
|
foreach (RegisteredAdviceHandle adviceHandle in handleRegistry.AdviceHandles)
|
|
{
|
|
long key = CreateItemKey(adviceHandle.ServerHandle, adviceHandle.ItemHandle);
|
|
if (!cleanedPairs.Add(key))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
try
|
|
{
|
|
mxAccessServer.UnAdvise(adviceHandle.ServerHandle, adviceHandle.ItemHandle);
|
|
handleRegistry.RemoveAdviceHandles(adviceHandle.ServerHandle, adviceHandle.ItemHandle);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
failures.Add(new MxAccessShutdownFailure(
|
|
nameof(UnAdvise),
|
|
adviceHandle.ServerHandle,
|
|
adviceHandle.ItemHandle,
|
|
exception));
|
|
}
|
|
}
|
|
}
|
|
|
|
private void CleanupItemHandles(ICollection<MxAccessShutdownFailure> failures)
|
|
{
|
|
foreach (RegisteredItemHandle itemHandle in handleRegistry.ItemHandles)
|
|
{
|
|
try
|
|
{
|
|
mxAccessServer.RemoveItem(itemHandle.ServerHandle, itemHandle.ItemHandle);
|
|
handleRegistry.RemoveItemHandle(itemHandle.ServerHandle, itemHandle.ItemHandle);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
failures.Add(new MxAccessShutdownFailure(
|
|
nameof(RemoveItem),
|
|
itemHandle.ServerHandle,
|
|
itemHandle.ItemHandle,
|
|
exception));
|
|
}
|
|
}
|
|
}
|
|
|
|
private void CleanupServerHandles(ICollection<MxAccessShutdownFailure> failures)
|
|
{
|
|
foreach (RegisteredServerHandle serverHandle in handleRegistry.ServerHandles)
|
|
{
|
|
try
|
|
{
|
|
mxAccessServer.Unregister(serverHandle.ServerHandle);
|
|
handleRegistry.UnregisterServerHandle(serverHandle.ServerHandle);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
failures.Add(new MxAccessShutdownFailure(
|
|
nameof(Unregister),
|
|
serverHandle.ServerHandle,
|
|
itemHandle: null,
|
|
exception));
|
|
}
|
|
}
|
|
}
|
|
|
|
private static long CreateItemKey(
|
|
int serverHandle,
|
|
int itemHandle)
|
|
{
|
|
return ((long)serverHandle << 32) | (uint)itemHandle;
|
|
}
|
|
|
|
private void DisposeCore(ICollection<MxAccessShutdownFailure>? failures)
|
|
{
|
|
try
|
|
{
|
|
eventSink.Detach();
|
|
}
|
|
catch (Exception exception) when (failures is not null)
|
|
{
|
|
failures.Add(new MxAccessShutdownFailure(
|
|
"DetachEvents",
|
|
serverHandle: null,
|
|
itemHandle: null,
|
|
exception));
|
|
}
|
|
|
|
try
|
|
{
|
|
if (Marshal.IsComObject(mxAccessComObject))
|
|
{
|
|
Marshal.FinalReleaseComObject(mxAccessComObject);
|
|
}
|
|
}
|
|
catch (Exception exception) when (failures is not null)
|
|
{
|
|
failures.Add(new MxAccessShutdownFailure(
|
|
"ReleaseComObject",
|
|
serverHandle: null,
|
|
itemHandle: null,
|
|
exception));
|
|
}
|
|
|
|
disposed = true;
|
|
}
|
|
|
|
private void ThrowIfDisposed()
|
|
{
|
|
if (disposed)
|
|
{
|
|
throw new ObjectDisposedException(nameof(MxAccessSession));
|
|
}
|
|
}
|
|
}
|