#region copyright //------------------------------------------------------------------------------ // 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 // 此代码版权(除特别声明外的代码)归作者本人Diego所有 // 源代码使用协议遵循本仓库的开源协议及附加协议 // Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway // Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway // 使用文档:https://diego2098.gitee.io/thingsgateway-docs/ // QQ群:605534569 //------------------------------------------------------------------------------ #endregion using System.Runtime.InteropServices; using ThingsGateway.Foundation.Adapter.OPCDA.Rcw; namespace ThingsGateway.Foundation.Adapter.OPCDA.Da; #pragma warning disable CA1416 // 验证平台兼容性 internal class OpcGroup : IOPCDataCallback, IDisposable { internal object groupPointer = null; internal int revisedUpdateRate = 0; internal int serverGroupHandle = 0; private static int _handle = 0; private bool _bSubscribe = false; private bool disposedValue; private int lcid = 0x0; private IOPCAsyncIO2 m_Async2IO = null; private IConnectionPoint m_ConnectionPoint = null; private int m_connectionpoint_cookie = 0; private IConnectionPointContainer m_ConnectionPointContainer = null; private IOPCItemMgt m_ItemManagement = null; private IOPCGroupStateMgt m_StateManagement = null; private IOPCSyncIO m_SyncIO = null; private GCHandle percendDeadBand = GCHandle.Alloc(0, GCHandleType.Pinned); private GCHandle timeBias = GCHandle.Alloc(0, GCHandleType.Pinned); internal OpcGroup(string name) { Name = name; ClientGroupHandle = ++_handle; } internal OpcGroup(string groupName, bool active, int reqUpdateRate, float deadBand) { Name = groupName; IsActive = active; RequestUpdateRate = reqUpdateRate; DeadBand = deadBand; ClientGroupHandle = ++_handle; } internal delegate void CancelCompletedHandler(int dwTransid, int hGroup); internal event CancelCompletedHandler OnCancelCompleted; internal event OnDataChangedHandler OnDataChanged; internal event OnReadCompletedHandler OnReadCompleted; internal event OnWriteCompletedHandler OnWriteCompleted; internal bool ActiveSubscribe { get { return _bSubscribe; } set { _bSubscribe = value; ActiveDataChanged(_bSubscribe); } } internal int ClientGroupHandle { get; private set; } internal float DeadBand { get; set; } = 0.0f; internal object GroupPointer => groupPointer; internal bool IsActive { get; set; } = true; internal int LCID { get => lcid; set => lcid = value; } internal string Name { get; private set; } = string.Empty; internal List OpcItems { get; private set; } = new List { }; internal GCHandle PercendDeadBand { get => percendDeadBand; set => percendDeadBand = value; } internal int RequestUpdateRate { get; set; } = 1000; internal int RevisedUpdateRate => revisedUpdateRate; internal int ServerGroupHandle => serverGroupHandle; internal GCHandle TimeBias { get => timeBias; set => timeBias = value; } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } public void OnCancelComplete(int dwTransid, int hGroup) { OnCancelCompleted?.Invoke(dwTransid, hGroup); } public void OnDataChange(int dwTransid, int hGroup, int hrMasterquality, int hrMastererror, int dwCount, int[] phClientItems, object[] pvValues, short[] pwQualities, System.Runtime.InteropServices.ComTypes.FILETIME[] pftTimeStamps, int[] pErrors) { List itemChanged = new(); for (int i = 0; i < dwCount; i++) { int index = OpcItems.FindIndex(x => x.ClientHandle == phClientItems[i]); if (index >= 0) { OpcItems[index].Value = pvValues[i]; OpcItems[index].Quality = pwQualities[i]; OpcItems[index].TimeStamp = Comn.Convert.FileTimeToDateTime(pftTimeStamps[i]); itemChanged.Add(new ItemReadResult { Name = OpcItems[index].ItemID, Value = pvValues[i], Quality = pwQualities[i], TimeStamp = OpcItems[index].TimeStamp }); } } OnDataChanged?.Invoke(itemChanged); } public void OnReadComplete(int dwTransid, int hGroup, int hrMasterquality, int hrMastererror, int dwCount, int[] phClientItems, object[] pvValues, short[] pwQualities, System.Runtime.InteropServices.ComTypes.FILETIME[] pftTimeStamps, int[] pErrors) { List itemChanged = new(); for (int i = 0; i < dwCount; i++) { int index = OpcItems.FindIndex(x => x.ClientHandle == phClientItems[i]); if (index >= 0) { OpcItems[index].Value = pvValues[i]; OpcItems[index].Quality = pwQualities[i]; OpcItems[index].TimeStamp = Comn.Convert.FileTimeToDateTime(pftTimeStamps[i]); itemChanged.Add(new ItemReadResult { Name = OpcItems[index].ItemID, Value = pvValues[i], Quality = pwQualities[i], TimeStamp = OpcItems[index].TimeStamp }); } } OnReadCompleted?.Invoke(itemChanged); } public void OnWriteComplete(int dwTransid, int hGroup, int hrMastererr, int dwCount, int[] pClienthandles, int[] pErrors) { List itemwrite = new(); for (int i = 0; i < dwCount; i++) { int index = OpcItems.FindIndex(x => x.ClientHandle == pClienthandles[i]); if (index >= 0) { itemwrite.Add(new ItemWriteResult { Name = OpcItems[index].ItemID, Exception = pErrors[i] }); } } OnWriteCompleted?.Invoke(itemwrite); } internal List> AddOpcItem(OpcItem[] items) { IntPtr pResults = IntPtr.Zero; IntPtr pErrors = IntPtr.Zero; OPCITEMDEF[] itemDefyArray = new OPCITEMDEF[items.Length]; int i = 0; int[] errors = new int[items.Length]; int[] itemServerHandle = new int[items.Length]; try { foreach (OpcItem item in items) { if (item != null) { itemDefyArray[i].szAccessPath = item.AccessPath; itemDefyArray[i].szItemID = item.ItemID; itemDefyArray[i].bActive = item.IsActive ? 1 : 0; itemDefyArray[i].hClient = item.ClientHandle; itemDefyArray[i].dwBlobSize = item.BlobSize; itemDefyArray[i].pBlob = item.Blob; i++; } } //添加OPC项组 m_ItemManagement?.AddItems(items.Length, itemDefyArray, out pResults, out pErrors); IntPtr Pos = pResults; Marshal.Copy(pErrors, errors, 0, items.Length); List> results = new(); for (int j = 0; j < items.Length; j++) { if (errors[j] == 0) { if (j != 0) { Pos = IntPtr.Add(Pos, Marshal.SizeOf(typeof(OPCITEMRESULT))); } object o = Marshal.PtrToStructure(Pos, typeof(OPCITEMRESULT)); if (o != null) { var result = (OPCITEMRESULT)o; items[j].RunTimeDataType = result.vtCanonicalDataType; itemServerHandle[j] = items[j].ServerHandle = result.hServer; Marshal.DestroyStructure(Pos, typeof(OPCITEMRESULT)); OpcItems.Add(items[j]); } } else { results.Add(Tuple.Create(items[j], errors[j])); } } return results; } finally { if (pResults != IntPtr.Zero) { Marshal.FreeCoTaskMem(pResults); } if (pErrors != IntPtr.Zero) { Marshal.FreeCoTaskMem(pErrors); } } } /// /// 建立连接 /// /// internal void InitIoInterfaces(object handle) { groupPointer = handle; m_ItemManagement = (IOPCItemMgt)groupPointer; m_Async2IO = (IOPCAsyncIO2)groupPointer; m_SyncIO = (IOPCSyncIO)groupPointer; m_StateManagement = (IOPCGroupStateMgt)groupPointer; m_ConnectionPointContainer = (IConnectionPointContainer)groupPointer; Guid iid = typeof(IOPCDataCallback).GUID; m_ConnectionPointContainer.FindConnectionPoint(ref iid, out m_ConnectionPoint); //创建客户端与服务端之间的连接 m_ConnectionPoint.Advise(this, out m_connectionpoint_cookie); } /// /// 组读取 /// /// internal void ReadAsync() { IntPtr pErrors = IntPtr.Zero; try { if (m_Async2IO != null) { int[] serverHandle = new int[OpcItems.Count]; int[] PErrors = new int[OpcItems.Count]; for (int j = 0; j < OpcItems.Count; j++) { serverHandle[j] = OpcItems[j].ServerHandle; } m_Async2IO.Read(OpcItems.Count, serverHandle, 2, out int cancelId, out pErrors); Marshal.Copy(pErrors, PErrors, 0, OpcItems.Count); if (PErrors.Any(a => a > 0)) { throw new("读取错误,错误代码:" + pErrors); } } else throw new("连接无效"); } finally { if (pErrors != IntPtr.Zero) { Marshal.FreeCoTaskMem(pErrors); } } } internal List> RemoveItem(OpcItem[] items) { IntPtr pErrors = IntPtr.Zero; int[] errors = new int[items.Length]; int[] handles = new int[items.Length]; for (int i = 0; i < items.Length; i++) { handles[i] = items[i].ServerHandle; } try { m_ItemManagement?.RemoveItems(handles.Length, handles, out pErrors); Marshal.Copy(pErrors, errors, 0, items.Length); } finally { if (pErrors != IntPtr.Zero) { Marshal.FreeCoTaskMem(pErrors); } } List> results = new(); for (int i = 0; i < errors.Length; i++) { if (errors[i] != 0) { results.Add(Tuple.Create(items[i], errors[i])); } else { OpcItems.Remove(items[i]); } } return results; } internal List> Write(object[] values, int[] serverHandle) { IntPtr pErrors = IntPtr.Zero; var errors = new int[values.Length]; if (m_Async2IO != null) { try { m_SyncIO.Write(values.Length, serverHandle, values, out pErrors); Marshal.Copy(pErrors, errors, 0, values.Length); List> results = new(); for (int i = 0; i < errors.Length; i++) { if (errors[i] != 0) { results.Add(Tuple.Create(serverHandle[i], errors[i])); } } return results; } finally { if (pErrors != IntPtr.Zero) { Marshal.FreeCoTaskMem(pErrors); } } } else throw new("连接无效"); } protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (TimeBias.IsAllocated) { TimeBias.Free(); } if (PercendDeadBand.IsAllocated) { PercendDeadBand.Free(); } ActiveSubscribe = false; m_ConnectionPoint?.Unadvise(m_connectionpoint_cookie); m_connectionpoint_cookie = 0; if (null != m_ConnectionPoint) Marshal.ReleaseComObject(m_ConnectionPoint); m_ConnectionPoint = null; if (null != m_ConnectionPointContainer) Marshal.ReleaseComObject(m_ConnectionPointContainer); m_ConnectionPointContainer = null; if (m_Async2IO != null) { Marshal.ReleaseComObject(m_Async2IO); m_Async2IO = null; } if (m_SyncIO != null) { Marshal.ReleaseComObject(m_SyncIO); m_SyncIO = null; } if (m_StateManagement != null) { Marshal.ReleaseComObject(m_StateManagement); m_StateManagement = null; } if (groupPointer != null) { Marshal.ReleaseComObject(groupPointer); groupPointer = null; } m_ItemManagement = null; disposedValue = true; } } private void ActiveDataChanged(bool active) { IntPtr pRequestedUpdateRate = IntPtr.Zero; IntPtr hClientGroup = IntPtr.Zero; IntPtr pTimeBias = IntPtr.Zero; IntPtr pDeadband = IntPtr.Zero; IntPtr pLCID = IntPtr.Zero; int nActive = 0; GCHandle hActive = GCHandle.Alloc(nActive, GCHandleType.Pinned); hActive.Target = active ? 1 : 0; try { m_StateManagement?.SetState(pRequestedUpdateRate, out int nRevUpdateRate, hActive.AddrOfPinnedObject(), pTimeBias, pDeadband, pLCID, hClientGroup); } finally { hActive.Free(); } } }