I solved the problem by turning the createDeviceConnection()
method into an NModbusDeviceConnection
disposable class. In the static constructor of the NModbusDeviceConnection
is where the AssemblyResolve
event is subscribed to. Now I can create fakes of Controller
without it triggering the Modbus
assembly load.
For details, here is the NModbusDeviceConnection
class:
// ---------------------------------------------------------------------------------------------------------------------
// <copyright file="NModbusDeviceConnection.cs" company="Care Controls">
// Copyright (c) Care Controls Inc. All rights reserved.
// </copyright>
// ---------------------------------------------------------------------------------------------------------------------
namespace CareControls.Modbus.Tcp.Internal
{
using System;
using System.Net.Sockets;
using System.Reflection;
using global::Modbus.Device;
internal sealed class NModbusDeviceConnection : IDisposable
{
static NModbusDeviceConnection()
{
AppDomain.CurrentDomain.AssemblyResolve += (sender, e) => loadEmbeddedAssembly(e.Name);
}
public NModbusDeviceConnection(string ip)
{
const int port = 502;
var client = new TcpClient();
client.BeginConnect(ip, port, null, null).AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(2));
if (!client.Connected)
{
throw new Exception("Cannot connect to " + ip + ":" + port);
}
this.connection = ModbusIpMaster.CreateIp(client);
}
public ushort[] ReadHoldingRegisters(ushort startAddress, ushort numberOfRegisters)
{
return this.connection.ReadHoldingRegisters(startAddress, numberOfRegisters);
}
public ushort[] ReadInputRegisters(ushort startAddress, ushort numberOfRegisters)
{
return this.connection.ReadInputRegisters(startAddress, numberOfRegisters);
}
public bool[] ReadCoils(ushort startAddress, ushort numberOfCoils)
{
return this.connection.ReadCoils(startAddress, numberOfCoils);
}
public bool[] ReadInputs(ushort startAddress, ushort numberOfInputs)
{
return this.connection.ReadInputs(startAddress, numberOfInputs);
}
public void WriteSingleCoil(ushort address, bool value)
{
this.connection.WriteSingleCoil(address, value);
}
public void WriteMultipleCoils(ushort startAddress, bool[] values)
{
this.connection.WriteMultipleCoils(startAddress, values);
}
public void WriteSingleHoldingRegister(ushort address, ushort value)
{
this.connection.WriteSingleRegister(address, value);
}
public void WriteMultipleHoldingRegisters(ushort address, ushort[] values)
{
this.connection.WriteMultipleRegisters(address, values);
}
public void Dispose()
{
if (this.connection != null)
{
this.connection.Dispose();
}
}
private static Assembly loadEmbeddedAssembly(string name)
{
if (name.EndsWith("Retargetable=Yes"))
{
return Assembly.Load(new AssemblyName(name));
}
var container = Assembly.GetExecutingAssembly();
var path = new AssemblyName(name).Name + ".dll";
using (var stream = container.GetManifestResourceStream(path))
{
if (stream == null)
{
return null;
}
var bytes = new byte[stream.Length];
stream.Read(bytes, 0, bytes.Length);
return Assembly.Load(bytes);
}
}
private readonly ModbusIpMaster connection;
}
}
and here is the modified Controller
class:
// ---------------------------------------------------------------------------------------------------------------------
// <copyright file="Controller.cs" company="Care Controls">
// Copyright (c) Care Controls Inc. All rights reserved.
// </copyright>
// ---------------------------------------------------------------------------------------------------------------------
namespace CareControls.Modbus.Tcp
{
using System;
using CareControls.Modbus.Tcp.Internal;
public class Controller
: ConnectionInfo,
HoldingRegisterReader,
InputRegisterReader,
CoilReader,
InputReader,
CoilWriter,
HoldingRegisterWriter
{
public Controller()
{
this.newDeviceConnection = () => new NModbusDeviceConnection(this.IP);
}
public virtual string IP { get; set; }
public virtual ushort[] ReadHoldingRegisters(ushort startAddress, ushort numberOfRegisters)
{
using (var connection = this.newDeviceConnection())
{
return connection.ReadHoldingRegisters(startAddress, numberOfRegisters);
}
}
public virtual ushort[] ReadInputRegisters(ushort startAddress, ushort numberOfRegisters)
{
using (var connection = this.newDeviceConnection())
{
return connection.ReadInputRegisters(startAddress, numberOfRegisters);
}
}
public virtual bool[] ReadCoils(ushort startAddress, ushort numberOfCoils)
{
using (var connection = this.newDeviceConnection())
{
return connection.ReadCoils(startAddress, numberOfCoils);
}
}
public virtual bool[] ReadInputs(ushort startAddress, ushort numberOfInputs)
{
using (var connection = this.newDeviceConnection())
{
return connection.ReadInputs(startAddress, numberOfInputs);
}
}
public virtual void WriteSingleCoil(ushort address, bool value)
{
using (var connection = this.newDeviceConnection())
{
connection.WriteSingleCoil(address, value);
}
}
public virtual void WriteMultipleCoils(ushort startAddress, bool[] values)
{
using (var connection = this.newDeviceConnection())
{
connection.WriteMultipleCoils(startAddress, values);
}
}
public virtual void WriteSingleHoldingRegister(ushort address, ushort value)
{
using (var connection = this.newDeviceConnection())
{
connection.WriteSingleHoldingRegister(address, value);
}
}
public virtual void WriteMultipleHoldingRegisters(ushort startAddress, ushort[] values)
{
using (var connection = this.newDeviceConnection())
{
connection.WriteMultipleHoldingRegisters(startAddress, values);
}
}
string ConnectionInfo.IP
{
get
{
return this.IP;
}
set
{
this.IP = value;
}
}
ushort[] HoldingRegisterReader.Read(ushort startAddress, ushort numberOfRegisters)
{
return this.ReadHoldingRegisters(startAddress, numberOfRegisters);
}
ushort[] InputRegisterReader.Read(ushort startAddress, ushort numberOfRegisters)
{
return this.ReadInputRegisters(startAddress, numberOfRegisters);
}
bool[] CoilReader.Read(ushort startAddress, ushort numberOfCoils)
{
return this.ReadCoils(startAddress, numberOfCoils);
}
bool[] InputReader.Read(ushort startAddress, ushort numberOfInputs)
{
return this.ReadInputs(startAddress, numberOfInputs);
}
void CoilWriter.WriteSingle(ushort address, bool value)
{
this.WriteSingleCoil(address, value);
}
void CoilWriter.WriteMultiple(ushort startAddress, bool[] values)
{
this.WriteMultipleCoils(startAddress, values);
}
void HoldingRegisterWriter.WriteSingle(ushort address, ushort value)
{
this.WriteSingleHoldingRegister(address, value);
}
void HoldingRegisterWriter.WriteMultiple(ushort startAddress, ushort[] values)
{
this.WriteMultipleHoldingRegisters(startAddress, values);
}
private readonly Func<NModbusDeviceConnection> newDeviceConnection;
}
}