458 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			458 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| #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<OpcItem> OpcItems { get; private set; } = new List<OpcItem> { };
 | ||
|     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<ItemReadResult> 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<ItemReadResult> 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<ItemWriteResult> 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<Tuple<OpcItem, int>> 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<Tuple<OpcItem, int>> 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);
 | ||
|             }
 | ||
|         }
 | ||
|     }
 | ||
|     /// <summary>
 | ||
|     /// 建立连接
 | ||
|     /// </summary>
 | ||
|     /// <param name="handle"></param>
 | ||
|     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);
 | ||
|     }
 | ||
|     /// <summary>
 | ||
|     /// 组读取
 | ||
|     /// </summary>
 | ||
|     /// <exception cref="ExternalException"></exception>
 | ||
|     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<Tuple<OpcItem, int>> 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<Tuple<OpcItem, int>> 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<Tuple<int, int>> 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<Tuple<int, int>> 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();
 | ||
|         }
 | ||
|     }
 | ||
| }
 | 
