diff --git a/TitaniumAS.Opc.Client.Tests/TitaniumAS.Opc.Client.Tests.csproj b/TitaniumAS.Opc.Client.Tests/TitaniumAS.Opc.Client.Tests.csproj index f795428..9b9b6cc 100644 --- a/TitaniumAS.Opc.Client.Tests/TitaniumAS.Opc.Client.Tests.csproj +++ b/TitaniumAS.Opc.Client.Tests/TitaniumAS.Opc.Client.Tests.csproj @@ -8,7 +8,7 @@ Properties TitaniumAS.Opc.Client.Tests TitaniumAS.Opc.Client.Tests - v4.5 + v4.8 512 {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 10.0 diff --git a/TitaniumAS.Opc.Client.Tests/app.config b/TitaniumAS.Opc.Client.Tests/app.config index d61b593..6af320a 100644 --- a/TitaniumAS.Opc.Client.Tests/app.config +++ b/TitaniumAS.Opc.Client.Tests/app.config @@ -8,4 +8,4 @@ - + diff --git a/TitaniumAS.Opc.Client/Da/OpcDaServer.cs b/TitaniumAS.Opc.Client/Da/OpcDaServer.cs index 1adbeeb..cd035e8 100644 --- a/TitaniumAS.Opc.Client/Da/OpcDaServer.cs +++ b/TitaniumAS.Opc.Client/Da/OpcDaServer.cs @@ -19,592 +19,603 @@ namespace TitaniumAS.Opc.Client.Da { + /// + /// Represents the OPC DA server. + /// + /// + public class OpcDaServer : IOpcDaServer + { + private static readonly ILog Log = LogManager.GetLogger(); + private readonly List _groups = new List(); + private readonly ConnectionPoint _shutdownConnectionPoint; + private string _clientName; + private bool _disposed; + private OpcServerDescription _serverDescription; + /// - /// Represents the OPC DA server. + /// Initializes a new instance of the class. /// - /// - public class OpcDaServer : IOpcDaServer - { - private static readonly ILog Log = LogManager.GetLogger(); - private readonly List _groups = new List(); - private readonly ConnectionPoint _shutdownConnectionPoint; - private string _clientName; - private bool _disposed; - private OpcServerDescription _serverDescription; - - /// - /// Initializes a new instance of the class. - /// - /// The server URI. - public OpcDaServer(Uri uri) - { - UrlValidator.CheckOpcUrl(uri); + /// The server URI. + public OpcDaServer(Uri uri, ComProxyBlanket comProxyBlanket = null) + { + UrlValidator.CheckOpcUrl(uri); - Uri = uri; - ComProxyBlanket = ComProxyBlanket.Default; + Uri = uri; + ComProxyBlanket = comProxyBlanket ?? ComProxyBlanket.Default; - var shutdown = new OpcShutdown(); - shutdown.Shutdown += OnShutdown; - ComWrapper.RpcFailed += OnRpcFailed; + var shutdown = new OpcShutdown(); + shutdown.Shutdown += OnShutdown; + ComWrapper.RpcFailed += OnRpcFailed; - _shutdownConnectionPoint = new ConnectionPoint(shutdown); - _clientName = Path.GetFileName(Process.GetCurrentProcess().MainModule.FileName); - } + _shutdownConnectionPoint = new ConnectionPoint(shutdown); + _clientName = Path.GetFileName(Process.GetCurrentProcess().MainModule.FileName); + } - /// - /// Initializes a new instance of the class. - /// - /// The OPC server programmatic identifier or class identifier. - /// The OPC server host. - public OpcDaServer(string progIdOrClsid, string host = null) - : this(UrlBuilder.Build(progIdOrClsid, host)) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The OPC server programmatic identifier or class identifier. + /// The OPC server host. + public OpcDaServer(string progIdOrClsid, string host = null, ComProxyBlanket comProxyBlanket = null) + : this(UrlBuilder.Build(progIdOrClsid, host), comProxyBlanket) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The OPC server class identifier. - /// The OPC server host. - public OpcDaServer(Guid clsid, string host = null) - : this(UrlBuilder.Build(clsid, host)) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The OPC server class identifier. + /// The OPC server host. + public OpcDaServer(Guid clsid, string host = null, ComProxyBlanket comProxyBlanket = null) + : this(UrlBuilder.Build(clsid, host), comProxyBlanket) + { + } - /// - /// Gets the COM proxy blanket of this instance. - /// - /// - /// The COM proxy blanket. - /// - public ComProxyBlanket ComProxyBlanket { get; set; } - - /// - /// Gets the actual COM object. - /// - /// - /// The actual COM object. - /// - public object ComObject { get; private set; } - - /// - /// Gets the server description. - /// - /// - /// The server description. - /// - public OpcServerDescription ServerDescription - { - get - { - if (_serverDescription == null) - { - var enumerator = new OpcServerEnumeratorAuto(); - _serverDescription = UrlParser.Parse( - Uri, - (host, progId) => enumerator.GetServerDescription(host, progId), - (host, clsid) => enumerator.GetServerDescription(host, clsid)); - } - return _serverDescription; - } - } + /// + /// Gets the COM proxy blanket of this instance. + /// + /// + /// The COM proxy blanket. + /// + public ComProxyBlanket ComProxyBlanket { get; set; } - /// - /// Connects the server instance to COM server. - /// - /// Already connected to the OPC DA server. - public void Connect() - { - if (IsConnected) - throw new InvalidOperationException("Already connected to the OPC DA server."); - - Log.TraceFormat("Connecting to '{0}' opc server", Uri); - - var enumerator = new OpcServerEnumeratorAuto(); - Tuple hostAndCLSID = UrlParser.Parse( - Uri, - (host, progId) => new Tuple(host, enumerator.CLSIDFromProgId(progId, host)), - (host, clsid) => new Tuple(host, clsid)); - - ComObject = Com.CreateInstanceWithBlanket(hostAndCLSID.Item2, hostAndCLSID.Item1, null, ComProxyBlanket); - _shutdownConnectionPoint.TryConnect(ComObject); - - Log.TraceFormat("Connected to '{0}' opc server.", Uri); - try - { - ClientName = _clientName; - } - catch (Exception ex) - { - Log.Warn("Cannot setup name of client.", ex); - } - OnConnectionStateChanged(true); - } + /// + /// Gets the actual COM object. + /// + /// + /// The actual COM object. + /// + public object ComObject { get; private set; } - /// - /// Asynchronout connect method - /// - /// - /// A running task to wait on for connect - /// - /// - public Task ConnectAsync() - { - return Task.Factory.StartNew(() => { Connect(); }); - } + /// + /// Gets the server description. + /// + /// + /// The server description. + /// + public OpcServerDescription ServerDescription + { + get + { + if (_serverDescription == null) + { + var enumerator = new OpcServerEnumeratorAuto(); + _serverDescription = UrlParser.Parse( + Uri, + (host, progId) => enumerator.GetServerDescription(host, progId), + (host, clsid) => enumerator.GetServerDescription(host, clsid)); + } + return _serverDescription; + } + } - private void DisconnectImpl(bool rpcFailed = false) - { - if (!IsConnected) - return; - - Log.TraceFormat("Disconnecting from '{0}' opc server", Uri); - if (!rpcFailed) - { - _shutdownConnectionPoint.Disconnect(); - } - RemoveAllGroups(rpcFailed); - - if (ComObject != null) - { - try - { - ComObject.ReleaseComServer(); - } - catch (Exception ex) - { - Log.Error("Failed to release opc server's COM object", ex); - } - } - - ComObject = null; - Log.TraceFormat("Disconnected from '{0}' opc server", Uri); - OnConnectionStateChanged(false); - } + /// + /// Connects the server instance to COM server. + /// + /// Already connected to the OPC DA server. + public void Connect() + { + if (IsConnected) + throw new InvalidOperationException("Already connected to the OPC DA server."); + + Log.TraceFormat("Connecting to '{0}' opc server", Uri); + + var enumerator = new OpcServerEnumeratorAuto(); + Tuple hostAndCLSID = UrlParser.Parse( + Uri, + (host, progId) => new Tuple(host, enumerator.CLSIDFromProgId(progId, host)), + (host, clsid) => new Tuple(host, clsid)); + + ComObject = Com.CreateInstanceWithBlanket(hostAndCLSID.Item2, hostAndCLSID.Item1, null, ComProxyBlanket); + _shutdownConnectionPoint.TryConnect(ComObject); + + Log.TraceFormat("Connected to '{0}' opc server.", Uri); + try + { + ClientName = _clientName; + } + catch (Exception ex) + { + Log.Warn("Cannot setup name of client.", ex); + } + OnConnectionStateChanged(true); + } + /// + /// Asynchronout connect method + /// + /// + /// A running task to wait on for connect + /// + /// + public Task ConnectAsync() + { + return Task.Factory.StartNew(() => { Connect(); }); + } - /// - /// Releases connection to COM server for the server instance. - /// - public void Disconnect() - { - DisconnectImpl(); - } + private void DisconnectImpl(bool rpcFailed = false) + { + if (!IsConnected) + return; - /// - /// Gets the server URI. The URL follows a template: opcda://host/progIdOrCLSID. - /// - /// - /// The server URI. - /// - public Uri Uri { get; private set; } - - /// - /// Gets a value indicating whether the server instance is connected to COM server. - /// - /// - /// true if the server instance is connected to COM server; otherwise, false. - /// - public bool IsConnected - { - get { return ComObject != null; } - } + Log.TraceFormat("Disconnecting from '{0}' opc server", Uri); + if (!rpcFailed) + { + _shutdownConnectionPoint.Disconnect(); + } + RemoveAllGroups(rpcFailed); - /// - /// Gets or sets the current culture for the server. - /// - /// - /// The current culture for the server. - /// - public CultureInfo Culture + if (ComObject != null) + { + try { - get - { - CheckConnected(); - int localeId = As().LocaleID; - return CultureHelper.GetCultureInfo(localeId); - } - set - { - CheckConnected(); - As().LocaleID = CultureHelper.GetLocaleId(value); - } + ComObject.ReleaseComServer(); } - - /// - /// Queries available cultures of the server. - /// - /// - /// Array of available cultures. - /// - public CultureInfo[] QueryAvailableCultures() + catch (Exception ex) { - CheckConnected(); - return As().QueryAvailableLocaleIDs().Select(CultureHelper.GetCultureInfo).ToArray(); + Log.Error("Failed to release opc server's COM object", ex); } + } - /// - /// Gets or sets the client name. - /// - /// - /// The client name. - /// - public string ClientName - { - get { return _clientName; } - set - { - _clientName = value; - if (IsConnected) - { - As().ClientName = value; - } - } - } + ComObject = null; + Log.TraceFormat("Disconnected from '{0}' opc server", Uri); + OnConnectionStateChanged(false); + } - /// - /// Gets the groups of the server. - /// - /// - /// The server groups. - /// - public ReadOnlyCollection Groups - { - get { return _groups.AsReadOnly(); } - } + /// + /// Releases connection to COM server for the server instance. + /// + public void Disconnect() + { + DisconnectImpl(); + } - /// - /// Gets or attachs arbitrary user data to the server. - /// - /// - /// The user data. - /// - public object UserData { get; set; } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } + /// + /// Gets the server URI. The URL follows a template: opcda://host/progIdOrCLSID. + /// + /// + /// The server URI. + /// + public Uri Uri { get; private set; } - /// - /// Occurs when OPC server shutdowns right after connection to COM server has releases. - /// - public event EventHandler Shutdown; - - /// - /// Occurs when the server either connects or releases connection to COM server. - /// - public event EventHandler ConnectionStateChanged; - - /// - /// Occurs when the server groups have changed. - /// - public event EventHandler GroupsChanged; - - /// - /// Removes the group from the server. - /// - /// The server group. - /// The group doesn't belong to the server;group - public void RemoveGroup(OpcDaGroup group) - { - CheckConnected(); + /// + /// Gets a value indicating whether the server instance is connected to COM server. + /// + /// + /// true if the server instance is connected to COM server; otherwise, false. + /// + public bool IsConnected + { + get { return ComObject != null; } + } - if (group.Server != this) - throw new ArgumentException("The group doesn't belong to the server", "group"); - TryRemoveGroup(@group); - } + /// + /// Gets or sets the current culture for the server. + /// + /// + /// The current culture for the server. + /// + public CultureInfo Culture + { + get + { + CheckConnected(); + int localeId = As().LocaleID; + return CultureHelper.GetCultureInfo(localeId); + } + set + { + CheckConnected(); + As().LocaleID = CultureHelper.GetLocaleId(value); + } + } - /// - /// Gets the server status. - /// - /// - /// The server status. - /// - public OpcDaServerStatus GetStatus() - { - CheckConnected(); - return new OpcDaServerStatus(As().GetStatus()); - } + /// + /// Queries available cultures of the server. + /// + /// + /// Array of available cultures. + /// + public CultureInfo[] QueryAvailableCultures() + { + CheckConnected(); + return As().QueryAvailableLocaleIDs().Select(CultureHelper.GetCultureInfo).ToArray(); + } - /// - /// Reads one or more values, qualities and timestamps for the items specified using MaxAge. If the information in the - /// cache is within the MaxAge, then the data will be obtained from the cache, otherwise the server must access the - /// device for the requested information. - /// - /// The server item identifiers. - /// The list of MaxAges for the server items. - /// - /// The values of the server items. - /// - public OpcDaVQTE[] Read(IList itemIds, IList maxAge) + /// + /// Gets or sets the client name. + /// + /// + /// The client name. + /// + public string ClientName + { + get { return _clientName; } + set + { + _clientName = value; + if (IsConnected) { - CheckConnected(); - return As().Read(itemIds, maxAge); + As().ClientName = value; } + } + } - /// - /// Writes one or more values, qualities and timestamps for the items specified. If a client attempts to write VQ, VT, - /// or VQT it should expect that the server will write them all or none at all. - /// - /// The server item identifiers. - /// The list of values, qualities and timestamps. - /// - /// Array of HRESULTs indicating the success of the individual item Writes. - /// - public HRESULT[] WriteVQT(IList itemIds, IList values) - { - CheckConnected(); - return As().WriteVQT(itemIds, values); - } + /// + /// Gets the groups of the server. + /// + /// + /// The server groups. + /// + public ReadOnlyCollection Groups + { + get { return _groups.AsReadOnly(); } + } - /// - /// Adds the server group by group name and group state (optional). - /// - /// The group name. - /// The group state. - /// - /// Added group. - /// - public OpcDaGroup AddGroup(string name, OpcDaGroupState state = null) - { - CheckConnected(); - - if (state == null) - state = new OpcDaGroupState(); - - int serverGroupHandle; - TimeSpan revisedUpdateRate; - int localeId = CultureHelper.GetLocaleId(state.Culture); - - object opcDaGroup = As().AddGroup(name, - state.IsActive.GetValueOrDefault(false), - state.UpdateRate.GetValueOrDefault(TimeSpan.FromSeconds(1)), - state.ClientHandle.GetValueOrDefault(0), - state.TimeBias, - state.PercentDeadband, - localeId, out serverGroupHandle, out revisedUpdateRate); - - OpcDaGroup @group = CreateGroupWrapper(opcDaGroup); - @group.UserData = state.UserData; - OnGroupsChanged(new OpcDaServerGroupsChangedEventArgs(group, null)); - return @group; - } + /// + /// Gets or attachs arbitrary user data to the server. + /// + /// + /// The user data. + /// + public object UserData { get; set; } - /// - /// Synchronizes the server groups. It means existing groups will be replaced with new groups. It fires GroupsChanged - /// event after synchronization. - /// - public void SyncGroups() - { - CheckConnected(); - List newGroups = EnumerateGroups(); - - // Dispose old groups - foreach (OpcDaGroup @group in _groups) - { - ((IDisposable) @group).Dispose(); - OnGroupsChanged(new OpcDaServerGroupsChangedEventArgs(null, @group)); - } - _groups.Clear(); - - // Add new groups - _groups.InsertRange(0, newGroups); - foreach (OpcDaGroup @group in _groups) - { - OnGroupsChanged(new OpcDaServerGroupsChangedEventArgs(@group, null)); - } - } + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } - private void OnRpcFailed(object sender, RpcFailedEventArgs args) - { - if (args.UserData != this) return; - try - { - DisconnectImpl(true); - } - catch (Exception ex) - { - Log.Error("Disconnect failed.",ex); - } - try - { - OnShutdown(new OpcShutdownEventArgs("RPC failed", args.Error)); - } - catch (Exception ex) - { - Log.ErrorFormat("Error during shutdown of '{0}' opc server.", ex, Uri); - } - } + /// + /// Occurs when OPC server shutdowns right after connection to COM server has releases. + /// + public event EventHandler Shutdown; - private void OnShutdown(object sender, OpcShutdownEventArgs args) - { - try - { - DisconnectImpl(); - } - catch (Exception ex) - { - Log.Error("Disconnect failed.", ex); - } - - try - { - OnShutdown(new OpcShutdownEventArgs(args.Reason, args.Error)); - } - catch (Exception ex) - { - Log.ErrorFormat("Error during shutdown of '{0}' opc server.", ex, Uri); - } - } + /// + /// Occurs when the server either connects or releases connection to COM server. + /// + public event EventHandler ConnectionStateChanged; - /// - /// Try to cast this instance to specified COM wrapper type. - /// - /// The COM wrapper type. - /// The wrapped instance or null. - public T As() where T : ComWrapper - { - try - { - if (ComObject == null) - return default(T); - return (T) Activator.CreateInstance(typeof (T), ComObject, this); - } - catch - { - return default(T); - } - } + /// + /// Occurs when the server groups have changed. + /// + public event EventHandler GroupsChanged; - /// - /// Determines whether this instance is of specified COM wrapper type. - /// - /// The COM wrapper type. - /// true if this instance is of specified COM wrapper type; otherwise, false. - public bool Is() where T : ComWrapper - { - return As() != null; - } + /// + /// Removes the group from the server. + /// + /// The server group. + /// The group doesn't belong to the server;group + public void RemoveGroup(OpcDaGroup group) + { + CheckConnected(); - private void RemoveAllGroups(bool rpcFailed = false) - { - foreach (OpcDaGroup opcDaGroup in _groups.ToArray()) - { - TryRemoveGroup(opcDaGroup,rpcFailed); - } - _groups.Clear(); - } + if (group.Server != this) + throw new ArgumentException("The group doesn't belong to the server", "group"); + TryRemoveGroup(@group); + } - /// - /// Returns a that represents this instance. - /// - /// - /// A that represents this instance. - /// - public override string ToString() - { - return Uri.ToString(); - } + /// + /// Gets the server status. + /// + /// + /// The server status. + /// + public OpcDaServerStatus GetStatus() + { + CheckConnected(); + return new OpcDaServerStatus(As().GetStatus()); + } - private void CheckConnected() - { - if (!IsConnected) - throw ExceptionHelper.NotConnected(); - } + /// + /// Reads one or more values, qualities and timestamps for the items specified using MaxAge. If the information in the + /// cache is within the MaxAge, then the data will be obtained from the cache, otherwise the server must access the + /// device for the requested information. + /// + /// The server item identifiers. + /// The list of MaxAges for the server items. + /// + /// The values of the server items. + /// + public OpcDaVQTE[] Read(IList itemIds, IList maxAge) + { + CheckConnected(); + return As().Read(itemIds, maxAge); + } - private List EnumerateGroupNames(OpcDaEnumScope scope = OpcDaEnumScope.All) - { - object enumeratorObj = As().CreateGroupEnumerator((int) scope); - var enumerator = (IEnumString) enumeratorObj; - return enumerator.EnumareateAllAndRelease(OpcConfiguration.BatchSize); - } + /// + /// Writes one or more values, qualities and timestamps for the items specified. If a client attempts to write VQ, VT, + /// or VQT it should expect that the server will write them all or none at all. + /// + /// The server item identifiers. + /// The list of values, qualities and timestamps. + /// + /// Array of HRESULTs indicating the success of the individual item Writes. + /// + public HRESULT[] WriteVQT(IList itemIds, IList values) + { + CheckConnected(); + return As().WriteVQT(itemIds, values); + } - private List EnumerateGroups(OpcDaEnumScope scope = OpcDaEnumScope.All) - { - object enumeratorObj = As().CreateGroupEnumerator((int) scope); - var enumerator = (IEnumUnknown) enumeratorObj; - List interfaces = enumerator.EnumareateAllAndRelease(OpcConfiguration.BatchSize); - return interfaces.Select(i => new OpcDaGroup(i, this)).ToList(); - } + /// + /// Adds the server group by group name and group state (optional). + /// + /// The group name. + /// The group state. + /// + /// Added group. + /// + public OpcDaGroup AddGroup(string name, OpcDaGroupState state = null) + { + try + { + CheckConnected(); + + if (state == null) + state = new OpcDaGroupState(); + + int serverGroupHandle; + TimeSpan revisedUpdateRate; + int localeId = CultureHelper.GetLocaleId(state.Culture); + + object opcDaGroup = As().AddGroup(name, + state.IsActive.GetValueOrDefault(false), + state.UpdateRate.GetValueOrDefault(TimeSpan.FromSeconds(1)), + state.ClientHandle.GetValueOrDefault(0), + state.TimeBias, + state.PercentDeadband, + localeId, out serverGroupHandle, out revisedUpdateRate); + + OpcDaGroup @group = CreateGroupWrapper(opcDaGroup); + @group.UserData = state.UserData; + + OnGroupsChanged(new OpcDaServerGroupsChangedEventArgs(group, null)); + + return @group; + } + catch (Exception ex) + { + throw new Exception("Failed to add group (internal dll).", ex); + } + } + + /// + /// Synchronizes the server groups. It means existing groups will be replaced with new groups. It fires GroupsChanged + /// event after synchronization. + /// + public void SyncGroups() + { + CheckConnected(); + List newGroups = EnumerateGroups(); + + // Dispose old groups + foreach (OpcDaGroup @group in _groups) + { + ((IDisposable)@group).Dispose(); + OnGroupsChanged(new OpcDaServerGroupsChangedEventArgs(null, @group)); + } + _groups.Clear(); + + // Add new groups + _groups.InsertRange(0, newGroups); + foreach (OpcDaGroup @group in _groups) + { + OnGroupsChanged(new OpcDaServerGroupsChangedEventArgs(@group, null)); + } + } - private OpcDaGroup GetGroupByName(string name) + private void OnRpcFailed(object sender, RpcFailedEventArgs args) + { + if (args.UserData != this) return; + try + { + DisconnectImpl(true); + } + catch (Exception ex) + { + Log.Error("Disconnect failed.", ex); + } + try + { + OnShutdown(new OpcShutdownEventArgs("RPC failed", args.Error)); + } + catch (Exception ex) + { + Log.ErrorFormat("Error during shutdown of '{0}' opc server.", ex, Uri); + } + } + + private void OnShutdown(object sender, OpcShutdownEventArgs args) + { + try + { + DisconnectImpl(); + } + catch (Exception ex) + { + Log.Error("Disconnect failed.", ex); + } + + try + { + OnShutdown(new OpcShutdownEventArgs(args.Reason, args.Error)); + } + catch (Exception ex) + { + Log.ErrorFormat("Error during shutdown of '{0}' opc server.", ex, Uri); + } + } + + /// + /// Try to cast this instance to specified COM wrapper type. + /// + /// The COM wrapper type. + /// The wrapped instance or null. + public T As() where T : ComWrapper + { + try + { + if (ComObject == null) { - return new OpcDaGroup(As().GetGroupByName(name), this); + return default(T); } - // Protected implementation of Dispose pattern. - protected virtual void Dispose(bool disposing) - { - if (_disposed) - return; + return (T)Activator.CreateInstance(typeof(T), ComObject, this); + } + catch (Exception ex) + { + throw new Exception("Failed to create instance of " + typeof(T).Name + ".", ex); + } + } - if (disposing) - { - // Free any other managed objects here. - // - } + /// + /// Determines whether this instance is of specified COM wrapper type. + /// + /// The COM wrapper type. + /// true if this instance is of specified COM wrapper type; otherwise, false. + public bool Is() where T : ComWrapper + { + return As() != null; + } - // Free any unmanaged objects here. - // - DisconnectImpl(); + private void RemoveAllGroups(bool rpcFailed = false) + { + foreach (OpcDaGroup opcDaGroup in _groups.ToArray()) + { + TryRemoveGroup(opcDaGroup, rpcFailed); + } + _groups.Clear(); + } - _disposed = true; - Log.DebugFormat("Opc server '{0}' disposed.", Uri); - } + /// + /// Returns a that represents this instance. + /// + /// + /// A that represents this instance. + /// + public override string ToString() + { + return Uri.ToString(); + } - private bool TryRemoveGroup(OpcDaGroup @group, bool rpcFailed = false) - { - try - { - ((IDisposable) @group).Dispose(); - if (!rpcFailed) - { - As().RemoveGroup(@group.ServerHandle, false); - } - _groups.Remove(group); - OnGroupsChanged(new OpcDaServerGroupsChangedEventArgs(null, group)); - return true; - } - catch (Exception ex) - { - Log.ErrorFormat("Cannot remove group '{0}'.", ex, @group.Name); - return false; - } - } + private void CheckConnected() + { + if (!IsConnected) + throw ExceptionHelper.NotConnected(); + } - ~OpcDaServer() - { - Dispose(false); - } + private List EnumerateGroupNames(OpcDaEnumScope scope = OpcDaEnumScope.All) + { + object enumeratorObj = As().CreateGroupEnumerator((int)scope); + var enumerator = (IEnumString)enumeratorObj; + return enumerator.EnumareateAllAndRelease(OpcConfiguration.BatchSize); + } - protected virtual void OnShutdown(OpcShutdownEventArgs args) - { - EventHandler handler = Shutdown; - if (handler != null) handler(this, args); - } + private List EnumerateGroups(OpcDaEnumScope scope = OpcDaEnumScope.All) + { + object enumeratorObj = As().CreateGroupEnumerator((int)scope); + var enumerator = (IEnumUnknown)enumeratorObj; + List interfaces = enumerator.EnumareateAllAndRelease(OpcConfiguration.BatchSize); + return interfaces.Select(i => new OpcDaGroup(i, this)).ToList(); + } - internal OpcDaGroup CreateGroupWrapper(object opcDaGroup) - { - var group = new OpcDaGroup(opcDaGroup, this); - _groups.Add(group); - return group; - } + private OpcDaGroup GetGroupByName(string name) + { + return new OpcDaGroup(As().GetGroupByName(name), this); + } - protected virtual void OnConnectionStateChanged(bool isConnected) - { - EventHandler handler = ConnectionStateChanged; - if (handler != null) handler(this, new OpcDaServerConnectionStateChangedEventArgs(isConnected)); - } + // Protected implementation of Dispose pattern. + protected virtual void Dispose(bool disposing) + { + if (_disposed) + return; - protected virtual void OnGroupsChanged(OpcDaServerGroupsChangedEventArgs e) - { - EventHandler handler = GroupsChanged; - if (handler != null) handler(this, e); - } + if (disposing) + { + // Free any other managed objects here. + // + } + + // Free any unmanaged objects here. + // + DisconnectImpl(); + + _disposed = true; + Log.DebugFormat("Opc server '{0}' disposed.", Uri); + } + + private bool TryRemoveGroup(OpcDaGroup @group, bool rpcFailed = false) + { + try + { + ((IDisposable)@group).Dispose(); + if (!rpcFailed) + { + As().RemoveGroup(@group.ServerHandle, false); + } + _groups.Remove(group); + OnGroupsChanged(new OpcDaServerGroupsChangedEventArgs(null, group)); + return true; + } + catch (Exception ex) + { + Log.ErrorFormat("Cannot remove group '{0}'.", ex, @group.Name); + return false; + } + } + + ~OpcDaServer() + { + Dispose(false); + } + + protected virtual void OnShutdown(OpcShutdownEventArgs args) + { + EventHandler handler = Shutdown; + if (handler != null) handler(this, args); + } + + internal OpcDaGroup CreateGroupWrapper(object opcDaGroup) + { + var group = new OpcDaGroup(opcDaGroup, this); + _groups.Add(group); + return group; + } + + protected virtual void OnConnectionStateChanged(bool isConnected) + { + EventHandler handler = ConnectionStateChanged; + if (handler != null) handler(this, new OpcDaServerConnectionStateChangedEventArgs(isConnected)); + } + + protected virtual void OnGroupsChanged(OpcDaServerGroupsChangedEventArgs e) + { + EventHandler handler = GroupsChanged; + if (handler != null) handler(this, e); } + } } \ No newline at end of file diff --git a/TitaniumAS.Opc.Client/Da/Wrappers/OpcServer.cs b/TitaniumAS.Opc.Client/Da/Wrappers/OpcServer.cs index c83f0c4..8a670a7 100644 --- a/TitaniumAS.Opc.Client/Da/Wrappers/OpcServer.cs +++ b/TitaniumAS.Opc.Client/Da/Wrappers/OpcServer.cs @@ -7,93 +7,100 @@ namespace TitaniumAS.Opc.Client.Da.Wrappers { - public class OpcServer : ComWrapper + public class OpcServer : ComWrapper + { + public OpcServer(object comObject, object userData) : base(userData) { - public OpcServer(object comObject, object userData) : base(userData) - { - if (comObject == null) throw new ArgumentNullException("comObject"); - ComObject = DoComCall(comObject, "IUnknown::QueryInterface", - () => comObject.QueryInterface()); - } + if (comObject == null) throw new ArgumentNullException("comObject"); + ComObject = DoComCall(comObject, "IUnknown::QueryInterface", + () => comObject.QueryInterface()); + } - internal IOPCServer ComObject { get; set; } + internal IOPCServer ComObject { get; set; } - public object AddGroup(string name, bool active, TimeSpan requestedUpdateRate, int clientHandle, - TimeSpan? timeBias, float? percentDeadband, int lcid, out int serverHandle, out TimeSpan revisedUpdateRate) + public object AddGroup(string name, bool active, TimeSpan requestedUpdateRate, int clientHandle, + TimeSpan? timeBias, float? percentDeadband, int lcid, out int serverHandle, out TimeSpan revisedUpdateRate) + { + try + { + int pRevisedUpdateRate = 0; + Guid interfaceGuid = typeof(IOPCItemMgt).GUID; + object opcDaGroup = null; + int[] pTimeBias = null; + if (timeBias.HasValue) { - int pRevisedUpdateRate = 0; - Guid interfaceGuid = typeof (IOPCItemMgt).GUID; - object opcDaGroup = null; - int[] pTimeBias = null; - if (timeBias.HasValue) - { - pTimeBias = new[] {(int) timeBias.Value.TotalMinutes}; - } - float[] pPercentDeadband = null; - if (percentDeadband.HasValue) - { - pPercentDeadband = new[] {percentDeadband.Value}; - } - int _serverHandle = 0; - DoComCall(ComObject, - "IOpcServer::AdGroup", () => ComObject.AddGroup(name, active, (int) requestedUpdateRate.TotalMilliseconds, clientHandle, - pTimeBias, pPercentDeadband, lcid, out _serverHandle, out pRevisedUpdateRate, ref interfaceGuid, - out opcDaGroup), name, active, requestedUpdateRate.TotalMilliseconds, clientHandle, - pTimeBias, pPercentDeadband, lcid); - serverHandle = _serverHandle; - revisedUpdateRate = TimeSpan.FromMilliseconds(pRevisedUpdateRate); - return opcDaGroup; + pTimeBias = new[] { (int)timeBias.Value.TotalMinutes }; } - - public string GetErrorString(int error, int locale) + float[] pPercentDeadband = null; + if (percentDeadband.HasValue) { - return DoComCall(ComObject, "IOpcServer::GetErrorString", () => - { - string str; - ComObject.GetErrorString(error, locale, out str); - return str; - }, error, locale); + pPercentDeadband = new[] { percentDeadband.Value }; } + int _serverHandle = 0; + DoComCall(ComObject, + "IOpcServer::AdGroup", () => ComObject.AddGroup(name, active, (int)requestedUpdateRate.TotalMilliseconds, clientHandle, + pTimeBias, pPercentDeadband, lcid, out _serverHandle, out pRevisedUpdateRate, ref interfaceGuid, + out opcDaGroup), name, active, requestedUpdateRate.TotalMilliseconds, clientHandle, + pTimeBias, pPercentDeadband, lcid); + serverHandle = _serverHandle; + revisedUpdateRate = TimeSpan.FromMilliseconds(pRevisedUpdateRate); + return opcDaGroup; + } + catch (Exception ex) + { + throw new Exception("Failed to add group - " + ex.StackTrace, ex); + } + } - public object GetGroupByName(string name) - { - return DoComCall(ComObject, "IOpcServer::GetGroupName", () => - { - Guid interfaceGuid = typeof (IOPCItemMgt).GUID; - object ppUnk; - ComObject.GetGroupByName(name, ref interfaceGuid, out ppUnk); - return ppUnk; - }, name); - } + public string GetErrorString(int error, int locale) + { + return DoComCall(ComObject, "IOpcServer::GetErrorString", () => + { + string str; + ComObject.GetErrorString(error, locale, out str); + return str; + }, error, locale); + } - public OPCSERVERSTATUS GetStatus() - { - return DoComCall(ComObject, "IOpcServer::GetStatus", () => - { - IntPtr serverStatus; - ComObject.GetStatus(out serverStatus); - var ss = (OPCSERVERSTATUS) Marshal.PtrToStructure(serverStatus, typeof (OPCSERVERSTATUS)); - Marshal.DestroyStructure(serverStatus, typeof (OPCSERVERSTATUS)); - Marshal.FreeCoTaskMem(serverStatus); - return ss; - }); - } + public object GetGroupByName(string name) + { + return DoComCall(ComObject, "IOpcServer::GetGroupName", () => + { + Guid interfaceGuid = typeof(IOPCItemMgt).GUID; + object ppUnk; + ComObject.GetGroupByName(name, ref interfaceGuid, out ppUnk); + return ppUnk; + }, name); + } - public void RemoveGroup(int hServerGroup, bool bForce) - { - DoComCall(ComObject, "IOpcServer::RemoveGroup", () => ComObject.RemoveGroup(hServerGroup, bForce), - hServerGroup, bForce); - } + public OPCSERVERSTATUS GetStatus() + { + return DoComCall(ComObject, "IOpcServer::GetStatus", () => + { + IntPtr serverStatus; + ComObject.GetStatus(out serverStatus); + var ss = (OPCSERVERSTATUS)Marshal.PtrToStructure(serverStatus, typeof(OPCSERVERSTATUS)); + Marshal.DestroyStructure(serverStatus, typeof(OPCSERVERSTATUS)); + Marshal.FreeCoTaskMem(serverStatus); + return ss; + }); + } - public object CreateGroupEnumerator(int scope) - { - return DoComCall(ComObject, "IOpcServer::CreateGroupEnumerator", () => - { - Guid iid = typeof (IEnumUnknown).GUID; - object ppUnk; - ComObject.CreateGroupEnumerator(scope, ref iid, out ppUnk); - return ppUnk; - }, scope); - } + public void RemoveGroup(int hServerGroup, bool bForce) + { + DoComCall(ComObject, "IOpcServer::RemoveGroup", () => ComObject.RemoveGroup(hServerGroup, bForce), + hServerGroup, bForce); + } + + public object CreateGroupEnumerator(int scope) + { + return DoComCall(ComObject, "IOpcServer::CreateGroupEnumerator", () => + { + Guid iid = typeof(IEnumUnknown).GUID; + object ppUnk; + ComObject.CreateGroupEnumerator(scope, ref iid, out ppUnk); + return ppUnk; + }, scope); } + } } \ No newline at end of file diff --git a/TitaniumAS.Opc.Client/Interop/System/ServerInfo.cs b/TitaniumAS.Opc.Client/Interop/System/ServerInfo.cs index a6e7a43..ab25c51 100644 --- a/TitaniumAS.Opc.Client/Interop/System/ServerInfo.cs +++ b/TitaniumAS.Opc.Client/Interop/System/ServerInfo.cs @@ -4,108 +4,108 @@ namespace TitaniumAS.Opc.Client.Interop.System { + /// + /// A class used to allocate and deallocate the elements of a COSERVERINFO structure. + /// + internal class ServerInfo + { + private GCHandle _hAuthInfo; + private GCHandle _hDomain; + private GCHandle _hIdentity; + private GCHandle _hPassword; + private GCHandle _hUserName; + /// - /// A class used to allocate and deallocate the elements of a COSERVERINFO structure. + /// Allocates a COSERVERINFO structure. /// - internal class ServerInfo + public COSERVERINFO Allocate(string hostName, NetworkCredential credential) { - private GCHandle _hAuthInfo; - private GCHandle _hDomain; - private GCHandle _hIdentity; - private GCHandle _hPassword; - private GCHandle _hUserName; - - /// - /// Allocates a COSERVERINFO structure. - /// - public COSERVERINFO Allocate(string hostName, NetworkCredential credential) - { - string userName = null; - string password = null; - string domain = null; - - if (credential != null) - { - userName = credential.UserName; - password = credential.Password; - domain = credential.Domain; - } - - _hUserName = GCHandle.Alloc(userName, GCHandleType.Pinned); - _hPassword = GCHandle.Alloc(password, GCHandleType.Pinned); - _hDomain = GCHandle.Alloc(domain, GCHandleType.Pinned); - - _hIdentity = new GCHandle(); - - if (!string.IsNullOrEmpty(userName)) - { - var identity = new COAUTHIDENTITY - { - User = _hUserName.AddrOfPinnedObject(), - UserLength = (uint) (userName != null ? userName.Length : 0), - Password = _hPassword.AddrOfPinnedObject(), - PasswordLength = (uint) (password != null ? password.Length : 0), - Domain = _hDomain.AddrOfPinnedObject(), - DomainLength = (uint) (domain != null ? domain.Length : 0), - Flags = ComConstants.SEC_WINNT_AUTH_IDENTITY_UNICODE - }; - - _hIdentity = GCHandle.Alloc(identity, GCHandleType.Pinned); - } - - var authInfo = new COAUTHINFO - { - dwAuthnSvc = ComConstants.RPC_C_AUTHN_WINNT, - dwAuthzSvc = ComConstants.RPC_C_AUTHZ_NONE, - pwszServerPrincName = IntPtr.Zero, - dwAuthnLevel = ComConstants.RPC_C_AUTHN_LEVEL_CONNECT, - dwImpersonationLevel = ComConstants.RPC_C_IMP_LEVEL_IMPERSONATE, - pAuthIdentityData = (_hIdentity.IsAllocated) ? _hIdentity.AddrOfPinnedObject() : IntPtr.Zero, - dwCapabilities = ComConstants.EOAC_NONE - }; - - _hAuthInfo = GCHandle.Alloc(authInfo, GCHandleType.Pinned); - - var serverInfo = new COSERVERINFO - { - pwszName = hostName, - pAuthInfo = credential != null ? _hAuthInfo.AddrOfPinnedObject() : IntPtr.Zero, - dwReserved1 = 0, - dwReserved2 = 0 - }; - - return serverInfo; - } - - /// - /// Deallocated memory allocated when the COSERVERINFO structure was created. - /// - public void Deallocate() + string userName = null; + string password = null; + string domain = null; + + if (credential != null) + { + userName = credential.UserName; + password = credential.Password; + domain = credential.Domain; + } + + _hUserName = GCHandle.Alloc(userName, GCHandleType.Pinned); + _hPassword = GCHandle.Alloc(password, GCHandleType.Pinned); + _hDomain = GCHandle.Alloc(domain, GCHandleType.Pinned); + + _hIdentity = new GCHandle(); + + if (!string.IsNullOrEmpty(userName)) + { + var identity = new COAUTHIDENTITY { - if (_hUserName.IsAllocated) - { - _hUserName.Free(); - } - - if (_hPassword.IsAllocated) - { - _hPassword.Free(); - } - - if (_hDomain.IsAllocated) - { - _hDomain.Free(); - } - - if (_hIdentity.IsAllocated) - { - _hIdentity.Free(); - } - - if (_hAuthInfo.IsAllocated) - { - _hAuthInfo.Free(); - } - } + User = _hUserName.AddrOfPinnedObject(), + UserLength = (uint)(userName != null ? userName.Length : 0), + Password = _hPassword.AddrOfPinnedObject(), + PasswordLength = (uint)(password != null ? password.Length : 0), + Domain = _hDomain.AddrOfPinnedObject(), + DomainLength = (uint)(domain != null ? domain.Length : 0), + Flags = ComConstants.SEC_WINNT_AUTH_IDENTITY_UNICODE + }; + + _hIdentity = GCHandle.Alloc(identity, GCHandleType.Pinned); + } + + var authInfo = new COAUTHINFO + { + dwAuthnSvc = ComConstants.RPC_C_AUTHN_WINNT, + dwAuthzSvc = ComConstants.RPC_C_AUTHZ_NONE, + pwszServerPrincName = IntPtr.Zero, + dwAuthnLevel = ComConstants.RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, + dwImpersonationLevel = ComConstants.RPC_C_IMP_LEVEL_IMPERSONATE, + pAuthIdentityData = (_hIdentity.IsAllocated) ? _hIdentity.AddrOfPinnedObject() : IntPtr.Zero, + dwCapabilities = ComConstants.EOAC_NONE + }; + + _hAuthInfo = GCHandle.Alloc(authInfo, GCHandleType.Pinned); + + var serverInfo = new COSERVERINFO + { + pwszName = hostName, + pAuthInfo = credential != null ? _hAuthInfo.AddrOfPinnedObject() : IntPtr.Zero, + dwReserved1 = 0, + dwReserved2 = 0 + }; + + return serverInfo; + } + + /// + /// Deallocated memory allocated when the COSERVERINFO structure was created. + /// + public void Deallocate() + { + if (_hUserName.IsAllocated) + { + _hUserName.Free(); + } + + if (_hPassword.IsAllocated) + { + _hPassword.Free(); + } + + if (_hDomain.IsAllocated) + { + _hDomain.Free(); + } + + if (_hIdentity.IsAllocated) + { + _hIdentity.Free(); + } + + if (_hAuthInfo.IsAllocated) + { + _hAuthInfo.Free(); + } } + } } \ No newline at end of file diff --git a/TitaniumAS.Opc.Client/Properties/AssemblyInfo.cs b/TitaniumAS.Opc.Client/Properties/AssemblyInfo.cs index e188615..f1707ef 100644 --- a/TitaniumAS.Opc.Client/Properties/AssemblyInfo.cs +++ b/TitaniumAS.Opc.Client/Properties/AssemblyInfo.cs @@ -2,7 +2,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -// General Information about an assembly is controlled through the following +// General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. @@ -15,8 +15,8 @@ [assembly: AssemblyTrademark("Titanium")] [assembly: AssemblyCulture("")] -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] @@ -28,14 +28,14 @@ // Version information for an assembly consists of the following four values: // // Major Version -// Minor Version +// Minor Version // Build Number // Revision // -// You can specify all the values or you can default the Build and Revision Numbers +// You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.2.0")] -[assembly: AssemblyFileVersion("1.0.2.0")] +[assembly: AssemblyVersion("1.0.2.1")] +[assembly: AssemblyFileVersion("1.0.2.1")] [assembly: InternalsVisibleTo("TitaniumAS.Opc.Client.Tests")] \ No newline at end of file diff --git a/TitaniumAS.Opc.Client/TitaniumAS.Opc.Client.csproj b/TitaniumAS.Opc.Client/TitaniumAS.Opc.Client.csproj index bd437c6..42dcc25 100644 --- a/TitaniumAS.Opc.Client/TitaniumAS.Opc.Client.csproj +++ b/TitaniumAS.Opc.Client/TitaniumAS.Opc.Client.csproj @@ -9,7 +9,7 @@ Properties TitaniumAS.Opc.Client TitaniumAS.Opc.Client - v4.0 + v4.8 512 .\ diff --git a/TitaniumAS.Opc.Client/app.config b/TitaniumAS.Opc.Client/app.config index 11a829f..6af320a 100644 --- a/TitaniumAS.Opc.Client/app.config +++ b/TitaniumAS.Opc.Client/app.config @@ -8,4 +8,4 @@ - +