Merge remote-tracking branch 'origin/main' into agent-3/issue-19-gateway-e2e-smoke-with-fake-worker
This commit is contained in:
@@ -218,6 +218,8 @@ Live tests:
|
|||||||
|
|
||||||
Labels: `area:worker`, `type:feature`, `priority:p0`
|
Labels: `area:worker`, `type:feature`, `priority:p0`
|
||||||
|
|
||||||
|
Status: implemented.
|
||||||
|
|
||||||
Deliverables:
|
Deliverables:
|
||||||
|
|
||||||
- `AddItem`,
|
- `AddItem`,
|
||||||
|
|||||||
@@ -432,6 +432,25 @@ HRESULT and converts the reply to `ProtocolStatusCode.MxaccessFailure`.
|
|||||||
`MxAccessStaSession.GetRegisteredServerHandlesAsync` returns an STA-read
|
`MxAccessStaSession.GetRegisteredServerHandlesAsync` returns an STA-read
|
||||||
snapshot of tracked server handles for diagnostics and future cleanup logic.
|
snapshot of tracked server handles for diagnostics and future cleanup logic.
|
||||||
|
|
||||||
|
`MxAccessCommandExecutor` also implements the item lifecycle commands:
|
||||||
|
|
||||||
|
- `AddItem` calls `LMXProxyServerClass.AddItem` with the requested server
|
||||||
|
handle and item definition. It preserves the returned item handle in both
|
||||||
|
`ReturnValue` and `AddItemReply.ItemHandle`.
|
||||||
|
- `AddItem2` calls `LMXProxyServerClass.AddItem2` with the requested server
|
||||||
|
handle, item definition, and context string. The context string is passed to
|
||||||
|
MXAccess exactly as received.
|
||||||
|
- `RemoveItem` calls `LMXProxyServerClass.RemoveItem` with the requested server
|
||||||
|
handle and item handle. The reply has no method-specific payload because the
|
||||||
|
public MXAccess method returns `void`.
|
||||||
|
|
||||||
|
The worker records item handles only after `AddItem` or `AddItem2` returns
|
||||||
|
normally, and removes item handles only after `RemoveItem` returns normally.
|
||||||
|
The registry does not prevalidate server or item handles, so invalid and
|
||||||
|
cross-server handle behavior remains owned by MXAccess. COM exceptions continue
|
||||||
|
through `StaCommandDispatcher`, which preserves the HRESULT and leaves
|
||||||
|
diagnostic registry state unchanged for failed cleanup calls.
|
||||||
|
|
||||||
## Handle Registry
|
## Handle Registry
|
||||||
|
|
||||||
The worker should track MXAccess state for diagnostics and cleanup, while still
|
The worker should track MXAccess state for diagnostics and cleanup, while still
|
||||||
@@ -454,6 +473,8 @@ Rules:
|
|||||||
- Do not rewrite handles returned by MXAccess.
|
- Do not rewrite handles returned by MXAccess.
|
||||||
- Record server handles only after `Register` succeeds.
|
- Record server handles only after `Register` succeeds.
|
||||||
- Remove server handles only after `Unregister` succeeds.
|
- Remove server handles only after `Unregister` succeeds.
|
||||||
|
- Record item handles only after `AddItem` or `AddItem2` succeeds.
|
||||||
|
- Remove item handles only after `RemoveItem` succeeds.
|
||||||
- Preserve invalid-handle behavior from MXAccess.
|
- Preserve invalid-handle behavior from MXAccess.
|
||||||
- Preserve cross-server handle behavior from MXAccess.
|
- Preserve cross-server handle behavior from MXAccess.
|
||||||
- Use registry state for cleanup and diagnostics, not semantic correction.
|
- Use registry state for cleanup and diagnostics, not semantic correction.
|
||||||
@@ -697,6 +718,10 @@ Live MXAccess tests:
|
|||||||
|
|
||||||
Live tests should be opt-in and clearly marked because they depend on installed
|
Live tests should be opt-in and clearly marked because they depend on installed
|
||||||
MXAccess COM and provider state.
|
MXAccess COM and provider state.
|
||||||
|
The worker test suite uses `MXGATEWAY_RUN_LIVE_MXACCESS_TESTS=1` for these
|
||||||
|
tests. `AddItem` uses `TestChildObject.TestInt` by default and accepts an
|
||||||
|
override through `MXGATEWAY_LIVE_MXACCESS_ITEM`; `AddItem2` uses the captured
|
||||||
|
parity fixture shape `AddItem2("TestInt", "TestChildObject")`.
|
||||||
|
|
||||||
## Initial Implementation Slice
|
## Initial Implementation Slice
|
||||||
|
|
||||||
|
|||||||
@@ -78,6 +78,166 @@ public sealed class MxAccessCommandExecutorTests
|
|||||||
Assert.Equal(44, registeredServerHandle.ServerHandle);
|
Assert.Equal(44, registeredServerHandle.ServerHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DispatchAsync_AddItem_CallsMxAccessOnStaAndTracksItemHandle()
|
||||||
|
{
|
||||||
|
FakeMxAccessComObject fakeComObject = new(
|
||||||
|
registerHandle: 46,
|
||||||
|
addItemHandle: 501);
|
||||||
|
FakeMxAccessComObjectFactory factory = new(fakeComObject);
|
||||||
|
using StaRuntime runtime = CreateRuntime();
|
||||||
|
using MxAccessStaSession session = new(runtime, factory, new NoopEventSink());
|
||||||
|
await session.StartAsync(workerProcessId: 1234);
|
||||||
|
await session.DispatchAsync(CreateRegisterCommand("register-before-add", "client-a"));
|
||||||
|
|
||||||
|
MxCommandReply reply = await session.DispatchAsync(CreateAddItemCommand(
|
||||||
|
"add-item",
|
||||||
|
46,
|
||||||
|
"Galaxy.Tag.Value"));
|
||||||
|
|
||||||
|
Assert.Equal(ProtocolStatusCode.Ok, reply.ProtocolStatus.Code);
|
||||||
|
Assert.True(reply.HasHresult);
|
||||||
|
Assert.Equal(0, reply.Hresult);
|
||||||
|
Assert.Equal(501, reply.AddItem.ItemHandle);
|
||||||
|
Assert.Equal(MxDataType.Integer, reply.ReturnValue.DataType);
|
||||||
|
Assert.Equal(501, reply.ReturnValue.Int32Value);
|
||||||
|
Assert.Equal(46, fakeComObject.AddItemServerHandle);
|
||||||
|
Assert.Equal("Galaxy.Tag.Value", fakeComObject.AddItemDefinition);
|
||||||
|
Assert.Equal(runtime.StaThreadId, fakeComObject.AddItemThreadId);
|
||||||
|
|
||||||
|
RegisteredItemHandle registeredItemHandle = Assert.Single(
|
||||||
|
await session.GetRegisteredItemHandlesAsync());
|
||||||
|
Assert.Equal(46, registeredItemHandle.ServerHandle);
|
||||||
|
Assert.Equal(501, registeredItemHandle.ItemHandle);
|
||||||
|
Assert.Equal("Galaxy.Tag.Value", registeredItemHandle.ItemDefinition);
|
||||||
|
Assert.Equal(string.Empty, registeredItemHandle.ItemContext);
|
||||||
|
Assert.False(registeredItemHandle.HasItemContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DispatchAsync_AddItem2_PassesContextExactlyAndTracksItemHandle()
|
||||||
|
{
|
||||||
|
FakeMxAccessComObject fakeComObject = new(
|
||||||
|
registerHandle: 47,
|
||||||
|
addItem2Handle: 502);
|
||||||
|
FakeMxAccessComObjectFactory factory = new(fakeComObject);
|
||||||
|
using StaRuntime runtime = CreateRuntime();
|
||||||
|
using MxAccessStaSession session = new(runtime, factory, new NoopEventSink());
|
||||||
|
await session.StartAsync(workerProcessId: 1234);
|
||||||
|
await session.DispatchAsync(CreateRegisterCommand("register-before-add2", "client-a"));
|
||||||
|
|
||||||
|
MxCommandReply reply = await session.DispatchAsync(CreateAddItem2Command(
|
||||||
|
"add-item2",
|
||||||
|
47,
|
||||||
|
"TestInt",
|
||||||
|
"TestChildObject"));
|
||||||
|
|
||||||
|
Assert.Equal(ProtocolStatusCode.Ok, reply.ProtocolStatus.Code);
|
||||||
|
Assert.Equal(502, reply.AddItem2.ItemHandle);
|
||||||
|
Assert.Equal(MxDataType.Integer, reply.ReturnValue.DataType);
|
||||||
|
Assert.Equal(502, reply.ReturnValue.Int32Value);
|
||||||
|
Assert.Equal(47, fakeComObject.AddItem2ServerHandle);
|
||||||
|
Assert.Equal("TestInt", fakeComObject.AddItem2Definition);
|
||||||
|
Assert.Equal("TestChildObject", fakeComObject.AddItem2Context);
|
||||||
|
Assert.Equal(runtime.StaThreadId, fakeComObject.AddItem2ThreadId);
|
||||||
|
|
||||||
|
RegisteredItemHandle registeredItemHandle = Assert.Single(
|
||||||
|
await session.GetRegisteredItemHandlesAsync());
|
||||||
|
Assert.Equal(47, registeredItemHandle.ServerHandle);
|
||||||
|
Assert.Equal(502, registeredItemHandle.ItemHandle);
|
||||||
|
Assert.Equal("TestInt", registeredItemHandle.ItemDefinition);
|
||||||
|
Assert.Equal("TestChildObject", registeredItemHandle.ItemContext);
|
||||||
|
Assert.True(registeredItemHandle.HasItemContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DispatchAsync_RemoveItem_CallsMxAccessOnStaAndRemovesTrackedItemHandle()
|
||||||
|
{
|
||||||
|
FakeMxAccessComObject fakeComObject = new(
|
||||||
|
registerHandle: 48,
|
||||||
|
addItemHandle: 503);
|
||||||
|
FakeMxAccessComObjectFactory factory = new(fakeComObject);
|
||||||
|
using StaRuntime runtime = CreateRuntime();
|
||||||
|
using MxAccessStaSession session = new(runtime, factory, new NoopEventSink());
|
||||||
|
await session.StartAsync(workerProcessId: 1234);
|
||||||
|
await session.DispatchAsync(CreateRegisterCommand("register-before-remove", "client-a"));
|
||||||
|
await session.DispatchAsync(CreateAddItemCommand("add-before-remove", 48, "Galaxy.Tag.Value"));
|
||||||
|
|
||||||
|
MxCommandReply reply = await session.DispatchAsync(CreateRemoveItemCommand(
|
||||||
|
"remove-item",
|
||||||
|
48,
|
||||||
|
503));
|
||||||
|
|
||||||
|
Assert.Equal(ProtocolStatusCode.Ok, reply.ProtocolStatus.Code);
|
||||||
|
Assert.True(reply.HasHresult);
|
||||||
|
Assert.Equal(0, reply.Hresult);
|
||||||
|
Assert.Equal(48, fakeComObject.RemoveItemServerHandle);
|
||||||
|
Assert.Equal(503, fakeComObject.RemovedItemHandle);
|
||||||
|
Assert.Equal(runtime.StaThreadId, fakeComObject.RemoveItemThreadId);
|
||||||
|
Assert.Empty(await session.GetRegisteredItemHandlesAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DispatchAsync_RemoveItemWithCrossServerHandle_PreservesHResultAndKeepsTrackedItemHandle()
|
||||||
|
{
|
||||||
|
const int hresult = unchecked((int)0x80070057);
|
||||||
|
FakeMxAccessComObject fakeComObject = new(
|
||||||
|
registerHandle: 49,
|
||||||
|
addItemHandle: 504,
|
||||||
|
removeItemException: new COMException("Invalid item handle.", hresult));
|
||||||
|
FakeMxAccessComObjectFactory factory = new(fakeComObject);
|
||||||
|
using StaRuntime runtime = CreateRuntime();
|
||||||
|
using MxAccessStaSession session = new(runtime, factory, new NoopEventSink());
|
||||||
|
await session.StartAsync(workerProcessId: 1234);
|
||||||
|
await session.DispatchAsync(CreateRegisterCommand("register-before-remove-failure", "client-a"));
|
||||||
|
await session.DispatchAsync(CreateAddItemCommand("add-before-remove-failure", 49, "Galaxy.Tag.Value"));
|
||||||
|
|
||||||
|
MxCommandReply reply = await session.DispatchAsync(CreateRemoveItemCommand(
|
||||||
|
"remove-item-failure",
|
||||||
|
999,
|
||||||
|
504));
|
||||||
|
|
||||||
|
Assert.Equal(ProtocolStatusCode.MxaccessFailure, reply.ProtocolStatus.Code);
|
||||||
|
Assert.True(reply.HasHresult);
|
||||||
|
Assert.Equal(hresult, reply.Hresult);
|
||||||
|
Assert.Contains("0x80070057", reply.DiagnosticMessage);
|
||||||
|
Assert.Equal(999, fakeComObject.RemoveItemServerHandle);
|
||||||
|
Assert.Equal(504, fakeComObject.RemovedItemHandle);
|
||||||
|
|
||||||
|
RegisteredItemHandle registeredItemHandle = Assert.Single(
|
||||||
|
await session.GetRegisteredItemHandlesAsync());
|
||||||
|
Assert.Equal(49, registeredItemHandle.ServerHandle);
|
||||||
|
Assert.Equal(504, registeredItemHandle.ItemHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DispatchAsync_AddItem2WhenMxAccessThrows_PreservesHResultAndDoesNotTrackItemHandle()
|
||||||
|
{
|
||||||
|
const int hresult = unchecked((int)0x80070057);
|
||||||
|
FakeMxAccessComObject fakeComObject = new(
|
||||||
|
registerHandle: 50,
|
||||||
|
addItem2Exception: new COMException("Invalid server handle.", hresult));
|
||||||
|
FakeMxAccessComObjectFactory factory = new(fakeComObject);
|
||||||
|
using StaRuntime runtime = CreateRuntime();
|
||||||
|
using MxAccessStaSession session = new(runtime, factory, new NoopEventSink());
|
||||||
|
await session.StartAsync(workerProcessId: 1234);
|
||||||
|
|
||||||
|
MxCommandReply reply = await session.DispatchAsync(CreateAddItem2Command(
|
||||||
|
"add-item2-failure",
|
||||||
|
9001,
|
||||||
|
"TestInt",
|
||||||
|
"TestChildObject"));
|
||||||
|
|
||||||
|
Assert.Equal(ProtocolStatusCode.MxaccessFailure, reply.ProtocolStatus.Code);
|
||||||
|
Assert.True(reply.HasHresult);
|
||||||
|
Assert.Equal(hresult, reply.Hresult);
|
||||||
|
Assert.Contains("0x80070057", reply.DiagnosticMessage);
|
||||||
|
Assert.Equal(9001, fakeComObject.AddItem2ServerHandle);
|
||||||
|
Assert.Equal("TestInt", fakeComObject.AddItem2Definition);
|
||||||
|
Assert.Equal("TestChildObject", fakeComObject.AddItem2Context);
|
||||||
|
Assert.Empty(await session.GetRegisteredItemHandlesAsync());
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task DispatchAsync_RegisterWithoutPayload_ReturnsInvalidRequest()
|
public async Task DispatchAsync_RegisterWithoutPayload_ReturnsInvalidRequest()
|
||||||
{
|
{
|
||||||
@@ -98,6 +258,26 @@ public sealed class MxAccessCommandExecutorTests
|
|||||||
Assert.Null(factory.FakeComObject.RegisteredClientName);
|
Assert.Null(factory.FakeComObject.RegisteredClientName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DispatchAsync_AddItemWithoutPayload_ReturnsInvalidRequest()
|
||||||
|
{
|
||||||
|
FakeMxAccessComObjectFactory factory = new(new FakeMxAccessComObject(registerHandle: 51));
|
||||||
|
using StaRuntime runtime = CreateRuntime();
|
||||||
|
using MxAccessStaSession session = new(runtime, factory, new NoopEventSink());
|
||||||
|
await session.StartAsync(workerProcessId: 1234);
|
||||||
|
|
||||||
|
MxCommandReply reply = await session.DispatchAsync(new StaCommand(
|
||||||
|
"session-1",
|
||||||
|
"missing-add-payload",
|
||||||
|
new MxCommand
|
||||||
|
{
|
||||||
|
Kind = MxCommandKind.AddItem,
|
||||||
|
}));
|
||||||
|
|
||||||
|
Assert.Equal(ProtocolStatusCode.InvalidRequest, reply.ProtocolStatus.Code);
|
||||||
|
Assert.Null(factory.FakeComObject.AddItemDefinition);
|
||||||
|
}
|
||||||
|
|
||||||
private static StaCommand CreateRegisterCommand(
|
private static StaCommand CreateRegisterCommand(
|
||||||
string correlationId,
|
string correlationId,
|
||||||
string clientName)
|
string clientName)
|
||||||
@@ -132,6 +312,65 @@ public sealed class MxAccessCommandExecutorTests
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static StaCommand CreateAddItemCommand(
|
||||||
|
string correlationId,
|
||||||
|
int serverHandle,
|
||||||
|
string itemDefinition)
|
||||||
|
{
|
||||||
|
return new StaCommand(
|
||||||
|
"session-1",
|
||||||
|
correlationId,
|
||||||
|
new MxCommand
|
||||||
|
{
|
||||||
|
Kind = MxCommandKind.AddItem,
|
||||||
|
AddItem = new AddItemCommand
|
||||||
|
{
|
||||||
|
ServerHandle = serverHandle,
|
||||||
|
ItemDefinition = itemDefinition,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static StaCommand CreateAddItem2Command(
|
||||||
|
string correlationId,
|
||||||
|
int serverHandle,
|
||||||
|
string itemDefinition,
|
||||||
|
string itemContext)
|
||||||
|
{
|
||||||
|
return new StaCommand(
|
||||||
|
"session-1",
|
||||||
|
correlationId,
|
||||||
|
new MxCommand
|
||||||
|
{
|
||||||
|
Kind = MxCommandKind.AddItem2,
|
||||||
|
AddItem2 = new AddItem2Command
|
||||||
|
{
|
||||||
|
ServerHandle = serverHandle,
|
||||||
|
ItemDefinition = itemDefinition,
|
||||||
|
ItemContext = itemContext,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static StaCommand CreateRemoveItemCommand(
|
||||||
|
string correlationId,
|
||||||
|
int serverHandle,
|
||||||
|
int itemHandle)
|
||||||
|
{
|
||||||
|
return new StaCommand(
|
||||||
|
"session-1",
|
||||||
|
correlationId,
|
||||||
|
new MxCommand
|
||||||
|
{
|
||||||
|
Kind = MxCommandKind.RemoveItem,
|
||||||
|
RemoveItem = new RemoveItemCommand
|
||||||
|
{
|
||||||
|
ServerHandle = serverHandle,
|
||||||
|
ItemHandle = itemHandle,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private static StaRuntime CreateRuntime()
|
private static StaRuntime CreateRuntime()
|
||||||
{
|
{
|
||||||
return new StaRuntime(
|
return new StaRuntime(
|
||||||
@@ -143,14 +382,29 @@ public sealed class MxAccessCommandExecutorTests
|
|||||||
private sealed class FakeMxAccessComObject
|
private sealed class FakeMxAccessComObject
|
||||||
{
|
{
|
||||||
private readonly int registerHandle;
|
private readonly int registerHandle;
|
||||||
|
private readonly int addItemHandle;
|
||||||
|
private readonly int addItem2Handle;
|
||||||
private readonly Exception? unregisterException;
|
private readonly Exception? unregisterException;
|
||||||
|
private readonly Exception? addItemException;
|
||||||
|
private readonly Exception? addItem2Exception;
|
||||||
|
private readonly Exception? removeItemException;
|
||||||
|
|
||||||
public FakeMxAccessComObject(
|
public FakeMxAccessComObject(
|
||||||
int registerHandle,
|
int registerHandle,
|
||||||
Exception? unregisterException = null)
|
int addItemHandle = 0,
|
||||||
|
int addItem2Handle = 0,
|
||||||
|
Exception? unregisterException = null,
|
||||||
|
Exception? addItemException = null,
|
||||||
|
Exception? addItem2Exception = null,
|
||||||
|
Exception? removeItemException = null)
|
||||||
{
|
{
|
||||||
this.registerHandle = registerHandle;
|
this.registerHandle = registerHandle;
|
||||||
|
this.addItemHandle = addItemHandle;
|
||||||
|
this.addItem2Handle = addItem2Handle;
|
||||||
this.unregisterException = unregisterException;
|
this.unregisterException = unregisterException;
|
||||||
|
this.addItemException = addItemException;
|
||||||
|
this.addItem2Exception = addItem2Exception;
|
||||||
|
this.removeItemException = removeItemException;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string? RegisteredClientName { get; private set; }
|
public string? RegisteredClientName { get; private set; }
|
||||||
@@ -161,6 +415,26 @@ public sealed class MxAccessCommandExecutorTests
|
|||||||
|
|
||||||
public int? UnregisterThreadId { get; private set; }
|
public int? UnregisterThreadId { get; private set; }
|
||||||
|
|
||||||
|
public int? AddItemServerHandle { get; private set; }
|
||||||
|
|
||||||
|
public string? AddItemDefinition { get; private set; }
|
||||||
|
|
||||||
|
public int? AddItemThreadId { get; private set; }
|
||||||
|
|
||||||
|
public int? AddItem2ServerHandle { get; private set; }
|
||||||
|
|
||||||
|
public string? AddItem2Definition { get; private set; }
|
||||||
|
|
||||||
|
public string? AddItem2Context { get; private set; }
|
||||||
|
|
||||||
|
public int? AddItem2ThreadId { get; private set; }
|
||||||
|
|
||||||
|
public int? RemoveItemServerHandle { get; private set; }
|
||||||
|
|
||||||
|
public int? RemovedItemHandle { get; private set; }
|
||||||
|
|
||||||
|
public int? RemoveItemThreadId { get; private set; }
|
||||||
|
|
||||||
public int Register(string clientName)
|
public int Register(string clientName)
|
||||||
{
|
{
|
||||||
RegisteredClientName = clientName;
|
RegisteredClientName = clientName;
|
||||||
@@ -179,6 +453,54 @@ public sealed class MxAccessCommandExecutorTests
|
|||||||
throw unregisterException;
|
throw unregisterException;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int AddItem(
|
||||||
|
int serverHandle,
|
||||||
|
string itemDefinition)
|
||||||
|
{
|
||||||
|
AddItemServerHandle = serverHandle;
|
||||||
|
AddItemDefinition = itemDefinition;
|
||||||
|
AddItemThreadId = Environment.CurrentManagedThreadId;
|
||||||
|
|
||||||
|
if (addItemException is not null)
|
||||||
|
{
|
||||||
|
throw addItemException;
|
||||||
|
}
|
||||||
|
|
||||||
|
return addItemHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int AddItem2(
|
||||||
|
int serverHandle,
|
||||||
|
string itemDefinition,
|
||||||
|
string itemContext)
|
||||||
|
{
|
||||||
|
AddItem2ServerHandle = serverHandle;
|
||||||
|
AddItem2Definition = itemDefinition;
|
||||||
|
AddItem2Context = itemContext;
|
||||||
|
AddItem2ThreadId = Environment.CurrentManagedThreadId;
|
||||||
|
|
||||||
|
if (addItem2Exception is not null)
|
||||||
|
{
|
||||||
|
throw addItem2Exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
return addItem2Handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveItem(
|
||||||
|
int serverHandle,
|
||||||
|
int itemHandle)
|
||||||
|
{
|
||||||
|
RemoveItemServerHandle = serverHandle;
|
||||||
|
RemovedItemHandle = itemHandle;
|
||||||
|
RemoveItemThreadId = Environment.CurrentManagedThreadId;
|
||||||
|
|
||||||
|
if (removeItemException is not null)
|
||||||
|
{
|
||||||
|
throw removeItemException;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class FakeMxAccessComObjectFactory : IMxAccessComObjectFactory
|
private sealed class FakeMxAccessComObjectFactory : IMxAccessComObjectFactory
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ namespace MxGateway.Worker.Tests.MxAccess;
|
|||||||
|
|
||||||
public sealed class MxAccessLiveComCreationTests
|
public sealed class MxAccessLiveComCreationTests
|
||||||
{
|
{
|
||||||
|
private const string LiveClientName = "MxGateway.Worker.Tests";
|
||||||
|
private const string DefaultLiveAddItemReference = "TestChildObject.TestInt";
|
||||||
|
private const string DefaultLiveAddItem2Definition = "TestInt";
|
||||||
|
private const string DefaultLiveAddItem2Context = "TestChildObject";
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task StartAsync_WhenOptedIn_CreatesInstalledMxAccessComObjectOnSta()
|
public async Task StartAsync_WhenOptedIn_CreatesInstalledMxAccessComObjectOnSta()
|
||||||
{
|
{
|
||||||
@@ -43,7 +48,7 @@ public sealed class MxAccessLiveComCreationTests
|
|||||||
Kind = MxCommandKind.Register,
|
Kind = MxCommandKind.Register,
|
||||||
Register = new RegisterCommand
|
Register = new RegisterCommand
|
||||||
{
|
{
|
||||||
ClientName = "MxGateway.Worker.Tests",
|
ClientName = LiveClientName,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -65,6 +70,151 @@ public sealed class MxAccessLiveComCreationTests
|
|||||||
Assert.Equal(ProtocolStatusCode.Ok, unregisterReply.ProtocolStatus.Code);
|
Assert.Equal(ProtocolStatusCode.Ok, unregisterReply.ProtocolStatus.Code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AddItemAndRemoveItem_WhenOptedIn_RoundTripsInstalledMxAccessItemHandle()
|
||||||
|
{
|
||||||
|
if (!RunLiveMxAccessTests())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using MxAccessStaSession session = new();
|
||||||
|
await session.StartAsync(workerProcessId: 1234);
|
||||||
|
|
||||||
|
MxCommandReply registerReply = await RegisterLiveSessionAsync(session, "live-add-register");
|
||||||
|
int serverHandle = registerReply.Register.ServerHandle;
|
||||||
|
int itemHandle = 0;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
MxCommandReply addItemReply = await session.DispatchAsync(new StaCommand(
|
||||||
|
"session-1",
|
||||||
|
"live-add-item",
|
||||||
|
new MxCommand
|
||||||
|
{
|
||||||
|
Kind = MxCommandKind.AddItem,
|
||||||
|
AddItem = new AddItemCommand
|
||||||
|
{
|
||||||
|
ServerHandle = serverHandle,
|
||||||
|
ItemDefinition = GetLiveAddItemReference(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
Assert.Equal(ProtocolStatusCode.Ok, addItemReply.ProtocolStatus.Code);
|
||||||
|
Assert.True(addItemReply.AddItem.ItemHandle > 0);
|
||||||
|
itemHandle = addItemReply.AddItem.ItemHandle;
|
||||||
|
|
||||||
|
MxCommandReply removeItemReply = await session.DispatchAsync(new StaCommand(
|
||||||
|
"session-1",
|
||||||
|
"live-remove-item",
|
||||||
|
new MxCommand
|
||||||
|
{
|
||||||
|
Kind = MxCommandKind.RemoveItem,
|
||||||
|
RemoveItem = new RemoveItemCommand
|
||||||
|
{
|
||||||
|
ServerHandle = serverHandle,
|
||||||
|
ItemHandle = itemHandle,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
Assert.Equal(ProtocolStatusCode.Ok, removeItemReply.ProtocolStatus.Code);
|
||||||
|
itemHandle = 0;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (itemHandle > 0)
|
||||||
|
{
|
||||||
|
await session.DispatchAsync(new StaCommand(
|
||||||
|
"session-1",
|
||||||
|
"live-remove-item-cleanup",
|
||||||
|
new MxCommand
|
||||||
|
{
|
||||||
|
Kind = MxCommandKind.RemoveItem,
|
||||||
|
RemoveItem = new RemoveItemCommand
|
||||||
|
{
|
||||||
|
ServerHandle = serverHandle,
|
||||||
|
ItemHandle = itemHandle,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
await UnregisterLiveSessionAsync(session, serverHandle, "live-add-unregister");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AddItem2AndRemoveItem_WhenOptedIn_PreservesContextForInstalledMxAccess()
|
||||||
|
{
|
||||||
|
if (!RunLiveMxAccessTests())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using MxAccessStaSession session = new();
|
||||||
|
await session.StartAsync(workerProcessId: 1234);
|
||||||
|
|
||||||
|
MxCommandReply registerReply = await RegisterLiveSessionAsync(session, "live-add2-register");
|
||||||
|
int serverHandle = registerReply.Register.ServerHandle;
|
||||||
|
int itemHandle = 0;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
MxCommandReply addItem2Reply = await session.DispatchAsync(new StaCommand(
|
||||||
|
"session-1",
|
||||||
|
"live-add-item2",
|
||||||
|
new MxCommand
|
||||||
|
{
|
||||||
|
Kind = MxCommandKind.AddItem2,
|
||||||
|
AddItem2 = new AddItem2Command
|
||||||
|
{
|
||||||
|
ServerHandle = serverHandle,
|
||||||
|
ItemDefinition = DefaultLiveAddItem2Definition,
|
||||||
|
ItemContext = DefaultLiveAddItem2Context,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
Assert.Equal(ProtocolStatusCode.Ok, addItem2Reply.ProtocolStatus.Code);
|
||||||
|
Assert.True(addItem2Reply.AddItem2.ItemHandle > 0);
|
||||||
|
itemHandle = addItem2Reply.AddItem2.ItemHandle;
|
||||||
|
|
||||||
|
MxCommandReply removeItemReply = await session.DispatchAsync(new StaCommand(
|
||||||
|
"session-1",
|
||||||
|
"live-remove-item2",
|
||||||
|
new MxCommand
|
||||||
|
{
|
||||||
|
Kind = MxCommandKind.RemoveItem,
|
||||||
|
RemoveItem = new RemoveItemCommand
|
||||||
|
{
|
||||||
|
ServerHandle = serverHandle,
|
||||||
|
ItemHandle = itemHandle,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
Assert.Equal(ProtocolStatusCode.Ok, removeItemReply.ProtocolStatus.Code);
|
||||||
|
itemHandle = 0;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (itemHandle > 0)
|
||||||
|
{
|
||||||
|
await session.DispatchAsync(new StaCommand(
|
||||||
|
"session-1",
|
||||||
|
"live-remove-item2-cleanup",
|
||||||
|
new MxCommand
|
||||||
|
{
|
||||||
|
Kind = MxCommandKind.RemoveItem,
|
||||||
|
RemoveItem = new RemoveItemCommand
|
||||||
|
{
|
||||||
|
ServerHandle = serverHandle,
|
||||||
|
ItemHandle = itemHandle,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
await UnregisterLiveSessionAsync(session, serverHandle, "live-add2-unregister");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static bool RunLiveMxAccessTests()
|
private static bool RunLiveMxAccessTests()
|
||||||
{
|
{
|
||||||
return string.Equals(
|
return string.Equals(
|
||||||
@@ -72,4 +222,55 @@ public sealed class MxAccessLiveComCreationTests
|
|||||||
"1",
|
"1",
|
||||||
StringComparison.Ordinal);
|
StringComparison.Ordinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string GetLiveAddItemReference()
|
||||||
|
{
|
||||||
|
string itemReference = Environment.GetEnvironmentVariable("MXGATEWAY_LIVE_MXACCESS_ITEM");
|
||||||
|
|
||||||
|
return string.IsNullOrWhiteSpace(itemReference)
|
||||||
|
? DefaultLiveAddItemReference
|
||||||
|
: itemReference;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<MxCommandReply> RegisterLiveSessionAsync(
|
||||||
|
MxAccessStaSession session,
|
||||||
|
string correlationId)
|
||||||
|
{
|
||||||
|
MxCommandReply reply = await session.DispatchAsync(new StaCommand(
|
||||||
|
"session-1",
|
||||||
|
correlationId,
|
||||||
|
new MxCommand
|
||||||
|
{
|
||||||
|
Kind = MxCommandKind.Register,
|
||||||
|
Register = new RegisterCommand
|
||||||
|
{
|
||||||
|
ClientName = LiveClientName,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
Assert.Equal(ProtocolStatusCode.Ok, reply.ProtocolStatus.Code);
|
||||||
|
Assert.True(reply.Register.ServerHandle > 0);
|
||||||
|
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task UnregisterLiveSessionAsync(
|
||||||
|
MxAccessStaSession session,
|
||||||
|
int serverHandle,
|
||||||
|
string correlationId)
|
||||||
|
{
|
||||||
|
MxCommandReply unregisterReply = await session.DispatchAsync(new StaCommand(
|
||||||
|
"session-1",
|
||||||
|
correlationId,
|
||||||
|
new MxCommand
|
||||||
|
{
|
||||||
|
Kind = MxCommandKind.Unregister,
|
||||||
|
Unregister = new UnregisterCommand
|
||||||
|
{
|
||||||
|
ServerHandle = serverHandle,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
Assert.Equal(ProtocolStatusCode.Ok, unregisterReply.ProtocolStatus.Code);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,4 +5,17 @@ public interface IMxAccessServer
|
|||||||
int Register(string clientName);
|
int Register(string clientName);
|
||||||
|
|
||||||
void Unregister(int serverHandle);
|
void Unregister(int serverHandle);
|
||||||
|
|
||||||
|
int AddItem(
|
||||||
|
int serverHandle,
|
||||||
|
string itemDefinition);
|
||||||
|
|
||||||
|
int AddItem2(
|
||||||
|
int serverHandle,
|
||||||
|
string itemDefinition,
|
||||||
|
string itemContext);
|
||||||
|
|
||||||
|
void RemoveItem(
|
||||||
|
int serverHandle,
|
||||||
|
int itemHandle);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,44 @@ public sealed class MxAccessComServer : IMxAccessServer
|
|||||||
Invoke(nameof(Unregister), serverHandle);
|
Invoke(nameof(Unregister), serverHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int AddItem(
|
||||||
|
int serverHandle,
|
||||||
|
string itemDefinition)
|
||||||
|
{
|
||||||
|
if (mxAccessComObject is ILMXProxyServer mxAccessServer)
|
||||||
|
{
|
||||||
|
return mxAccessServer.AddItem(serverHandle, itemDefinition);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int)Invoke(nameof(AddItem), serverHandle, itemDefinition);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int AddItem2(
|
||||||
|
int serverHandle,
|
||||||
|
string itemDefinition,
|
||||||
|
string itemContext)
|
||||||
|
{
|
||||||
|
if (mxAccessComObject is ILMXProxyServer3 mxAccessServer)
|
||||||
|
{
|
||||||
|
return mxAccessServer.AddItem2(serverHandle, itemDefinition, itemContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int)Invoke(nameof(AddItem2), serverHandle, itemDefinition, itemContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveItem(
|
||||||
|
int serverHandle,
|
||||||
|
int itemHandle)
|
||||||
|
{
|
||||||
|
if (mxAccessComObject is ILMXProxyServer mxAccessServer)
|
||||||
|
{
|
||||||
|
mxAccessServer.RemoveItem(serverHandle, itemHandle);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Invoke(nameof(RemoveItem), serverHandle, itemHandle);
|
||||||
|
}
|
||||||
|
|
||||||
private object Invoke(
|
private object Invoke(
|
||||||
string methodName,
|
string methodName,
|
||||||
params object[] arguments)
|
params object[] arguments)
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ public sealed class MxAccessCommandExecutor : IStaCommandExecutor
|
|||||||
{
|
{
|
||||||
MxCommandKind.Register => ExecuteRegister(command),
|
MxCommandKind.Register => ExecuteRegister(command),
|
||||||
MxCommandKind.Unregister => ExecuteUnregister(command),
|
MxCommandKind.Unregister => ExecuteUnregister(command),
|
||||||
|
MxCommandKind.AddItem => ExecuteAddItem(command),
|
||||||
|
MxCommandKind.AddItem2 => ExecuteAddItem2(command),
|
||||||
|
MxCommandKind.RemoveItem => ExecuteRemoveItem(command),
|
||||||
_ => CreateInvalidRequestReply(command, $"Unsupported MXAccess command kind {command.Kind}."),
|
_ => CreateInvalidRequestReply(command, $"Unsupported MXAccess command kind {command.Kind}."),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -67,6 +70,66 @@ public sealed class MxAccessCommandExecutor : IStaCommandExecutor
|
|||||||
return CreateOkReply(command);
|
return CreateOkReply(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MxCommandReply ExecuteAddItem(StaCommand command)
|
||||||
|
{
|
||||||
|
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.AddItem)
|
||||||
|
{
|
||||||
|
return CreateInvalidRequestReply(command, "AddItem command payload is required.");
|
||||||
|
}
|
||||||
|
|
||||||
|
AddItemCommand addItemCommand = command.Command.AddItem;
|
||||||
|
int itemHandle = session.AddItem(
|
||||||
|
addItemCommand.ServerHandle,
|
||||||
|
addItemCommand.ItemDefinition);
|
||||||
|
|
||||||
|
MxCommandReply reply = CreateOkReply(command);
|
||||||
|
reply.ReturnValue = variantConverter.Convert(itemHandle);
|
||||||
|
reply.AddItem = new AddItemReply
|
||||||
|
{
|
||||||
|
ItemHandle = itemHandle,
|
||||||
|
};
|
||||||
|
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MxCommandReply ExecuteAddItem2(StaCommand command)
|
||||||
|
{
|
||||||
|
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.AddItem2)
|
||||||
|
{
|
||||||
|
return CreateInvalidRequestReply(command, "AddItem2 command payload is required.");
|
||||||
|
}
|
||||||
|
|
||||||
|
AddItem2Command addItem2Command = command.Command.AddItem2;
|
||||||
|
int itemHandle = session.AddItem2(
|
||||||
|
addItem2Command.ServerHandle,
|
||||||
|
addItem2Command.ItemDefinition,
|
||||||
|
addItem2Command.ItemContext);
|
||||||
|
|
||||||
|
MxCommandReply reply = CreateOkReply(command);
|
||||||
|
reply.ReturnValue = variantConverter.Convert(itemHandle);
|
||||||
|
reply.AddItem2 = new AddItem2Reply
|
||||||
|
{
|
||||||
|
ItemHandle = itemHandle,
|
||||||
|
};
|
||||||
|
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MxCommandReply ExecuteRemoveItem(StaCommand command)
|
||||||
|
{
|
||||||
|
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.RemoveItem)
|
||||||
|
{
|
||||||
|
return CreateInvalidRequestReply(command, "RemoveItem command payload is required.");
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoveItemCommand removeItemCommand = command.Command.RemoveItem;
|
||||||
|
session.RemoveItem(
|
||||||
|
removeItemCommand.ServerHandle,
|
||||||
|
removeItemCommand.ItemHandle);
|
||||||
|
|
||||||
|
return CreateOkReply(command);
|
||||||
|
}
|
||||||
|
|
||||||
private static MxCommandReply CreateOkReply(StaCommand command)
|
private static MxCommandReply CreateOkReply(StaCommand command)
|
||||||
{
|
{
|
||||||
return new MxCommandReply
|
return new MxCommandReply
|
||||||
|
|||||||
@@ -6,12 +6,19 @@ namespace MxGateway.Worker.MxAccess;
|
|||||||
public sealed class MxAccessHandleRegistry
|
public sealed class MxAccessHandleRegistry
|
||||||
{
|
{
|
||||||
private readonly Dictionary<int, RegisteredServerHandle> serverHandles = new();
|
private readonly Dictionary<int, RegisteredServerHandle> serverHandles = new();
|
||||||
|
private readonly Dictionary<long, RegisteredItemHandle> itemHandles = new();
|
||||||
|
|
||||||
public IReadOnlyList<RegisteredServerHandle> ServerHandles => serverHandles
|
public IReadOnlyList<RegisteredServerHandle> ServerHandles => serverHandles
|
||||||
.Values
|
.Values
|
||||||
.OrderBy(handle => handle.ServerHandle)
|
.OrderBy(handle => handle.ServerHandle)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
|
public IReadOnlyList<RegisteredItemHandle> ItemHandles => itemHandles
|
||||||
|
.Values
|
||||||
|
.OrderBy(handle => handle.ServerHandle)
|
||||||
|
.ThenBy(handle => handle.ItemHandle)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
public void RegisterServerHandle(
|
public void RegisterServerHandle(
|
||||||
int serverHandle,
|
int serverHandle,
|
||||||
string clientName)
|
string clientName)
|
||||||
@@ -22,10 +29,54 @@ public sealed class MxAccessHandleRegistry
|
|||||||
public void UnregisterServerHandle(int serverHandle)
|
public void UnregisterServerHandle(int serverHandle)
|
||||||
{
|
{
|
||||||
serverHandles.Remove(serverHandle);
|
serverHandles.Remove(serverHandle);
|
||||||
|
|
||||||
|
foreach (long key in itemHandles
|
||||||
|
.Where(pair => pair.Value.ServerHandle == serverHandle)
|
||||||
|
.Select(pair => pair.Key)
|
||||||
|
.ToArray())
|
||||||
|
{
|
||||||
|
itemHandles.Remove(key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ContainsServerHandle(int serverHandle)
|
public bool ContainsServerHandle(int serverHandle)
|
||||||
{
|
{
|
||||||
return serverHandles.ContainsKey(serverHandle);
|
return serverHandles.ContainsKey(serverHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void RegisterItemHandle(
|
||||||
|
int serverHandle,
|
||||||
|
int itemHandle,
|
||||||
|
string itemDefinition,
|
||||||
|
string itemContext,
|
||||||
|
bool hasItemContext)
|
||||||
|
{
|
||||||
|
itemHandles[CreateItemKey(serverHandle, itemHandle)] = new RegisteredItemHandle(
|
||||||
|
serverHandle,
|
||||||
|
itemHandle,
|
||||||
|
itemDefinition,
|
||||||
|
itemContext,
|
||||||
|
hasItemContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveItemHandle(
|
||||||
|
int serverHandle,
|
||||||
|
int itemHandle)
|
||||||
|
{
|
||||||
|
itemHandles.Remove(CreateItemKey(serverHandle, itemHandle));
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ContainsItemHandle(
|
||||||
|
int serverHandle,
|
||||||
|
int itemHandle)
|
||||||
|
{
|
||||||
|
return itemHandles.ContainsKey(CreateItemKey(serverHandle, itemHandle));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long CreateItemKey(
|
||||||
|
int serverHandle,
|
||||||
|
int itemHandle)
|
||||||
|
{
|
||||||
|
return ((long)serverHandle << 32) | (uint)itemHandle;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,6 +106,51 @@ public sealed class MxAccessSession : IDisposable
|
|||||||
handleRegistry.UnregisterServerHandle(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 Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (disposed)
|
if (disposed)
|
||||||
|
|||||||
@@ -81,6 +81,19 @@ public sealed class MxAccessStaSession : IDisposable
|
|||||||
cancellationToken);
|
cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<IReadOnlyList<RegisteredItemHandle>> GetRegisteredItemHandlesAsync(
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (session is null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("MXAccess COM session has not been started.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return staRuntime.InvokeAsync(
|
||||||
|
() => session.HandleRegistry.ItemHandles,
|
||||||
|
cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (disposed)
|
if (disposed)
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
namespace MxGateway.Worker.MxAccess;
|
||||||
|
|
||||||
|
public sealed class RegisteredItemHandle
|
||||||
|
{
|
||||||
|
public RegisteredItemHandle(
|
||||||
|
int serverHandle,
|
||||||
|
int itemHandle,
|
||||||
|
string itemDefinition,
|
||||||
|
string itemContext,
|
||||||
|
bool hasItemContext)
|
||||||
|
{
|
||||||
|
ServerHandle = serverHandle;
|
||||||
|
ItemHandle = itemHandle;
|
||||||
|
ItemDefinition = itemDefinition;
|
||||||
|
ItemContext = itemContext;
|
||||||
|
HasItemContext = hasItemContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int ServerHandle { get; }
|
||||||
|
|
||||||
|
public int ItemHandle { get; }
|
||||||
|
|
||||||
|
public string ItemDefinition { get; }
|
||||||
|
|
||||||
|
public string ItemContext { get; }
|
||||||
|
|
||||||
|
public bool HasItemContext { get; }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user