#region Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved //----------------------------------------------------------------------------- // Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved // Web: https://www.technosoftware.com // // The source code in this file is covered under a dual-license scenario: // - Owner of a purchased license: SCLA 1.0 // - GPL V3: everybody else // // SCLA license terms accompanied with this source code. // See SCLA 1.0: https://technosoftware.com/license/Source_Code_License_Agreement.pdf // // GNU General Public License as published by the Free Software Foundation; // version 3 of the License are accompanied with this source code. // See https://technosoftware.com/license/GPLv3License.txt // // This source code is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. //----------------------------------------------------------------------------- #endregion Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved #region Using Directives using System; using System.Collections; using System.Runtime.InteropServices; using Technosoftware.DaAeHdaClient.Hda; using Technosoftware.OpcRcw.Hda; #endregion #pragma warning disable CS0618 namespace Technosoftware.DaAeHdaClient.Com.Hda { /// /// An in-process wrapper for a remote OPC COM-HDA server (thread-safe). /// internal class Server : Technosoftware.DaAeHdaClient.Com.Server, ITsCHdaServer { #region Constructor //====================================================================== // Construction /// /// Initializes the object. /// internal Server() { } /// /// Initializes the object with the specifed COM server. /// internal Server(OpcUrl url, object server) { if (url == null) throw new ArgumentNullException(nameof(url)); url_ = (OpcUrl)url.Clone(); server_ = server; // establish the callback. Advise(); } #endregion #region IDisposable Members /// /// Dispose(bool disposing) executes in two distinct scenarios. /// If disposing equals true, the method has been called directly /// or indirectly by a user's code. Managed and unmanaged resources /// can be disposed. /// If disposing equals false, the method has been called by the /// runtime from inside the finalizer and you should not reference /// other objects. Only unmanaged resources can be disposed. /// /// If true managed and unmanaged resources can be disposed. If false only unmanaged resources. protected override void Dispose(bool disposing) { if (!disposed_) { lock (lock_) { if (disposing) { // Release managed resources. // close the callback. Unadvise(); } // Release unmanaged resources. // Set large fields to null. disposed_ = true; } } base.Dispose(disposing); } #endregion #region Server Info //====================================================================== // GetStatus /// /// Returns the current server status. /// /// The current server status. public OpcServerStatus GetServerStatus() { lock (this) { if (server_ == null) throw new NotConnectedException(); // initialize arguments. var pStatus = IntPtr.Zero; var wStatus = OPCHDA_SERVERSTATUS.OPCHDA_INDETERMINATE; var pftCurrentTime = IntPtr.Zero; var pftStartTime = IntPtr.Zero; short wMajorVersion = 0; short wMinorVersion = 0; short wBuildNumber = 0; var dwMaxReturnValues = 0; string szStatusString = null; string szVendorInfo = null; // invoke COM method. try { ((IOPCHDA_Server)server_).GetHistorianStatus( out wStatus, out pftCurrentTime, out pftStartTime, out wMajorVersion, out wMinorVersion, out wBuildNumber, out dwMaxReturnValues, out szStatusString, out szVendorInfo); } catch (Exception e) { throw Utilities.Interop.CreateException("IOPCHDA_Server.GetHistorianStatus", e); } // unmarshal return parameters and free memory. var status = new OpcServerStatus(); status.VendorInfo = szVendorInfo; status.ProductVersion = string.Format("{0}.{1}.{2}", wMajorVersion, wMinorVersion, wBuildNumber); switch (wStatus) { case OPCHDA_SERVERSTATUS.OPCHDA_DOWN: status.ServerState = OpcServerState.NotOperational; break; case OPCHDA_SERVERSTATUS.OPCHDA_INDETERMINATE: status.ServerState = OpcServerState.Unknown; break; case OPCHDA_SERVERSTATUS.OPCHDA_UP: status.ServerState = OpcServerState.Operational; break; default: status.ServerState = OpcServerState.Unknown; break; } status.ServerState = (OpcServerState)wStatus; status.StatusInfo = szStatusString; status.StartTime = DateTime.MinValue; status.CurrentTime = DateTime.MinValue; status.MaxReturnValues = dwMaxReturnValues; if (pftStartTime != IntPtr.Zero) { status.StartTime = Utilities.Interop.GetDateTime(pftStartTime); Marshal.FreeCoTaskMem(pftStartTime); } if (pftCurrentTime != IntPtr.Zero) { status.CurrentTime = Utilities.Interop.GetDateTime(pftCurrentTime); Marshal.FreeCoTaskMem(pftCurrentTime); } return status; } } //====================================================================== // GetAttributes /// /// Returns the item attributes supported by the server. /// /// The a set of item attributes and their descriptions. public TsCHdaAttribute[] GetAttributes() { lock (this) { if (server_ == null) throw new NotConnectedException(); // initialize arguments. var count = 0; var pIDs = IntPtr.Zero; var pNames = IntPtr.Zero; var pDescriptions = IntPtr.Zero; var pDataTypes = IntPtr.Zero; try { ((IOPCHDA_Server)server_).GetItemAttributes( out count, out pIDs, out pNames, out pDescriptions, out pDataTypes); } catch (Exception e) { throw Utilities.Interop.CreateException("IOPCHDA_Server.GetItemAttributes", e); } // check if no attributes supported. if (count == 0) { return new TsCHdaAttribute[0]; } // unmarshal return parameters and free memory. var ids = Utilities.Interop.GetInt32s(ref pIDs, count, true); var names = Utilities.Interop.GetUnicodeStrings(ref pNames, count, true); var descriptions = Utilities.Interop.GetUnicodeStrings(ref pDescriptions, count, true); var datatypes = Utilities.Interop.GetInt16s(ref pDataTypes, count, true); // verify return parameters. if (ids == null || names == null || descriptions == null || datatypes == null) { throw new OpcResultException(new OpcResult((int)OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null), "The response from the server was invalid or incomplete"); } var attributes = new TsCHdaAttribute[count]; for (var ii = 0; ii < count; ii++) { attributes[ii] = new TsCHdaAttribute(); attributes[ii].ID = ids[ii]; attributes[ii].Name = names[ii]; attributes[ii].Description = descriptions[ii]; attributes[ii].DataType = Utilities.Interop.GetType((VarEnum)Enum.ToObject(typeof(VarEnum), datatypes[ii])); } // return results. return attributes; } } //====================================================================== // GetAggregates /// /// Returns the aggregates supported by the server. /// /// The a set of aggregates and their descriptions. public TsCHdaAggregate[] GetAggregates() { lock (this) { if (server_ == null) throw new NotConnectedException(); // initialize arguments. var count = 0; var pIDs = IntPtr.Zero; var pNames = IntPtr.Zero; var pDescriptions = IntPtr.Zero; try { ((IOPCHDA_Server)server_).GetAggregates( out count, out pIDs, out pNames, out pDescriptions); } catch (Exception e) { throw Utilities.Interop.CreateException("IOPCHDA_Server.GetAggregates", e); } // check if no aggregates supported. if (count == 0) { return new TsCHdaAggregate[0]; } // unmarshal return parameters and free memory. var ids = Utilities.Interop.GetInt32s(ref pIDs, count, true); var names = Utilities.Interop.GetUnicodeStrings(ref pNames, count, true); var descriptions = Utilities.Interop.GetUnicodeStrings(ref pDescriptions, count, true); // verify return parameters. if (ids == null || names == null || descriptions == null) { throw new OpcResultException(new OpcResult((int)OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null), "The response from the server was invalid or incomplete"); } var aggregates = new TsCHdaAggregate[count]; for (var ii = 0; ii < count; ii++) { aggregates[ii] = new TsCHdaAggregate(); aggregates[ii].Id = ids[ii]; aggregates[ii].Name = names[ii]; aggregates[ii].Description = descriptions[ii]; } // return results. return aggregates; } } //====================================================================== // CreateBrowser /// /// Creates a object used to browse the server address space. /// /// The set of attribute filters to use when browsing. /// A result code for each individual filter. /// A browser object that must be released by calling Dispose(). public ITsCHdaBrowser CreateBrowser(TsCHdaBrowseFilter[] filters, out OpcResult[] results) { lock (this) { if (server_ == null) throw new NotConnectedException(); // initialize arguments. var count = (filters != null) ? filters.Length : 0; // marshal input parameters. var ids = new int[count]; var values = new object[count]; var operators = new OPCHDA_OPERATORCODES[count]; for (var ii = 0; ii < count; ii++) { ids[ii] = filters[ii].AttributeID; operators[ii] = (OPCHDA_OPERATORCODES)Enum.ToObject(typeof(OPCHDA_OPERATORCODES), filters[ii].Operator); values[ii] = Utilities.Interop.GetVARIANT(filters[ii].FilterValue); } // initialize output parameners IOPCHDA_Browser pBrowser = null; var pErrors = IntPtr.Zero; // call COM server. try { ((IOPCHDA_Server)server_).CreateBrowse( count, ids, operators, values, out pBrowser, out pErrors); } catch (Exception e) { throw Utilities.Interop.CreateException("IOPCHDA_Server.CreateBrowse", e); } // unmarshal return parameters and free memory. var errors = Utilities.Interop.GetInt32s(ref pErrors, count, true); // verify return parameters. if ((count > 0 && errors == null) || pBrowser == null) { throw new OpcResultException(new OpcResult((int)OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null), "The browse operation cannot continue"); } results = new OpcResult[count]; for (var ii = 0; ii < count; ii++) { results[ii] = Utilities.Interop.GetResultId(errors[ii]); } // return browser. return new Browser(this, pBrowser, filters, results); } } #endregion #region Item Management //====================================================================== // CreateItems /// /// Creates a set of items. /// /// The identifiers for the items to create. /// The results for each item containing the server handle and result code. public OpcItemResult[] CreateItems(OpcItem[] items) { if (items == null) throw new ArgumentNullException(nameof(items)); lock (this) { if (server_ == null) throw new NotConnectedException(); // handle trivial case. if (items.Length == 0) { return new OpcItemResult[0]; } // initialize input parameters. var itemIDs = new string[items.Length]; var clientHandles = new int[items.Length]; for (var ii = 0; ii < items.Length; ii++) { if (items[ii] != null) { itemIDs[ii] = items[ii].ItemName; clientHandles[ii] = CreateHandle(); } } // initialize output arguments. var pServerHandles = IntPtr.Zero; var pErrors = IntPtr.Zero; // invoke COM method. try { ((IOPCHDA_Server)server_).GetItemHandles( items.Length, itemIDs, clientHandles, out pServerHandles, out pErrors); } catch (Exception e) { throw Utilities.Interop.CreateException("IOPCHDA_Server.GetItemHandles", e); } // unmarshal return parameters and free memory. var serverHandles = Utilities.Interop.GetInt32s(ref pServerHandles, items.Length, true); var errors = Utilities.Interop.GetInt32s(ref pErrors, items.Length, true); // verify return parameters. if (serverHandles == null || errors == null) { throw new OpcResultException(new OpcResult((int)OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null), "The browse operation cannot continue"); } var results = new OpcItemResult[items.Length]; for (var ii = 0; ii < results.Length; ii++) { results[ii] = new OpcItemResult(items[ii]); results[ii].Result = Utilities.Interop.GetResultId(errors[ii]); if (results[ii].Result.Succeeded()) { // cache item id locally to store remote server handle/local client handle mapping. var itemID = new OpcItem(); itemID.ItemName = items[ii].ItemName; itemID.ItemPath = items[ii].ItemPath; itemID.ServerHandle = serverHandles[ii]; itemID.ClientHandle = items[ii].ClientHandle; items_.Add(clientHandles[ii], itemID); // return correct handles in result. results[ii].ServerHandle = clientHandles[ii]; results[ii].ClientHandle = items[ii].ClientHandle; } } return results; } } //====================================================================== // ReleaseItems /// /// Releases a set of previously created items. /// /// The server handles for the items to release. /// The results for each item containing the result code. public OpcItemResult[] ReleaseItems(OpcItem[] items) { if (items == null) throw new ArgumentNullException(nameof(items)); lock (this) { if (server_ == null) throw new NotConnectedException(); // handle trivial case. if (items.Length == 0) { return new OpcItemResult[0]; } // initialize input parameters. var serverHandles = GetServerHandles(items); // initialize output arguments. var pErrors = IntPtr.Zero; // invoke COM method. try { ((IOPCHDA_Server)server_).ReleaseItemHandles( items.Length, serverHandles, out pErrors); } catch (Exception e) { throw Utilities.Interop.CreateException("IOPCHDA_Server.ReleaseItemHandles", e); } // unmarshal return parameters and free memory. var errors = Utilities.Interop.GetInt32s(ref pErrors, items.Length, true); // verify return parameters. if (errors == null) { throw new OpcResultException(new OpcResult((int)OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null), "The browse operation cannot continue"); } var results = new OpcItemResult[items.Length]; for (var ii = 0; ii < results.Length; ii++) { results[ii] = new OpcItemResult(items[ii]); results[ii].Result = Utilities.Interop.GetResultId(errors[ii]); if (results[ii].Result.Succeeded() && items[ii].ServerHandle != null) { // lookup locally cached item id. var itemID = (OpcItem)items_[items[ii].ServerHandle]; // remove the locally cached item. if (itemID != null) { results[ii].ItemName = itemID.ItemName; results[ii].ItemPath = itemID.ItemPath; results[ii].ClientHandle = itemID.ClientHandle; items_.Remove(items[ii].ServerHandle); } } } // return results. return results; } } //====================================================================== // ValidateItems /// /// Validates a set of items. /// /// The identifiers for the items to validate. /// The results for each item containing the result code. public OpcItemResult[] ValidateItems(OpcItem[] items) { if (items == null) throw new ArgumentNullException(nameof(items)); lock (this) { if (server_ == null) throw new NotConnectedException(); // handle trivial case. if (items.Length == 0) { return new OpcItemResult[0]; } // initialize input parameters. var itemIDs = new string[items.Length]; for (var ii = 0; ii < items.Length; ii++) { if (items[ii] != null) { itemIDs[ii] = items[ii].ItemName; } } // initialize output arguments. var pErrors = IntPtr.Zero; // invoke COM method. try { ((IOPCHDA_Server)server_).ValidateItemIDs( items.Length, itemIDs, out pErrors); } catch (Exception e) { throw Utilities.Interop.CreateException("IOPCHDA_Server.ValidateItemIDs", e); } // unmarshal return parameters and free memory. var errors = Utilities.Interop.GetInt32s(ref pErrors, items.Length, true); // verify return parameters. if (errors == null) { throw new OpcResultException(new OpcResult((int)OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null), "The browse operation cannot continue"); } var results = new OpcItemResult[items.Length]; for (var ii = 0; ii < results.Length; ii++) { results[ii] = new OpcItemResult(items[ii]); results[ii].Result = Utilities.Interop.GetResultId(errors[ii]); } return results; } } #endregion #region Read Raw //====================================================================== // ReadRaw /// /// Reads raw (unprocessed) data from the historian database for a set of items. /// /// The beginning of the history period to read. /// The end of the history period to be read. /// The number of values to be read for each item. /// Whether the bounding item values should be returned. /// The set of items to read (must include the server handle). /// A set of values, qualities and timestamps within the requested time range for each item. public TsCHdaItemValueCollection[] ReadRaw( TsCHdaTime startTime, TsCHdaTime endTime, int maxValues, bool includeBounds, OpcItem[] items) { if (items == null) throw new ArgumentNullException(nameof(items)); lock (this) { if (server_ == null) throw new NotConnectedException(); // handle trivial case. if (items.Length == 0) { return new TsCHdaItemValueCollection[0]; } // initialize input parameters. var serverHandles = GetServerHandles(items); var pStartTime = Interop.GetTime(startTime); var pEndTime = Interop.GetTime(endTime); // initialize output arguments. var pValues = IntPtr.Zero; var pErrors = IntPtr.Zero; // invoke COM method. try { ((IOPCHDA_SyncRead)server_).ReadRaw( ref pStartTime, ref pEndTime, maxValues, (includeBounds) ? 1 : 0, serverHandles.Length, serverHandles, out pValues, out pErrors); } catch (Exception e) { throw Utilities.Interop.CreateException("IOPCHDA_SyncRead.ReadRaw", e); } // unmarhal modified item structures. var results = Interop.GetItemValueCollections(ref pValues, items.Length, true); // update result with error code and info from the item argument. UpdateResults(items, results, ref pErrors); // store actual items in result. UpdateActualTimes(results, pStartTime, pEndTime); // completed successfully. return results; } } /// /// Sends an asynchronous request to read raw data from the historian database for a set of items. /// /// The beginning of the history period to read. /// The end of the history period to be read. /// The number of values to be read for each item. /// Whether the bounding item values should be returned. /// The set of items to read (must include the server handle). /// An identifier for the request assigned by the caller. /// A delegate used to receive notifications when the request completes. /// An object that contains the state of the request (used to cancel the request). /// A set of results containing any errors encountered when the server validated the items. public OpcItemResult[] ReadRaw( TsCHdaTime startTime, TsCHdaTime endTime, int maxValues, bool includeBounds, OpcItem[] items, object requestHandle, TsCHdaReadValuesCompleteEventHandler callback, out IOpcRequest request) { if (items == null) throw new ArgumentNullException(nameof(items)); if (callback == null) throw new ArgumentNullException(nameof(callback)); request = null; lock (this) { if (server_ == null) throw new NotConnectedException(); // handle trivial case. if (items.Length == 0) { return new OpcItemResult[0]; } var internalRequest = callback_.CreateRequest(requestHandle, callback); // initialize input parameters. var requestID = internalRequest.RequestID; var cancelID = 0; var serverHandles = GetServerHandles(items); var pStartTime = Interop.GetTime(startTime); var pEndTime = Interop.GetTime(endTime); // initialize output arguments. var pErrors = IntPtr.Zero; // invoke COM method. try { ((IOPCHDA_AsyncRead)server_).ReadRaw( internalRequest.RequestID, ref pStartTime, ref pEndTime, maxValues, (includeBounds) ? 1 : 0, serverHandles.Length, serverHandles, out cancelID, out pErrors); } catch (Exception e) { throw Utilities.Interop.CreateException("IOPCHDA_AsyncRead.ReadRaw", e); } // create result objects. var results = new OpcItemResult[items.Length]; for (var ii = 0; ii < items.Length; ii++) { results[ii] = new OpcItemResult(); } // update result with error code and info from the item argument. UpdateResults(items, results, ref pErrors); // check if request has already completed. if (internalRequest.Update(cancelID, results)) { // discard the request. request = null; callback_.CancelRequest(internalRequest, (TsCHdaCancelCompleteEventHandler)null); // return results. return results; } // store actual items in request object. UpdateActualTimes(new ITsCHdaActualTime[] { internalRequest }, pStartTime, pEndTime); // return request object. request = internalRequest; // completed successfully. return results; } } /// /// Requests that the server periodically send notifications when new data becomes available for a set of items. /// /// The beginning of the history period to read. /// The frequency, in seconds, that the server should check for new data. /// The set of items to read (must include the server handle). /// An identifier for the request assigned by the caller. /// A delegate used to receive notifications when the request completes. /// An object that contains the state of the request (used to cancel the request). /// A set of results containing any errors encountered when the server validated the items. public OpcItemResult[] AdviseRaw( TsCHdaTime startTime, decimal updateInterval, OpcItem[] items, object requestHandle, TsCHdaDataUpdateEventHandler callback, out IOpcRequest request) { if (items == null) throw new ArgumentNullException(nameof(items)); if (callback == null) throw new ArgumentNullException(nameof(callback)); request = null; lock (this) { if (server_ == null) throw new NotConnectedException(); // handle trivial case. if (items.Length == 0) { return new OpcItemResult[0]; } var internalRequest = callback_.CreateRequest(requestHandle, callback); // initialize input parameters. var requestID = internalRequest.RequestID; var cancelID = 0; var serverHandles = GetServerHandles(items); var pStartTime = Interop.GetTime(startTime); var ftUpdateInterval = Interop.GetFILETIME(updateInterval); // initialize output arguments. var pErrors = IntPtr.Zero; // invoke COM method. try { ((IOPCHDA_AsyncRead)server_).AdviseRaw( internalRequest.RequestID, ref pStartTime, ftUpdateInterval, serverHandles.Length, serverHandles, out cancelID, out pErrors); } catch (Exception e) { throw Utilities.Interop.CreateException("IOPCHDA_AsyncRead.AdviseRaw", e); } // create result objects. var results = new OpcItemResult[items.Length]; for (var ii = 0; ii < items.Length; ii++) { results[ii] = new OpcItemResult(); } // update result with error code and info from the item argument. UpdateResults(items, results, ref pErrors); // send callbacks for any data that has already arrived. internalRequest.Update(cancelID, results); // return request object. request = internalRequest; // completed successfully. return results; } } /// /// Begins the playback raw data from the historian database for a set of items. /// /// The beginning of the history period to read. /// The end of the history period to be read. /// The number of values to be read for each item. /// The frequency, in seconds, that the server send data. /// The duration, in seconds, of the timespan returned with each update. /// The set of items to read (must include the server handle). /// An identifier for the request assigned by the caller. /// A delegate used to receive notifications when the request completes. /// An object that contains the state of the request (used to cancel the request). /// A set of results containing any errors encountered when the server validated the items. public OpcItemResult[] PlaybackRaw( TsCHdaTime startTime, TsCHdaTime endTime, int maxValues, decimal updateInterval, decimal playbackDuration, OpcItem[] items, object requestHandle, TsCHdaDataUpdateEventHandler callback, out IOpcRequest request) { if (items == null) throw new ArgumentNullException(nameof(items)); if (callback == null) throw new ArgumentNullException(nameof(callback)); request = null; lock (this) { if (server_ == null) throw new NotConnectedException(); // handle trivial case. if (items.Length == 0) { return new OpcItemResult[0]; } var internalRequest = callback_.CreateRequest(requestHandle, callback); // initialize input parameters. var requestID = internalRequest.RequestID; var cancelID = 0; var serverHandles = GetServerHandles(items); var pStartTime = Interop.GetTime(startTime); var pEndTime = Interop.GetTime(endTime); var ftUpdateInterval = Interop.GetFILETIME(updateInterval); var ftUpdateDuration = Interop.GetFILETIME(playbackDuration); // initialize output arguments. var pErrors = IntPtr.Zero; // invoke COM method. try { ((IOPCHDA_Playback)server_).ReadRawWithUpdate( internalRequest.RequestID, ref pStartTime, ref pEndTime, maxValues, ftUpdateDuration, ftUpdateInterval, serverHandles.Length, serverHandles, out cancelID, out pErrors); } catch (Exception e) { throw Utilities.Interop.CreateException("IOPCHDA_Playback.ReadRawWithUpdate", e); } // create result objects. var results = new OpcItemResult[items.Length]; for (var ii = 0; ii < items.Length; ii++) { results[ii] = new OpcItemResult(); } // update result with error code and info from the item argument. UpdateResults(items, results, ref pErrors); // send callbacks for any data that has already arrived. internalRequest.Update(cancelID, results); // return request object. request = internalRequest; // completed successfully. return results; } } #endregion #region Read Processed /// /// Reads processed data from the historian database for a set of items. /// /// The beginning of the history period to read. /// The end of the history period to be read. /// The interval between returned values. /// The set of items to read (must include the server handle). /// A set of values, qualities and timestamps within the requested time range for each item. public TsCHdaItemValueCollection[] ReadProcessed( TsCHdaTime startTime, TsCHdaTime endTime, decimal resampleInterval, TsCHdaItem[] items) { if (items == null) throw new ArgumentNullException(nameof(items)); lock (this) { if (server_ == null) throw new NotConnectedException(); // handle trivial case. if (items.Length == 0) { return new TsCHdaItemValueCollection[0]; } // initialize input parameters. var serverHandles = GetServerHandles(items); var aggregateIDs = GetAggregateIDs(items); var pStartTime = Interop.GetTime(startTime); var pEndTime = Interop.GetTime(endTime); var ftResampleInterval = Interop.GetFILETIME(resampleInterval); // initialize output arguments. var pValues = IntPtr.Zero; var pErrors = IntPtr.Zero; // invoke COM method. try { ((IOPCHDA_SyncRead)server_).ReadProcessed( ref pStartTime, ref pEndTime, ftResampleInterval, serverHandles.Length, serverHandles, aggregateIDs, out pValues, out pErrors); } catch (Exception e) { throw Utilities.Interop.CreateException("IOPCHDA_SyncRead.ReadProcessed", e); } // unmarhal modified item structures. var results = Interop.GetItemValueCollections(ref pValues, items.Length, true); // update result with error code and info from the item argument. UpdateResults(items, results, ref pErrors); // store actual items in result. UpdateActualTimes(results, pStartTime, pEndTime); // completed successfully. return results; } } /// /// Sends an asynchronous request to read processed data from the historian database for a set of items. /// /// The beginning of the history period to read. /// The end of the history period to be read. /// The interval between returned values. /// The set of items to read (must include the server handle). /// An identifier for the request assigned by the caller. /// A delegate used to receive notifications when the request completes. /// An object that contains the state of the request (used to cancel the request). /// A set of results containing any errors encountered when the server validated the items. public OpcItemResult[] ReadProcessed( TsCHdaTime startTime, TsCHdaTime endTime, decimal resampleInterval, TsCHdaItem[] items, object requestHandle, TsCHdaReadValuesCompleteEventHandler callback, out IOpcRequest request) { if (items == null) throw new ArgumentNullException(nameof(items)); if (callback == null) throw new ArgumentNullException(nameof(callback)); request = null; lock (this) { if (server_ == null) throw new NotConnectedException(); // handle trivial case. if (items.Length == 0) { return new OpcItemResult[0]; } var internalRequest = callback_.CreateRequest(requestHandle, callback); // initialize input parameters. var requestID = internalRequest.RequestID; var cancelID = 0; var serverHandles = GetServerHandles(items); var aggregateIDs = GetAggregateIDs(items); var pStartTime = Interop.GetTime(startTime); var pEndTime = Interop.GetTime(endTime); var ftResampleInterval = Interop.GetFILETIME(resampleInterval); // initialize output arguments. var pErrors = IntPtr.Zero; // invoke COM method. try { ((IOPCHDA_AsyncRead)server_).ReadProcessed( internalRequest.RequestID, ref pStartTime, ref pEndTime, ftResampleInterval, serverHandles.Length, serverHandles, aggregateIDs, out cancelID, out pErrors); } catch (Exception e) { throw Utilities.Interop.CreateException("IOPCHDA_AsyncRead.ReadProcessed", e); } // create result objects. var results = new OpcItemResult[items.Length]; for (var ii = 0; ii < items.Length; ii++) { results[ii] = new OpcItemResult(); } // update result with error code and info from the item argument. UpdateResults(items, results, ref pErrors); // check if request has already completed. if (internalRequest.Update(cancelID, results)) { // discard the request. request = null; callback_.CancelRequest(internalRequest, (TsCHdaCancelCompleteEventHandler)null); // return results. return results; } // store actual items in request object. UpdateActualTimes(new ITsCHdaActualTime[] { internalRequest }, pStartTime, pEndTime); // return request object. request = internalRequest; // completed successfully. return results; } } /// /// Requests that the server periodically send notifications when new data becomes available for a set of items. /// /// The beginning of the history period to read. /// The interval between returned values. /// The number of resample intervals that the server should return in each callback. /// The set of items to read (must include the server handle). /// An identifier for the request assigned by the caller. /// A delegate used to receive notifications when the request completes. /// An object that contains the state of the request (used to cancel the request). /// A set of results containing any errors encountered when the server validated the items. public OpcItemResult[] AdviseProcessed( TsCHdaTime startTime, decimal resampleInterval, int numberOfIntervals, TsCHdaItem[] items, object requestHandle, TsCHdaDataUpdateEventHandler callback, out IOpcRequest request) { if (items == null) throw new ArgumentNullException(nameof(items)); if (callback == null) throw new ArgumentNullException(nameof(callback)); request = null; lock (this) { if (server_ == null) throw new NotConnectedException(); // handle trivial case. if (items.Length == 0) { return new OpcItemResult[0]; } var internalRequest = callback_.CreateRequest(requestHandle, callback); // initialize input parameters. var requestID = internalRequest.RequestID; var cancelID = 0; var serverHandles = GetServerHandles(items); var aggregateIDs = GetAggregateIDs(items); var pStartTime = Interop.GetTime(startTime); var ftResampleInterval = Interop.GetFILETIME(resampleInterval); // initialize output arguments. var pErrors = IntPtr.Zero; // invoke COM method. try { ((IOPCHDA_AsyncRead)server_).AdviseProcessed( internalRequest.RequestID, ref pStartTime, ftResampleInterval, serverHandles.Length, serverHandles, aggregateIDs, numberOfIntervals, out cancelID, out pErrors); } catch (Exception e) { throw Utilities.Interop.CreateException("IOPCHDA_AsyncRead.AdviseProcessed", e); } // create result objects. var results = new OpcItemResult[items.Length]; for (var ii = 0; ii < items.Length; ii++) { results[ii] = new OpcItemResult(); } // update result with error code and info from the item argument. UpdateResults(items, results, ref pErrors); // send callbacks for any data that has already arrived. internalRequest.Update(cancelID, results); // return request object. request = internalRequest; // completed successfully. return results; } } /// /// Begins the playback of processed data from the historian database for a set of items. /// /// The beginning of the history period to read. /// The end of the history period to be read. /// The interval between returned values. /// The number of resample intervals that the server should return in each callback. /// The frequency, in seconds, that the server send data. /// The set of items to read (must include the server handle). /// An identifier for the request assigned by the caller. /// A delegate used to receive notifications when the request completes. /// An object that contains the state of the request (used to cancel the request). /// A set of results containing any errors encountered when the server validated the items. public OpcItemResult[] PlaybackProcessed( TsCHdaTime startTime, TsCHdaTime endTime, decimal resampleInterval, int numberOfIntervals, decimal updateInterval, TsCHdaItem[] items, object requestHandle, TsCHdaDataUpdateEventHandler callback, out IOpcRequest request) { if (items == null) throw new ArgumentNullException(nameof(items)); if (callback == null) throw new ArgumentNullException(nameof(callback)); request = null; lock (this) { if (server_ == null) throw new NotConnectedException(); // handle trivial case. if (items.Length == 0) { return new OpcItemResult[0]; } var internalRequest = callback_.CreateRequest(requestHandle, callback); // initialize input parameters. var requestID = internalRequest.RequestID; var cancelID = 0; var serverHandles = GetServerHandles(items); var aggregateIDs = GetAggregateIDs(items); var pStartTime = Interop.GetTime(startTime); var pEndTime = Interop.GetTime(endTime); var ftResampleInterval = Interop.GetFILETIME(resampleInterval); var ftUpdateInterval = Interop.GetFILETIME(updateInterval); // initialize output arguments. var pErrors = IntPtr.Zero; // invoke COM method. try { ((IOPCHDA_Playback)server_).ReadProcessedWithUpdate( internalRequest.RequestID, ref pStartTime, ref pEndTime, ftResampleInterval, numberOfIntervals, ftUpdateInterval, serverHandles.Length, serverHandles, aggregateIDs, out cancelID, out pErrors); } catch (Exception e) { throw Utilities.Interop.CreateException("IOPCHDA_Playback.ReadProcessedWithUpdate", e); } // create result objects. var results = new OpcItemResult[items.Length]; for (var ii = 0; ii < items.Length; ii++) { results[ii] = new OpcItemResult(); } // update result with error code and info from the item argument. UpdateResults(items, results, ref pErrors); // send callbacks for any data that has already arrived. internalRequest.Update(cancelID, results); // return request object. request = internalRequest; // completed successfully. return results; } } #endregion #region Read At Time /// /// Reads data from the historian database for a set of items at specific times. /// /// The set of timestamps to use when reading items values. /// The set of items to read (must include the server handle). /// A set of values, qualities and timestamps within the requested time range for each item. public TsCHdaItemValueCollection[] ReadAtTime(DateTime[] timestamps, OpcItem[] items) { if (items == null) throw new ArgumentNullException(nameof(items)); lock (this) { if (server_ == null) throw new NotConnectedException(); // handle trivial case. if (items.Length == 0) { return new TsCHdaItemValueCollection[0]; } // initialize input parameters. var serverHandles = GetServerHandles(items); var ftTimestamps = Interop.GetFILETIMEs(timestamps); // initialize output arguments. var pValues = IntPtr.Zero; var pErrors = IntPtr.Zero; // invoke COM method. try { ((IOPCHDA_SyncRead)server_).ReadAtTime( ftTimestamps.Length, ftTimestamps, serverHandles.Length, serverHandles, out pValues, out pErrors); } catch (Exception e) { throw Utilities.Interop.CreateException("IOPCHDA_SyncRead.ReadAtTime", e); } // unmarhal modified item structures. var results = Interop.GetItemValueCollections(ref pValues, items.Length, true); // update result with error code and info from the item argument. UpdateResults(items, results, ref pErrors); // completed successfully. return results; } } /// /// Sends an asynchronous request to read item values at specific times. /// /// The set of timestamps to use when reading items values. /// The set of items to read (must include the server handle). /// An identifier for the request assigned by the caller. /// A delegate used to receive notifications when the request completes. /// An object that contains the state of the request (used to cancel the request). /// A set of results containing any errors encountered when the server validated the items. public OpcItemResult[] ReadAtTime( DateTime[] timestamps, OpcItem[] items, object requestHandle, TsCHdaReadValuesCompleteEventHandler callback, out IOpcRequest request) { if (items == null) throw new ArgumentNullException(nameof(items)); if (callback == null) throw new ArgumentNullException(nameof(callback)); request = null; lock (this) { if (server_ == null) throw new NotConnectedException(); // handle trivial case. if (items.Length == 0) { return new OpcItemResult[0]; } var internalRequest = callback_.CreateRequest(requestHandle, callback); // initialize input parameters. var requestID = internalRequest.RequestID; var cancelID = 0; var serverHandles = GetServerHandles(items); var ftTimestamps = Interop.GetFILETIMEs(timestamps); // initialize output arguments. var pErrors = IntPtr.Zero; // invoke COM method. try { ((IOPCHDA_AsyncRead)server_).ReadAtTime( internalRequest.RequestID, ftTimestamps.Length, ftTimestamps, serverHandles.Length, serverHandles, out cancelID, out pErrors); } catch (Exception e) { throw Utilities.Interop.CreateException("IOPCHDA_AsyncRead.ReadAtTime", e); } // create result objects. var results = new OpcItemResult[items.Length]; for (var ii = 0; ii < items.Length; ii++) { results[ii] = new OpcItemResult(); } // update result with error code and info from the item argument. UpdateResults(items, results, ref pErrors); // check if request has already completed. if (internalRequest.Update(cancelID, results)) { // discard the request. request = null; callback_.CancelRequest(internalRequest, (TsCHdaCancelCompleteEventHandler)null); // return results. return results; } // return request object. request = internalRequest; // completed successfully. return results; } } #endregion #region Read Modified /// /// Reads item values that have been deleted or replaced. /// /// The beginning of the history period to read. /// The end of the history period to be read. /// The number of values to be read for each item. /// The set of items to read (must include the server handle). /// A set of values, qualities and timestamps within the requested time range for each item. public TsCHdaModifiedValueCollection[] ReadModified( TsCHdaTime startTime, TsCHdaTime endTime, int maxValues, OpcItem[] items) { if (items == null) throw new ArgumentNullException(nameof(items)); lock (this) { if (server_ == null) throw new NotConnectedException(); // handle trivial case. if (items.Length == 0) { return new TsCHdaModifiedValueCollection[0]; } // initialize input parameters. var serverHandles = GetServerHandles(items); var pStartTime = Interop.GetTime(startTime); var pEndTime = Interop.GetTime(endTime); // initialize output arguments. var pValues = IntPtr.Zero; var pErrors = IntPtr.Zero; // invoke COM method. try { ((IOPCHDA_SyncRead)server_).ReadModified( ref pStartTime, ref pEndTime, maxValues, serverHandles.Length, serverHandles, out pValues, out pErrors); } catch (Exception e) { throw Utilities.Interop.CreateException("IOPCHDA_SyncRead.ReadModified", e); } // unmarhal modified item structures. var results = Interop.GetModifiedValueCollections(ref pValues, items.Length, true); // update result with error code and info from the item argument. UpdateResults(items, results, ref pErrors); // store actual items in result. UpdateActualTimes(results, pStartTime, pEndTime); // completed successfully. return results; } } /// /// Sends an asynchronous request to read item values that have been deleted or replaced. /// /// The beginning of the history period to read. /// The end of the history period to be read. /// The number of values to be read for each item. /// The set of items to read (must include the server handle). /// An identifier for the request assigned by the caller. /// A delegate used to receive notifications when the request completes. /// An object that contains the state of the request (used to cancel the request). /// A set of results containing any errors encountered when the server validated the items. public OpcItemResult[] ReadModified( TsCHdaTime startTime, TsCHdaTime endTime, int maxValues, OpcItem[] items, object requestHandle, TsCHdaReadValuesCompleteEventHandler callback, out IOpcRequest request) { if (items == null) throw new ArgumentNullException(nameof(items)); if (callback == null) throw new ArgumentNullException(nameof(callback)); request = null; lock (this) { if (server_ == null) throw new NotConnectedException(); // handle trivial case. if (items.Length == 0) { return new OpcItemResult[0]; } var internalRequest = callback_.CreateRequest(requestHandle, callback); // initialize input parameters. var requestID = internalRequest.RequestID; var cancelID = 0; var serverHandles = GetServerHandles(items); var pStartTime = Interop.GetTime(startTime); var pEndTime = Interop.GetTime(endTime); // initialize output arguments. var pErrors = IntPtr.Zero; // invoke COM method. try { ((IOPCHDA_AsyncRead)server_).ReadModified( internalRequest.RequestID, ref pStartTime, ref pEndTime, maxValues, serverHandles.Length, serverHandles, out cancelID, out pErrors); } catch (Exception e) { throw Utilities.Interop.CreateException("IOPCHDA_AsyncRead.ReadModified", e); } // create result objects. var results = new OpcItemResult[items.Length]; for (var ii = 0; ii < items.Length; ii++) { results[ii] = new OpcItemResult(); } // update result with error code and info from the item argument. UpdateResults(items, results, ref pErrors); // check if request has already completed. if (internalRequest.Update(cancelID, results)) { // discard the request. request = null; callback_.CancelRequest(internalRequest, (TsCHdaCancelCompleteEventHandler)null); // return results. return results; } // store actual items in request object. UpdateActualTimes(new ITsCHdaActualTime[] { internalRequest }, pStartTime, pEndTime); // return request object. request = internalRequest; // completed successfully. return results; } } #endregion #region Read Attributes /// /// Reads the current or historical values for the attributes of an item. /// /// The beginning of the history period to read. /// The end of the history period to be read. /// The item to read (must include the server handle). /// The attributes to read. /// A set of attribute values for each requested attribute. public TsCHdaItemAttributeCollection ReadAttributes( TsCHdaTime startTime, TsCHdaTime endTime, OpcItem item, int[] attributeIDs) { if (item == null) throw new ArgumentNullException(nameof(item)); if (attributeIDs == null) throw new ArgumentNullException(nameof(attributeIDs)); lock (this) { if (server_ == null) throw new NotConnectedException(); // handle trivial case. if (attributeIDs.Length == 0) { return new TsCHdaItemAttributeCollection(item); } // initialize input parameters. var serverHandles = GetServerHandles(new OpcItem[] { item }); var pStartTime = Interop.GetTime(startTime); var pEndTime = Interop.GetTime(endTime); // initialize output arguments. var pValues = IntPtr.Zero; var pErrors = IntPtr.Zero; // invoke COM method. try { ((IOPCHDA_SyncRead)server_).ReadAttribute( ref pStartTime, ref pEndTime, serverHandles[0], attributeIDs.Length, attributeIDs, out pValues, out pErrors); } catch (Exception e) { throw Utilities.Interop.CreateException("IOPCHDA_SyncRead.ReadAttribute", e); } // unmarhal item attribute structures. var attributes = Interop.GetAttributeValueCollections(ref pValues, attributeIDs.Length, true); // create item level result collection. var result = UpdateResults(item, attributes, ref pErrors); // store actual items in result. UpdateActualTimes(new ITsCHdaActualTime[] { result }, pStartTime, pEndTime); // completed successfully. return result; } } /// /// Sends an asynchronous request to read the attributes of an item. /// /// The beginning of the history period to read. /// The end of the history period to be read. /// The item to read (must include the server handle). /// The attributes to read. /// An identifier for the request assigned by the caller. /// A delegate used to receive notifications when the request completes. /// An object that contains the state of the request (used to cancel the request). /// A set of results containing any errors encountered when the server validated the attribute ids. public TsCHdaResultCollection ReadAttributes( TsCHdaTime startTime, TsCHdaTime endTime, OpcItem item, int[] attributeIDs, object requestHandle, TsCHdaReadAttributesCompleteEventHandler callback, out IOpcRequest request) { if (item == null) throw new ArgumentNullException(nameof(item)); if (attributeIDs == null) throw new ArgumentNullException(nameof(attributeIDs)); if (callback == null) throw new ArgumentNullException(nameof(callback)); request = null; lock (this) { if (server_ == null) throw new NotConnectedException(); // handle trivial case. if (attributeIDs.Length == 0) { return new TsCHdaResultCollection(); } var internalRequest = callback_.CreateRequest(requestHandle, callback); // initialize input parameters. var requestID = internalRequest.RequestID; var cancelID = 0; var serverHandles = GetServerHandles(new OpcItem[] { item }); var pStartTime = Interop.GetTime(startTime); var pEndTime = Interop.GetTime(endTime); // initialize output arguments. var pErrors = IntPtr.Zero; // invoke COM method. try { ((IOPCHDA_AsyncRead)server_).ReadAttribute( internalRequest.RequestID, ref pStartTime, ref pEndTime, serverHandles[0], attributeIDs.Length, attributeIDs, out cancelID, out pErrors); } catch (Exception e) { throw Utilities.Interop.CreateException("IOPCHDA_AsyncRead.ReadAttribute", e); } // create result objects. var results = new TsCHdaResultCollection(item); // update result with error code and info from the item argument. UpdateResult(item, results, 0); // unmarshal return parameters and free memory. var errors = Utilities.Interop.GetInt32s(ref pErrors, attributeIDs.Length, true); // verify return parameters. if (errors == null) { throw new OpcResultException(new OpcResult((int)OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null), "The browse operation cannot continue"); } // add results for each attribute. foreach (var error in errors) { var result = new TsCHdaResult(Utilities.Interop.GetResultId(error)); results.Add(result); } // check if request has already completed. if (internalRequest.Update(cancelID, new TsCHdaResultCollection[] { results })) { // discard the request. request = null; callback_.CancelRequest(internalRequest, (TsCHdaCancelCompleteEventHandler)null); // return results. return results; } // store actual items in request object. UpdateActualTimes(new ITsCHdaActualTime[] { internalRequest }, pStartTime, pEndTime); // return request object. request = internalRequest; // completed successfully. return results; } } #endregion #region Annotations /// /// Reads any annotations for an item within the a time interval. /// /// The beginning of the history period to read. /// The end of the history period to be read. /// The set of items to read (must include the server handle). /// A set of annotations within the requested time range for each item. public TsCHdaAnnotationValueCollection[] ReadAnnotations( TsCHdaTime startTime, TsCHdaTime endTime, OpcItem[] items) { if (items == null) throw new ArgumentNullException(nameof(items)); lock (this) { if (server_ == null) throw new NotConnectedException(); // handle trivial case. if (items.Length == 0) { return new TsCHdaAnnotationValueCollection[0]; } // initialize input parameters. var serverHandles = GetServerHandles(items); var pStartTime = Interop.GetTime(startTime); var pEndTime = Interop.GetTime(endTime); // initialize output arguments. var pValues = IntPtr.Zero; var pErrors = IntPtr.Zero; // invoke COM method. try { ((IOPCHDA_SyncAnnotations)server_).Read( ref pStartTime, ref pEndTime, serverHandles.Length, serverHandles, out pValues, out pErrors); } catch (Exception e) { throw Utilities.Interop.CreateException("IOPCHDA_SyncAnnotations.Read", e); } // unmarhal modified item structures. var results = Interop.GetAnnotationValueCollections(ref pValues, items.Length, true); // update result with error code and info from the item argument. UpdateResults(items, results, ref pErrors); // store actual items in result. UpdateActualTimes(results, pStartTime, pEndTime); // completed successfully. return results; } } /// /// Sends an asynchronous request to read the annotations for a set of items. /// /// The beginning of the history period to read. /// The end of the history period to be read. /// The set of items to read (must include the server handle). /// An identifier for the request assigned by the caller. /// A delegate used to receive notifications when the request completes. /// An object that contains the state of the request (used to cancel the request). /// A set of results containing any errors encountered when the server validated the items. public OpcItemResult[] ReadAnnotations( TsCHdaTime startTime, TsCHdaTime endTime, OpcItem[] items, object requestHandle, TsCHdaReadAnnotationsCompleteEventHandler callback, out IOpcRequest request) { if (items == null) throw new ArgumentNullException(nameof(items)); if (callback == null) throw new ArgumentNullException(nameof(callback)); request = null; lock (this) { if (server_ == null) throw new NotConnectedException(); // handle trivial case. if (items.Length == 0) { return new OpcItemResult[0]; } var internalRequest = callback_.CreateRequest(requestHandle, callback); // initialize input parameters. var requestID = internalRequest.RequestID; var cancelID = 0; var serverHandles = GetServerHandles(items); var pStartTime = Interop.GetTime(startTime); var pEndTime = Interop.GetTime(endTime); // initialize output arguments. var pErrors = IntPtr.Zero; // invoke COM method. try { ((IOPCHDA_AsyncAnnotations)server_).Read( internalRequest.RequestID, ref pStartTime, ref pEndTime, serverHandles.Length, serverHandles, out cancelID, out pErrors); } catch (Exception e) { throw Utilities.Interop.CreateException("IOPCHDA_AsyncAnnotations.Read", e); } // create result objects. var results = new OpcItemResult[items.Length]; for (var ii = 0; ii < items.Length; ii++) { results[ii] = new OpcItemResult(); } // update result with error code and info from the item argument. UpdateResults(items, results, ref pErrors); // check if request has already completed. if (internalRequest.Update(cancelID, results)) { // discard the request. request = null; callback_.CancelRequest(internalRequest, (TsCHdaCancelCompleteEventHandler)null); // return results. return results; } // store actual items in request object. UpdateActualTimes(new ITsCHdaActualTime[] { internalRequest }, pStartTime, pEndTime); // return request object. request = internalRequest; // completed successfully. return results; } } /// /// Inserts annotations for one or more items. /// /// A list of annotations to add for each item (must include the server handle). /// The results of the insert operation for each annotation set. public TsCHdaResultCollection[] InsertAnnotations(TsCHdaAnnotationValueCollection[] items) { if (items == null) throw new ArgumentNullException(nameof(items)); lock (this) { if (server_ == null) throw new NotConnectedException(); // handle trivial case. if (items.Length == 0) { return new TsCHdaResultCollection[0]; } // create empty set of result collections. var results = CreateResultCollections(items); // initialize input parameters. int[] serverHandles = null; OPCHDA_ANNOTATION[] pAnnotations = null; OPCHDA_FILETIME[] pTimestamps = null; // flatten out list of collections into a set of single arrays. var count = MarshalAnnotatations( items, ref serverHandles, ref pTimestamps, ref pAnnotations); // handle trivial case. if (count == 0) { return results; } // initialize output arguments. var pErrors = IntPtr.Zero; // invoke COM method. try { ((IOPCHDA_SyncAnnotations)server_).Insert( serverHandles.Length, serverHandles, pTimestamps, pAnnotations, out pErrors); } catch (Exception e) { throw Utilities.Interop.CreateException("IOPCHDA_SyncAnnotations.Insert", e); } // free memory allocated for input arguments. for (var ii = 0; ii < pAnnotations.Length; ii++) { Utilities.Interop.GetDateTimes(ref pAnnotations[ii].ftTimeStamps, 1, true); Utilities.Interop.GetUnicodeStrings(ref pAnnotations[ii].szAnnotation, 1, true); Utilities.Interop.GetDateTimes(ref pAnnotations[ii].ftAnnotationTime, 1, true); Utilities.Interop.GetUnicodeStrings(ref pAnnotations[ii].szUser, 1, true); } // unmarshal return parameters and free memory. UpdateResults(items, results, count, ref pErrors); // completed successfully. return results; } } /// /// Sends an asynchronous request to inserts annotations for one or more items. /// /// A list of annotations to add for each item (must include the server handle). /// An identifier for the request assigned by the caller. /// A delegate used to receive notifications when the request completes. /// An object that contains the state of the request (used to cancel the request). /// A set of results containing any errors encountered when the server validated the items. public OpcItemResult[] InsertAnnotations( TsCHdaAnnotationValueCollection[] items, object requestHandle, TsCHdaUpdateCompleteEventHandler callback, out IOpcRequest request) { if (items == null) throw new ArgumentNullException(nameof(items)); request = null; lock (this) { if (server_ == null) throw new NotConnectedException(); // handle trivial case. if (items.Length == 0) { return new OpcItemResult[0]; } // create empty set of result collections. var results = CreateResultCollections(items); // initialize input parameters. int[] serverHandles = null; OPCHDA_ANNOTATION[] pAnnotations = null; OPCHDA_FILETIME[] pTimestamps = null; // flatten out list of collections into a set of single arrays. var count = MarshalAnnotatations( items, ref serverHandles, ref pTimestamps, ref pAnnotations); // handle trivial case. if (count == 0) { return GetIdentifiedResults(results); } // create request. var internalRequest = callback_.CreateRequest(requestHandle, callback); // initialize output arguments. var pErrors = IntPtr.Zero; var cancelID = 0; // invoke COM method. try { ((IOPCHDA_AsyncAnnotations)server_).Insert( internalRequest.RequestID, serverHandles.Length, serverHandles, pTimestamps, pAnnotations, out cancelID, out pErrors); } catch (Exception e) { throw Utilities.Interop.CreateException("IOPCHDA_AsyncAnnotations.Insert", e); } // free memory allocated for input arguments. for (var ii = 0; ii < pAnnotations.Length; ii++) { Utilities.Interop.GetDateTimes(ref pAnnotations[ii].ftTimeStamps, 1, true); Utilities.Interop.GetUnicodeStrings(ref pAnnotations[ii].szAnnotation, 1, true); Utilities.Interop.GetDateTimes(ref pAnnotations[ii].ftAnnotationTime, 1, true); Utilities.Interop.GetUnicodeStrings(ref pAnnotations[ii].szUser, 1, true); } // unmarshal return parameters and free memory. UpdateResults(items, results, count, ref pErrors); // check if request has already completed. if (internalRequest.Update(cancelID, results)) { // discard the request. request = null; callback_.CancelRequest(internalRequest, (TsCHdaCancelCompleteEventHandler)null); // return results. return GetIdentifiedResults(results); } // return request object. request = internalRequest; // completed successfully. return GetIdentifiedResults(results); } } #endregion #region Insert/Replace /// /// Inserts the values into the history database for one or more items. /// /// The set of values to insert. /// Whether existing values should be replaced. /// public TsCHdaResultCollection[] Insert(TsCHdaItemValueCollection[] items, bool replace) { if (items == null) throw new ArgumentNullException(nameof(items)); lock (this) { if (server_ == null) throw new NotConnectedException(); // handle trivial case. if (items.Length == 0) { return new TsCHdaResultCollection[0]; } // create empty set of result collections. var results = CreateResultCollections(items); // initialize input parameters. int[] serverHandles = null; object[] values = null; int[] qualities = null; DateTime[] timestamps = null; // flatten out list of collections into a set of single arrays. var count = MarshalValues( items, ref serverHandles, ref values, ref qualities, ref timestamps); // handle trivial case. if (count == 0) { return results; } var ftTimestamps = Interop.GetFILETIMEs(timestamps); // initialize output arguments. var pErrors = IntPtr.Zero; // invoke COM method. if (replace) { try { ((IOPCHDA_SyncUpdate)server_).InsertReplace( serverHandles.Length, serverHandles, ftTimestamps, values, qualities, out pErrors); } catch (Exception e) { throw Utilities.Interop.CreateException("IOPCHDA_SyncUpdate.InsertReplace", e); } } else { try { ((IOPCHDA_SyncUpdate)server_).Insert( serverHandles.Length, serverHandles, ftTimestamps, values, qualities, out pErrors); } catch (Exception e) { throw Utilities.Interop.CreateException("IOPCHDA_SyncUpdate.Insert", e); } } // unmarshal return parameters and free memory. UpdateResults(items, results, count, ref pErrors); // completed successfully. return results; } } /// /// Sends an asynchronous request to inserts values for one or more items. /// /// The set of values to insert. /// Whether existing values should be replaced. /// An identifier for the request assigned by the caller. /// A delegate used to receive notifications when the request completes. /// An object that contains the state of the request (used to cancel the request). /// A set of results containing any errors encountered when the server validated the items. public OpcItemResult[] Insert( TsCHdaItemValueCollection[] items, bool replace, object requestHandle, TsCHdaUpdateCompleteEventHandler callback, out IOpcRequest request) { if (items == null) throw new ArgumentNullException(nameof(items)); request = null; lock (this) { if (server_ == null) throw new NotConnectedException(); // handle trivial case. if (items.Length == 0) { return new OpcItemResult[0]; } // create empty set of result collections. var results = CreateResultCollections(items); // initialize input parameters. int[] serverHandles = null; object[] values = null; int[] qualities = null; DateTime[] timestamps = null; // flatten out list of collections into a set of single arrays. var count = MarshalValues( items, ref serverHandles, ref values, ref qualities, ref timestamps); // handle trivial case. if (count == 0) { return GetIdentifiedResults(results); } var ftTimestamps = Interop.GetFILETIMEs(timestamps); // create request. var internalRequest = callback_.CreateRequest(requestHandle, callback); // initialize output arguments. var pErrors = IntPtr.Zero; var cancelID = 0; // invoke COM method. if (replace) { try { ((IOPCHDA_AsyncUpdate)server_).InsertReplace( internalRequest.RequestID, serverHandles.Length, serverHandles, ftTimestamps, values, qualities, out cancelID, out pErrors); } catch (Exception e) { throw Utilities.Interop.CreateException("IOPCHDA_AsyncUpdate.InsertReplace", e); } } else { try { ((IOPCHDA_AsyncUpdate)server_).Insert( internalRequest.RequestID, serverHandles.Length, serverHandles, ftTimestamps, values, qualities, out cancelID, out pErrors); } catch (Exception e) { throw Utilities.Interop.CreateException("IOPCHDA_AsyncUpdate.Insert", e); } } // unmarshal return parameters and free memory. UpdateResults(items, results, count, ref pErrors); // check if request has already completed. if (internalRequest.Update(cancelID, results)) { // discard the request. request = null; callback_.CancelRequest(internalRequest, (TsCHdaCancelCompleteEventHandler)null); // return results. return GetIdentifiedResults(results); } // return request object. request = internalRequest; // completed successfully. return GetIdentifiedResults(results); } } /// /// Replace the values into the history database for one or more items. /// /// The set of values to replace. /// public TsCHdaResultCollection[] Replace(TsCHdaItemValueCollection[] items) { lock (this) { if (server_ == null) throw new NotConnectedException(); // handle trivial case. if (items.Length == 0) { return new TsCHdaResultCollection[0]; } // create empty set of result collections. var results = CreateResultCollections(items); // initialize input parameters. int[] serverHandles = null; object[] values = null; int[] qualities = null; DateTime[] timestamps = null; // flatten out list of collections into a set of single arrays. var count = MarshalValues( items, ref serverHandles, ref values, ref qualities, ref timestamps); // handle trivial case. if (count == 0) { return results; } var ftTimestamps = Interop.GetFILETIMEs(timestamps); // initialize output arguments. var pErrors = IntPtr.Zero; // invoke COM method. try { ((IOPCHDA_SyncUpdate)server_).Replace( serverHandles.Length, serverHandles, ftTimestamps, values, qualities, out pErrors); } catch (Exception e) { throw Utilities.Interop.CreateException("IOPCHDA_SyncUpdate.Replace", e); } // unmarshal return parameters and free memory. UpdateResults(items, results, count, ref pErrors); // completed successfully. return results; } } /// /// Sends an asynchronous request to replace values for one or more items. /// /// The set of values to replace. /// An identifier for the request assigned by the caller. /// A delegate used to receive notifications when the request completes. /// An object that contains the state of the request (used to cancel the request). /// A set of results containing any errors encountered when the server validated the items. public OpcItemResult[] Replace( TsCHdaItemValueCollection[] items, object requestHandle, TsCHdaUpdateCompleteEventHandler callback, out IOpcRequest request) { if (items == null) throw new ArgumentNullException(nameof(items)); request = null; lock (this) { if (server_ == null) throw new NotConnectedException(); // handle trivial case. if (items.Length == 0) { return new OpcItemResult[0]; } // create empty set of result collections. var results = CreateResultCollections(items); // initialize input parameters. int[] serverHandles = null; object[] values = null; int[] qualities = null; DateTime[] timestamps = null; // flatten out list of collections into a set of single arrays. var count = MarshalValues( items, ref serverHandles, ref values, ref qualities, ref timestamps); // handle trivial case. if (count == 0) { return GetIdentifiedResults(results); } var ftTimestamps = Interop.GetFILETIMEs(timestamps); // create request. var internalRequest = callback_.CreateRequest(requestHandle, callback); // initialize output arguments. var pErrors = IntPtr.Zero; var cancelID = 0; try { ((IOPCHDA_AsyncUpdate)server_).Replace( internalRequest.RequestID, serverHandles.Length, serverHandles, ftTimestamps, values, qualities, out cancelID, out pErrors); } catch (Exception e) { throw Utilities.Interop.CreateException("IOPCHDA_AsyncUpdate.Replace", e); } // unmarshal return parameters and free memory. UpdateResults(items, results, count, ref pErrors); // check if request has already completed. if (internalRequest.Update(cancelID, results)) { // discard the request. request = null; callback_.CancelRequest(internalRequest, (TsCHdaCancelCompleteEventHandler)null); // return results. return GetIdentifiedResults(results); } // return request object. request = internalRequest; // completed successfully. return GetIdentifiedResults(results); } } #endregion #region Delete /// /// Deletes the values with the specified time domain for one or more items. /// /// The beginning of the history period to delete. /// The end of the history period to be delete. /// The set of items to delete (must include the server handle). /// The results of the delete operation for each item. public OpcItemResult[] Delete( TsCHdaTime startTime, TsCHdaTime endTime, OpcItem[] items) { if (items == null) throw new ArgumentNullException(nameof(items)); lock (this) { if (server_ == null) throw new NotConnectedException(); // handle trivial case. if (items.Length == 0) { return new OpcItemResult[0]; } // initialize input parameters. var serverHandles = GetServerHandles(items); var pStartTime = Interop.GetTime(startTime); var pEndTime = Interop.GetTime(endTime); // initialize output arguments. var pErrors = IntPtr.Zero; // invoke COM method. try { ((IOPCHDA_SyncUpdate)server_).DeleteRaw( ref pStartTime, ref pEndTime, serverHandles.Length, serverHandles, out pErrors); } catch (Exception e) { throw Utilities.Interop.CreateException("IOPCHDA_SyncUpdate.DeleteRaw", e); } // create result objects. var results = new OpcItemResult[items.Length]; for (var ii = 0; ii < items.Length; ii++) { results[ii] = new OpcItemResult(); } // update result with error code and info from the item argument. UpdateResults(items, results, ref pErrors); // completed successfully. return results; } } /// /// Sends an asynchronous request to delete values for one or more items. /// /// The beginning of the history period to delete. /// The end of the history period to be delete. /// The set of items to delete (must include the server handle). /// An identifier for the request assigned by the caller. /// A delegate used to receive notifications when the request completes. /// An object that contains the state of the request (used to cancel the request). /// A set of results containing any errors encountered when the server validated the items. public OpcItemResult[] Delete( TsCHdaTime startTime, TsCHdaTime endTime, OpcItem[] items, object requestHandle, TsCHdaUpdateCompleteEventHandler callback, out IOpcRequest request) { if (items == null) throw new ArgumentNullException(nameof(items)); if (callback == null) throw new ArgumentNullException(nameof(callback)); request = null; lock (this) { if (server_ == null) throw new NotConnectedException(); // handle trivial case. if (items.Length == 0) { return new OpcItemResult[0]; } var internalRequest = callback_.CreateRequest(requestHandle, callback); // initialize input parameters. var requestID = internalRequest.RequestID; var cancelID = 0; var serverHandles = GetServerHandles(items); var pStartTime = Interop.GetTime(startTime); var pEndTime = Interop.GetTime(endTime); // initialize output arguments. var pErrors = IntPtr.Zero; // invoke COM method. try { ((IOPCHDA_AsyncUpdate)server_).DeleteRaw( internalRequest.RequestID, ref pStartTime, ref pEndTime, serverHandles.Length, serverHandles, out cancelID, out pErrors); } catch (Exception e) { throw Utilities.Interop.CreateException("IOPCHDA_AsyncUpdate.DeleteRaw", e); } // create result objects. var results = new OpcItemResult[items.Length]; for (var ii = 0; ii < items.Length; ii++) { results[ii] = new OpcItemResult(); } // update result with error code and info from the item argument. UpdateResults(items, results, ref pErrors); // check if request has already completed. if (internalRequest.Update(cancelID, results)) { // discard the request. request = null; callback_.CancelRequest(internalRequest, (TsCHdaCancelCompleteEventHandler)null); // return results. return results; } // store actual items in request object. UpdateActualTimes(new ITsCHdaActualTime[] { internalRequest }, pStartTime, pEndTime); // return request object. request = internalRequest; // completed successfully. return results; } } /// /// Deletes the values at the specified times for one or more items. /// /// The set of timestamps to delete for one or more items. /// The results of the operation for each timestamp. public TsCHdaResultCollection[] DeleteAtTime(TsCHdaItemTimeCollection[] items) { if (items == null) throw new ArgumentNullException(nameof(items)); lock (this) { if (server_ == null) throw new NotConnectedException(); // handle trivial case. if (items.Length == 0) { return new TsCHdaResultCollection[0]; } // create empty set of result collections. var results = CreateResultCollections(items); // initialize input parameters. int[] serverHandles = null; DateTime[] timestamps = null; // flatten out list of collections into a set of single arrays. var count = MarshalTimestamps( items, ref serverHandles, ref timestamps); // handle trivial case. if (count == 0) { return results; } var ftTimestamps = Interop.GetFILETIMEs(timestamps); // initialize output arguments. var pErrors = IntPtr.Zero; // invoke COM method. try { ((IOPCHDA_SyncUpdate)server_).DeleteAtTime( serverHandles.Length, serverHandles, ftTimestamps, out pErrors); } catch (Exception e) { throw Utilities.Interop.CreateException("IOPCHDA_SyncUpdate.DeleteAtTime", e); } // unmarshal return parameters and free memory. UpdateResults(items, results, count, ref pErrors); // completed successfully. return results; } } /// /// Sends an asynchronous request to delete values for one or more items at a specified times. /// /// The set of timestamps to delete for one or more items. /// An identifier for the request assigned by the caller. /// A delegate used to receive notifications when the request completes. /// An object that contains the state of the request (used to cancel the request). /// A set of results containing any errors encountered when the server validated the items. public OpcItemResult[] DeleteAtTime( TsCHdaItemTimeCollection[] items, object requestHandle, TsCHdaUpdateCompleteEventHandler callback, out IOpcRequest request) { if (items == null) throw new ArgumentNullException(nameof(items)); request = null; lock (this) { if (server_ == null) throw new NotConnectedException(); // handle trivial case. if (items.Length == 0) { return new OpcItemResult[0]; } // create empty set of result collections. var results = CreateResultCollections(items); // initialize input parameters. int[] serverHandles = null; DateTime[] timestamps = null; // flatten out list of collections into a set of single arrays. var count = MarshalTimestamps( items, ref serverHandles, ref timestamps); // handle trivial case. if (count == 0) { return GetIdentifiedResults(results); } var ftTimestamps = Interop.GetFILETIMEs(timestamps); // create request. var internalRequest = callback_.CreateRequest(requestHandle, callback); // initialize output arguments. var pErrors = IntPtr.Zero; var cancelID = 0; try { ((IOPCHDA_AsyncUpdate)server_).DeleteAtTime( internalRequest.RequestID, serverHandles.Length, serverHandles, ftTimestamps, out cancelID, out pErrors); } catch (Exception e) { throw Utilities.Interop.CreateException("IOPCHDA_AsyncUpdate.DeleteAtTime", e); } // unmarshal return parameters and free memory. UpdateResults(items, results, count, ref pErrors); // check if request has already completed. if (internalRequest.Update(cancelID, results)) { // discard the request. request = null; callback_.CancelRequest(internalRequest, (TsCHdaCancelCompleteEventHandler)null); // return results. return GetIdentifiedResults(results); } // return request object. request = internalRequest; // completed successfully. return GetIdentifiedResults(results); } } #endregion #region Cancel //====================================================================== // CancelRequest /// /// Cancels an asynchronous request. /// /// The state object for the request to cancel. public void CancelRequest(IOpcRequest request) { CancelRequest(request, (TsCHdaCancelCompleteEventHandler)null); } /// /// Cancels an asynchronous request. /// /// The state object for the request to cancel. /// A delegate used to receive notifications when the request completes. public void CancelRequest(IOpcRequest request, TsCHdaCancelCompleteEventHandler callback) { if (request == null) throw new ArgumentNullException(nameof(request)); lock (this) { if (server_ == null) throw new NotConnectedException(); var internalRequest = (Request)request; // register the cancel request callback. callback_.CancelRequest(internalRequest, callback); // invoke COM method. try { ((IOPCHDA_AsyncRead)server_).Cancel(internalRequest.CancelID); } catch (Exception e) { // a return code of E_FAIL indicates the request does not exist or can't be cancelled. if (OpcResult.E_FAIL.Code != Marshal.GetHRForException(e)) { throw Utilities.Interop.CreateException("IOPCHDA_AsyncRead.Cancel", e); } } } } #endregion #region Private Methods /// /// Establishes a connection point callback with the COM server. /// private void Advise() { if (connection_ == null) { try { connection_ = new ConnectionPoint(server_, typeof(IOPCHDA_DataCallback).GUID); connection_.Advise(callback_); } catch { connection_ = null; } } } /// /// Closes a connection point callback with the COM server. /// private void Unadvise() { if (connection_ != null) { if (connection_.Unadvise() == 0) { connection_.Dispose(); connection_ = null; } } } /// /// Creates a unique handle for an item. /// private int CreateHandle() { return nextHandle_++; } /// /// Finds an invalid server handle. /// private int GetInvalidHandle() { var max = 0; foreach (OpcItem item in items_.Values) { var handle = (int)item.ServerHandle; if (max < handle) { max = handle; } } return max + 1; } /// /// Gets the total count for multiple collections. /// private int GetCount(ICollection[] collections) { var count = 0; if (collections != null) { foreach (var collection in collections) { if (collection != null) { count += collection.Count; } } } return count; } /// /// Initializes a set of result collections from a set of item ids. /// TsCHdaResultCollection[] CreateResultCollections(OpcItem[] items) { TsCHdaResultCollection[] results = null; if (items != null) { results = new TsCHdaResultCollection[items.Length]; for (var ii = 0; ii < items.Length; ii++) { results[ii] = new TsCHdaResultCollection(); if (items[ii] != null) { UpdateResult(items[ii], results[ii], 0); } } } return results; } /// /// Returns an array of item server handles. /// private int[] GetServerHandles(OpcItem[] items) { // use this if the client passes an unrecognized server handle. var invalidHandle = GetInvalidHandle(); // create server handle array. var serverHandles = new int[items.Length]; for (var ii = 0; ii < items.Length; ii++) { serverHandles[ii] = invalidHandle; if (items[ii] != null && items[ii].ServerHandle != null) { // lookup cached handle. var item = (OpcItem)items_[items[ii].ServerHandle]; if (item != null) { serverHandles[ii] = (int)item.ServerHandle; } } } // return handles. return serverHandles; } /// /// Returns an array of item aggregate ids. /// private int[] GetAggregateIDs(TsCHdaItem[] items) { var aggregateIDs = new int[items.Length]; for (var ii = 0; ii < items.Length; ii++) { aggregateIDs[ii] = 0; if (items[ii].Aggregate != TsCHdaAggregateID.NoAggregate) { aggregateIDs[ii] = items[ii].Aggregate; } } return aggregateIDs; } /// /// Updates the result with locally cached item information. /// void UpdateResult(OpcItem item, OpcItem result, int error) { result.ItemName = item.ItemName; result.ItemPath = item.ItemPath; result.ClientHandle = item.ClientHandle; result.ServerHandle = item.ServerHandle; if (error >= 0 && item.ServerHandle != null) { // lookup locally cached item id. var itemID = (OpcItem)items_[item.ServerHandle]; // update result with locally cached information. if (itemID != null) { result.ItemName = itemID.ItemName; result.ItemPath = itemID.ItemPath; result.ClientHandle = itemID.ClientHandle; } } } /// /// Adds the actual start/end times to a result collection. /// void UpdateActualTimes( ITsCHdaActualTime[] results, OPCHDA_TIME pStartTime, OPCHDA_TIME pEndTime) { // unmarshal actual times from input arguments. var startTime = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Interop.Convert(pStartTime.ftTime)); var endTime = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Interop.Convert(pEndTime.ftTime)); foreach (var result in results) { result.StartTime = startTime; result.EndTime = endTime; } } /// /// Updates the attribute value objects before returing them to the client. /// TsCHdaItemAttributeCollection UpdateResults( OpcItem item, TsCHdaAttributeValueCollection[] attributes, ref IntPtr pErrors) { // unmarshal return parameters and free memory. var errors = Utilities.Interop.GetInt32s(ref pErrors, attributes.Length, true); // verify return parameters. if (attributes == null || errors == null) { throw new OpcResultException(new OpcResult((int)OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null), "The browse operation cannot continue"); } // set attribute level errors. for (var ii = 0; ii < attributes.Length; ii++) { attributes[ii].Result = Utilities.Interop.GetResultId(errors[ii]); } // create item level collection. var result = new TsCHdaItemAttributeCollection(); foreach (var attribute in attributes) { result.Add(attribute); } // add locally cached item information. UpdateResult(item, result, 0); // all done. return result; } /// /// Updates the annotation value objects before returing them to the client. /// void UpdateResults( OpcItem[] items, OpcItem[] results, ref IntPtr pErrors) { // unmarshal return parameters and free memory. var errors = Utilities.Interop.GetInt32s(ref pErrors, items.Length, true); // verify return parameters. if (results == null || errors == null) { throw new OpcResultException(new OpcResult((int)OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null), "The browse operation cannot continue"); } for (var ii = 0; ii < results.Length; ii++) { // get cached item information. UpdateResult(items[ii], results[ii], errors[ii]); // lookup the error code. if (typeof(IOpcResult).IsInstanceOfType(results[ii])) { ((IOpcResult)results[ii]).Result = Utilities.Interop.GetResultId(errors[ii]); } } } /// /// Unmarshals the errors array and updates the result objects. /// void UpdateResults(ICollection[] items, TsCHdaResultCollection[] results, int count, ref IntPtr pErrors) { // unmarshal return parameters and free memory. var errors = Utilities.Interop.GetInt32s(ref pErrors, count, true); // verify return parameters. if (errors == null) { throw new OpcResultException(new OpcResult((int)OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null), "The browse operation cannot continue"); } // create result object and lookup error code. var index = 0; for (var ii = 0; ii < items.Length; ii++) { for (var jj = 0; jj < items[ii].Count; jj++) { if (index >= count) { break; } var result = new TsCHdaResult(Utilities.Interop.GetResultId(errors[index++])); results[ii].Add(result); } } } /// /// Flattens a set of item value collections into an set of single arrays. /// private int MarshalValues( TsCHdaItemValueCollection[] items, ref int[] handles, ref object[] values, ref int[] qualities, ref DateTime[] timestamps) { // determine the total length. var count = GetCount(items); // flatten out list of collections into a set of single arrays. handles = new int[count]; timestamps = new DateTime[count]; values = new object[count]; qualities = new int[count]; // initialize input parameters. var serverHandles = GetServerHandles(items); var index = 0; for (var ii = 0; ii < items.Length; ii++) { foreach (TsCHdaItemValue value in items[ii]) { handles[index] = serverHandles[ii]; timestamps[index] = value.Timestamp; values[index] = Utilities.Interop.GetVARIANT(value.Value); qualities[index] = value.Quality.GetCode(); index++; } } // return the total count. return count; } /// /// Flattens a set of item time collections into an set of single arrays. /// private int MarshalTimestamps( TsCHdaItemTimeCollection[] items, ref int[] handles, ref DateTime[] timestamps) { // determine the total length. var count = GetCount(items); // flatten out list of collections into a set of single arrays. handles = new int[count]; timestamps = new DateTime[count]; // initialize input parameters. var serverHandles = GetServerHandles(items); var index = 0; for (var ii = 0; ii < items.Length; ii++) { foreach (DateTime value in items[ii]) { handles[index] = serverHandles[ii]; timestamps[index] = value; index++; } } // return the total count. return count; } /// /// Marshals a set of annotation collections into an set of arrays. /// private int MarshalAnnotatations( TsCHdaAnnotationValueCollection[] items, ref int[] serverHandles, ref OPCHDA_FILETIME[] ftTimestamps, ref OPCHDA_ANNOTATION[] annotations) { // determine the total length. var count = GetCount(items); // fetch item server handles. var remoteHandles = GetServerHandles(items); // allocate input arrays. serverHandles = new int[count]; annotations = new OPCHDA_ANNOTATION[count]; var timestamps = new DateTime[count]; // flatten array of collections into a single array. var index = 0; for (var ii = 0; ii < items.Length; ii++) { for (var jj = 0; jj < items[ii].Count; jj++) { serverHandles[index] = remoteHandles[ii]; timestamps[index] = items[ii][jj].Timestamp; annotations[index] = new OPCHDA_ANNOTATION(); annotations[index].dwNumValues = 1; annotations[index].ftTimeStamps = Utilities.Interop.GetFILETIMEs(new DateTime[] { timestamps[jj] }); annotations[index].szAnnotation = Utilities.Interop.GetUnicodeStrings(new string[] { items[ii][jj].Annotation }); annotations[index].ftAnnotationTime = Utilities.Interop.GetFILETIMEs(new DateTime[] { items[ii][jj].CreationTime }); annotations[index].szUser = Utilities.Interop.GetUnicodeStrings(new string[] { items[ii][jj].User }); index++; } } ftTimestamps = Interop.GetFILETIMEs(timestamps); // return the total number of annotations. return count; } /// /// Collapses a set of result collections into a single result code. /// private OpcItemResult[] GetIdentifiedResults(TsCHdaResultCollection[] results) { // handle trival case. if (results == null || results.Length == 0) { return new OpcItemResult[0]; } // fetch the results from each collection. var items = new OpcItemResult[results.Length]; for (var ii = 0; ii < results.Length; ii++) { items[ii] = new OpcItemResult(results[ii]); // check if data actually exists. if (results[ii] == null || results[ii].Count == 0) { items[ii].Result = OpcResult.Hda.S_NODATA; continue; } // start with the first result code. var resultID = results[ii][0].Result; foreach (TsCHdaResult result in results[ii]) { // all result in the collection should have the same error. if (resultID.Code != result.Result.Code) { resultID = OpcResult.E_FAIL; break; } } } // all done. return items; } #endregion #region Private Members private static int nextHandle_ = 1; private Hashtable items_ = new Hashtable(); private DataCallback callback_ = new DataCallback(); private ConnectionPoint connection_; /// /// The synchronization object for subscription access /// private static volatile object lock_ = new object(); private bool disposed_ = false; #endregion } }