This commit is contained in:
luosheng
2023-07-12 06:42:28 +08:00
parent 25555cad18
commit db591e0367
188 changed files with 56088 additions and 9 deletions

View File

@@ -0,0 +1,876 @@
#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.Runtime.InteropServices;
using Technosoftware.DaAeHdaClient.Ae;
using Technosoftware.DaAeHdaClient.Da;
#endregion
#pragma warning disable 0618
namespace Technosoftware.DaAeHdaClient.Com.Ae
{
/// <summary>
/// Defines COM marshalling/unmarshalling functions for AE.
/// </summary>
internal class Interop
{
/// <summary>
/// Converts a standard FILETIME to an OpcRcw.Ae.FILETIME structure.
/// </summary>
internal static OpcRcw.Ae.FILETIME Convert(FILETIME input)
{
var output = new OpcRcw.Ae.FILETIME();
output.dwLowDateTime = input.dwLowDateTime;
output.dwHighDateTime = input.dwHighDateTime;
return output;
}
/// <summary>
/// Converts an OpcRcw.Ae.FILETIME to a standard FILETIME structure.
/// </summary>
internal static FILETIME Convert(OpcRcw.Ae.FILETIME input)
{
var output = new FILETIME();
output.dwLowDateTime = input.dwLowDateTime;
output.dwHighDateTime = input.dwHighDateTime;
return output;
}
/// <summary>
/// Converts the HRESULT to a system type.
/// </summary>
internal static OpcResult GetResultID(int input)
{
// must check for this error because of a code collision with a DA code.
if (input == Result.E_INVALIDBRANCHNAME)
{
return OpcResult.Ae.E_INVALIDBRANCHNAME;
}
return Technosoftware.DaAeHdaClient.Com.Interop.GetResultID(input);
}
/// <summary>
/// Unmarshals and deallocates a OPCEVENTSERVERSTATUS structure.
/// </summary>
internal static OpcServerStatus GetServerStatus(ref IntPtr pInput, bool deallocate)
{
OpcServerStatus output = null;
if (pInput != IntPtr.Zero)
{
var status = (OpcRcw.Ae.OPCEVENTSERVERSTATUS)Marshal.PtrToStructure(pInput, typeof(OpcRcw.Ae.OPCEVENTSERVERSTATUS));
output = new OpcServerStatus();
output.VendorInfo = status.szVendorInfo;
output.ProductVersion = string.Format("{0}.{1}.{2}", status.wMajorVersion, status.wMinorVersion, status.wBuildNumber);
output.MajorVersion = status.wMajorVersion;
output.MinorVersion = status.wMinorVersion;
output.BuildNumber = status.wBuildNumber;
output.ServerState = (OpcServerState)status.dwServerState;
output.StatusInfo = null;
output.StartTime = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(status.ftStartTime));
output.CurrentTime = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(status.ftCurrentTime));
output.LastUpdateTime = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(status.ftLastUpdateTime));
if (deallocate)
{
Marshal.DestroyStructure(pInput, typeof(OpcRcw.Ae.OPCEVENTSERVERSTATUS));
Marshal.FreeCoTaskMem(pInput);
pInput = IntPtr.Zero;
}
}
return output;
}
/// <summary>
/// Converts a NodeType value to the OPCAEBROWSETYPE equivalent.
/// </summary>
internal static OpcRcw.Ae.OPCAEBROWSETYPE GetBrowseType(TsCAeBrowseType input)
{
switch (input)
{
case TsCAeBrowseType.Area: return OpcRcw.Ae.OPCAEBROWSETYPE.OPC_AREA;
case TsCAeBrowseType.Source: return OpcRcw.Ae.OPCAEBROWSETYPE.OPC_SOURCE;
}
return OpcRcw.Ae.OPCAEBROWSETYPE.OPC_AREA;
}
/// <summary>
/// Converts an array of ONEVENTSTRUCT structs to an array of EventNotification objects.
/// </summary>
internal static TsCAeEventNotification[] GetEventNotifications(OpcRcw.Ae.ONEVENTSTRUCT[] input)
{
TsCAeEventNotification[] output = null;
if (input != null && input.Length > 0)
{
output = new TsCAeEventNotification[input.Length];
for (var ii = 0; ii < input.Length; ii++)
{
output[ii] = GetEventNotification(input[ii]);
}
}
return output;
}
/// <summary>
/// Converts a ONEVENTSTRUCT struct to a EventNotification object.
/// </summary>
internal static TsCAeEventNotification GetEventNotification(OpcRcw.Ae.ONEVENTSTRUCT input)
{
var output = new TsCAeEventNotification();
output.SourceID = input.szSource;
output.Time = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(input.ftTime));
output.Severity = input.dwSeverity;
output.Message = input.szMessage;
output.EventType = (TsCAeEventType)input.dwEventType;
output.EventCategory = input.dwEventCategory;
output.ChangeMask = input.wChangeMask;
output.NewState = input.wNewState;
output.Quality = new TsCDaQuality(input.wQuality);
output.ConditionName = input.szConditionName;
output.SubConditionName = input.szSubconditionName;
output.AckRequired = input.bAckRequired != 0;
output.ActiveTime = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(input.ftActiveTime));
output.Cookie = input.dwCookie;
output.ActorID = input.szActorID;
var attributes = Technosoftware.DaAeHdaClient.Com.Interop.GetVARIANTs(ref input.pEventAttributes, input.dwNumEventAttrs, false);
output.SetAttributes(attributes);
return output;
}
/// <summary>
/// Converts an array of OPCCONDITIONSTATE structs to an array of Condition objects.
/// </summary>
internal static TsCAeCondition[] GetConditions(ref IntPtr pInput, int count, bool deallocate)
{
TsCAeCondition[] output = null;
if (pInput != IntPtr.Zero && count > 0)
{
output = new TsCAeCondition[count];
var pos = pInput;
for (var ii = 0; ii < count; ii++)
{
var condition = (OpcRcw.Ae.OPCCONDITIONSTATE)Marshal.PtrToStructure(pos, typeof(OpcRcw.Ae.OPCCONDITIONSTATE));
output[ii] = new TsCAeCondition();
output[ii].State = condition.wState;
output[ii].Quality = new TsCDaQuality(condition.wQuality);
output[ii].Comment = condition.szComment;
output[ii].AcknowledgerID = condition.szAcknowledgerID;
output[ii].CondLastActive = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(condition.ftCondLastActive));
output[ii].CondLastInactive = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(condition.ftCondLastInactive));
output[ii].SubCondLastActive = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(condition.ftSubCondLastActive));
output[ii].LastAckTime = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(condition.ftLastAckTime));
output[ii].ActiveSubCondition.Name = condition.szActiveSubCondition;
output[ii].ActiveSubCondition.Definition = condition.szASCDefinition;
output[ii].ActiveSubCondition.Severity = condition.dwASCSeverity;
output[ii].ActiveSubCondition.Description = condition.szASCDescription;
// unmarshal sub-conditions.
var names = Technosoftware.DaAeHdaClient.Com.Interop.GetUnicodeStrings(ref condition.pszSCNames, condition.dwNumSCs, deallocate);
var severities = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref condition.pdwSCSeverities, condition.dwNumSCs, deallocate);
var definitions = Technosoftware.DaAeHdaClient.Com.Interop.GetUnicodeStrings(ref condition.pszSCDefinitions, condition.dwNumSCs, deallocate);
var descriptions = Technosoftware.DaAeHdaClient.Com.Interop.GetUnicodeStrings(ref condition.pszSCDescriptions, condition.dwNumSCs, deallocate);
output[ii].SubConditions.Clear();
if (condition.dwNumSCs > 0)
{
for (var jj = 0; jj < names.Length; jj++)
{
var subcondition = new TsCAeSubCondition();
subcondition.Name = names[jj];
subcondition.Severity = severities[jj];
subcondition.Definition = definitions[jj];
subcondition.Description = descriptions[jj];
output[ii].SubConditions.Add(subcondition);
}
}
// unmarshal attributes.
var values = Technosoftware.DaAeHdaClient.Com.Interop.GetVARIANTs(ref condition.pEventAttributes, condition.dwNumEventAttrs, deallocate);
var errors = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref condition.pErrors, condition.dwNumEventAttrs, deallocate);
output[ii].Attributes.Clear();
if (condition.dwNumEventAttrs > 0)
{
for (var jj = 0; jj < values.Length; jj++)
{
var attribute = new TsCAeAttributeValue();
attribute.ID = 0;
attribute.Value = values[jj];
attribute.Result = GetResultID(errors[jj]);
output[ii].Attributes.Add(attribute);
}
}
// deallocate structure.
if (deallocate)
{
Marshal.DestroyStructure(pos, typeof(OpcRcw.Ae.OPCCONDITIONSTATE));
}
pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Ae.OPCCONDITIONSTATE)));
}
// deallocate array.
if (deallocate)
{
Marshal.FreeCoTaskMem(pInput);
pInput = IntPtr.Zero;
}
}
return output;
}
/*
/// <summary>
/// Converts an array of COM HRESULTs structures to .NET ResultID objects.
/// </summary>
internal static ResultID[] GetResultIDs(ref IntPtr pInput, int count, bool deallocate)
{
ResultID[] output = null;
if (pInput != IntPtr.Zero && count > 0)
{
output = new ResultID[count];
int[] errors = OpcCom.Interop.GetInt32s(ref pInput, count, deallocate);
for (int ii = 0; ii < count; ii++)
{
output[ii] = OpcCom.Interop.GetResultID(errors[ii]);
}
}
return output;
}
/// <summary>
/// Converts an array of COM SourceServer structures to .NET SourceServer objects.
/// </summary>
internal static SourceServer[] GetSourceServers(ref IntPtr pInput, int count, bool deallocate)
{
SourceServer[] output = null;
if (pInput != IntPtr.Zero && count > 0)
{
output = new SourceServer[count];
IntPtr pos = pInput;
for (int ii = 0; ii < count; ii++)
{
OpcRcw.Dx.SourceServer server = (OpcRcw.Dx.SourceServer)Marshal.PtrToStructure(pos, typeof(OpcRcw.Dx.SourceServer));
output[ii] = new SourceServer();
output[ii].ItemName = server.szItemName;
output[ii].ItemPath = server.szItemPath;
output[ii].Version = server.szVersion;
output[ii].Name = server.szName;
output[ii].Description = server.szDescription;
output[ii].ServerType = server.szServerType;
output[ii].ServerURL = server.szServerURL;
output[ii].DefaultConnected = server.bDefaultSourceServerConnected != 0;
pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Dx.SourceServer)));
}
if (deallocate)
{
Marshal.FreeCoTaskMem(pInput);
pInput = IntPtr.Zero;
}
}
return output;
}
/// <summary>
/// Converts an array of .NET SourceServer objects to COM SourceServer structures.
/// </summary>
internal static OpcRcw.Dx.SourceServer[] GetSourceServers(SourceServer[] input)
{
OpcRcw.Dx.SourceServer[] output = null;
if (input != null && input.Length > 0)
{
output = new OpcRcw.Dx.SourceServer[input.Length];
for (int ii = 0; ii < input.Length; ii++)
{
output[ii] = new OpcRcw.Dx.SourceServer();
output[ii].dwMask = (uint)OpcRcw.Dx.Mask.All;
output[ii].szItemName = input[ii].ItemName;
output[ii].szItemPath = input[ii].ItemPath;
output[ii].szVersion = input[ii].Version;
output[ii].szName = input[ii].Name;
output[ii].szDescription = input[ii].Description;
output[ii].szServerType = input[ii].ServerType;
output[ii].szServerURL = input[ii].ServerURL;
output[ii].bDefaultSourceServerConnected = (input[ii].DefaultConnected)?1:0;
}
}
return output;
}
/// <summary>
/// Converts an array of COM DXGeneralResponse structure to a .NET GeneralResponse object.
/// </summary>
internal static GeneralResponse GetGeneralResponse(OpcRcw.Dx.DXGeneralResponse input, bool deallocate)
{
Opc.Dx.IdentifiedResult[] results = Interop.GetIdentifiedResults(ref input.pIdentifiedResults, input.dwCount, deallocate);
return new GeneralResponse(input.szConfigurationVersion, results);
}
/// <summary>
/// Converts an array of COM IdentifiedResult structures to .NET IdentifiedResult objects.
/// </summary>
internal static Opc.Dx.IdentifiedResult[] GetIdentifiedResults(ref IntPtr pInput, int count, bool deallocate)
{
Opc.Dx.IdentifiedResult[] output = null;
if (pInput != IntPtr.Zero && count > 0)
{
output = new Opc.Dx.IdentifiedResult[count];
IntPtr pos = pInput;
for (int ii = 0; ii < count; ii++)
{
OpcRcw.Dx.IdentifiedResult result = (OpcRcw.Dx.IdentifiedResult)Marshal.PtrToStructure(pos, typeof(OpcRcw.Dx.IdentifiedResult));
output[ii] = new Opc.Dx.IdentifiedResult();
output[ii].ItemName = result.szItemName;
output[ii].ItemPath = result.szItemPath;
output[ii].Version = result.szVersion;
output[ii].ResultID = OpcCom.Interop.GetResultID(result.hResultCode);
if (deallocate)
{
Marshal.DestroyStructure(pos, typeof(OpcRcw.Dx.IdentifiedResult));
}
pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Dx.IdentifiedResult)));
}
if (deallocate)
{
Marshal.FreeCoTaskMem(pInput);
pInput = IntPtr.Zero;
}
}
return output;
}
/// <summary>
/// Converts an array of COM DXConnection structures to .NET DXConnection objects.
/// </summary>
internal static DXConnection[] GetDXConnections(ref IntPtr pInput, int count, bool deallocate)
{
DXConnection[] output = null;
if (pInput != IntPtr.Zero && count > 0)
{
output = new DXConnection[count];
IntPtr pos = pInput;
for (int ii = 0; ii < count; ii++)
{
OpcRcw.Dx.DXConnection connection = (OpcRcw.Dx.DXConnection)Marshal.PtrToStructure(pos, typeof(OpcRcw.Dx.DXConnection));
output[ii] = GetDXConnection(connection, deallocate);
if (deallocate)
{
Marshal.DestroyStructure(pos, typeof(OpcRcw.Dx.DXConnection));
}
pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Dx.DXConnection)));
}
if (deallocate)
{
Marshal.FreeCoTaskMem(pInput);
pInput = IntPtr.Zero;
}
}
return output;
}
/// <summary>
/// Converts an array of .NET DXConnection objects to COM DXConnection structures.
/// </summary>
internal static OpcRcw.Dx.DXConnection[] GetDXConnections(DXConnection[] input)
{
OpcRcw.Dx.DXConnection[] output = null;
if (input != null && input.Length > 0)
{
output = new OpcRcw.Dx.DXConnection[input.Length];
for (int ii = 0; ii < input.Length; ii++)
{
output[ii] = GetDXConnection(input[ii]);
}
}
return output;
}
/// <summary>
/// Converts a .NET DXConnection object to COM DXConnection structure.
/// </summary>
internal static OpcRcw.Dx.DXConnection GetDXConnection(DXConnection input)
{
OpcRcw.Dx.DXConnection output = new OpcRcw.Dx.DXConnection();
// set output default values.
output.dwMask = 0;
output.szItemPath = null;
output.szItemName = null;
output.szVersion = null;
output.dwBrowsePathCount = 0;
output.pszBrowsePaths = IntPtr.Zero;
output.szName = null;
output.szDescription = null;
output.szKeyword = null;
output.bDefaultSourceItemConnected = 0;
output.bDefaultTargetItemConnected = 0;
output.bDefaultOverridden = 0;
output.vDefaultOverrideValue = null;
output.vSubstituteValue = null;
output.bEnableSubstituteValue = 0;
output.szTargetItemPath = null;
output.szTargetItemName = null;
output.szSourceServerName = null;
output.szSourceItemPath = null;
output.szSourceItemName = null;
output.dwSourceItemQueueSize = 0;
output.dwUpdateRate = 0;
output.fltDeadBand = 0;
output.szVendorData = null;
// item name
if (input.ItemName != null)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.ItemName;
output.szItemName = input.ItemName;
}
// item path
if (input.ItemPath != null)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.ItemPath;
output.szItemPath = input.ItemPath;
}
// version
if (input.Version != null)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.Version;
output.szVersion = input.Version;
}
// browse paths
if (input.BrowsePaths.Count > 0)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.BrowsePaths;
output.dwBrowsePathCount = input.BrowsePaths.Count;
output.pszBrowsePaths = OpcCom.Interop.GetUnicodeStrings(input.BrowsePaths.ToArray());
}
// name
if (input.Name != null)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.Name;
output.szName = input.Name;
}
// description
if (input.Description != null)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.Description;
output.szDescription = input.Description;
}
// keyword
if (input.Keyword != null)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.Keyword;
output.szKeyword = input.Keyword;
}
// default source item connected
if (input.DefaultSourceItemConnectedSpecified)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.DefaultSourceItemConnected;
output.bDefaultSourceItemConnected = (input.DefaultSourceItemConnected)?1:0;
}
// default target item connected
if (input.DefaultTargetItemConnectedSpecified)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.DefaultTargetItemConnected;
output.bDefaultTargetItemConnected = (input.DefaultTargetItemConnected)?1:0;
}
// default overridden
if (input.DefaultOverriddenSpecified)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.DefaultOverridden;
output.bDefaultOverridden = (input.DefaultOverridden)?1:0;
}
// default override value
if (input.DefaultOverrideValue != null)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.DefaultOverrideValue;
output.vDefaultOverrideValue = input.DefaultOverrideValue;
}
// substitute value
if (input.SubstituteValue != null)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.SubstituteValue;
output.vSubstituteValue = input.SubstituteValue;
}
// enable substitute value
if (input.EnableSubstituteValueSpecified)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.EnableSubstituteValue;
output.bEnableSubstituteValue = (input.EnableSubstituteValue)?1:0;
}
// target item name
if (input.TargetItemName != null)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.TargetItemName;
output.szTargetItemName = input.TargetItemName;
}
// target item path
if (input.TargetItemPath != null)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.TargetItemPath;
output.szTargetItemPath = input.TargetItemPath;
}
// source server name
if (input.SourceServerName != null)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.SourceServerName;
output.szSourceServerName = input.SourceServerName;
}
// source item name
if (input.SourceItemName != null)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.SourceItemName;
output.szSourceItemName = input.SourceItemName;
}
// source item path
if (input.SourceItemPath != null)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.SourceItemPath;
output.szSourceItemPath = input.SourceItemPath;
}
// source item queue size
if (input.SourceItemQueueSizeSpecified)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.SourceItemQueueSize;
output.dwSourceItemQueueSize = input.SourceItemQueueSize;
}
// update rate
if (input.UpdateRateSpecified)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.UpdateRate;
output.dwUpdateRate = input.UpdateRate;
}
// deadband
if (input.DeadbandSpecified)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.DeadBand;
output.fltDeadBand = input.Deadband;
}
// vendor data
if (input.VendorData != null)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.VendorData;
output.szVendorData = input.VendorData;
}
return output;
}
/// <summary>
/// Converts a COM DXConnection structure to a .NET DXConnection object.
/// </summary>
internal static DXConnection GetDXConnection(OpcRcw.Dx.DXConnection input, bool deallocate)
{
DXConnection output = new DXConnection();
// set output default values.
output.ItemPath = null;
output.ItemName = null;
output.Version = null;
output.BrowsePaths.Clear();
output.Name = null;
output.Description = null;
output.Keyword = null;
output.DefaultSourceItemConnected = false;
output.DefaultSourceItemConnectedSpecified = false;
output.DefaultTargetItemConnected = false;
output.DefaultTargetItemConnectedSpecified = false;
output.DefaultOverridden = false;
output.DefaultOverriddenSpecified = false;
output.DefaultOverrideValue = null;
output.SubstituteValue = null;
output.EnableSubstituteValue = false;
output.EnableSubstituteValueSpecified = false;
output.TargetItemPath = null;
output.TargetItemName = null;
output.SourceServerName = null;
output.SourceItemPath = null;
output.SourceItemName = null;
output.SourceItemQueueSize = 0;
output.SourceItemQueueSizeSpecified = false;
output.UpdateRate = 0;
output.UpdateRateSpecified = false;
output.Deadband = 0;
output.DeadbandSpecified = false;
output.VendorData = null;
// item name
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.ItemName) != 0)
{
output.ItemName = input.szItemName;
}
// item path
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.ItemPath) != 0)
{
output.ItemPath = input.szItemPath;
}
// version
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.Version) != 0)
{
output.Version = input.szVersion;
}
// browse paths
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.BrowsePaths) != 0)
{
string[] browsePaths = OpcCom.Interop.GetUnicodeStrings(ref input.pszBrowsePaths, input.dwBrowsePathCount, deallocate);
if (browsePaths != null)
{
output.BrowsePaths.AddRange(browsePaths);
}
}
// name
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.Name) != 0)
{
output.Name = input.szName;
}
// description
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.Description) != 0)
{
output.Description = input.szDescription;
}
// keyword
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.Keyword) != 0)
{
output.Keyword = input.szKeyword;
}
// default source item connected
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.DefaultSourceItemConnected) != 0)
{
output.DefaultSourceItemConnected = input.bDefaultSourceItemConnected != 0;
output.DefaultSourceItemConnectedSpecified = true;
}
// default target item connected
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.DefaultTargetItemConnected) != 0)
{
output.DefaultTargetItemConnected = input.bDefaultTargetItemConnected != 0;
output.DefaultTargetItemConnectedSpecified = true;
}
// default overridden
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.DefaultOverridden) != 0)
{
output.DefaultOverridden = input.bDefaultOverridden != 0;
output.DefaultOverriddenSpecified = true;
}
// default override value
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.DefaultOverrideValue) != 0)
{
output.DefaultOverrideValue = input.vDefaultOverrideValue;
}
// substitute value
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.SubstituteValue) != 0)
{
output.SubstituteValue = input.vSubstituteValue;
}
// enable substitute value
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.EnableSubstituteValue) != 0)
{
output.EnableSubstituteValue = input.bEnableSubstituteValue != 0;
output.EnableSubstituteValueSpecified = true;
}
// target item name
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.TargetItemName) != 0)
{
output.TargetItemName = input.szTargetItemName;
}
// target item path
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.TargetItemPath) != 0)
{
output.TargetItemPath = input.szTargetItemPath;
}
// source server name
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.SourceServerName) != 0)
{
output.SourceServerName = input.szSourceServerName;
}
// source item name
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.SourceItemName) != 0)
{
output.SourceItemName = input.szSourceItemName;
}
// source item path
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.SourceItemPath) != 0)
{
output.SourceItemPath = input.szSourceItemPath;
}
// source item queue size
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.SourceItemQueueSize) != 0)
{
output.SourceItemQueueSize = input.dwSourceItemQueueSize;
output.SourceItemQueueSizeSpecified = true;
}
// update rate
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.UpdateRate) != 0)
{
output.UpdateRate = input.dwUpdateRate;
output.UpdateRateSpecified = true;
}
// deadband
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.DeadBand) != 0)
{
output.Deadband = input.fltDeadBand;
output.DeadbandSpecified = true;
}
// vendor data
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.VendorData) != 0)
{
output.VendorData = input.szVendorData;
}
return output;
}
/// <summary>
/// Converts an array of .NET ItemIdentifier objects to COM ItemIdentifier structures.
/// </summary>
internal static OpcRcw.Dx.ItemIdentifier[] GetItemIdentifiers(Opc.Dx.ItemIdentifier[] input)
{
OpcRcw.Dx.ItemIdentifier[] output = null;
if (input != null && input.Length > 0)
{
output = new OpcRcw.Dx.ItemIdentifier[input.Length];
for (int ii = 0; ii < input.Length; ii++)
{
output[ii] = new OpcRcw.Dx.ItemIdentifier();
output[ii].szItemName = input[ii].ItemName;
output[ii].szItemPath = input[ii].ItemPath;
output[ii].szVersion = input[ii].Version;
}
}
return output;
}
*/
}
}

View File

@@ -0,0 +1,51 @@
#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
#endregion
namespace Technosoftware.DaAeHdaClient.Com.Ae
{
/// <summary>
/// Defines all well known COM AE HRESULT codes.
/// </summary>
internal struct Result
{
/// <remarks/>
public const int S_ALREADYACKED = +0x00040200; // 0x00040200
/// <remarks/>
public const int S_INVALIDBUFFERTIME = +0x00040201; // 0x00040201
/// <remarks/>
public const int S_INVALIDMAXSIZE = +0x00040202; // 0x00040202
/// <remarks/>
public const int S_INVALIDKEEPALIVETIME = +0x00040203; // 0x00040203
/// <remarks/>
public const int E_INVALIDBRANCHNAME = -0x3FFBFDFD; // 0xC0040203
/// <remarks/>
public const int E_INVALIDTIME = -0x3FFBFDFC; // 0xC0040204
/// <remarks/>
public const int E_BUSY = -0x3FFBFDFB; // 0xC0040205
/// <remarks/>
public const int E_NOINFO = -0x3FFBFDFA; // 0xC0040206
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,610 @@
#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.Runtime.InteropServices;
using Technosoftware.DaAeHdaClient.Ae;
using Technosoftware.OpcRcw.Ae;
using Technosoftware.DaAeHdaClient.Utilities;
#endregion
namespace Technosoftware.DaAeHdaClient.Com.Ae
{
/// <summary>
/// A .NET wrapper for a COM server that implements the AE subscription interfaces.
/// </summary>
[Serializable]
internal class Subscription : ITsCAeSubscription
{
#region Constructors
/// <summary>
/// Initializes the object with the specified URL and COM server.
/// </summary>
internal Subscription(TsCAeSubscriptionState state, object subscription)
{
subscription_ = subscription;
clientHandle_ = OpcConvert.Clone(state.ClientHandle);
supportsAe11_ = true;
callback_ = new Callback(state.ClientHandle);
// check if the V1.1 interfaces are supported.
try
{
var server = (IOPCEventSubscriptionMgt2)subscription_;
}
catch
{
supportsAe11_ = false;
}
}
#endregion
#region IDisposable Members
/// <summary>
/// The finalizer.
/// </summary>
~Subscription()
{
Dispose(false);
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="disposing">If true managed and unmanaged resources can be disposed. If false only unmanaged resources.</param>
protected virtual void Dispose(bool disposing)
{
if (!disposed_)
{
lock (lock_)
{
if (disposing)
{
// Free other state (managed objects).
if (subscription_ != null)
{
// close all connections.
if (connection_ != null)
{
try
{
connection_.Dispose();
}
catch
{
// Ignore. COM Server probably no longer connected
}
connection_ = null;
}
}
}
// Free your own state (unmanaged objects).
// Set large fields to null.
if (subscription_ != null)
{
// release subscription object.
try
{
Technosoftware.DaAeHdaClient.Com.Interop.ReleaseServer(subscription_);
}
catch
{
// Ignore. COM Server probably no longer connected
}
subscription_ = null;
}
}
disposed_ = true;
}
}
#endregion
#region Technosoftware.DaAeHdaClient.ISubscription Members
/// <summary>
/// An event to receive data change updates.
/// </summary>
public event TsCAeDataChangedEventHandler DataChangedEvent
{
add { lock (this) { Advise(); callback_.DataChangedEvent += value; } }
remove { lock (this) { callback_.DataChangedEvent -= value; Unadvise(); } }
}
//======================================================================
// State Management
/// <summary>
/// Returns the current state of the subscription.
/// </summary>
/// <returns>The current state of the subscription.</returns>
public TsCAeSubscriptionState GetState()
{
lock (this)
{
// verify state and arguments.
if (subscription_ == null) throw new NotConnectedException();
// initialize arguments.
int pbActive;
int pdwBufferTime;
int pdwMaxSize;
var pdwKeepAliveTime = 0;
// invoke COM method.
try
{
((IOPCEventSubscriptionMgt)subscription_).GetState(
out pbActive,
out pdwBufferTime,
out pdwMaxSize,
out _);
}
catch (Exception e)
{
throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventSubscriptionMgt.GetState", e);
}
// get keep alive.
if (supportsAe11_)
{
try
{
((IOPCEventSubscriptionMgt2)subscription_).GetKeepAlive(out pdwKeepAliveTime);
}
catch (Exception e)
{
throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventSubscriptionMgt2.GetKeepAlive", e);
}
}
// build results
var state = new TsCAeSubscriptionState
{
Active = pbActive != 0,
ClientHandle = clientHandle_,
BufferTime = pdwBufferTime,
MaxSize = pdwMaxSize,
KeepAlive = pdwKeepAliveTime
};
// return results.
return state;
}
}
/// <summary>
/// Changes the state of a subscription.
/// </summary>
/// <param name="masks">A bit mask that indicates which elements of the subscription state are changing.</param>
/// <param name="state">The new subscription state.</param>
/// <returns>The actual subscription state after applying the changes.</returns>
public TsCAeSubscriptionState ModifyState(int masks, TsCAeSubscriptionState state)
{
lock (this)
{
// verify state and arguments.
if (subscription_ == null) throw new NotConnectedException();
// initialize arguments.
var active = (state.Active) ? 1 : 0;
var hActive = GCHandle.Alloc(active, GCHandleType.Pinned);
var hBufferTime = GCHandle.Alloc(state.BufferTime, GCHandleType.Pinned);
var hMaxSize = GCHandle.Alloc(state.MaxSize, GCHandleType.Pinned);
var pbActive = ((masks & (int)TsCAeStateMask.Active) != 0) ? hActive.AddrOfPinnedObject() : IntPtr.Zero;
var pdwBufferTime = ((masks & (int)TsCAeStateMask.BufferTime) != 0) ? hBufferTime.AddrOfPinnedObject() : IntPtr.Zero;
var pdwMaxSize = ((masks & (int)TsCAeStateMask.MaxSize) != 0) ? hMaxSize.AddrOfPinnedObject() : IntPtr.Zero;
var phClientSubscription = 0;
// invoke COM method.
try
{
((IOPCEventSubscriptionMgt)subscription_).SetState(
pbActive,
pdwBufferTime,
pdwMaxSize,
phClientSubscription,
out _,
out _);
}
catch (Exception e)
{
throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventSubscriptionMgt.SetState", e);
}
finally
{
if (hActive.IsAllocated) hActive.Free();
if (hBufferTime.IsAllocated) hBufferTime.Free();
if (hMaxSize.IsAllocated) hMaxSize.Free();
}
// update keep alive.
if (((masks & (int)TsCAeStateMask.KeepAlive) != 0) && supportsAe11_)
{
try
{
((IOPCEventSubscriptionMgt2)subscription_).SetKeepAlive(
state.KeepAlive,
out _);
}
catch (Exception e)
{
throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventSubscriptionMgt2.SetKeepAlive", e);
}
}
// return current state.
return GetState();
}
}
//======================================================================
// Filter Management
/// <summary>
/// Returns the current filters for the subscription.
/// </summary>
/// <returns>The current filters for the subscription.</returns>
public TsCAeSubscriptionFilters GetFilters()
{
lock (this)
{
// verify state and arguments.
if (subscription_ == null) throw new NotConnectedException();
// initialize arguments.
int pdwEventType;
int pdwNumCategories;
IntPtr ppidEventCategories;
int pdwLowSeverity;
int pdwHighSeverity;
int pdwNumAreas;
IntPtr ppsAreaList;
int pdwNumSources;
IntPtr ppsSourceList;
// invoke COM method.
try
{
((IOPCEventSubscriptionMgt)subscription_).GetFilter(
out pdwEventType,
out pdwNumCategories,
out ppidEventCategories,
out pdwLowSeverity,
out pdwHighSeverity,
out pdwNumAreas,
out ppsAreaList,
out pdwNumSources,
out ppsSourceList);
}
catch (Exception e)
{
throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventSubscriptionMgt.GetFilter", e);
}
// marshal results
var categoryIDs = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref ppidEventCategories, pdwNumCategories, true);
var areaIDs = Technosoftware.DaAeHdaClient.Com.Interop.GetUnicodeStrings(ref ppsAreaList, pdwNumAreas, true);
var sourceIDs = Technosoftware.DaAeHdaClient.Com.Interop.GetUnicodeStrings(ref ppsSourceList, pdwNumSources, true);
// build results.
var filters = new TsCAeSubscriptionFilters
{
EventTypes = pdwEventType, LowSeverity = pdwLowSeverity, HighSeverity = pdwHighSeverity
};
filters.Categories.AddRange(categoryIDs);
filters.Areas.AddRange(areaIDs);
filters.Sources.AddRange(sourceIDs);
// return results.
return filters;
}
}
/// <summary>
/// Sets the current filters for the subscription.
/// </summary>
/// <param name="filters">The new filters to use for the subscription.</param>
public void SetFilters(TsCAeSubscriptionFilters filters)
{
lock (this)
{
// verify state and arguments.
if (subscription_ == null) throw new NotConnectedException();
// invoke COM method.
try
{
((IOPCEventSubscriptionMgt)subscription_).SetFilter(
filters.EventTypes,
filters.Categories.Count,
filters.Categories.ToArray(),
filters.LowSeverity,
filters.HighSeverity,
filters.Areas.Count,
filters.Areas.ToArray(),
filters.Sources.Count,
filters.Sources.ToArray());
}
catch (Exception e)
{
throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventSubscriptionMgt.SetFilter", e);
}
}
}
//======================================================================
// Attribute Management
/// <summary>
/// Returns the set of attributes to return with event notifications.
/// </summary>
/// <returns>The set of attributes to returned with event notifications.</returns>
public int[] GetReturnedAttributes(int eventCategory)
{
lock (this)
{
// verify state and arguments.
if (subscription_ == null) throw new NotConnectedException();
// initialize arguments.
int pdwCount;
IntPtr ppidAttributeIDs;
// invoke COM method.
try
{
((IOPCEventSubscriptionMgt)subscription_).GetReturnedAttributes(
eventCategory,
out pdwCount,
out ppidAttributeIDs);
}
catch (Exception e)
{
throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventSubscriptionMgt.GetReturnedAttributes", e);
}
// marshal results
var attributeIDs = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref ppidAttributeIDs, pdwCount, true);
// return results.
return attributeIDs;
}
}
/// <summary>
/// Selects the set of attributes to return with event notifications.
/// </summary>
/// <param name="eventCategory">The specific event category for which the attributes apply.</param>
/// <param name="attributeIDs">The list of attribute ids to return.</param>
public void SelectReturnedAttributes(int eventCategory, int[] attributeIDs)
{
lock (this)
{
// verify state and arguments.
if (subscription_ == null) throw new NotConnectedException();
// invoke COM method.
try
{
((IOPCEventSubscriptionMgt)subscription_).SelectReturnedAttributes(
eventCategory,
attributeIDs?.Length ?? 0,
attributeIDs ?? Array.Empty<int>());
}
catch (Exception e)
{
throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventSubscriptionMgt.SelectReturnedAttributes", e);
}
}
}
//======================================================================
// Refresh
/// <summary>
/// Force a refresh for all active conditions and inactive, unacknowledged conditions whose event notifications match the filter of the event subscription.
/// </summary>
public void Refresh()
{
lock (this)
{
// verify state and arguments.
if (subscription_ == null) throw new NotConnectedException();
// invoke COM method.
try
{
((IOPCEventSubscriptionMgt)subscription_).Refresh(0);
}
catch (Exception e)
{
throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventSubscriptionMgt.Refresh", e);
}
}
}
/// <summary>
/// Cancels an outstanding refresh request.
/// </summary>
public void CancelRefresh()
{
lock (this)
{
// verify state and arguments.
if (subscription_ == null) throw new NotConnectedException();
// invoke COM method.
try
{
((IOPCEventSubscriptionMgt)subscription_).CancelRefresh(0);
}
catch (Exception e)
{
throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventSubscriptionMgt.CancelRefresh", e);
}
}
}
#endregion
#region IOPCEventSink Members
/// <summary>
/// A class that implements the IOPCEventSink interface.
/// </summary>
private class Callback : IOPCEventSink
{
/// <summary>
/// Initializes the object with the containing subscription object.
/// </summary>
public Callback(object clientHandle)
{
clientHandle_ = clientHandle;
}
/// <summary>
/// Raised when data changed callbacks arrive.
/// </summary>
public event TsCAeDataChangedEventHandler DataChangedEvent
{
add { lock (this) { DataChangedEventHandler += value; } }
remove { lock (this) { DataChangedEventHandler -= value; } }
}
/// <summary>
/// Called when a data changed event is received.
/// </summary>
public void OnEvent(
int hClientSubscription,
int bRefresh,
int bLastRefresh,
int dwCount,
ONEVENTSTRUCT[] pEvents)
{
LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.AlarmsConditions, true);
try
{
lock (this)
{
// do nothing if no connections.
if (DataChangedEventHandler == null) return;
// un marshal item values.
var notifications = Interop.GetEventNotifications(pEvents);
foreach (var notification in notifications)
{
notification.ClientHandle = clientHandle_;
}
if (!LicenseHandler.IsExpired)
{
// invoke the callback.
DataChangedEventHandler?.Invoke(notifications, bRefresh != 0, bLastRefresh != 0);
}
}
}
catch (Exception e)
{
Utils.Trace(e, "Exception '{0}' in event handler.", e.Message);
}
}
#region Private Members
private object clientHandle_;
private event TsCAeDataChangedEventHandler DataChangedEventHandler;
#endregion
}
#endregion
#region Private Methods
/// <summary>
/// Establishes a connection point callback with the COM server.
/// </summary>
private void Advise()
{
if (connection_ == null)
{
connection_ = new ConnectionPoint(subscription_, typeof(IOPCEventSink).GUID);
connection_.Advise(callback_);
}
}
/// <summary>
/// Closes a connection point callback with the COM server.
/// </summary>
private void Unadvise()
{
if (connection_ != null)
{
if (connection_.Unadvise() == 0)
{
connection_.Dispose();
connection_ = null;
}
}
}
#endregion
#region Private Members
private object subscription_;
private object clientHandle_;
private bool supportsAe11_ = true;
private ConnectionPoint connection_;
private Callback callback_;
/// <summary>
/// The synchronization object for subscription access
/// </summary>
private static volatile object lock_ = new object();
private bool disposed_;
#endregion
}
}

View File

@@ -0,0 +1,97 @@
#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;
#endregion
namespace Technosoftware.DaAeHdaClient.Com
{
/// <summary>
/// Manages the license to enable the different product versions.
/// </summary>
public partial class ApplicationInstance
{
#region Nested Enums
/// <summary>
/// The possible authentication levels.
/// </summary>
[Flags]
public enum AuthenticationLevel : uint
{
/// <summary>
/// Tells DCOM to choose the authentication level using its normal security blanket negotiation algorithm.
/// </summary>
Default = 0,
/// <summary>
/// Performs no authentication.
/// </summary>
None = 1,
/// <summary>
/// Authenticates the credentials of the client only when the client establishes a relationship with the server. Datagram transports always use Packet instead.
/// </summary>
Connect = 2,
/// <summary>
/// Authenticates only at the beginning of each remote procedure call when the server receives the request. Datagram transports use Packet instead.
/// </summary>
Call = 3,
/// <summary>
/// Authenticates that all data received is from the expected client.
/// </summary>
Packet = 4,
/// <summary>
/// Authenticates and verifies that none of the data transferred between client and server has been modified.
/// </summary>
Integrity = 5,
/// <summary>
/// Authenticates all previous levels and encrypts the argument value of each remote procedure call.
/// </summary>
Privacy = 6,
}
#endregion
#region Public Methods
/// <summary>
/// Initializes COM security. This should be called directly at the beginning of an application and can only be called once.
/// </summary>
/// <param name="authenticationLevel">The default authentication level for the process. Both servers and clients use this parameter when they call CoInitializeSecurity. With the Windows Update KB5004442 a higher authentication level of Integrity must be used.</param>
public static void InitializeSecurity(AuthenticationLevel authenticationLevel)
{
if (!InitializeSecurityCalled)
{
Com.Interop.InitializeSecurity((uint)authenticationLevel);
InitializeSecurityCalled = true;
}
}
#endregion
#region Internal Fields
internal static bool InitializeSecurityCalled;
#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,107 @@
#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 Technosoftware.OpcRcw.Comn;
#endregion
namespace Technosoftware.DaAeHdaClient.Com
{
/// <summary>
/// Adds and removes a connection point to a server.
/// </summary>
internal class ConnectionPoint : IDisposable
{
/// <summary>
/// The COM server that supports connection points.
/// </summary>
private IConnectionPoint server_;
/// <summary>
/// The id assigned to the connection by the COM server.
/// </summary>
private int cookie_;
/// <summary>
/// The number of times Advise() has been called without a matching Unadvise().
/// </summary>
private int refs_;
/// <summary>
/// Initializes the object by finding the specified connection point.
/// </summary>
public ConnectionPoint(object server, Guid iid)
{
((IConnectionPointContainer)server).FindConnectionPoint(ref iid, out server_);
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
if (server_ != null)
{
while (Unadvise() > 0)
{
}
try
{
Utilities.Interop.ReleaseServer(server_);
}
catch
{
// Ignore. COM Server probably no longer connected
}
server_ = null;
}
}
/// <summary>
/// The cookie returned in the advise call.
/// </summary>
public int Cookie => cookie_;
//=====================================================================
// IConnectionPoint
/// <summary>
/// Establishes a connection, if necessary and increments the reference count.
/// </summary>
public int Advise(object callback)
{
if (refs_++ == 0) server_.Advise(callback, out cookie_);
return refs_;
}
/// <summary>
/// Decrements the reference count and closes the connection if no more references.
/// </summary>
public int Unadvise()
{
if (--refs_ == 0) server_.Unadvise(cookie_);
return refs_;
}
}
}

View File

@@ -0,0 +1,60 @@
#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 Technosoftware.DaAeHdaClient.Da;
#endregion
namespace Technosoftware.DaAeHdaClient.Com.Da
{
/// <summary>
/// Implements an object that handles multi-step browse operations.
/// </summary>
[Serializable]
internal class BrowsePosition : TsCDaBrowsePosition
{
/// <summary>
/// The continuation point for a browse operation.
/// </summary>
internal string ContinuationPoint = null;
/// <summary>
/// Indicates that elements that meet the filter criteria have not been returned.
/// </summary>
internal bool MoreElements = false;
/// <summary>
/// Initializes a browse position
/// </summary>
internal BrowsePosition(
OpcItem itemID,
TsCDaBrowseFilters filters,
string continuationPoint)
:
base(itemID, filters)
{
ContinuationPoint = continuationPoint;
}
}
}

View File

@@ -0,0 +1,906 @@
#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 System.Reflection;
using Technosoftware.DaAeHdaClient.Da;
#endregion
#pragma warning disable 0618
namespace Technosoftware.DaAeHdaClient.Com.Da
{
/// <summary>
/// Contains state information for a single asynchronous Technosoftware.DaAeHdaClient.Com.Da.Interop.
/// </summary>
internal class Interop
{
/// <summary>
/// Converts a standard FILETIME to an OpcRcw.Da.FILETIME structure.
/// </summary>
internal static OpcRcw.Da.FILETIME Convert(FILETIME input)
{
var output = new OpcRcw.Da.FILETIME();
output.dwLowDateTime = input.dwLowDateTime;
output.dwHighDateTime = input.dwHighDateTime;
return output;
}
/// <summary>
/// Converts an OpcRcw.Da.FILETIME to a standard FILETIME structure.
/// </summary>
internal static FILETIME Convert(OpcRcw.Da.FILETIME input)
{
var output = new FILETIME();
output.dwLowDateTime = input.dwLowDateTime;
output.dwHighDateTime = input.dwHighDateTime;
return output;
}
/// <summary>
/// Allocates and marshals a OPCSERVERSTATUS structure.
/// </summary>
internal static OpcRcw.Da.OPCSERVERSTATUS GetServerStatus(OpcServerStatus input, int groupCount)
{
var output = new OpcRcw.Da.OPCSERVERSTATUS();
if (input != null)
{
output.szVendorInfo = input.VendorInfo;
output.wMajorVersion = 0;
output.wMinorVersion = 0;
output.wBuildNumber = 0;
output.dwServerState = (OpcRcw.Da.OPCSERVERSTATE)input.ServerState;
output.ftStartTime = Convert(Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(input.StartTime));
output.ftCurrentTime = Convert(Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(input.CurrentTime));
output.ftLastUpdateTime = Convert(Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(input.LastUpdateTime));
output.dwBandWidth = -1;
output.dwGroupCount = groupCount;
output.wReserved = 0;
if (input.ProductVersion != null)
{
var versions = input.ProductVersion.Split(new char[] { '.' });
if (versions.Length > 0)
{
try { output.wMajorVersion = System.Convert.ToInt16(versions[0]); }
catch { output.wMajorVersion = 0; }
}
if (versions.Length > 1)
{
try { output.wMinorVersion = System.Convert.ToInt16(versions[1]); }
catch { output.wMinorVersion = 0; }
}
output.wBuildNumber = 0;
for (var ii = 2; ii < versions.Length; ii++)
{
try
{
output.wBuildNumber = (short)(output.wBuildNumber * 100 + System.Convert.ToInt16(versions[ii]));
}
catch
{
output.wBuildNumber = 0;
break;
}
}
}
}
return output;
}
/// <summary>
/// Unmarshals and deallocates a OPCSERVERSTATUS structure.
/// </summary>
internal static OpcServerStatus GetServerStatus(ref IntPtr pInput, bool deallocate)
{
OpcServerStatus output = null;
if (pInput != IntPtr.Zero)
{
var status = (OpcRcw.Da.OPCSERVERSTATUS)Marshal.PtrToStructure(pInput, typeof(OpcRcw.Da.OPCSERVERSTATUS));
output = new OpcServerStatus();
output.VendorInfo = status.szVendorInfo;
output.ProductVersion = string.Format("{0}.{1}.{2}", status.wMajorVersion, status.wMinorVersion, status.wBuildNumber);
output.ServerState = (OpcServerState)status.dwServerState;
output.StatusInfo = null;
output.StartTime = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(status.ftStartTime));
output.CurrentTime = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(status.ftCurrentTime));
output.LastUpdateTime = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(status.ftLastUpdateTime));
if (deallocate)
{
Marshal.DestroyStructure(pInput, typeof(OpcRcw.Da.OPCSERVERSTATUS));
Marshal.FreeCoTaskMem(pInput);
pInput = IntPtr.Zero;
}
}
return output;
}
/// <summary>
/// Converts a browseFilter values to the COM equivalent.
/// </summary>
internal static OpcRcw.Da.OPCBROWSEFILTER GetBrowseFilter(TsCDaBrowseFilter input)
{
switch (input)
{
case TsCDaBrowseFilter.All: return OpcRcw.Da.OPCBROWSEFILTER.OPC_BROWSE_FILTER_ALL;
case TsCDaBrowseFilter.Branch: return OpcRcw.Da.OPCBROWSEFILTER.OPC_BROWSE_FILTER_BRANCHES;
case TsCDaBrowseFilter.Item: return OpcRcw.Da.OPCBROWSEFILTER.OPC_BROWSE_FILTER_ITEMS;
}
return OpcRcw.Da.OPCBROWSEFILTER.OPC_BROWSE_FILTER_ALL;
}
/// <summary>
/// Converts a browseFilter values from the COM equivalent.
/// </summary>
internal static TsCDaBrowseFilter GetBrowseFilter(OpcRcw.Da.OPCBROWSEFILTER input)
{
switch (input)
{
case OpcRcw.Da.OPCBROWSEFILTER.OPC_BROWSE_FILTER_ALL: return TsCDaBrowseFilter.All;
case OpcRcw.Da.OPCBROWSEFILTER.OPC_BROWSE_FILTER_BRANCHES: return TsCDaBrowseFilter.Branch;
case OpcRcw.Da.OPCBROWSEFILTER.OPC_BROWSE_FILTER_ITEMS: return TsCDaBrowseFilter.Item;
}
return TsCDaBrowseFilter.All;
}
/// <summary>
/// Allocates and marshals an array of HRESULT codes.
/// </summary>
internal static IntPtr GetHRESULTs(IOpcResult[] results)
{
// extract error codes from results.
var errors = new int[results.Length];
for (var ii = 0; ii < results.Length; ii++)
{
if (results[ii] != null)
{
errors[ii] = Technosoftware.DaAeHdaClient.Com.Interop.GetResultID(results[ii].Result);
}
else
{
errors[ii] = Result.E_INVALIDHANDLE;
}
}
// marshal error codes.
var pErrors = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(int)) * results.Length);
Marshal.Copy(errors, 0, pErrors, results.Length);
// return results.
return pErrors;
}
/// <summary>
/// Unmarshals and deallocates an array of OPCBROWSEELEMENT structures.
/// </summary>
internal static TsCDaBrowseElement[] GetBrowseElements(ref IntPtr pInput, int count, bool deallocate)
{
TsCDaBrowseElement[] output = null;
if (pInput != IntPtr.Zero && count > 0)
{
output = new TsCDaBrowseElement[count];
var pos = pInput;
for (var ii = 0; ii < count; ii++)
{
output[ii] = GetBrowseElement(pos, deallocate);
pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Da.OPCBROWSEELEMENT)));
}
if (deallocate)
{
Marshal.FreeCoTaskMem(pInput);
pInput = IntPtr.Zero;
}
}
return output;
}
/// <summary>
/// Allocates and marshals an array of OPCBROWSEELEMENT structures.
/// </summary>
internal static IntPtr GetBrowseElements(TsCDaBrowseElement[] input, bool propertiesRequested)
{
var output = IntPtr.Zero;
if (input != null && input.Length > 0)
{
output = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(OpcRcw.Da.OPCBROWSEELEMENT)) * input.Length);
var pos = output;
for (var ii = 0; ii < input.Length; ii++)
{
var element = GetBrowseElement(input[ii], propertiesRequested);
Marshal.StructureToPtr(element, pos, false);
pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Da.OPCBROWSEELEMENT)));
}
}
return output;
}
/// <summary>
/// Unmarshals and deallocates a OPCBROWSEELEMENT structures.
/// </summary>
internal static TsCDaBrowseElement GetBrowseElement(IntPtr pInput, bool deallocate)
{
TsCDaBrowseElement output = null;
if (pInput != IntPtr.Zero)
{
var element = (OpcRcw.Da.OPCBROWSEELEMENT)Marshal.PtrToStructure(pInput, typeof(OpcRcw.Da.OPCBROWSEELEMENT));
output = new TsCDaBrowseElement();
output.Name = element.szName;
output.ItemPath = null;
output.ItemName = element.szItemID;
output.IsItem = ((element.dwFlagValue & OpcRcw.Da.Constants.OPC_BROWSE_ISITEM) != 0);
output.HasChildren = ((element.dwFlagValue & OpcRcw.Da.Constants.OPC_BROWSE_HASCHILDREN) != 0);
output.Properties = GetItemProperties(ref element.ItemProperties, deallocate);
if (deallocate)
{
Marshal.DestroyStructure(pInput, typeof(OpcRcw.Da.OPCBROWSEELEMENT));
}
}
return output;
}
/// <summary>
/// Allocates and marshals an OPCBROWSEELEMENT structure.
/// </summary>
internal static OpcRcw.Da.OPCBROWSEELEMENT GetBrowseElement(TsCDaBrowseElement input, bool propertiesRequested)
{
var output = new OpcRcw.Da.OPCBROWSEELEMENT();
if (input != null)
{
output.szName = input.Name;
output.szItemID = input.ItemName;
output.dwFlagValue = 0;
output.ItemProperties = GetItemProperties(input.Properties);
if (input.IsItem)
{
output.dwFlagValue |= OpcRcw.Da.Constants.OPC_BROWSE_ISITEM;
}
if (input.HasChildren)
{
output.dwFlagValue |= OpcRcw.Da.Constants.OPC_BROWSE_HASCHILDREN;
}
}
return output;
}
/// <summary>
/// Creates an array of property codes.
/// </summary>
internal static int[] GetPropertyIDs(TsDaPropertyID[] propertyIDs)
{
var output = new ArrayList();
if (propertyIDs != null)
{
foreach (var propertyID in propertyIDs)
{
output.Add(propertyID.Code);
}
}
return (int[])output.ToArray(typeof(int));
}
/// <summary>
/// Creates an array of property codes.
/// </summary>
internal static TsDaPropertyID[] GetPropertyIDs(int[] propertyIDs)
{
var output = new ArrayList();
if (propertyIDs != null)
{
foreach (var propertyID in propertyIDs)
{
output.Add(GetPropertyID(propertyID));
}
}
return (TsDaPropertyID[])output.ToArray(typeof(TsDaPropertyID));
}
/// <summary>
/// Unmarshals and deallocates an array of OPCITEMPROPERTIES structures.
/// </summary>
internal static TsCDaItemPropertyCollection[] GetItemPropertyCollections(ref IntPtr pInput, int count, bool deallocate)
{
TsCDaItemPropertyCollection[] output = null;
if (pInput != IntPtr.Zero && count > 0)
{
output = new TsCDaItemPropertyCollection[count];
var pos = pInput;
for (var ii = 0; ii < count; ii++)
{
var list = (OpcRcw.Da.OPCITEMPROPERTIES)Marshal.PtrToStructure(pos, typeof(OpcRcw.Da.OPCITEMPROPERTIES));
output[ii] = new TsCDaItemPropertyCollection();
output[ii].ItemPath = null;
output[ii].ItemName = null;
output[ii].Result = Technosoftware.DaAeHdaClient.Com.Interop.GetResultID(list.hrErrorID);
var properties = GetItemProperties(ref list, deallocate);
if (properties != null)
{
output[ii].AddRange(properties);
}
if (deallocate)
{
Marshal.DestroyStructure(pos, typeof(OpcRcw.Da.OPCITEMPROPERTIES));
}
pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Da.OPCITEMPROPERTIES)));
}
if (deallocate)
{
Marshal.FreeCoTaskMem(pInput);
pInput = IntPtr.Zero;
}
}
return output;
}
/// <summary>
/// Allocates and marshals an array of OPCITEMPROPERTIES structures.
/// </summary>
internal static IntPtr GetItemPropertyCollections(TsCDaItemPropertyCollection[] input)
{
var output = IntPtr.Zero;
if (input != null && input.Length > 0)
{
output = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(OpcRcw.Da.OPCITEMPROPERTIES)) * input.Length);
var pos = output;
for (var ii = 0; ii < input.Length; ii++)
{
var properties = new OpcRcw.Da.OPCITEMPROPERTIES();
if (input[ii].Count > 0)
{
properties = GetItemProperties((TsCDaItemProperty[])input[ii].ToArray(typeof(TsCDaItemProperty)));
}
properties.hrErrorID = Technosoftware.DaAeHdaClient.Com.Interop.GetResultID(input[ii].Result);
Marshal.StructureToPtr(properties, pos, false);
pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Da.OPCITEMPROPERTIES)));
}
}
return output;
}
/// <summary>
/// Unmarshals and deallocates a OPCITEMPROPERTIES structures.
/// </summary>
internal static TsCDaItemProperty[] GetItemProperties(ref OpcRcw.Da.OPCITEMPROPERTIES input, bool deallocate)
{
TsCDaItemProperty[] output = null;
if (input.dwNumProperties > 0)
{
output = new TsCDaItemProperty[input.dwNumProperties];
var pos = input.pItemProperties;
for (var ii = 0; ii < output.Length; ii++)
{
try
{
output[ii] = GetItemProperty(pos, deallocate);
}
catch (Exception e)
{
output[ii] = new TsCDaItemProperty();
output[ii].Description = e.Message;
output[ii].Result = OpcResult.E_FAIL;
}
pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Da.OPCITEMPROPERTY)));
}
if (deallocate)
{
Marshal.FreeCoTaskMem(input.pItemProperties);
input.pItemProperties = IntPtr.Zero;
}
}
return output;
}
/// <summary>
/// Allocates and marshals an array of OPCITEMPROPERTIES structures.
/// </summary>
internal static OpcRcw.Da.OPCITEMPROPERTIES GetItemProperties(TsCDaItemProperty[] input)
{
var output = new OpcRcw.Da.OPCITEMPROPERTIES();
if (input != null && input.Length > 0)
{
output.hrErrorID = Result.S_OK;
output.dwReserved = 0;
output.dwNumProperties = input.Length;
output.pItemProperties = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(OpcRcw.Da.OPCITEMPROPERTY)) * input.Length);
var error = false;
var pos = output.pItemProperties;
for (var ii = 0; ii < input.Length; ii++)
{
var property = GetItemProperty(input[ii]);
Marshal.StructureToPtr(property, pos, false);
pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Da.OPCITEMPROPERTY)));
if (input[ii].Result.Failed())
{
error = true;
}
}
// set flag indicating one or more properties contained errors.
if (error)
{
output.hrErrorID = Result.S_FALSE;
}
}
return output;
}
/// <summary>
/// Unmarshals and deallocates a OPCITEMPROPERTY structures.
/// </summary>
internal static TsCDaItemProperty GetItemProperty(IntPtr pInput, bool deallocate)
{
TsCDaItemProperty output = null;
if (pInput != IntPtr.Zero)
{
try
{
var property = (OpcRcw.Da.OPCITEMPROPERTY)Marshal.PtrToStructure(pInput, typeof(OpcRcw.Da.OPCITEMPROPERTY));
output = new TsCDaItemProperty();
output.ID = GetPropertyID(property.dwPropertyID);
output.Description = property.szDescription;
output.DataType = Technosoftware.DaAeHdaClient.Com.Interop.GetType((VarEnum)property.vtDataType);
output.ItemPath = null;
output.ItemName = property.szItemID;
output.Value = UnmarshalPropertyValue(output.ID, property.vValue);
output.Result = Technosoftware.DaAeHdaClient.Com.Interop.GetResultID(property.hrErrorID);
// convert COM DA code to unified DA code.
if (property.hrErrorID == Result.E_BADRIGHTS) output.Result = new OpcResult(OpcResult.Da.E_WRITEONLY, Result.E_BADRIGHTS);
}
catch (Exception)
{
}
if (deallocate)
{
Marshal.DestroyStructure(pInput, typeof(OpcRcw.Da.OPCITEMPROPERTY));
}
}
return output;
}
/// <summary>
/// Allocates and marshals an arary of OPCITEMPROPERTY structures.
/// </summary>
internal static OpcRcw.Da.OPCITEMPROPERTY GetItemProperty(TsCDaItemProperty input)
{
var output = new OpcRcw.Da.OPCITEMPROPERTY();
if (input != null)
{
output.dwPropertyID = input.ID.Code;
output.szDescription = input.Description;
output.vtDataType = (short)Technosoftware.DaAeHdaClient.Com.Interop.GetType(input.DataType);
output.vValue = MarshalPropertyValue(input.ID, input.Value);
output.wReserved = 0;
output.hrErrorID = Technosoftware.DaAeHdaClient.Com.Interop.GetResultID(input.Result);
// set the property data type.
var description = TsDaPropertyDescription.Find(input.ID);
if (description != null)
{
output.vtDataType = (short)Technosoftware.DaAeHdaClient.Com.Interop.GetType(description.Type);
}
// convert unified DA code to COM DA code.
if (input.Result == OpcResult.Da.E_WRITEONLY) output.hrErrorID = Result.E_BADRIGHTS;
}
return output;
}
/// <remarks/>
public static TsDaPropertyID GetPropertyID(int input)
{
var fields = typeof(TsDaProperty).GetFields(BindingFlags.Static | BindingFlags.Public);
foreach (var field in fields)
{
var property = (TsDaPropertyID)field.GetValue(typeof(TsDaPropertyID));
if (input == property.Code)
{
return property;
}
}
return new TsDaPropertyID(input);
}
/// <summary>
/// Converts the property value to a type supported by the unified interface.
/// </summary>
internal static object UnmarshalPropertyValue(TsDaPropertyID propertyID, object input)
{
if (input == null) return null;
try
{
if (propertyID == TsDaProperty.DATATYPE)
{
return Technosoftware.DaAeHdaClient.Com.Interop.GetType((VarEnum)System.Convert.ToUInt16(input));
}
if (propertyID == TsDaProperty.ACCESSRIGHTS)
{
switch (System.Convert.ToInt32(input))
{
case OpcRcw.Da.Constants.OPC_READABLE: return TsDaAccessRights.Readable;
case OpcRcw.Da.Constants.OPC_WRITEABLE: return TsDaAccessRights.Writable;
case OpcRcw.Da.Constants.OPC_READABLE | OpcRcw.Da.Constants.OPC_WRITEABLE:
{
return TsDaAccessRights.ReadWritable;
}
}
return null;
}
if (propertyID == TsDaProperty.EUTYPE)
{
switch ((OpcRcw.Da.OPCEUTYPE)input)
{
case OpcRcw.Da.OPCEUTYPE.OPC_NOENUM: return TsDaEuType.NoEnum;
case OpcRcw.Da.OPCEUTYPE.OPC_ANALOG: return TsDaEuType.Analog;
case OpcRcw.Da.OPCEUTYPE.OPC_ENUMERATED: return TsDaEuType.Enumerated;
}
return null;
}
if (propertyID == TsDaProperty.QUALITY)
{
return new TsCDaQuality(System.Convert.ToInt16(input));
}
// convert UTC time in property to local time for the unified DA interface.
if (propertyID == TsDaProperty.TIMESTAMP)
{
if (input.GetType() == typeof(DateTime))
{
var dateTime = (DateTime)input;
if (dateTime != DateTime.MinValue)
{
return dateTime.ToLocalTime();
}
return dateTime;
}
}
}
catch { }
return input;
}
/// <summary>
/// Converts the property value to a type supported by COM-DA interface.
/// </summary>
internal static object MarshalPropertyValue(TsDaPropertyID propertyID, object input)
{
if (input == null) return null;
try
{
if (propertyID == TsDaProperty.DATATYPE)
{
return (short)Technosoftware.DaAeHdaClient.Com.Interop.GetType((Type)input);
}
if (propertyID == TsDaProperty.ACCESSRIGHTS)
{
switch ((TsDaAccessRights)input)
{
case TsDaAccessRights.Readable: return OpcRcw.Da.Constants.OPC_READABLE;
case TsDaAccessRights.Writable: return OpcRcw.Da.Constants.OPC_WRITEABLE;
case TsDaAccessRights.ReadWritable: return OpcRcw.Da.Constants.OPC_READABLE | OpcRcw.Da.Constants.OPC_WRITEABLE;
}
return null;
}
if (propertyID == TsDaProperty.EUTYPE)
{
switch ((TsDaEuType)input)
{
case TsDaEuType.NoEnum: return OpcRcw.Da.OPCEUTYPE.OPC_NOENUM;
case TsDaEuType.Analog: return OpcRcw.Da.OPCEUTYPE.OPC_ANALOG;
case TsDaEuType.Enumerated: return OpcRcw.Da.OPCEUTYPE.OPC_ENUMERATED;
}
return null;
}
if (propertyID == TsDaProperty.QUALITY)
{
return ((TsCDaQuality)input).GetCode();
}
// convert local time in property to UTC time for the COM DA interface.
if (propertyID == TsDaProperty.TIMESTAMP)
{
if (input.GetType() == typeof(DateTime))
{
var dateTime = (DateTime)input;
if (dateTime != DateTime.MinValue)
{
return dateTime.ToUniversalTime();
}
return dateTime;
}
}
}
catch { }
return input;
}
/// <summary>
/// Converts an array of item values to an array of OPCITEMVQT objects.
/// </summary>
internal static OpcRcw.Da.OPCITEMVQT[] GetOPCITEMVQTs(TsCDaItemValue[] input)
{
OpcRcw.Da.OPCITEMVQT[] output = null;
if (input != null)
{
output = new OpcRcw.Da.OPCITEMVQT[input.Length];
for (var ii = 0; ii < input.Length; ii++)
{
output[ii] = new OpcRcw.Da.OPCITEMVQT();
var timestamp = (input[ii].TimestampSpecified) ? input[ii].Timestamp : DateTime.MinValue;
output[ii].vDataValue = Technosoftware.DaAeHdaClient.Com.Interop.GetVARIANT(input[ii].Value);
output[ii].bQualitySpecified = (input[ii].QualitySpecified) ? 1 : 0;
output[ii].wQuality = (input[ii].QualitySpecified) ? input[ii].Quality.GetCode() : (short)0;
output[ii].bTimeStampSpecified = (input[ii].TimestampSpecified) ? 1 : 0;
output[ii].ftTimeStamp = Convert(Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(timestamp));
}
}
return output;
}
/// <summary>
/// Converts an array of item objects to an array of GetOPCITEMDEF objects.
/// </summary>
internal static OpcRcw.Da.OPCITEMDEF[] GetOPCITEMDEFs(TsCDaItem[] input)
{
OpcRcw.Da.OPCITEMDEF[] output = null;
if (input != null)
{
output = new OpcRcw.Da.OPCITEMDEF[input.Length];
for (var ii = 0; ii < input.Length; ii++)
{
output[ii] = new OpcRcw.Da.OPCITEMDEF();
output[ii].szItemID = input[ii].ItemName;
output[ii].szAccessPath = (input[ii].ItemPath == null) ? string.Empty : input[ii].ItemPath;
output[ii].bActive = (input[ii].ActiveSpecified) ? ((input[ii].Active) ? 1 : 0) : 1;
output[ii].vtRequestedDataType = (short)Technosoftware.DaAeHdaClient.Com.Interop.GetType(input[ii].ReqType);
output[ii].hClient = 0;
output[ii].dwBlobSize = 0;
output[ii].pBlob = IntPtr.Zero;
}
}
return output;
}
/// <summary>
/// Unmarshals and deallocates a OPCITEMSTATE structures.
/// </summary>
internal static TsCDaItemValue[] GetItemValues(ref IntPtr pInput, int count, bool deallocate)
{
TsCDaItemValue[] output = null;
if (pInput != IntPtr.Zero && count > 0)
{
output = new TsCDaItemValue[count];
var pos = pInput;
for (var ii = 0; ii < count; ii++)
{
var result = (OpcRcw.Da.OPCITEMSTATE)Marshal.PtrToStructure(pos, typeof(OpcRcw.Da.OPCITEMSTATE));
output[ii] = new TsCDaItemValue();
output[ii].ClientHandle = result.hClient;
output[ii].Value = result.vDataValue;
output[ii].Quality = new TsCDaQuality(result.wQuality);
output[ii].QualitySpecified = true;
output[ii].Timestamp = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(result.ftTimeStamp));
output[ii].TimestampSpecified = output[ii].Timestamp != DateTime.MinValue;
if (deallocate)
{
Marshal.DestroyStructure(pos, typeof(OpcRcw.Da.OPCITEMSTATE));
}
pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Da.OPCITEMSTATE)));
}
if (deallocate)
{
Marshal.FreeCoTaskMem(pInput);
pInput = IntPtr.Zero;
}
}
return output;
}
/// <summary>
/// Unmarshals and deallocates a OPCITEMRESULT structures.
/// </summary>
internal static int[] GetItemResults(ref IntPtr pInput, int count, bool deallocate)
{
int[] output = null;
if (pInput != IntPtr.Zero && count > 0)
{
output = new int[count];
var pos = pInput;
for (var ii = 0; ii < count; ii++)
{
var result = (OpcRcw.Da.OPCITEMRESULT)Marshal.PtrToStructure(pos, typeof(OpcRcw.Da.OPCITEMRESULT));
output[ii] = result.hServer;
if (deallocate)
{
Marshal.FreeCoTaskMem(result.pBlob);
result.pBlob = IntPtr.Zero;
Marshal.DestroyStructure(pos, typeof(OpcRcw.Da.OPCITEMRESULT));
}
pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Da.OPCITEMRESULT)));
}
if (deallocate)
{
Marshal.FreeCoTaskMem(pInput);
pInput = IntPtr.Zero;
}
}
return output;
}
/// <summary>
/// Allocates and marshals an array of OPCBROWSEELEMENT structures.
/// </summary>
internal static IntPtr GetItemStates(TsCDaItemValueResult[] input)
{
var output = IntPtr.Zero;
if (input != null && input.Length > 0)
{
output = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(OpcRcw.Da.OPCITEMSTATE)) * input.Length);
var pos = output;
for (var ii = 0; ii < input.Length; ii++)
{
var item = new OpcRcw.Da.OPCITEMSTATE();
item.hClient = System.Convert.ToInt32(input[ii].ClientHandle);
item.vDataValue = input[ii].Value;
item.wQuality = (input[ii].QualitySpecified) ? input[ii].Quality.GetCode() : (short)0;
item.ftTimeStamp = Convert(Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(input[ii].Timestamp));
item.wReserved = 0;
Marshal.StructureToPtr(item, pos, false);
pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Da.OPCITEMSTATE)));
}
}
return output;
}
}
}

View File

@@ -0,0 +1,128 @@
#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
#endregion
namespace Technosoftware.DaAeHdaClient.Com
{
namespace Da
{
/// <summary>
/// Defines all well known COM DA HRESULT codes.
/// </summary>
internal struct Result
{
/// <remarks/>
public const int S_OK = +0x00000000; // 0x00000000
/// <remarks/>
public const int S_FALSE = +0x00000001; // 0x00000001
/// <remarks/>
public const int E_NOTIMPL = -0x7FFFBFFF; // 0x80004001
/// <remarks/>
public const int E_OUTOFMEMORY = -0x7FF8FFF2; // 0x8007000E
/// <remarks/>
public const int E_INVALIDARG = -0x7FF8FFA9; // 0x80070057
/// <remarks/>
public const int E_NOINTERFACE = -0x7FFFBFFE; // 0x80004002
/// <remarks/>
public const int E_POINTER = -0x7FFFBFFD; // 0x80004003
/// <remarks/>
public const int E_FAIL = -0x7FFFBFFB; // 0x80004005
/// <remarks/>
public const int CONNECT_E_NOCONNECTION = -0x7FFBFE00; // 0x80040200
/// <remarks/>
public const int CONNECT_E_ADVISELIMIT = -0x7FFBFDFF; // 0x80040201
/// <remarks/>
public const int DISP_E_TYPEMISMATCH = -0x7FFDFFFB; // 0x80020005
/// <remarks/>
public const int DISP_E_OVERFLOW = -0x7FFDFFF6; // 0x8002000A
/// <remarks/>
public const int E_INVALIDHANDLE = -0x3FFBFFFF; // 0xC0040001
/// <remarks/>
public const int E_BADTYPE = -0x3FFBFFFC; // 0xC0040004
/// <remarks/>
public const int E_PUBLIC = -0x3FFBFFFB; // 0xC0040005
/// <remarks/>
public const int E_BADRIGHTS = -0x3FFBFFFA; // 0xC0040006
/// <remarks/>
public const int E_UNKNOWNITEMID = -0x3FFBFFF9; // 0xC0040007
/// <remarks/>
public const int E_INVALIDITEMID = -0x3FFBFFF8; // 0xC0040008
/// <remarks/>
public const int E_INVALIDFILTER = -0x3FFBFFF7; // 0xC0040009
/// <remarks/>
public const int E_UNKNOWNPATH = -0x3FFBFFF6; // 0xC004000A
/// <remarks/>
public const int E_RANGE = -0x3FFBFFF5; // 0xC004000B
/// <remarks/>
public const int E_DUPLICATENAME = -0x3FFBFFF4; // 0xC004000C
/// <remarks/>
public const int S_UNSUPPORTEDRATE = +0x0004000D; // 0x0004000D
/// <remarks/>
public const int S_CLAMP = +0x0004000E; // 0x0004000E
/// <remarks/>
public const int S_INUSE = +0x0004000F; // 0x0004000F
/// <remarks/>
public const int E_INVALIDCONFIGFILE = -0x3FFBFFF0; // 0xC0040010
/// <remarks/>
public const int E_NOTFOUND = -0x3FFBFFEF; // 0xC0040011
/// <remarks/>
public const int E_INVALID_PID = -0x3FFBFDFD; // 0xC0040203
/// <remarks/>
public const int E_DEADBANDNOTSET = -0x3FFBFC00; // 0xC0040400
/// <remarks/>
public const int E_DEADBANDNOTSUPPORTED = -0x3FFBFBFF; // 0xC0040401
/// <remarks/>
public const int E_NOBUFFERING = -0x3FFBFBFE; // 0xC0040402
/// <remarks/>
public const int E_INVALIDCONTINUATIONPOINT = -0x3FFBFBFD; // 0xC0040403
/// <remarks/>
public const int S_DATAQUEUEOVERFLOW = +0x00040404; // 0x00040404
/// <remarks/>
public const int E_RATENOTSET = -0x3FFBFBFB; // 0xC0040405
/// <remarks/>
public const int E_NOTSUPPORTED = -0x3FFBFBFA; // 0xC0040406
}
}
namespace Cpx
{
/// <summary>
/// Defines all well known Complex Data HRESULT codes.
/// </summary>
internal struct Result
{
/// <remarks/>
public const int E_TYPE_CHANGED = -0x3FFBFBF9; // 0xC0040407
/// <remarks/>
public const int E_FILTER_DUPLICATE = -0x3FFBFBF8; // 0xC0040408
/// <remarks/>
public const int E_FILTER_INVALID = -0x3FFBFBF7; // 0xC0040409
/// <remarks/>
public const int E_FILTER_ERROR = -0x3FFBFBF6; // 0xC004040A
/// <remarks/>
public const int S_FILTER_NO_DATA = +0x0004040B; // 0xC004040B
}
}
}

View File

@@ -0,0 +1,984 @@
#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.Threading;
using System.Collections;
using System.Globalization;
using System.Runtime.InteropServices;
using Technosoftware.DaAeHdaClient.Da;
using Technosoftware.DaAeHdaClient.Utilities;
using Technosoftware.OpcRcw.Da;
using Technosoftware.DaAeHdaClient.Com.Utilities;
#endregion
namespace Technosoftware.DaAeHdaClient.Com.Da
{
/// <summary>
/// A .NET wrapper for a COM server that implements the DA server interfaces.
/// </summary>
internal class Server : Com.Server, ITsDaServer
{
#region Fields
/// <summary>
/// The default result filters for the server.
/// </summary>
private int filters_ = (int)TsCDaResultFilter.All | (int)TsCDaResultFilter.ClientHandle;
/// <summary>
/// A table of active subscriptions for the server.
/// </summary>
private readonly Hashtable subscriptions_ = new Hashtable();
#endregion
#region Constructors
/// <summary>
/// Initializes the object.
/// </summary>
internal Server() { }
/// <summary>
/// Initializes the object with the specified COM server.
/// </summary>
internal Server(OpcUrl url, object server)
{
if (url == null) throw new ArgumentNullException(nameof(url));
url_ = (OpcUrl)url.Clone();
server_ = server;
}
#endregion
#region IDisposable Members
/// <summary>
/// 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.
/// </summary>
/// <param name="disposing">If true managed and unmanaged resources can be disposed. If false only unmanaged resources.</param>
protected override void Dispose(bool disposing)
{
if (!disposed_)
{
lock (this)
{
if (disposing)
{
// Release managed resources.
if (server_ != null)
{
// release all groups.
foreach (Subscription subscription in subscriptions_.Values)
{
var methodName = "IOPCServer.RemoveGroup";
// remove subscription from server.
try
{
var state = subscription.GetState();
if (state != null)
{
var server = BeginComCall<IOPCServer>(methodName, true);
server?.RemoveGroup((int)state.ServerHandle, 0);
}
}
catch
{
// Ignore error during Dispose
}
finally
{
EndComCall(methodName);
}
// dispose of the subscription object (disconnects all subscription connections).
subscription.Dispose();
}
// clear subscription table.
subscriptions_.Clear();
}
}
// Release unmanaged resources.
// Set large fields to null.
if (server_ != null)
{
// release the COM server.
Technosoftware.DaAeHdaClient.Com.Interop.ReleaseServer(server_);
server_ = null;
}
}
// Call Dispose on your base class.
disposed_ = true;
}
base.Dispose(disposing);
}
private bool disposed_;
#endregion
#region Technosoftware.DaAeHdaClient.Com.Server Overrides
/// <summary>
/// Returns the localized text for the specified result code.
/// </summary>
/// <param name="locale">The locale name in the format "[languagecode]-[country/regioncode]".</param>
/// <param name="resultId">The result code identifier.</param>
/// <returns>A message localized for the best match for the requested locale.</returns>
public override string GetErrorText(string locale, OpcResult resultId)
{
lock (this)
{
if (server_ == null) throw new NotConnectedException();
var methodName = "IOPCServer.GetErrorString";
// invoke COM method.
try
{
var server = BeginComCall<IOPCServer>(methodName, true);
(server).GetErrorString(
resultId.Code,
Technosoftware.DaAeHdaClient.Com.Interop.GetLocale(locale),
out var errorText);
if (DCOMCallWatchdog.IsCancelled)
{
throw new Exception($"{methodName} call was cancelled due to response timeout");
}
return errorText;
}
catch (Exception e)
{
ComCallError(methodName, e);
throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCServer.GetErrorString", e);
}
finally
{
EndComCall(methodName);
}
}
}
#endregion
#region Technosoftware.DaAeHdaClient.IOpcServer Members
/// <summary>
/// Returns the filters applied by the server to any item results returned to the client.
/// </summary>
/// <returns>A bit mask indicating which fields should be returned in any item results.</returns>
public int GetResultFilters()
{
lock (this)
{
if (server_ == null) throw new NotConnectedException();
return filters_;
}
}
/// <summary>
/// Sets the filters applied by the server to any item results returned to the client.
/// </summary>
/// <param name="filters">A bit mask indicating which fields should be returned in any item results.</param>
public void SetResultFilters(int filters)
{
lock (this)
{
if (server_ == null) throw new NotConnectedException();
filters_ = filters;
}
}
/// <summary>
/// Returns the current server status.
/// </summary>
/// <returns>The current server status.</returns>
public OpcServerStatus GetServerStatus()
{
lock (this)
{
if (server_ == null) throw new NotConnectedException();
var methodName = "IOPCServer.GetStatus";
// initialize arguments.
IntPtr pStatus;
// invoke COM method.
try
{
var server = BeginComCall<IOPCServer>(methodName, true);
(server).GetStatus(out pStatus);
if (DCOMCallWatchdog.IsCancelled)
{
throw new Exception($"{methodName} call was cancelled due to response timeout");
}
}
catch (Exception e)
{
ComCallError(methodName, e);
throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException(methodName, e);
}
finally
{
EndComCall(methodName);
}
// return status.
return Interop.GetServerStatus(ref pStatus, true);
}
}
/// <summary>
/// Reads the current values for a set of items.
/// </summary>
/// <param name="items">The set of items to read.</param>
/// <returns>The results of the read operation for each item.</returns>
public virtual TsCDaItemValueResult[] Read(TsCDaItem[] items)
{
if (items == null) throw new ArgumentNullException(nameof(items));
lock (this)
{
var methodName = "IOPCItemIO.Read";
if (server_ == null) throw new NotConnectedException();
var count = items.Length;
if (count == 0) throw new ArgumentOutOfRangeException(nameof(items.Length), @"0");
// initialize arguments.
var itemIDs = new string[count];
var maxAges = new int[count];
for (var ii = 0; ii < count; ii++)
{
itemIDs[ii] = items[ii].ItemName;
maxAges[ii] = (items[ii].MaxAgeSpecified) ? items[ii].MaxAge : 0;
}
var pValues = IntPtr.Zero;
var pQualities = IntPtr.Zero;
var pTimestamps = IntPtr.Zero;
var pErrors = IntPtr.Zero;
// invoke COM method.
try
{
var server = BeginComCall<IOPCItemIO>(methodName, true);
server.Read(
count,
itemIDs,
maxAges,
out pValues,
out pQualities,
out pTimestamps,
out pErrors);
if (DCOMCallWatchdog.IsCancelled)
{
throw new Exception($"{methodName} call was cancelled due to response timeout");
}
}
catch (Exception e)
{
ComCallError(methodName, e);
throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException(methodName, e);
}
finally
{
EndComCall(methodName);
}
// unmarshal results.
var values = Technosoftware.DaAeHdaClient.Com.Interop.GetVARIANTs(ref pValues, count, true);
var qualities = Technosoftware.DaAeHdaClient.Com.Interop.GetInt16s(ref pQualities, count, true);
var timestamps = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIMEs(ref pTimestamps, count, true);
var errors = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref pErrors, count, true);
// pre-fetch the current locale to use for data conversions.
var locale = GetLocale();
// construct result array.
var results = new TsCDaItemValueResult[count];
for (var ii = 0; ii < results.Length; ii++)
{
results[ii] = new TsCDaItemValueResult(items[ii]);
results[ii].ServerHandle = null;
results[ii].Value = values[ii];
results[ii].Quality = new TsCDaQuality(qualities[ii]);
results[ii].QualitySpecified = true;
results[ii].Timestamp = timestamps[ii];
results[ii].TimestampSpecified = timestamps[ii] != DateTime.MinValue;
results[ii].Result = Utilities.Interop.GetResultId(errors[ii]);
results[ii].DiagnosticInfo = null;
// convert COM code to unified DA code.
if (errors[ii] == Result.E_BADRIGHTS) { results[ii].Result = new OpcResult(OpcResult.Da.E_WRITEONLY, Result.E_BADRIGHTS); }
// convert the data type since the server does not support the feature.
if (results[ii].Value != null && items[ii].ReqType != null)
{
try
{
results[ii].Value = ChangeType(values[ii], items[ii].ReqType, locale);
}
catch (Exception e)
{
results[ii].Value = null;
results[ii].Quality = TsCDaQuality.Bad;
results[ii].QualitySpecified = true;
results[ii].Timestamp = DateTime.MinValue;
results[ii].TimestampSpecified = false;
if (e.GetType() == typeof(OverflowException))
{
results[ii].Result = Utilities.Interop.GetResultId(Result.E_RANGE);
}
else
{
results[ii].Result = Utilities.Interop.GetResultId(Result.E_BADTYPE);
}
}
}
// apply request options.
if ((filters_ & (int)TsCDaResultFilter.ItemName) == 0) results[ii].ItemName = null;
if ((filters_ & (int)TsCDaResultFilter.ItemPath) == 0) results[ii].ItemPath = null;
if ((filters_ & (int)TsCDaResultFilter.ClientHandle) == 0) results[ii].ClientHandle = null;
if ((filters_ & (int)TsCDaResultFilter.ItemTime) == 0)
{
results[ii].Timestamp = DateTime.MinValue;
results[ii].TimestampSpecified = false;
}
}
// return results.
return results;
}
}
/// <summary>
/// Writes the value, quality and timestamp for a set of items.
/// </summary>
/// <param name="items">The set of item values to write.</param>
/// <returns>The results of the write operation for each item.</returns>
public virtual OpcItemResult[] Write(TsCDaItemValue[] items)
{
if (items == null) throw new ArgumentNullException(nameof(items));
lock (this)
{
if (server_ == null) throw new NotConnectedException();
var methodName = "IOPCItemIO.WriteVQT";
var count = items.Length;
if (count == 0) throw new ArgumentOutOfRangeException("items.Length", "0");
// initialize arguments.
var itemIDs = new string[count];
for (var ii = 0; ii < count; ii++)
{
itemIDs[ii] = items[ii].ItemName;
}
var values = Interop.GetOPCITEMVQTs(items);
var pErrors = IntPtr.Zero;
// invoke COM method.
try
{
var server = BeginComCall<IOPCItemIO>(methodName, true);
server.WriteVQT(
count,
itemIDs,
values,
out pErrors);
if (DCOMCallWatchdog.IsCancelled)
{
throw new Exception($"{methodName} call was cancelled due to response timeout");
}
}
catch (Exception e)
{
ComCallError(methodName, e);
throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException(methodName, e);
}
finally
{
EndComCall(methodName);
}
// unmarshal results.
var errors = Utilities.Interop.GetInt32s(ref pErrors, count, true);
// construct result array.
var results = new OpcItemResult[count];
for (var ii = 0; ii < count; ii++)
{
results[ii] = new OpcItemResult(items[ii]);
results[ii].ServerHandle = null;
results[ii].Result = Utilities.Interop.GetResultId(errors[ii]);
results[ii].DiagnosticInfo = null;
// convert COM code to unified DA code.
if (errors[ii] == Result.E_BADRIGHTS) { results[ii].Result = new OpcResult(OpcResult.Da.E_READONLY, Result.E_BADRIGHTS); }
// apply request options.
if ((filters_ & (int)TsCDaResultFilter.ItemName) == 0) results[ii].ItemName = null;
if ((filters_ & (int)TsCDaResultFilter.ItemPath) == 0) results[ii].ItemPath = null;
if ((filters_ & (int)TsCDaResultFilter.ClientHandle) == 0) results[ii].ClientHandle = null;
}
// return results.
return results;
}
}
/// <summary>
/// Creates a new subscription.
/// </summary>
/// <param name="state">The initial state of the subscription.</param>
/// <returns>The new subscription object.</returns>
public ITsCDaSubscription CreateSubscription(TsCDaSubscriptionState state)
{
if (state == null) throw new ArgumentNullException(nameof(state));
lock (this)
{
if (server_ == null) throw new NotConnectedException();
var methodName = "IOPCServer.AddGroup";
// copy the subscription state.
var result = (TsCDaSubscriptionState)state.Clone();
// initialize arguments.
var iid = typeof(IOPCItemMgt).GUID;
object group = null;
var serverHandle = 0;
var revisedUpdateRate = 0;
var hDeadband = GCHandle.Alloc(result.Deadband, GCHandleType.Pinned);
// invoke COM method.
try
{
var server = BeginComCall<IOPCServer>(methodName, true);
server.AddGroup(
(result.Name != null) ? result.Name : "",
(result.Active) ? 1 : 0,
result.UpdateRate,
0,
IntPtr.Zero,
hDeadband.AddrOfPinnedObject(),
Technosoftware.DaAeHdaClient.Com.Interop.GetLocale(result.Locale),
out serverHandle,
out revisedUpdateRate,
ref iid,
out group);
if (DCOMCallWatchdog.IsCancelled)
{
throw new Exception($"{methodName} call was cancelled due to response timeout");
}
}
catch (Exception e)
{
ComCallError(methodName, e);
throw Utilities.Interop.CreateException(methodName, e);
}
finally
{
if (hDeadband.IsAllocated)
{
hDeadband.Free();
}
EndComCall(methodName);
}
if (group == null) throw new OpcResultException(OpcResult.E_FAIL, "The subscription was not created.");
methodName = "IOPCGroupStateMgt2.SetKeepAlive";
// set the keep alive rate if requested.
try
{
var keepAlive = 0;
var comObject = BeginComCall<IOPCGroupStateMgt2>(group, methodName, true);
comObject.SetKeepAlive(result.KeepAlive, out keepAlive);
if (DCOMCallWatchdog.IsCancelled)
{
throw new Exception($"{methodName} call was cancelled due to response timeout");
}
result.KeepAlive = keepAlive;
}
catch (Exception e1)
{
result.KeepAlive = 0;
ComCallError(methodName, e1);
}
finally
{
EndComCall(methodName);
}
// save server handle.
result.ServerHandle = serverHandle;
// set the revised update rate.
if (revisedUpdateRate > result.UpdateRate)
{
result.UpdateRate = revisedUpdateRate;
}
// create the subscription object.
var subscription = CreateSubscription(group, result, filters_);
// index by server handle.
subscriptions_[serverHandle] = subscription;
// return subscription.
return subscription;
}
}
/// <summary>
/// Cancels a subscription and releases all resources allocated for it.
/// </summary>
/// <param name="subscription">The subscription to cancel.</param>
public void CancelSubscription(ITsCDaSubscription subscription)
{
if (subscription == null) throw new ArgumentNullException(nameof(subscription));
lock (this)
{
if (server_ == null) throw new NotConnectedException();
var methodName = "IOPCServer.RemoveGroup";
// validate argument.
if (!typeof(Subscription).IsInstanceOfType(subscription))
{
throw new ArgumentException("Incorrect object type.", nameof(subscription));
}
// get the subscription state.
var state = subscription.GetState();
if (!subscriptions_.ContainsKey(state.ServerHandle))
{
throw new ArgumentException("Handle not found.", nameof(subscription));
}
subscriptions_.Remove(state.ServerHandle);
// release all subscription resources.
subscription.Dispose();
// invoke COM method.
try
{
var server = BeginComCall<IOPCServer>(methodName, true);
server.RemoveGroup((int)state.ServerHandle, 0);
if (DCOMCallWatchdog.IsCancelled)
{
throw new Exception($"{methodName} call was cancelled due to response timeout");
}
}
catch (Exception e)
{
ComCallError(methodName, e);
throw Utilities.Interop.CreateException(methodName, e);
}
finally
{
EndComCall(methodName);
}
}
}
/// <summary>
/// Fetches the children of a branch that meet the filter criteria.
/// </summary>
/// <param name="itemId">The identifier of branch which is the target of the search.</param>
/// <param name="filters">The filters to use to limit the set of child elements returned.</param>
/// <param name="position">An object used to continue a browse that could not be completed.</param>
/// <returns>The set of elements found.</returns>
public virtual TsCDaBrowseElement[] Browse(
OpcItem itemId,
TsCDaBrowseFilters filters,
out TsCDaBrowsePosition position)
{
if (filters == null) throw new ArgumentNullException(nameof(filters));
lock (this)
{
if (server_ == null) throw new NotConnectedException();
var methodName = "IOPCBrowse.Browse";
position = null;
// initialize arguments.
var count = 0;
var moreElements = 0;
var pContinuationPoint = IntPtr.Zero;
var pElements = IntPtr.Zero;
// invoke COM method.
try
{
var server = BeginComCall<IOPCBrowse>(methodName, true);
server.Browse(
(itemId != null && itemId.ItemName != null) ? itemId.ItemName : "",
ref pContinuationPoint,
filters.MaxElementsReturned,
Interop.GetBrowseFilter(filters.BrowseFilter),
(filters.ElementNameFilter != null) ? filters.ElementNameFilter : "",
(filters.VendorFilter != null) ? filters.VendorFilter : "",
(filters.ReturnAllProperties) ? 1 : 0,
(filters.ReturnPropertyValues) ? 1 : 0,
(filters.PropertyIDs != null) ? filters.PropertyIDs.Length : 0,
Interop.GetPropertyIDs(filters.PropertyIDs),
out moreElements,
out count,
out pElements);
if (DCOMCallWatchdog.IsCancelled)
{
throw new Exception($"{methodName} call was cancelled due to response timeout");
}
}
catch (Exception e)
{
ComCallError(methodName, e);
throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException(methodName, e);
}
finally
{
EndComCall(methodName);
}
// unmarshal results.
var elements = Interop.GetBrowseElements(ref pElements, count, true);
var continuationPoint = Marshal.PtrToStringUni(pContinuationPoint);
Marshal.FreeCoTaskMem(pContinuationPoint);
// check if more results exist.
if (moreElements != 0 || (continuationPoint != null && continuationPoint != ""))
{
// allocate new browse position object.
position = new BrowsePosition(itemId, filters, continuationPoint);
}
// process results.
ProcessResults(elements, filters.PropertyIDs);
return elements;
}
}
/// <summary>
/// Continues a browse operation with previously specified search criteria.
/// </summary>
/// <param name="position">An object containing the browse operation state information.</param>
/// <returns>The set of elements found.</returns>
public virtual TsCDaBrowseElement[] BrowseNext(ref TsCDaBrowsePosition position)
{
lock (this)
{
if (server_ == null) throw new NotConnectedException();
var methodName = "IOPCBrowse.Browse";
// check for valid position object.
if (position == null || position.GetType() != typeof(BrowsePosition))
{
throw new BrowseCannotContinueException();
}
var pos = (BrowsePosition)position;
// check for valid continuation point.
if (pos == null || pos.ContinuationPoint == null || pos.ContinuationPoint == "")
{
throw new BrowseCannotContinueException();
}
// initialize arguments.
var count = 0;
var moreElements = 0;
var itemID = ((BrowsePosition)position).ItemID;
var filters = ((BrowsePosition)position).Filters;
var pContinuationPoint = Marshal.StringToCoTaskMemUni(pos.ContinuationPoint);
var pElements = IntPtr.Zero;
// invoke COM method.
try
{
var server = BeginComCall<IOPCBrowse>(methodName, true);
server.Browse(
(itemID != null && itemID.ItemName != null) ? itemID.ItemName : "",
ref pContinuationPoint,
filters.MaxElementsReturned,
Interop.GetBrowseFilter(filters.BrowseFilter),
(filters.ElementNameFilter != null) ? filters.ElementNameFilter : "",
(filters.VendorFilter != null) ? filters.VendorFilter : "",
(filters.ReturnAllProperties) ? 1 : 0,
(filters.ReturnPropertyValues) ? 1 : 0,
(filters.PropertyIDs != null) ? filters.PropertyIDs.Length : 0,
Interop.GetPropertyIDs(filters.PropertyIDs),
out moreElements,
out count,
out pElements);
if (DCOMCallWatchdog.IsCancelled)
{
throw new Exception($"{methodName} call was cancelled due to response timeout");
}
}
catch (Exception e)
{
ComCallError(methodName, e);
throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException(methodName, e);
}
finally
{
EndComCall(methodName);
}
// unmarshal results.
var elements = Interop.GetBrowseElements(ref pElements, count, true);
pos.ContinuationPoint = Marshal.PtrToStringUni(pContinuationPoint);
Marshal.FreeCoTaskMem(pContinuationPoint);
// check if more no results exist.
if (moreElements == 0 && (pos.ContinuationPoint == null || pos.ContinuationPoint == ""))
{
position = null;
}
// process results.
ProcessResults(elements, filters.PropertyIDs);
return elements;
}
}
/// <summary>
/// Returns the item properties for a set of items.
/// </summary>
/// <param name="itemIds">A list of item identifiers.</param>
/// <param name="propertyIDs">A list of properties to fetch for each item.</param>
/// <param name="returnValues">Whether the property values should be returned with the properties.</param>
/// <returns>A list of properties for each item.</returns>
public virtual TsCDaItemPropertyCollection[] GetProperties(
OpcItem[] itemIds,
TsDaPropertyID[] propertyIDs,
bool returnValues)
{
if (itemIds == null) throw new ArgumentNullException(nameof(itemIds));
lock (this)
{
if (server_ == null) throw new NotConnectedException();
var methodName = "IOPCBrowse.GetProperties";
// initialize arguments.
var pItemIDs = new string[itemIds.Length];
for (var ii = 0; ii < itemIds.Length; ii++)
{
pItemIDs[ii] = itemIds[ii].ItemName;
}
var pPropertyLists = IntPtr.Zero;
// invoke COM method.
try
{
var server = BeginComCall<IOPCBrowse>(methodName, true);
server.GetProperties(
itemIds.Length,
pItemIDs,
(returnValues) ? 1 : 0,
(propertyIDs != null) ? propertyIDs.Length : 0,
Interop.GetPropertyIDs(propertyIDs),
out pPropertyLists);
if (DCOMCallWatchdog.IsCancelled)
{
throw new Exception($"{methodName} call was cancelled due to response timeout");
}
}
catch (Exception e)
{
ComCallError(methodName, e);
throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException(methodName, e);
}
finally
{
EndComCall(methodName);
}
// unmarshal results.
var resultLists = Interop.GetItemPropertyCollections(ref pPropertyLists, itemIds.Length, true);
// replace integer codes with qnames passed in.
if (propertyIDs != null && propertyIDs.Length > 0)
{
foreach (var resultList in resultLists)
{
for (var ii = 0; ii < resultList.Count; ii++)
{
resultList[ii].ID = propertyIDs[ii];
}
}
}
// return the results.
return resultLists;
}
}
#endregion
#region Private Methods
/// <summary>
/// Converts a value to the specified type using the specified locale.
/// </summary>
protected object ChangeType(object source, Type type, string locale)
{
var culture = Thread.CurrentThread.CurrentCulture;
// override the current thread culture to ensure conversions happen correctly.
try
{
Thread.CurrentThread.CurrentCulture = new CultureInfo(locale);
}
catch
{
Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
}
try
{
var result = OpcConvert.ChangeType(source, type);
// check for overflow converting to float.
if (typeof(float) == type)
{
if (float.IsInfinity(Convert.ToSingle(result)))
{
throw new OverflowException();
}
}
return result;
}
// restore the current thread culture after conversion.
finally
{
Thread.CurrentThread.CurrentCulture = culture;
}
}
/// <summary>
/// Creates a new instance of a subscription.
/// </summary>
protected virtual Subscription CreateSubscription(
object group,
TsCDaSubscriptionState state,
int filters)
{
return new Subscription(group, state, filters);
}
/// <summary>
/// Updates the properties to convert COM values to OPC .NET API results.
/// </summary>
private void ProcessResults(TsCDaBrowseElement[] elements, TsDaPropertyID[] propertyIds)
{
// check for null.
if (elements == null)
{
return;
}
// process each element.
foreach (var element in elements)
{
// check if no properties.
if (element.Properties == null)
{
continue;
}
// process each property.
foreach (var property in element.Properties)
{
// replace the property ids which on contain the codes with the proper qualified names passed in.
if (propertyIds != null)
{
foreach (var propertyId in propertyIds)
{
if (property.ID.Code == propertyId.Code)
{
property.ID = propertyId;
break;
}
}
}
}
}
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,586 @@
#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 Technosoftware.DaAeHdaClient.Da;
using Technosoftware.DaAeHdaClient.Com.Da;
using Technosoftware.DaAeHdaClient.Utilities;
using Technosoftware.OpcRcw.Da;
using Technosoftware.DaAeHdaClient.Com.Utilities;
#endregion
namespace Technosoftware.DaAeHdaClient.Com.Da20
{
/// <summary>
/// An in-process wrapper for a remote OPC Data Access 2.0X subscription.
/// </summary>
internal class Subscription : Technosoftware.DaAeHdaClient.Com.Da.Subscription
{
#region Constructors
/// <summary>
/// Initializes a new instance of a subscription.
/// </summary>
internal Subscription(object subscription, TsCDaSubscriptionState state, int filters) :
base(subscription, state, filters)
{
}
#endregion
#region ISubscription Members
/// <summary>
/// Returns the current state of the subscription.
/// </summary>
/// <returns>The current state of the subscription.</returns>
public override TsCDaSubscriptionState GetState()
{
if (subscription_ == null) throw new NotConnectedException();
lock (lock_)
{
var methodName = "IOPCGroupStateMgt.GetState";
var state = new TsCDaSubscriptionState { ClientHandle = _handle };
string name = null;
try
{
var active = 0;
var updateRate = 0;
float deadband = 0;
var timebias = 0;
var localeID = 0;
var clientHandle = 0;
var serverHandle = 0;
var subscription = BeginComCall<IOPCGroupStateMgt>(methodName, true);
subscription.GetState(
out updateRate,
out active,
out name,
out timebias,
out deadband,
out localeID,
out clientHandle,
out serverHandle);
if (DCOMCallWatchdog.IsCancelled)
{
throw new Exception($"{methodName} call was cancelled due to response timeout");
}
state.Name = name;
state.ServerHandle = serverHandle;
state.Active = active != 0;
state.UpdateRate = updateRate;
state.TimeBias = timebias;
state.Deadband = deadband;
state.Locale = Technosoftware.DaAeHdaClient.Com.Interop.GetLocale(localeID);
// cache the name separately.
name_ = state.Name;
}
catch (Exception e)
{
ComCallError(methodName, e);
throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException(methodName, e);
}
finally
{
EndComCall(methodName);
}
state.KeepAlive = 0;
return state;
}
}
/// <summary>
/// Tells the server to send an data change update for all subscription items containing the cached values.
/// </summary>
public override void Refresh()
{
if (subscription_ == null) throw new NotConnectedException();
lock (lock_)
{
var methodName = "IOPCAsyncIO2.Refresh2";
try
{
var cancelID = 0;
var subscription = BeginComCall<IOPCAsyncIO2>(methodName, true);
subscription.Refresh2(OPCDATASOURCE.OPC_DS_CACHE, ++_counter, out cancelID);
if (DCOMCallWatchdog.IsCancelled)
{
throw new Exception($"{methodName} call was cancelled due to response timeout");
}
}
catch (Exception e)
{
ComCallError(methodName, e);
throw Utilities.Interop.CreateException(methodName, e);
}
finally
{
EndComCall(methodName);
}
}
}
/// <summary>
/// Sets whether data change callbacks are enabled.
/// </summary>
public override void SetEnabled(bool enabled)
{
if (subscription_ == null) throw new NotConnectedException();
lock (lock_)
{
var methodName = "IOPCAsyncIO2.SetEnable";
try
{
var subscription = BeginComCall<IOPCAsyncIO2>(methodName, true);
subscription.SetEnable((enabled) ? 1 : 0);
if (DCOMCallWatchdog.IsCancelled)
{
throw new Exception($"{methodName} call was cancelled due to response timeout");
}
}
catch (Exception e)
{
ComCallError(methodName, e);
throw Utilities.Interop.CreateException(methodName, e);
}
finally
{
EndComCall(methodName);
}
}
}
/// <summary>
/// Gets whether data change callbacks are enabled.
/// </summary>
public override bool GetEnabled()
{
if (subscription_ == null) throw new NotConnectedException();
lock (lock_)
{
var methodName = "IOPCAsyncIO2.GetEnable";
try
{
var enabled = 0;
var subscription = BeginComCall<IOPCAsyncIO2>(methodName, true);
subscription.GetEnable(out enabled);
if (DCOMCallWatchdog.IsCancelled)
{
throw new Exception($"{methodName} call was cancelled due to response timeout");
}
return enabled != 0;
}
catch (Exception e)
{
ComCallError(methodName, e);
throw Utilities.Interop.CreateException(methodName, e);
}
finally
{
EndComCall(methodName);
}
}
}
#endregion
#region Private and Protected Members
/// <summary>
/// Reads a set of items using DA2.0 interfaces.
/// </summary>
protected override TsCDaItemValueResult[] Read(OpcItem[] itemIDs, TsCDaItem[] items)
{
if (subscription_ == null) throw new NotConnectedException();
// create result list.
var results = new TsCDaItemValueResult[itemIDs.Length];
// separate into cache reads and device reads.
var cacheReads = new ArrayList();
var deviceReads = new ArrayList();
for (var ii = 0; ii < itemIDs.Length; ii++)
{
results[ii] = new TsCDaItemValueResult(itemIDs[ii]);
if (items[ii].MaxAgeSpecified && (items[ii].MaxAge < 0 || items[ii].MaxAge == int.MaxValue))
{
cacheReads.Add(results[ii]);
}
else
{
deviceReads.Add(results[ii]);
}
}
// read items from cache.
if (cacheReads.Count > 0)
{
Read((TsCDaItemValueResult[])cacheReads.ToArray(typeof(TsCDaItemValueResult)), true);
}
// read items from device.
if (deviceReads.Count > 0)
{
Read((TsCDaItemValueResult[])deviceReads.ToArray(typeof(TsCDaItemValueResult)), false);
}
// return results.
return results;
}
/// <summary>
/// Reads a set of values.
/// </summary>
private void Read(TsCDaItemValueResult[] items, bool cache)
{
if (items.Length == 0) return;
// marshal input parameters.
var serverHandles = new int[items.Length];
for (var ii = 0; ii < items.Length; ii++)
{
serverHandles[ii] = (int)items[ii].ServerHandle;
}
// initialize output parameters.
var pValues = IntPtr.Zero;
var pErrors = IntPtr.Zero;
var methodName = "IOPCSyncIO.Read";
try
{
var subscription = BeginComCall<IOPCSyncIO>(methodName, true);
subscription.Read(
(cache) ? OPCDATASOURCE.OPC_DS_CACHE : OPCDATASOURCE.OPC_DS_DEVICE,
items.Length,
serverHandles,
out pValues,
out pErrors);
if (DCOMCallWatchdog.IsCancelled)
{
throw new Exception($"{methodName} call was cancelled due to response timeout");
}
}
catch (Exception e)
{
ComCallError(methodName, e);
throw Utilities.Interop.CreateException(methodName, e);
}
finally
{
EndComCall(methodName);
}
// unmarshal output parameters.
var values = Technosoftware.DaAeHdaClient.Com.Da.Interop.GetItemValues(ref pValues, items.Length, true);
var errors = Utilities.Interop.GetInt32s(ref pErrors, items.Length, true);
// construct results list.
for (var ii = 0; ii < items.Length; ii++)
{
items[ii].Result = Utilities.Interop.GetResultId(errors[ii]);
items[ii].DiagnosticInfo = null;
// convert COM code to unified DA code.
if (errors[ii] == Result.E_BADRIGHTS) { items[ii].Result = new OpcResult(OpcResult.Da.E_WRITEONLY, Result.E_BADRIGHTS); }
if (items[ii].Result.Succeeded())
{
items[ii].Value = values[ii].Value;
items[ii].Quality = values[ii].Quality;
items[ii].QualitySpecified = values[ii].QualitySpecified;
items[ii].Timestamp = values[ii].Timestamp;
items[ii].TimestampSpecified = values[ii].TimestampSpecified;
}
}
}
/// <summary>
/// Writes a set of items using DA2.0 interfaces.
/// </summary>
protected override OpcItemResult[] Write(OpcItem[] itemIDs, TsCDaItemValue[] items)
{
if (subscription_ == null) throw new NotConnectedException();
// create result list.
var results = new OpcItemResult[itemIDs.Length];
// construct list of valid items to write.
var writeItems = new ArrayList(itemIDs.Length);
var writeValues = new ArrayList(itemIDs.Length);
for (var ii = 0; ii < items.Length; ii++)
{
results[ii] = new OpcItemResult(itemIDs[ii]);
if (items[ii].QualitySpecified || items[ii].TimestampSpecified)
{
results[ii].Result = OpcResult.Da.E_NO_WRITEQT;
results[ii].DiagnosticInfo = null;
continue;
}
writeItems.Add(results[ii]);
writeValues.Add(items[ii]);
}
// check if there is nothing to do.
if (writeItems.Count == 0)
{
return results;
}
// initialize input parameters.
var serverHandles = new int[writeItems.Count];
var values = new object[writeItems.Count];
for (var ii = 0; ii < serverHandles.Length; ii++)
{
serverHandles[ii] = (int)((OpcItemResult)writeItems[ii]).ServerHandle;
values[ii] = Utilities.Interop.GetVARIANT(((TsCDaItemValue)writeValues[ii]).Value);
}
var pErrors = IntPtr.Zero;
// write item values.
var methodName = "IOPCSyncIO.Write";
try
{
var subscription = BeginComCall<IOPCSyncIO>(methodName, true);
subscription.Write(
writeItems.Count,
serverHandles,
values,
out pErrors);
if (DCOMCallWatchdog.IsCancelled)
{
throw new Exception($"{methodName} call was cancelled due to response timeout");
}
}
catch (Exception e)
{
ComCallError(methodName, e);
throw Utilities.Interop.CreateException(methodName, e);
}
finally
{
EndComCall(methodName);
}
// unmarshal results.
var errors = Utilities.Interop.GetInt32s(ref pErrors, writeItems.Count, true);
for (var ii = 0; ii < writeItems.Count; ii++)
{
var result = (OpcItemResult)writeItems[ii];
result.Result = Utilities.Interop.GetResultId(errors[ii]);
result.DiagnosticInfo = null;
// convert COM code to unified DA code.
if (errors[ii] == Result.E_BADRIGHTS) { results[ii].Result = new OpcResult(OpcResult.Da.E_READONLY, Result.E_BADRIGHTS); }
}
// return results.
return results;
}
/// <summary>
/// Begins an asynchronous read of a set of items using DA2.0 interfaces.
/// </summary>
protected override OpcItemResult[] BeginRead(
OpcItem[] itemIDs,
TsCDaItem[] items,
int requestID,
out int cancelID)
{
var methodName = "IOPCAsyncIO2.Read";
try
{
// marshal input parameters.
var serverHandles = new int[itemIDs.Length];
for (var ii = 0; ii < itemIDs.Length; ii++)
{
serverHandles[ii] = (int)itemIDs[ii].ServerHandle;
}
// initialize output parameters.
var pErrors = IntPtr.Zero;
var subscription = BeginComCall<IOPCAsyncIO2>(methodName, true);
subscription.Read(
itemIDs.Length,
serverHandles,
requestID,
out cancelID,
out pErrors);
if (DCOMCallWatchdog.IsCancelled)
{
throw new Exception($"{methodName} call was cancelled due to response timeout");
}
// unmarshal output parameters.
var errors = Utilities.Interop.GetInt32s(ref pErrors, itemIDs.Length, true);
// create item results.
var results = new OpcItemResult[itemIDs.Length];
for (var ii = 0; ii < itemIDs.Length; ii++)
{
results[ii] = new OpcItemResult(itemIDs[ii]);
results[ii].Result = Utilities.Interop.GetResultId(errors[ii]);
results[ii].DiagnosticInfo = null;
// convert COM code to unified DA code.
if (errors[ii] == Result.E_BADRIGHTS) { results[ii].Result = new OpcResult(OpcResult.Da.E_WRITEONLY, Result.E_BADRIGHTS); }
}
// return results.
return results;
}
catch (Exception e)
{
ComCallError(methodName, e);
throw Utilities.Interop.CreateException(methodName, e);
}
finally
{
EndComCall(methodName);
}
}
/// <summary>
/// Begins an asynchronous write for a set of items using DA2.0 interfaces.
/// </summary>
protected override OpcItemResult[] BeginWrite(
OpcItem[] itemIDs,
TsCDaItemValue[] items,
int requestID,
out int cancelID)
{
cancelID = 0;
var validItems = new ArrayList();
var validValues = new ArrayList();
// construct initial result list.
var results = new OpcItemResult[itemIDs.Length];
for (var ii = 0; ii < itemIDs.Length; ii++)
{
results[ii] = new OpcItemResult(itemIDs[ii]);
results[ii].Result = OpcResult.S_OK;
results[ii].DiagnosticInfo = null;
if (items[ii].QualitySpecified || items[ii].TimestampSpecified)
{
results[ii].Result = OpcResult.Da.E_NO_WRITEQT;
results[ii].DiagnosticInfo = null;
continue;
}
validItems.Add(results[ii]);
validValues.Add(Utilities.Interop.GetVARIANT(items[ii].Value));
}
// check if any valid items exist.
if (validItems.Count == 0)
{
return results;
}
var methodName = "IOPCAsyncIO2.Write";
try
{
// initialize input parameters.
var serverHandles = new int[validItems.Count];
for (var ii = 0; ii < validItems.Count; ii++)
{
serverHandles[ii] = (int)((OpcItemResult)validItems[ii]).ServerHandle;
}
// write to sever.
var pErrors = IntPtr.Zero;
var subscription = BeginComCall<IOPCAsyncIO2>(methodName, true);
subscription.Write(
validItems.Count,
serverHandles,
(object[])validValues.ToArray(typeof(object)),
requestID,
out cancelID,
out pErrors);
if (DCOMCallWatchdog.IsCancelled)
{
throw new Exception($"{methodName} call was cancelled due to response timeout");
}
// unmarshal results.
var errors = Utilities.Interop.GetInt32s(ref pErrors, validItems.Count, true);
// create result list.
for (var ii = 0; ii < validItems.Count; ii++)
{
var result = (OpcItemResult)validItems[ii];
result.Result = Utilities.Interop.GetResultId(errors[ii]);
result.DiagnosticInfo = null;
// convert COM code to unified DA code.
if (errors[ii] == Result.E_BADRIGHTS) { results[ii].Result = new OpcResult(OpcResult.Da.E_READONLY, Result.E_BADRIGHTS); }
}
}
catch (Exception e)
{
ComCallError(methodName, e);
throw Utilities.Interop.CreateException(methodName, e);
}
finally
{
EndComCall(methodName);
}
// return results.
return results;
}
#endregion
}
}

View File

@@ -0,0 +1,128 @@
#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.Runtime.InteropServices;
using Technosoftware.OpcRcw.Comn;
#endregion
namespace Technosoftware.DaAeHdaClient.Com
{
/// <summary>
/// A wrapper for the COM IEnumString interface.
/// </summary>
internal class EnumString : IDisposable
{
/// <summary>
/// A reference to the remote COM object.
/// </summary>
private IEnumString m_enumerator = null;
/// <summary>
/// Initializes the object with an enumerator.
/// </summary>
public EnumString(object enumerator)
{
m_enumerator = (IEnumString)enumerator;
}
/// <summary>
/// Releases the remote COM object.
/// </summary>
public void Dispose()
{
Utilities.Interop.ReleaseServer(m_enumerator);
m_enumerator = null;
}
//=====================================================================
// IEnumString
/// <summary>
/// Fetches the next subscription of strings.
/// </summary>
public string[] Next(int count)
{
try
{
// create buffer.
var buffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(IntPtr))*count);
try
{
// fetch next subscription of strings.
var fetched = 0;
m_enumerator.RemoteNext(
count,
buffer,
out fetched);
// return empty array if at end of list.
if (fetched == 0)
{
return new string[0];
}
return Interop.GetUnicodeStrings(ref buffer, fetched, true);
}
finally
{
Marshal.FreeCoTaskMem(buffer);
}
}
// return null on any error.
catch (Exception)
{
return null;
}
}
/// <summary>
/// Skips a number of strings.
/// </summary>
public void Skip(int count)
{
m_enumerator.Skip(count);
}
/// <summary>
/// Sets pointer to the start of the list.
/// </summary>
public void Reset()
{
m_enumerator.Reset();
}
/// <summary>
/// Clones the enumerator.
/// </summary>
public EnumString Clone()
{
IEnumString enumerator;
m_enumerator.Clone(out enumerator);
return new EnumString(enumerator);
}
}
}

View File

@@ -0,0 +1,44 @@
#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
#endregion
namespace Technosoftware.DaAeHdaClient.Com
{
/// <summary>
/// The COM FILETIME structure.
/// </summary>
public struct FILETIME
{
/// <summary>
/// The least significant DWORD.
/// </summary>
public int dwLowDateTime;
/// <summary>
/// The most significant DWORD.
/// </summary>
public int dwHighDateTime;
}
}

View File

@@ -0,0 +1,290 @@
#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.Xml;
using System.Net;
using System.Text;
using System.Collections;
using System.Globalization;
using System.Runtime.Serialization;
using System.Runtime.InteropServices;
using System.Collections.Generic;
#endregion
namespace Technosoftware.DaAeHdaClient.Com
{
/// <summary>
/// The default class used to instantiate server objects.
/// </summary>
[Serializable]
public class Factory : OpcFactory
{
//======================================================================
// Construction
/// <summary>
/// Initializes an instance for use for in process objects.
/// </summary>
public Factory() : base(null)
{
// do nothing.
}
//======================================================================
// ISerializable
/// <summary>
/// Contructs a server by de-serializing its URL from the stream.
/// </summary>
protected Factory(SerializationInfo info, StreamingContext context) : base(info, context)
{
// do nothing.
}
//======================================================================
// IOpcFactory
/// <summary>
/// Creates a new instance of the server.
/// </summary>
public override IOpcServer CreateInstance(OpcUrl url, OpcConnectData connectData)
{
var comServer = Factory.Connect(url, connectData);
if (comServer == null)
{
return null;
}
Server server = null;
Type interfaceType = null;
try
{
if (string.IsNullOrEmpty(url.Scheme))
{
throw new NotSupportedException(string.Format("The URL scheme '{0}' is not supported.", url.Scheme));
}
// DA
else if (url.Scheme == OpcUrlScheme.DA)
{
// Verify that it is a DA server.
if (!typeof(OpcRcw.Da.IOPCServer).IsInstanceOfType(comServer))
{
interfaceType = typeof(OpcRcw.Da.IOPCServer);
throw new NotSupportedException();
}
// DA 3.00
if (!ForceDa20Usage && typeof(OpcRcw.Da.IOPCBrowse).IsInstanceOfType(comServer) && typeof(OpcRcw.Da.IOPCItemIO).IsInstanceOfType(comServer))
{
server = new Technosoftware.DaAeHdaClient.Com.Da.Server(url, comServer);
}
// DA 2.XX
else if (typeof(OpcRcw.Da.IOPCItemProperties).IsInstanceOfType(comServer))
{
server = new Technosoftware.DaAeHdaClient.Com.Da20.Server(url, comServer);
}
else
{
interfaceType = typeof(OpcRcw.Da.IOPCItemProperties);
throw new NotSupportedException();
}
}
// AE
else if (url.Scheme == OpcUrlScheme.AE)
{
// Verify that it is a AE server.
if (!typeof(OpcRcw.Ae.IOPCEventServer).IsInstanceOfType(comServer))
{
interfaceType = typeof(OpcRcw.Ae.IOPCEventServer);
throw new NotSupportedException();
}
server = new Technosoftware.DaAeHdaClient.Com.Ae.Server(url, comServer);
}
// HDA
else if (url.Scheme == OpcUrlScheme.HDA)
{
// Verify that it is a HDA server.
if (!typeof(OpcRcw.Hda.IOPCHDA_Server).IsInstanceOfType(comServer))
{
interfaceType = typeof(OpcRcw.Hda.IOPCHDA_Server);
throw new NotSupportedException();
}
server = new Technosoftware.DaAeHdaClient.Com.Hda.Server(url, comServer);
}
// All other specifications not supported yet.
else
{
throw new NotSupportedException(string.Format("The URL scheme '{0}' is not supported.", url.Scheme));
}
}
catch (NotSupportedException)
{
Utilities.Interop.ReleaseServer(server);
server = null;
if (interfaceType != null)
{
var message = new StringBuilder();
message.AppendFormat("The COM server does not support the interface ");
message.AppendFormat("'{0}'.", interfaceType.FullName);
message.Append("\r\n\r\nThis problem could be caused by:\r\n");
message.Append("- incorrectly installed proxy/stubs.\r\n");
message.Append("- problems with the DCOM security settings.\r\n");
message.Append("- a personal firewall (sometimes activated by default).\r\n");
throw new NotSupportedException(message.ToString());
}
throw;
}
catch (Exception)
{
Utilities.Interop.ReleaseServer(server);
throw;
}
// initialize the wrapper object.
if (server != null)
{
server.Initialize(url, connectData);
}
SupportedSpecifications = new List<OpcSpecification>();
if (server is Com.Da20.Server)
{
SupportedSpecifications.Add(OpcSpecification.OPC_DA_20);
}
else if (server is Com.Da.Server)
{
SupportedSpecifications.Add(OpcSpecification.OPC_DA_30);
SupportedSpecifications.Add(OpcSpecification.OPC_DA_20);
}
else if (server is Com.Ae.Server)
{
SupportedSpecifications.Add(OpcSpecification.OPC_AE_10);
}
else if (server is Com.Hda.Server)
{
SupportedSpecifications.Add(OpcSpecification.OPC_HDA_10);
}
return server;
}
/// <summary>
/// Connects to the specified COM server.
/// </summary>
public static object Connect(OpcUrl url, OpcConnectData connectData)
{
// parse path to find prog id and clsid.
var progID = url.Path;
string clsid = null;
var index = url.Path.IndexOf('/');
if (index >= 0)
{
progID = url.Path.Substring(0, index);
clsid = url.Path.Substring(index + 1);
}
// look up prog id if clsid not specified in the url.
Guid guid;
if (clsid == null)
{
// use OpcEnum to lookup the prog id.
guid = new ServerEnumerator().CLSIDFromProgID(progID, url.HostName, connectData);
// check if prog id is actually a clsid string.
if (guid == Guid.Empty)
{
try
{
guid = new Guid(progID);
}
catch
{
throw new OpcResultException(new OpcResult((int)OpcResult.CO_E_CLASSSTRING.Code, OpcResult.FuncCallType.SysFuncCall, null), string.Format("Could not connect to server {0}", progID));
}
}
}
// convert clsid string to a guid.
else
{
try
{
guid = new Guid(clsid);
}
catch
{
throw new OpcResultException(new OpcResult((int)OpcResult.CO_E_CLASSSTRING.Code, OpcResult.FuncCallType.SysFuncCall, null), string.Format("Could not connect to server {0}", progID));
}
}
// get the credentials.
var credentials = (connectData != null) ? connectData.UserIdentity : null;
// instantiate the server using CoCreateInstanceEx.
if (connectData == null || connectData.LicenseKey == null)
{
try
{
return Utilities.Interop.CreateInstance(guid, url.HostName, credentials);
}
catch (Exception e)
{
throw new OpcResultException(OpcResult.CO_E_CLASSSTRING, e.Message, e);
}
}
// instantiate the server using IClassFactory2.
else
{
try
{
return null;
//return Technosoftware.DaAeHdaClient.Utilities.Interop.CreateInstanceWithLicenseKey(guid, url.HostName, credentials, connectData.LicenseKey);
}
catch (Exception e)
{
throw new OpcResultException(OpcResult.CO_E_CLASSSTRING, e.Message, e);
}
}
}
}
}

View File

@@ -0,0 +1,441 @@
#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 Technosoftware.DaAeHdaClient.Hda;
using Technosoftware.OpcRcw.Hda;
using Technosoftware.OpcRcw.Comn;
#endregion
namespace Technosoftware.DaAeHdaClient.Com.Hda
{
/// <summary>
/// An in-process wrapper an OPC HDA browser object.
/// </summary>
internal class Browser : ITsCHdaBrowser
{
//======================================================================
// Construction
/// <summary>
/// Initializes the object with the specifed COM server.
/// </summary>
internal Browser(Server server, IOPCHDA_Browser browser, TsCHdaBrowseFilter[] filters, OpcResult[] results)
{
if (browser == null) throw new ArgumentNullException(nameof(browser));
// save the server object that created the browser.
m_server = server;
// save the COM server (released in Dispose()).
m_browser = browser;
// save only the filters that were accepted.
if (filters != null)
{
var validFilters = new ArrayList();
for (var ii = 0; ii < filters.Length; ii++)
{
if (results[ii].Succeeded())
{
validFilters.Add(filters[ii]);
}
}
m_filters = new TsCHdaBrowseFilterCollection(validFilters);
}
}
#region IDisposable Members
/// <summary>
/// This must be called explicitly by clients to ensure the COM server is released.
/// </summary>
public virtual void Dispose()
{
lock (this)
{
m_server = null;
Utilities.Interop.ReleaseServer(m_browser);
m_browser = null;
}
}
#endregion
//======================================================================
// Filters
/// <summary>
/// Returns the set of attribute filters used by the browser.
/// </summary>
public TsCHdaBrowseFilterCollection Filters
{
get
{
lock (this)
{
return (TsCHdaBrowseFilterCollection)m_filters.Clone();
}
}
}
//======================================================================
// Browse
/// <summary>
/// Browses the server's address space at the specified branch.
/// </summary>
/// <param name="itemID">The item id of the branch to search.</param>
/// <returns>The set of elements that meet the filter criteria.</returns>
public TsCHdaBrowseElement[] Browse(OpcItem itemID)
{
IOpcBrowsePosition position;
var elements = Browse(itemID, 0, out position);
if (position != null)
{
position.Dispose();
}
return elements;
}
/// <summary>
/// Begins a browsing the server's address space at the specified branch.
/// </summary>
/// <param name="itemID">The item id of the branch to search.</param>
/// <param name="maxElements">The maximum number of elements to return.</param>
/// <param name="position">The position object used to continue a browse operation.</param>
/// <returns>The set of elements that meet the filter criteria.</returns>
public TsCHdaBrowseElement[] Browse(OpcItem itemID, int maxElements, out IOpcBrowsePosition position)
{
position = null;
// interpret invalid values as 'no limit'.
if (maxElements <= 0)
{
maxElements = int.MaxValue;
}
lock (this)
{
var branchPath = (itemID != null && itemID.ItemName != null)?itemID.ItemName:"";
// move to the correct position in the server's address space.
try
{
m_browser.ChangeBrowsePosition(OPCHDA_BROWSEDIRECTION.OPCHDA_BROWSE_DIRECT, branchPath);
}
catch (Exception e)
{
throw Utilities.Interop.CreateException("IOPCHDA_Browser.ChangeBrowsePosition", e);
}
// browse for branches
var enumerator = GetEnumerator(true);
var elements = FetchElements(enumerator, maxElements, true);
// check if max element count reached.
if (elements.Count >= maxElements)
{
position = new BrowsePosition(branchPath, enumerator, false);
return (TsCHdaBrowseElement[])elements.ToArray(typeof(TsCHdaBrowseElement));
}
// release enumerator.
enumerator.Dispose();
// browse for items
enumerator = GetEnumerator(false);
var items = FetchElements(enumerator, maxElements-elements.Count, false);
if (items != null)
{
elements.AddRange(items);
}
// check if max element count reached.
if (elements.Count >= maxElements)
{
position = new BrowsePosition(branchPath, enumerator, true);
return (TsCHdaBrowseElement[])elements.ToArray(typeof(TsCHdaBrowseElement));
}
// release enumerator.
enumerator.Dispose();
return (TsCHdaBrowseElement[])elements.ToArray(typeof(TsCHdaBrowseElement));
}
}
//======================================================================
// BrowseNext
/// <summary>
/// Continues browsing the server's address space at the specified position.
/// </summary>
/// <param name="maxElements">The maximum number of elements to return.</param>
/// <param name="position">The position object used to continue a browse operation.</param>
/// <returns>The set of elements that meet the filter criteria.</returns>
public TsCHdaBrowseElement[] BrowseNext(int maxElements, ref IOpcBrowsePosition position)
{
// check arguments.
if (position == null || position.GetType() != typeof(BrowsePosition))
{
throw new ArgumentException("Not a valid browse position object.", nameof(position));
}
// interpret invalid values as 'no limit'.
if (maxElements <= 0)
{
maxElements = int.MaxValue;
}
lock (this)
{
var pos = (BrowsePosition)position;
var elements = new ArrayList();
if (!pos.FetchingItems)
{
elements = FetchElements(pos.Enumerator, maxElements, true);
// check if max element count reached.
if (elements.Count >= maxElements)
{
return (TsCHdaBrowseElement[])elements.ToArray(typeof(TsCHdaBrowseElement));
}
// release enumerator.
pos.Enumerator.Dispose();
pos.Enumerator = null;
pos.FetchingItems = true;
// move to the correct position in the server's address space.
try
{
m_browser.ChangeBrowsePosition(OPCHDA_BROWSEDIRECTION.OPCHDA_BROWSE_DIRECT, pos.BranchPath);
}
catch (Exception e)
{
throw Utilities.Interop.CreateException("IOPCHDA_Browser.ChangeBrowsePosition", e);
}
// create enumerator for items.
pos.Enumerator = GetEnumerator(false);
}
// fetch next set of items.
var items = FetchElements(pos.Enumerator, maxElements-elements.Count, false);
if (items != null)
{
elements.AddRange(items);
}
// check if max element count reached.
if (elements.Count >= maxElements)
{
return (TsCHdaBrowseElement[])elements.ToArray(typeof(TsCHdaBrowseElement));
}
// release position object.
position.Dispose();
position = null;
// return elements.
return (TsCHdaBrowseElement[])elements.ToArray(typeof(TsCHdaBrowseElement));
}
}
#region Private Methods
/// <summary>
/// Creates an enumerator for the elements contained with the current branch.
/// </summary>
private EnumString GetEnumerator(bool isBranch)
{
try
{
var browseType = (isBranch)?OPCHDA_BROWSETYPE.OPCHDA_BRANCH:OPCHDA_BROWSETYPE.OPCHDA_LEAF;
IEnumString pEnumerator = null;
m_browser.GetEnum(browseType, out pEnumerator);
return new EnumString(pEnumerator);
}
catch (Exception e)
{
throw Utilities.Interop.CreateException("IOPCHDA_Browser.GetEnum", e);
}
}
/// <summary>
/// Fetches the element names and item ids for each element.
/// </summary>
private ArrayList FetchElements(EnumString enumerator, int maxElements, bool isBranch)
{
var elements = new ArrayList();
while (elements.Count < maxElements)
{
// fetch next batch of element names.
var count = BLOCK_SIZE;
if (elements.Count + count > maxElements)
{
count = maxElements - elements.Count;
}
var names = enumerator.Next(count);
// check if no more elements found.
if (names == null || names.Length == 0)
{
break;
}
// create new element objects.
foreach (var name in names)
{
var element = new TsCHdaBrowseElement();
element.Name = name;
// lookup item id for element.
try
{
string itemID = null;
m_browser.GetItemID(name, out itemID);
element.ItemName = itemID;
element.ItemPath = null;
element.HasChildren = isBranch;
}
catch
{
// ignore errors.
}
elements.Add(element);
}
}
// validate items - this is necessary to set the IsItem flag correctly.
var results = m_server.ValidateItems((OpcItem[])elements.ToArray(typeof(OpcItem)));
if (results != null)
{
for (var ii = 0; ii < results.Length; ii++)
{
if (results[ii].Result.Succeeded())
{
((TsCHdaBrowseElement)elements[ii]).IsItem = true;
}
}
}
// return results.
return elements;
}
#endregion
#region Private Members
private Server m_server = null;
private IOPCHDA_Browser m_browser = null;
private TsCHdaBrowseFilterCollection m_filters = new TsCHdaBrowseFilterCollection();
private const int BLOCK_SIZE = 10;
#endregion
}
/// <summary>
/// Stores the state of a browse operation that was halted.
/// </summary>
internal class BrowsePosition : TsCHdaBrowsePosition
{
/// <summary>
/// Initializes a the object with the browse operation state information.
/// </summary>
/// <param name="branchPath">The item id of branch used in the browse operation.</param>
/// <param name="enumerator">The enumerator used for the browse operation.</param>
/// <param name="fetchingItems">Whether the enumerator is return branches or items.</param>
internal BrowsePosition(string branchPath, EnumString enumerator, bool fetchingItems)
{
m_branchPath = branchPath;
m_enumerator = enumerator;
m_fetchingItems = fetchingItems;
}
/// <summary>
/// The item id of the branch being browsed.
/// </summary>
internal string BranchPath
{
get => m_branchPath;
set => m_branchPath = value;
}
/// <summary>
/// The enumerator that was in use when the browse halted.
/// </summary>
internal EnumString Enumerator
{
get => m_enumerator;
set => m_enumerator = value;
}
/// <summary>
/// Whether the browse halted while fetching items.
/// </summary>
internal bool FetchingItems
{
get => m_fetchingItems;
set => m_fetchingItems = value;
}
#region IDisposable Members
/// <summary>
/// Releases any unmanaged resources held by the object.
/// </summary>
public override void Dispose()
{
if (m_enumerator != null)
{
m_enumerator.Dispose();
m_enumerator = null;
}
base.Dispose();
}
#endregion
#region Private Members
private string m_branchPath = null;
private EnumString m_enumerator = null;
private bool m_fetchingItems = false;
#endregion
}
}

View File

@@ -0,0 +1,591 @@
#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 Technosoftware.DaAeHdaClient.Hda;
using Technosoftware.OpcRcw.Hda;
#endregion
namespace Technosoftware.DaAeHdaClient.Com.Hda
{
/// <summary>
/// A class that implements the HDA data callback interface.
/// </summary>
internal class DataCallback : IOPCHDA_DataCallback
{
/// <summary>
/// Initializes the object with the containing subscription object.
/// </summary>
public DataCallback() {}
/// <summary>
/// Fired when an exception occurs during callback processing.
/// </summary>
public event TsCHdaCallbackExceptionEventHandler CallbackExceptionEvent
{
add {lock (this) { _callbackExceptionEvent += value; }}
remove {lock (this) { _callbackExceptionEvent -= value; }}
}
/// <summary>
/// Creates a new request object.
/// </summary>
public Request CreateRequest(object requestHandle, Delegate callback)
{
lock (this)
{
// create a new request.
var request = new Request(requestHandle, callback, ++m_nextID);
// no items yet - callback may return before async call returns.
m_requests[request.RequestID] = request;
// return requests.
return request;
}
}
/// <summary>
/// Cancels an existing request.
/// </summary>
public bool CancelRequest(Request request, TsCHdaCancelCompleteEventHandler callback)
{
lock (this)
{
// check if it is a valid request.
if (!m_requests.Contains(request.RequestID))
{
return false;
}
// request will be removed when the cancel complete callback arrives.
if (callback != null)
{
request.CancelCompleteEvent += callback;
}
// no confirmation required - remove request immediately.
else
{
m_requests.Remove(request.RequestID);
}
// request will be cancelled.
return true;
}
}
#region IOPCHDA_DataCallback Members
/// <summary>
/// Called when new data arrives for a subscription.
/// </summary>
public void OnDataChange(
int dwTransactionID,
int hrStatus,
int dwNumItems,
OPCHDA_ITEM[] pItemValues,
int[] phrErrors)
{
try
{
lock (this)
{
// lookup request transaction.
var request = (Request)m_requests[dwTransactionID];
if (request == null)
{
return;
}
// unmarshal results.
var results = new TsCHdaItemValueCollection[pItemValues.Length];
for (var ii = 0; ii < pItemValues.Length; ii++)
{
results[ii] = Interop.GetItemValueCollection(pItemValues[ii], false);
results[ii].ServerHandle = results[ii].ClientHandle;
results[ii].ClientHandle = null;
results[ii].Result = Utilities.Interop.GetResultId(phrErrors[ii]);
}
// invoke callback - remove request if unexpected error occured.
if (request.InvokeCallback(results))
{
m_requests.Remove(request.RequestID);
}
}
}
catch (Exception exception)
{
HandleException(dwTransactionID, exception);
}
}
/// <summary>
/// Called when an asynchronous read request completes.
/// </summary>
public void OnReadComplete(
int dwTransactionID,
int hrStatus,
int dwNumItems,
OPCHDA_ITEM[] pItemValues,
int[] phrErrors)
{
try
{
lock (this)
{
// lookup request transaction.
var request = (Request)m_requests[dwTransactionID];
if (request == null)
{
return;
}
// unmarshal results.
var results = new TsCHdaItemValueCollection[pItemValues.Length];
for (var ii = 0; ii < pItemValues.Length; ii++)
{
results[ii] = Interop.GetItemValueCollection(pItemValues[ii], false);
results[ii].ServerHandle = pItemValues[ii].hClient;
results[ii].Result = Utilities.Interop.GetResultId(phrErrors[ii]);
}
// invoke callback - remove request if all results arrived.
if (request.InvokeCallback(results))
{
m_requests.Remove(request.RequestID);
}
}
}
catch (Exception exception)
{
HandleException(dwTransactionID, exception);
}
}
/// <summary>
/// Called when an asynchronous read modified request completes.
/// </summary>
public void OnReadModifiedComplete(
int dwTransactionID,
int hrStatus,
int dwNumItems,
OPCHDA_MODIFIEDITEM[] pItemValues,
int[] phrErrors)
{
try
{
lock (this)
{
// lookup request transaction.
var request = (Request)m_requests[dwTransactionID];
if (request == null)
{
return;
}
// unmarshal results.
var results = new TsCHdaModifiedValueCollection[pItemValues.Length];
for (var ii = 0; ii < pItemValues.Length; ii++)
{
results[ii] = Interop.GetModifiedValueCollection(pItemValues[ii], false);
results[ii].ServerHandle = pItemValues[ii].hClient;
results[ii].Result = Utilities.Interop.GetResultId(phrErrors[ii]);
}
// invoke callback - remove request if all results arrived.
if (request.InvokeCallback(results))
{
m_requests.Remove(request.RequestID);
}
}
}
catch (Exception exception)
{
HandleException(dwTransactionID, exception);
}
}
/// <summary>
/// Called when an asynchronous read attributes request completes.
/// </summary>
public void OnReadAttributeComplete(
int dwTransactionID,
int hrStatus,
int hClient,
int dwNumItems,
OPCHDA_ATTRIBUTE[] pAttributeValues,
int[] phrErrors)
{
try
{
lock (this)
{
// lookup request transaction.
var request = (Request)m_requests[dwTransactionID];
if (request == null)
{
return;
}
// create item object to collect results.
var item = new TsCHdaItemAttributeCollection();
item.ServerHandle = hClient;
// unmarshal results.
var results = new TsCHdaAttributeValueCollection[pAttributeValues.Length];
for (var ii = 0; ii < pAttributeValues.Length; ii++)
{
results[ii] = Interop.GetAttributeValueCollection(pAttributeValues[ii], false);
results[ii].Result = Utilities.Interop.GetResultId(phrErrors[ii]);
item.Add(results[ii]);
}
// invoke callback - remove request if all results arrived.
if (request.InvokeCallback(item))
{
m_requests.Remove(request.RequestID);
}
}
}
catch (Exception exception)
{
HandleException(dwTransactionID, exception);
}
}
/// <summary>
/// Called when an asynchronous read annotations request completes.
/// </summary>
public void OnReadAnnotations(
int dwTransactionID,
int hrStatus,
int dwNumItems,
OPCHDA_ANNOTATION[] pAnnotationValues,
int[] phrErrors)
{
try
{
lock (this)
{
// lookup request transaction.
var request = (Request)m_requests[dwTransactionID];
if (request == null)
{
return;
}
// unmarshal results.
var results = new TsCHdaAnnotationValueCollection[pAnnotationValues.Length];
for (var ii = 0; ii < pAnnotationValues.Length; ii++)
{
results[ii] = Interop.GetAnnotationValueCollection(pAnnotationValues[ii], false);
results[ii].ServerHandle = pAnnotationValues[ii].hClient;
results[ii].Result = Utilities.Interop.GetResultId(phrErrors[ii]);
}
// invoke callback - remove request if all results arrived.
if (request.InvokeCallback(results))
{
m_requests.Remove(request.RequestID);
}
}
}
catch (Exception exception)
{
HandleException(dwTransactionID, exception);
}
}
/// <summary>
/// Called when an asynchronous insert annotations request completes.
/// </summary>
public void OnInsertAnnotations(
int dwTransactionID,
int hrStatus,
int dwCount,
int[] phClients,
int[] phrErrors)
{
try
{
lock (this)
{
// lookup request transaction.
var request = (Request)m_requests[dwTransactionID];
if (request == null)
{
return;
}
// unmarshal results.
var results = new ArrayList();
if (dwCount > 0)
{
// subscription results in collections for the same item id.
var currentHandle = phClients[0];
var itemResults = new TsCHdaResultCollection();
for (var ii = 0; ii < dwCount; ii++)
{
// create a new collection for the next item's results.
if (phClients[ii] != currentHandle)
{
itemResults.ServerHandle = currentHandle;
results.Add(itemResults);
currentHandle = phClients[ii];
itemResults = new TsCHdaResultCollection();
}
var result = new TsCHdaResult(Utilities.Interop.GetResultId(phrErrors[ii]));
itemResults.Add(result);
}
// add the last set of item results.
itemResults.ServerHandle = currentHandle;
results.Add(itemResults);
}
// invoke callback - remove request if all results arrived.
if (request.InvokeCallback((TsCHdaResultCollection[])results.ToArray(typeof(TsCHdaResultCollection))))
{
m_requests.Remove(request.RequestID);
}
}
}
catch (Exception exception)
{
HandleException(dwTransactionID, exception);
}
}
/// <summary>
/// Called when a batch of data from playback request arrives.
/// </summary>
public void OnPlayback(
int dwTransactionID,
int hrStatus,
int dwNumItems,
IntPtr ppItemValues,
int[] phrErrors)
{
try
{
lock (this)
{
// lookup request transaction.
var request = (Request)m_requests[dwTransactionID];
if (request == null)
{
return;
}
// unmarshal results.
var results = new TsCHdaItemValueCollection[dwNumItems];
// the data is transfered as a array of pointers to items instead of simply
// as an array of items. This is due to a mistake in the HDA IDL.
var pItems = Utilities.Interop.GetInt32s(ref ppItemValues, dwNumItems, false);
for (var ii = 0; ii < dwNumItems; ii++)
{
// get pointer to item.
var pItem = (IntPtr)pItems[ii];
// unmarshal item as an array of length 1.
var item = Interop.GetItemValueCollections(ref pItem, 1, false);
if (item != null && item.Length == 1)
{
results[ii] = item[0];
results[ii].ServerHandle = results[ii].ClientHandle;
results[ii].ClientHandle = null;
results[ii].Result = Utilities.Interop.GetResultId(phrErrors[ii]);
}
}
// invoke callback - remove request if unexpected error occured.
if (request.InvokeCallback(results))
{
m_requests.Remove(request.RequestID);
}
}
}
catch (Exception exception)
{
HandleException(dwTransactionID, exception);
}
}
/// <summary>
/// Called when an asynchronous update request completes.
/// </summary>
public void OnUpdateComplete(
int dwTransactionID,
int hrStatus,
int dwCount,
int[] phClients,
int[] phrErrors)
{
try
{
lock (this)
{
// lookup request transaction.
var request = (Request)m_requests[dwTransactionID];
if (request == null)
{
return;
}
// unmarshal results.
var results = new ArrayList();
if (dwCount > 0)
{
// subscription results in collections for the same item id.
var currentHandle = phClients[0];
var itemResults = new TsCHdaResultCollection();
for (var ii = 0; ii < dwCount; ii++)
{
// create a new collection for the next item's results.
if (phClients[ii] != currentHandle)
{
itemResults.ServerHandle = currentHandle;
results.Add(itemResults);
currentHandle = phClients[ii];
itemResults = new TsCHdaResultCollection();
}
var result = new TsCHdaResult(Utilities.Interop.GetResultId(phrErrors[ii]));
itemResults.Add(result);
}
// add the last set of item results.
itemResults.ServerHandle = currentHandle;
results.Add(itemResults);
}
// invoke callback - remove request if all results arrived.
if (request.InvokeCallback((TsCHdaResultCollection[])results.ToArray(typeof(TsCHdaResultCollection))))
{
m_requests.Remove(request.RequestID);
}
}
}
catch (Exception exception)
{
HandleException(dwTransactionID, exception);
}
}
/// <summary>
/// Called when an asynchronous request was cancelled successfully.
/// </summary>
public void OnCancelComplete(int dwCancelID)
{
try
{
lock (this)
{
// lookup request.
var request = (Request)m_requests[dwCancelID];
if (request == null)
{
return;
}
// send the cancel complete notification.
request.OnCancelComplete();
// remove the request.
m_requests.Remove(request.RequestID);
}
}
catch (Exception exception)
{
HandleException(dwCancelID, exception);
}
}
#endregion
#region Private Methods
/// <summary>
/// Fires an event indicating an exception occurred during callback processing.
/// </summary>
void HandleException(int requestID, Exception exception)
{
lock (this)
{
// lookup request.
var request = (Request)m_requests[requestID];
if (request != null)
{
// send notification.
if (_callbackExceptionEvent != null)
{
_callbackExceptionEvent(request, exception);
}
}
}
}
#endregion
#region Private Members
private int m_nextID;
private Hashtable m_requests = new Hashtable();
private TsCHdaCallbackExceptionEventHandler _callbackExceptionEvent;
#endregion
}
}

View File

@@ -0,0 +1,441 @@
#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.Runtime.InteropServices;
using Technosoftware.DaAeHdaClient.Da;
using Technosoftware.DaAeHdaClient.Hda;
#endregion
#pragma warning disable 0618
namespace Technosoftware.DaAeHdaClient.Com.Hda
{
/// <summary>
/// Contains state information for a single asynchronous Technosoftware.DaAeHdaClient.Com.Hda.Interop.
/// </summary>
internal class Interop
{
/// <summary>
/// Converts a standard FILETIME to an OpcRcw.Da.FILETIME structure.
/// </summary>
internal static OpcRcw.Hda.OPCHDA_FILETIME Convert(FILETIME input)
{
var output = new OpcRcw.Hda.OPCHDA_FILETIME();
output.dwLowDateTime = input.dwLowDateTime;
output.dwHighDateTime = input.dwHighDateTime;
return output;
}
/// <summary>
/// Converts an OpcRcw.Da.FILETIME to a standard FILETIME structure.
/// </summary>
internal static FILETIME Convert(OpcRcw.Hda.OPCHDA_FILETIME input)
{
var output = new FILETIME();
output.dwLowDateTime = input.dwLowDateTime;
output.dwHighDateTime = input.dwHighDateTime;
return output;
}
/// <summary>
/// Converts a decimal value to a OpcRcw.Hda.OPCHDA_TIME structure.
/// </summary>
internal static OpcRcw.Hda.OPCHDA_FILETIME GetFILETIME(decimal input)
{
var output = new OpcRcw.Hda.OPCHDA_FILETIME();
output.dwHighDateTime = (int)((((ulong)(input*10000000)) & 0xFFFFFFFF00000000)>>32);
output.dwLowDateTime = (int)((((ulong)(input*10000000)) & 0x00000000FFFFFFFF));
return output;
}
/// <summary>
/// Returns an array of FILETIMEs.
/// </summary>
internal static OpcRcw.Hda.OPCHDA_FILETIME[] GetFILETIMEs(DateTime[] input)
{
OpcRcw.Hda.OPCHDA_FILETIME[] output = null;
if (input != null)
{
output = new OpcRcw.Hda.OPCHDA_FILETIME[input.Length];
for (var ii = 0; ii < input.Length; ii++)
{
output[ii] = Convert(Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(input[ii]));
}
}
return output;
}
/// <summary>
/// Converts a Technosoftware.DaAeHdaClient.Time object to a Technosoftware.DaAeHdaClient.Com.Hda.OPCHDA_TIME structure.
/// </summary>
internal static OpcRcw.Hda.OPCHDA_TIME GetTime(TsCHdaTime input)
{
var output = new OpcRcw.Hda.OPCHDA_TIME();
if (input != null)
{
output.ftTime = Convert(Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(input.AbsoluteTime));
output.szTime = (input.IsRelative)?input.ToString():"";
output.bString = (input.IsRelative)?1:0;
}
// create a null value for a time structure.
else
{
output.ftTime = Convert(Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(DateTime.MinValue));
output.szTime = "";
output.bString = 1;
}
return output;
}
/// <summary>
/// Unmarshals and deallocates an array of OPCHDA_ITEM structures.
/// </summary>
internal static TsCHdaItemValueCollection[] GetItemValueCollections(ref IntPtr pInput, int count, bool deallocate)
{
TsCHdaItemValueCollection[] output = null;
if (pInput != IntPtr.Zero && count > 0)
{
output = new TsCHdaItemValueCollection[count];
var pos = pInput;
for (var ii = 0; ii < count; ii++)
{
output[ii] = GetItemValueCollection(pos, deallocate);
pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Hda.OPCHDA_ITEM)));
}
if (deallocate)
{
Marshal.FreeCoTaskMem(pInput);
pInput = IntPtr.Zero;
}
}
return output;
}
/// <summary>
/// Unmarshals and deallocates an OPCHDA_ITEM structure.
/// </summary>
internal static TsCHdaItemValueCollection GetItemValueCollection(IntPtr pInput, bool deallocate)
{
TsCHdaItemValueCollection output = null;
if (pInput != IntPtr.Zero)
{
var item = Marshal.PtrToStructure(pInput, typeof(OpcRcw.Hda.OPCHDA_ITEM));
output = GetItemValueCollection((OpcRcw.Hda.OPCHDA_ITEM)item, deallocate);
if (deallocate)
{
Marshal.DestroyStructure(pInput, typeof(OpcRcw.Hda.OPCHDA_ITEM));
}
}
return output;
}
/// <summary>
/// Unmarshals and deallocates an OPCHDA_ITEM structure.
/// </summary>
internal static TsCHdaItemValueCollection GetItemValueCollection(OpcRcw.Hda.OPCHDA_ITEM input, bool deallocate)
{
var output = new TsCHdaItemValueCollection();
output.ClientHandle = input.hClient;
output.Aggregate = input.haAggregate;
var values = Com.Interop.GetVARIANTs(ref input.pvDataValues, input.dwCount, deallocate);
var timestamps = Utilities.Interop.GetDateTimes(ref input.pftTimeStamps, input.dwCount, deallocate);
var qualities = Utilities.Interop.GetInt32s(ref input.pdwQualities, input.dwCount, deallocate);
for (var ii = 0; ii < input.dwCount; ii++)
{
var value = new TsCHdaItemValue();
value.Value = values[ii];
value.Timestamp = timestamps[ii];
value.Quality = new TsCDaQuality((short)(qualities[ii] & 0x0000FFFF));
value.HistorianQuality = (TsCHdaQuality)((int)(qualities[ii] & 0xFFFF0000));
output.Add(value);
}
return output;
}
/// <summary>
/// Unmarshals and deallocates an array of OPCHDA_MODIFIEDITEM structures.
/// </summary>
internal static TsCHdaModifiedValueCollection[] GetModifiedValueCollections(ref IntPtr pInput, int count, bool deallocate)
{
TsCHdaModifiedValueCollection[] output = null;
if (pInput != IntPtr.Zero && count > 0)
{
output = new TsCHdaModifiedValueCollection[count];
var pos = pInput;
for (var ii = 0; ii < count; ii++)
{
output[ii] = GetModifiedValueCollection(pos, deallocate);
pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Hda.OPCHDA_MODIFIEDITEM)));
}
if (deallocate)
{
Marshal.FreeCoTaskMem(pInput);
pInput = IntPtr.Zero;
}
}
return output;
}
/// <summary>
/// Unmarshals and deallocates an OPCHDA_MODIFIEDITEM structure.
/// </summary>
internal static TsCHdaModifiedValueCollection GetModifiedValueCollection(IntPtr pInput, bool deallocate)
{
TsCHdaModifiedValueCollection output = null;
if (pInput != IntPtr.Zero)
{
var item = Marshal.PtrToStructure(pInput, typeof(OpcRcw.Hda.OPCHDA_MODIFIEDITEM));
output = GetModifiedValueCollection((OpcRcw.Hda.OPCHDA_MODIFIEDITEM)item, deallocate);
if (deallocate)
{
Marshal.DestroyStructure(pInput, typeof(OpcRcw.Hda.OPCHDA_MODIFIEDITEM));
}
}
return output;
}
/// <summary>
/// Unmarshals and deallocates an OPCHDA_MODIFIEDITEM structure.
/// </summary>
internal static TsCHdaModifiedValueCollection GetModifiedValueCollection(OpcRcw.Hda.OPCHDA_MODIFIEDITEM input, bool deallocate)
{
var output = new TsCHdaModifiedValueCollection();
output.ClientHandle = input.hClient;
var values = Com.Interop.GetVARIANTs(ref input.pvDataValues, input.dwCount, deallocate);
var timestamps = Utilities.Interop.GetDateTimes(ref input.pftTimeStamps, input.dwCount, deallocate);
var qualities = Utilities.Interop.GetInt32s(ref input.pdwQualities, input.dwCount, deallocate);
var modificationTimes = Utilities.Interop.GetDateTimes(ref input.pftModificationTime, input.dwCount, deallocate);
var editTypes = Utilities.Interop.GetInt32s(ref input.pEditType, input.dwCount, deallocate);
var users = Utilities.Interop.GetUnicodeStrings(ref input.szUser, input.dwCount, deallocate);
for (var ii = 0; ii < input.dwCount; ii++)
{
var value = new TsCHdaModifiedValue();
value.Value = values[ii];
value.Timestamp = timestamps[ii];
value.Quality = new TsCDaQuality((short)(qualities[ii] & 0x0000FFFF));
value.HistorianQuality = (TsCHdaQuality)((int)(qualities[ii] & 0xFFFF0000));
value.ModificationTime = modificationTimes[ii];
value.EditType = (TsCHdaEditType)editTypes[ii];
value.User = users[ii];
output.Add(value);
}
return output;
}
/// <summary>
/// Unmarshals and deallocates an array of OPCHDA_ATTRIBUTE structures.
/// </summary>
internal static TsCHdaAttributeValueCollection[] GetAttributeValueCollections(ref IntPtr pInput, int count, bool deallocate)
{
TsCHdaAttributeValueCollection[] output = null;
if (pInput != IntPtr.Zero && count > 0)
{
output = new TsCHdaAttributeValueCollection[count];
var pos = pInput;
for (var ii = 0; ii < count; ii++)
{
output[ii] = GetAttributeValueCollection(pos, deallocate);
pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Hda.OPCHDA_ATTRIBUTE)));
}
if (deallocate)
{
Marshal.FreeCoTaskMem(pInput);
pInput = IntPtr.Zero;
}
}
return output;
}
/// <summary>
/// Unmarshals and deallocates an OPCHDA_ATTRIBUTE structure.
/// </summary>
internal static TsCHdaAttributeValueCollection GetAttributeValueCollection(IntPtr pInput, bool deallocate)
{
TsCHdaAttributeValueCollection output = null;
if (pInput != IntPtr.Zero)
{
var item = Marshal.PtrToStructure(pInput, typeof(OpcRcw.Hda.OPCHDA_ATTRIBUTE));
output = GetAttributeValueCollection((OpcRcw.Hda.OPCHDA_ATTRIBUTE)item, deallocate);
if (deallocate)
{
Marshal.DestroyStructure(pInput, typeof(OpcRcw.Hda.OPCHDA_ATTRIBUTE));
}
}
return output;
}
/// <summary>
/// Unmarshals and deallocates an OPCHDA_ATTRIBUTE structure.
/// </summary>
internal static TsCHdaAttributeValueCollection GetAttributeValueCollection(OpcRcw.Hda.OPCHDA_ATTRIBUTE input, bool deallocate)
{
var output = new TsCHdaAttributeValueCollection();
output.AttributeID = input.dwAttributeID;
var values = Com.Interop.GetVARIANTs(ref input.vAttributeValues, input.dwNumValues, deallocate);
var timestamps = Utilities.Interop.GetDateTimes(ref input.ftTimeStamps, input.dwNumValues, deallocate);
for (var ii = 0; ii < input.dwNumValues; ii++)
{
var value = new TsCHdaAttributeValue();
value.Value = values[ii];
value.Timestamp = timestamps[ii];
output.Add(value);
}
return output;
}
/// <summary>
/// Unmarshals and deallocates an array of OPCHDA_ANNOTATION structures.
/// </summary>
internal static TsCHdaAnnotationValueCollection[] GetAnnotationValueCollections(ref IntPtr pInput, int count, bool deallocate)
{
TsCHdaAnnotationValueCollection[] output = null;
if (pInput != IntPtr.Zero && count > 0)
{
output = new TsCHdaAnnotationValueCollection[count];
var pos = pInput;
for (var ii = 0; ii < count; ii++)
{
output[ii] = GetAnnotationValueCollection(pos, deallocate);
pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Hda.OPCHDA_ANNOTATION)));
}
if (deallocate)
{
Marshal.FreeCoTaskMem(pInput);
pInput = IntPtr.Zero;
}
}
return output;
}
/// <summary>
/// Unmarshals and deallocates an OPCHDA_ANNOTATION structure.
/// </summary>
internal static TsCHdaAnnotationValueCollection GetAnnotationValueCollection(IntPtr pInput, bool deallocate)
{
TsCHdaAnnotationValueCollection output = null;
if (pInput != IntPtr.Zero)
{
var item = Marshal.PtrToStructure(pInput, typeof(OpcRcw.Hda.OPCHDA_ANNOTATION));
output = GetAnnotationValueCollection((OpcRcw.Hda.OPCHDA_ANNOTATION)item, deallocate);
if (deallocate)
{
Marshal.DestroyStructure(pInput, typeof(OpcRcw.Hda.OPCHDA_ANNOTATION));
}
}
return output;
}
/// <summary>
/// Unmarshals and deallocates an OPCHDA_ANNOTATION structure.
/// </summary>
internal static TsCHdaAnnotationValueCollection GetAnnotationValueCollection(OpcRcw.Hda.OPCHDA_ANNOTATION input, bool deallocate)
{
var output = new TsCHdaAnnotationValueCollection();
output.ClientHandle = input.hClient;
var timestamps = Utilities.Interop.GetDateTimes(ref input.ftTimeStamps, input.dwNumValues, deallocate);
var annotations = Utilities.Interop.GetUnicodeStrings(ref input.szAnnotation, input.dwNumValues, deallocate);
var creationTimes = Utilities.Interop.GetDateTimes(ref input.ftAnnotationTime, input.dwNumValues, deallocate);
var users = Utilities.Interop.GetUnicodeStrings(ref input.szUser, input.dwNumValues, deallocate);
for (var ii = 0; ii < input.dwNumValues; ii++)
{
var value = new TsCHdaAnnotationValue();
value.Timestamp = timestamps[ii];
value.Annotation = annotations[ii];
value.CreationTime = creationTimes[ii];
value.User = users[ii];
output.Add(value);
}
return output;
}
}
}

View File

@@ -0,0 +1,404 @@
#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 Technosoftware.DaAeHdaClient.Hda;
#endregion
namespace Technosoftware.DaAeHdaClient.Com.Hda
{
/// <summary>
/// An object that mainatains the state of asynchronous requests.
/// </summary>
internal class Request : IOpcRequest, ITsCHdaActualTime
{
/// <summary>
/// The unique id assigned to the request when it was created.
/// </summary>
public int RequestID => m_requestID;
/// <summary>
/// The unqiue id assigned by the server when it was created.
/// </summary>
public int CancelID => m_cancelID;
/// <summary>
/// Fired when the server acknowledges that a request was cancelled.
/// </summary>
public event TsCHdaCancelCompleteEventHandler CancelCompleteEvent
{
add { lock (this) { m_cancelCompleteEvent += value; } }
remove { lock (this) { m_cancelCompleteEvent -= value; } }
}
/// <summary>
/// Initializes the object with all required information.
/// </summary>
public Request(object requestHandle, Delegate callback, int requestID)
{
m_requestHandle = requestHandle;
m_callback = callback;
m_requestID = requestID;
}
/// <summary>
/// Updates the request with the initial results.
/// </summary>
public bool Update(int cancelID, OpcItem[] results)
{
lock (this)
{
// save the server assigned id.
m_cancelID = cancelID;
// create a table of items indexed by the handle returned by the server in a callback.
m_items = new Hashtable();
foreach (var result in results)
{
if (!typeof(IOpcResult).IsInstanceOfType(result) || ((IOpcResult)result).Result.Succeeded())
{
m_items[result.ServerHandle] = new OpcItem(result);
}
}
// nothing more to do - no good items.
if (m_items.Count == 0)
{
return true;
}
// invoke callbacks for results that have already arrived.
var complete = false;
if (m_results != null)
{
foreach (var result in m_results)
{
complete = InvokeCallback(result);
}
}
// all done.
return complete;
}
}
/// <summary>
/// Invokes the callback for the request.
/// </summary>
public bool InvokeCallback(object results)
{
lock (this)
{
// save the results if the initial call to the server has not completed yet.
if (m_items == null)
{
// create cache for results.
if (m_results == null)
{
m_results = new ArrayList();
}
m_results.Add(results);
// request not initialized completely
return false;
}
// invoke on data update callback.
if (typeof(TsCHdaDataUpdateEventHandler).IsInstanceOfType(m_callback))
{
return InvokeCallback((TsCHdaDataUpdateEventHandler)m_callback, results);
}
// invoke read completed callback.
if (typeof(TsCHdaReadValuesCompleteEventHandler).IsInstanceOfType(m_callback))
{
return InvokeCallback((TsCHdaReadValuesCompleteEventHandler)m_callback, results);
}
// invoke read attributes completed callback.
if (typeof(TsCHdaReadAttributesCompleteEventHandler).IsInstanceOfType(m_callback))
{
return InvokeCallback((TsCHdaReadAttributesCompleteEventHandler)m_callback, results);
}
// invoke read annotations completed callback.
if (typeof(TsCHdaReadAnnotationsCompleteEventHandler).IsInstanceOfType(m_callback))
{
return InvokeCallback((TsCHdaReadAnnotationsCompleteEventHandler)m_callback, results);
}
// invoke update completed callback.
if (typeof(TsCHdaUpdateCompleteEventHandler).IsInstanceOfType(m_callback))
{
return InvokeCallback((TsCHdaUpdateCompleteEventHandler)m_callback, results);
}
// callback not supported.
return true;
}
}
/// <summary>
/// Called when the server acknowledges that a request was cancelled.
/// </summary>
public void OnCancelComplete()
{
lock (this)
{
if (m_cancelCompleteEvent != null)
{
m_cancelCompleteEvent(this);
}
}
}
#region IOpcRequest Members
/// <summary>
/// An unique identifier, assigned by the client, for the request.
/// </summary>
public object Handle => m_requestHandle;
#endregion
#region IActualTime Members
/// <summary>
/// The actual start time used by a server while processing a request.
/// </summary>
public DateTime StartTime
{
get => m_startTime;
set => m_startTime = value;
}
/// <summary>
/// The actual end time used by a server while processing a request.
/// </summary>
public DateTime EndTime
{
get => m_endTime;
set => m_endTime = value;
}
#endregion
#region Private Methods
/// <summary>
/// Invokes callback for a data change update.
/// </summary>
private bool InvokeCallback(TsCHdaDataUpdateEventHandler callback, object results)
{
// check for valid result type.
if (!typeof(TsCHdaItemValueCollection[]).IsInstanceOfType(results))
{
return false;
}
var values = (TsCHdaItemValueCollection[])results;
// update item handles and actual times.
UpdateResults(values);
try
{
callback(this, values);
}
catch
{
// ignore exceptions in the callbacks.
}
// request never completes.
return false;
}
/// <summary>
/// Invokes callback for a read request.
/// </summary>
private bool InvokeCallback(TsCHdaReadValuesCompleteEventHandler callback, object results)
{
// check for valid result type.
if (!typeof(TsCHdaItemValueCollection[]).IsInstanceOfType(results))
{
return false;
}
var values = (TsCHdaItemValueCollection[])results;
// update item handles and actual times.
UpdateResults(values);
try
{
callback(this, values);
}
catch
{
// ignore exceptions in the callbacks.
}
// check if all data has been sent.
foreach (var value in values)
{
if (value.Result == OpcResult.Hda.S_MOREDATA)
{
return false;
}
}
// request is complete.
return true;
}
/// <summary>
/// Invokes callback for a read attributes request.
/// </summary>
private bool InvokeCallback(TsCHdaReadAttributesCompleteEventHandler callback, object results)
{
// check for valid result type.
if (!typeof(TsCHdaItemAttributeCollection).IsInstanceOfType(results))
{
return false;
}
var values = (TsCHdaItemAttributeCollection)results;
// update item handles and actual times.
UpdateResults(new TsCHdaItemAttributeCollection[] { values });
try
{
callback(this, values);
}
catch
{
// ignore exceptions in the callbacks.
}
// request always completes
return true;
}
/// <summary>
/// Invokes callback for a read annotations request.
/// </summary>
private bool InvokeCallback(TsCHdaReadAnnotationsCompleteEventHandler callback, object results)
{
// check for valid result type.
if (!typeof(TsCHdaAnnotationValueCollection[]).IsInstanceOfType(results))
{
return false;
}
var values = (TsCHdaAnnotationValueCollection[])results;
// update item handles and actual times.
UpdateResults(values);
try
{
callback(this, values);
}
catch
{
// ignore exceptions in the callbacks.
}
// request always completes
return true;
}
/// <summary>
/// Invokes callback for a read annotations request.
/// </summary>
private bool InvokeCallback(TsCHdaUpdateCompleteEventHandler callback, object results)
{
// check for valid result type.
if (!typeof(TsCHdaResultCollection[]).IsInstanceOfType(results))
{
return false;
}
var values = (TsCHdaResultCollection[])results;
// update item handles and actual times.
UpdateResults(values);
try
{
callback(this, values);
}
catch
{
// ignore exceptions in the callbacks.
}
// request always completes
return true;
}
/// <summary>
/// Updates the result objects with locally cached information.
/// </summary>
private void UpdateResults(OpcItem[] results)
{
foreach (var result in results)
{
// update actual times.
if (typeof(ITsCHdaActualTime).IsInstanceOfType(result))
{
((ITsCHdaActualTime)result).StartTime = StartTime;
((ITsCHdaActualTime)result).EndTime = EndTime;
}
// add item identifier to value collection.
var itemID = (OpcItem)m_items[result.ServerHandle];
if (itemID != null)
{
result.ItemName = itemID.ItemName;
result.ItemPath = itemID.ItemPath;
result.ServerHandle = itemID.ServerHandle;
result.ClientHandle = itemID.ClientHandle;
}
}
}
#endregion
#region Private Members
private object m_requestHandle = null;
private Delegate m_callback = null;
private int m_requestID = 0;
private int m_cancelID = 0;
private DateTime m_startTime = DateTime.MinValue;
private DateTime m_endTime = DateTime.MinValue;
private Hashtable m_items = null;
private ArrayList m_results = null;
private event TsCHdaCancelCompleteEventHandler m_cancelCompleteEvent = null;
#endregion
}
}

View File

@@ -0,0 +1,68 @@
#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
#endregion
namespace Technosoftware.DaAeHdaClient.Com
{
namespace Hda
{
/// <summary>
/// Defines all well known COM HDA HRESULT codes.
/// </summary>
internal struct Result
{
/// <remarks/>
public const int E_MAXEXCEEDED = -0X3FFBEFFF; // 0xC0041001
/// <remarks/>
public const int S_NODATA = +0x40041002; // 0x40041002
/// <remarks/>
public const int S_MOREDATA = +0x40041003; // 0x40041003
/// <remarks/>
public const int E_INVALIDAGGREGATE = -0X3FFBEFFC; // 0xC0041004
/// <remarks/>
public const int S_CURRENTVALUE = +0x40041005; // 0x40041005
/// <remarks/>
public const int S_EXTRADATA = +0x40041006; // 0x40041006
/// <remarks/>
public const int W_NOFILTER = -0x7FFBEFF9; // 0x80041007
/// <remarks/>
public const int E_UNKNOWNATTRID = -0x3FFBEFF8; // 0xC0041008
/// <remarks/>
public const int E_NOT_AVAIL = -0x3FFBEFF7; // 0xC0041009
/// <remarks/>
public const int E_INVALIDDATATYPE = -0x3FFBEFF6; // 0xC004100A
/// <remarks/>
public const int E_DATAEXISTS = -0x3FFBEFF5; // 0xC004100B
/// <remarks/>
public const int E_INVALIDATTRID = -0x3FFBEFF4; // 0xC004100C
/// <remarks/>
public const int E_NODATAEXISTS = -0x3FFBEFF3; // 0xC004100D
/// <remarks/>
public const int S_INSERTED = +0x4004100E; // 0x4004100E
/// <remarks/>
public const int S_REPLACED = +0x4004100F; // 0x4004100F
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,204 @@
#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.Generic;
#endregion
namespace Technosoftware.DaAeHdaClient.Com
{
/// <summary>Provides methods for discover (search) of OPC Servers.</summary>
public class OpcDiscovery
{
#region Fields
private static ServerEnumerator discovery_;
private static string hostName_;
private bool disposed_;
#endregion
#region Constructors, Destructor, Initialization
/// <summary>
/// The finalizer implementation.
/// </summary>
~OpcDiscovery()
{
Dispose(false);
}
/// <summary>
/// Implements <see cref="IDisposable.Dispose"/>.
/// </summary>
public virtual void Dispose()
{
Dispose(true);
// Take yourself off the Finalization queue
// to prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="disposing">If true managed and unmanaged resources can be disposed. If false only unmanaged resources.</param>
protected virtual void Dispose(bool disposing)
{
// Check to see if Dispose has already been called.
if (!disposed_)
{
// If disposing equals true, dispose all managed
// and unmanaged resources.
if (disposing)
{
discovery_?.Dispose();
}
// Release unmanaged resources. If disposing is false,
// only the following code is executed.
}
disposed_ = true;
}
#endregion
#region Public Methods (Host related)
/// <summary>
/// Returns a list of host names which could contain OPC servers.
/// </summary>
/// <returns>List of available network host names.</returns>
public static List<string> GetHostNames()
{
return ComUtils.EnumComputers();
}
#endregion
#region Public Methods (Returns a list of OpcServer)
/// <summary>
/// Returns a list of servers that support the specified specification.
/// </summary>
/// <param name="specification">Unique identifier for one OPC specification.</param>
/// <returns>Returns a list of found OPC servers.</returns>
public static List<OpcServer> GetServers(OpcSpecification specification)
{
var identity = new OpcUserIdentity("", "");
return GetServers(specification, null, identity);
}
/// <summary>
/// Returns a list of servers that support the specified specification.
/// </summary>
/// <param name="specification">Unique identifier for one OPC specification.</param>
/// <param name="discoveryServerUrl">The URL of the discovery server to be used.</param>
/// <returns>Returns a list of found OPC servers.</returns>
public static List<OpcServer> GetServers(OpcSpecification specification, string discoveryServerUrl)
{
var identity = new OpcUserIdentity("", "");
return GetServers(specification, discoveryServerUrl, identity);
}
/// <summary>
/// Returns a list of servers that support the specified specification.
/// </summary>
/// <param name="specification">Unique identifier for one OPC specification.</param>
/// <param name="discoveryServerUrl">The URL of the discovery server to be used.</param>
/// <param name="identity">The user identity to use when discovering the servers.</param>
/// <returns>Returns a list of found OPC servers.</returns>
public static List<OpcServer> GetServers(OpcSpecification specification, string discoveryServerUrl, OpcUserIdentity identity)
{
var serverList = new List<OpcServer>();
var discovery = specification == OpcSpecification.OPC_AE_10 || (specification == OpcSpecification.OPC_DA_20 ||
specification == OpcSpecification.OPC_DA_30) || specification == OpcSpecification.OPC_HDA_10;
if (discovery)
{
if (discovery_ == null || hostName_ != discoveryServerUrl)
{
discovery_?.Dispose();
hostName_ = discoveryServerUrl;
discovery_ = new ServerEnumerator();
}
var servers = discovery_.GetAvailableServers(specification);
if (servers != null)
{
foreach (var server in servers)
{
serverList.Add(server);
}
}
}
return serverList;
}
#endregion
#region Public Methods (Returns OpcServer object for a specific URL)
/// <summary>
/// Creates a server object for the specified URL.
/// </summary>
/// <param name="url">The OpcUrl of the OPC server.</param>
/// <returns>The OpcServer object.</returns>
public static OpcServer GetServer(OpcUrl url)
{
if (url == null) throw new ArgumentNullException(nameof(url));
OpcServer server = null;
// create an unconnected server object for COM based servers.
// DA
if (string.CompareOrdinal(url.Scheme, OpcUrlScheme.DA) == 0)
{
server = new Technosoftware.DaAeHdaClient.Da.TsCDaServer(new Factory(), url);
}
// AE
else if (string.CompareOrdinal(url.Scheme, OpcUrlScheme.AE) == 0)
{
server = new Technosoftware.DaAeHdaClient.Ae.TsCAeServer(new Factory(), url);
}
// HDA
else if (string.CompareOrdinal(url.Scheme, OpcUrlScheme.HDA) == 0)
{
server = new Technosoftware.DaAeHdaClient.Hda.TsCHdaServer(new Factory(), url);
}
// Other specifications not supported yet.
if (server == null)
{
throw new NotSupportedException(url.Scheme);
}
return server;
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,599 @@
#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 Technosoftware.DaAeHdaClient.Com.Utilities;
using Technosoftware.DaAeHdaClient.Utilities;
using Technosoftware.OpcRcw.Comn;
#endregion
namespace Technosoftware.DaAeHdaClient.Com
{
/// <summary>
/// An in-process wrapper for a remote OPC COM server (not thread safe).
/// </summary>
internal class Server : IOpcServer
{
#region Fields
/// <summary>
/// The COM server wrapped by the object.
/// </summary>
protected object server_;
/// <summary>
/// The URL containing host, prog id and clsid information for The remote server.
/// </summary>
protected OpcUrl url_;
/// <summary>
/// A connect point with the COM server.
/// </summary>
private ConnectionPoint connection_;
/// <summary>
/// The internal object that implements the IOPCShutdown interface.
/// </summary>
private Callback callback_;
/// <summary>
/// The synchronization object for server access
/// </summary>
private static volatile object lock_ = new object();
private int outstandingCalls_;
#endregion
#region Constructors
/// <summary>
/// Initializes the object.
/// </summary>
internal Server()
{
url_ = null;
server_ = null;
callback_ = new Callback(this);
}
/// <summary>
/// Initializes the object with the specifed COM server.
/// </summary>
internal Server(OpcUrl url, object server)
{
if (url == null) throw new ArgumentNullException(nameof(url));
url_ = (OpcUrl)url.Clone();
server_ = server;
callback_ = new Callback(this);
}
#endregion
#region IDisposable Members
/// <summary>
/// The finalizer.
/// </summary>
~Server()
{
Dispose(false);
}
/// <summary>
/// Releases unmanaged resources held by the object.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="disposing">If true managed and unmanaged resources can be disposed. If false only unmanaged resources.</param>
protected virtual void Dispose(bool disposing)
{
if (!disposed_)
{
lock (this)
{
if (disposing)
{
// Free other state (managed objects).
// close callback connections.
if (connection_ != null)
{
connection_.Dispose();
connection_ = null;
}
}
DisableDCOMCallCancellation();
// Free your own state (unmanaged objects).
// Set large fields to null.
// release server.
Interop.ReleaseServer(server_);
server_ = null;
}
disposed_ = true;
}
}
private bool disposed_;
#endregion
#region Public Methods
/// <summary>
/// Connects to the server with the specified URL and credentials.
/// </summary>
public virtual void Initialize(OpcUrl url, OpcConnectData connectData)
{
if (url == null) throw new ArgumentNullException(nameof(url));
lock (lock_)
{
// re-connect only if the url has changed or has not been initialized.
if (url_ == null || !url_.Equals(url))
{
// release the current server.
if (server_ != null)
{
Uninitialize();
}
// instantiate a new server.
server_ = (IOPCCommon)Factory.Connect(url, connectData);
}
// save url.
url_ = (OpcUrl)url.Clone();
}
}
/// <summary>
/// Releases The remote server.
/// </summary>
public virtual void Uninitialize()
{
lock (lock_)
{
Dispose();
}
}
/// <summary>
/// Allows the client to optionally register a client name with the server. This is included primarily for debugging purposes. The recommended behavior is that the client set his Node name and EXE name here.
/// </summary>
public virtual void SetClientName(string clientName)
{
try
{
((IOPCCommon)server_).SetClientName(clientName);
}
catch (Exception e)
{
throw Utilities.Interop.CreateException("IOPCCommon.SetClientName", e);
}
}
/// <summary>
/// Allows cancellation control of DCOM callbacks to the server - by default DCOM calls will wait the default DCOM timeout
/// to fail - this method allows for tigher control of the timeout to wait. Note that DOCM calls can only be controlled
/// on a COM Single Threaded Apartment thread - use [STAThread] attribute on your application entry point or use Thread SetThreadApartment
/// before the thread the server is operating on is created to STA.
/// </summary>
/// <param name="timeout">The DCOM call timeout - uses the default timeout if not specified</param>
public void EnableDCOMCallCancellation(TimeSpan timeout = default)
{
DCOMCallWatchdog.Enable(timeout);
}
/// <summary>
/// Disables cancellation control of DCOM calls to the server
/// </summary>
public void DisableDCOMCallCancellation()
{
DCOMCallWatchdog.Disable();
}
#endregion
#region IOpcServer Members
/// <summary>
/// An event to receive server shutdown notifications.
/// </summary>
public virtual event OpcServerShutdownEventHandler ServerShutdownEvent
{
add
{
lock (lock_)
{
try
{
Advise();
callback_.ServerShutdown += value;
}
catch
{
// shutdown not supported.
}
}
}
remove
{
lock (lock_)
{
callback_.ServerShutdown -= value;
Unadvise();
}
}
}
/// <summary>
/// The locale used in any error messages or results returned to the client.
/// </summary>
/// <returns>The locale name in the format "[languagecode]-[country/regioncode]".</returns>
public virtual string GetLocale()
{
lock (this)
{
if (server_ == null) throw new NotConnectedException();
try
{
((IOPCCommon)server_).GetLocaleID(out var localeId);
return Interop.GetLocale(localeId);
}
catch (Exception e)
{
throw Interop.CreateException("IOPCCommon.GetLocaleID", e);
}
}
}
/// <summary>
/// Sets the locale used in any error messages or results returned to the client.
/// </summary>
/// <param name="locale">The locale name in the format "[languagecode]-[country/regioncode]".</param>
/// <returns>A locale that the server supports and is the best match for the requested locale.</returns>
public virtual string SetLocale(string locale)
{
lock (this)
{
if (server_ == null) throw new NotConnectedException();
var lcid = Interop.GetLocale(locale);
try
{
((IOPCCommon)server_).SetLocaleID(lcid);
}
catch (Exception e)
{
if (lcid != 0)
{
throw Interop.CreateException("IOPCCommon.SetLocaleID", e);
}
// use LOCALE_SYSTEM_DEFAULT if the server does not support the Neutral LCID.
try { ((IOPCCommon)server_).SetLocaleID(0x800); }
catch { }
}
return GetLocale();
}
}
/// <summary>
/// Returns the locales supported by the server
/// </summary>
/// <remarks>The first element in the array must be the default locale for the server.</remarks>
/// <returns>An array of locales with the format "[languagecode]-[country/regioncode]".</returns>
public virtual string[] GetSupportedLocales()
{
lock (lock_)
{
if (server_ == null) throw new NotConnectedException();
try
{
var count = 0;
var pLocaleIDs = IntPtr.Zero;
((IOPCCommon)server_).QueryAvailableLocaleIDs(out count, out pLocaleIDs);
var localeIDs = Interop.GetInt32s(ref pLocaleIDs, count, true);
if (localeIDs != null)
{
var locales = new ArrayList();
foreach (var localeID in localeIDs)
{
try { locales.Add(Interop.GetLocale(localeID)); }
catch { }
}
return (string[])locales.ToArray(typeof(string));
}
return null;
}
catch
{
//throw Interop.CreateException("IOPCCommon.QueryAvailableLocaleIDs", e);
return null;
}
}
}
/// <summary>
/// Returns the localized text for the specified result code.
/// </summary>
/// <param name="locale">The locale name in the format "[languagecode]-[country/regioncode]".</param>
/// <param name="resultId">The result code identifier.</param>
/// <returns>A message localized for the best match for the requested locale.</returns>
public virtual string GetErrorText(string locale, OpcResult resultId)
{
lock (lock_)
{
if (server_ == null) throw new NotConnectedException();
try
{
var currentLocale = GetLocale();
if (currentLocale != locale)
{
SetLocale(locale);
}
((IOPCCommon)server_).GetErrorString(resultId.Code, out var errorText);
if (currentLocale != locale)
{
SetLocale(currentLocale);
}
return errorText;
}
catch (Exception e)
{
throw Utilities.Interop.CreateException("IOPCServer.GetErrorString", e);
}
}
}
#endregion
#region Protected Members
/// <summary>
/// Releases all references to the server.
/// </summary>
protected virtual void ReleaseServer()
{
lock (lock_)
{
SafeNativeMethods.ReleaseServer(server_);
server_ = null;
}
}
/// <summary>
/// Checks if the server supports the specified interface.
/// </summary>
/// <typeparam name="T">The interface to check.</typeparam>
/// <returns>True if the server supports the interface.</returns>
protected bool SupportsInterface<T>() where T : class
{
lock (lock_)
{
return server_ is T;
}
}
#endregion
#region COM Call Tracing
/// <summary>
/// Must be called before any COM call.
/// </summary>
/// <typeparam name="T">The interface to used when making the call.</typeparam>
/// <param name="methodName">Name of the method.</param>
/// <param name="isRequiredInterface">if set to <c>true</c> interface is an required interface and and exception is thrown on error.</param>
/// <returns></returns>
protected T BeginComCall<T>(string methodName, bool isRequiredInterface) where T : class
{
return BeginComCall<T>(server_, methodName, isRequiredInterface);
}
/// <summary>
/// Must be called before any COM call.
/// </summary>
/// <typeparam name="T">The interface to used when making the call.</typeparam>
/// <param name="parent">Parent COM object</param>
/// <param name="methodName">Name of the method.</param>
/// <param name="isRequiredInterface">if set to <c>true</c> interface is an required interface and and exception is thrown on error.</param>
/// <returns></returns>
protected T BeginComCall<T>(object parent, string methodName, bool isRequiredInterface) where T : class
{
Utils.Trace(Utils.TraceMasks.ExternalSystem, "{0} called.", methodName);
lock (lock_)
{
outstandingCalls_++;
if (parent == null)
{
if (isRequiredInterface)
{
throw new NotConnectedException();
}
}
var comObject = parent as T;
if (comObject == null)
{
if (isRequiredInterface)
{
throw new NotSupportedException(Utils.Format("OPC Interface '{0}' is a required interface but not supported by the server.", typeof(T).Name));
}
else
{
Utils.Trace(Utils.TraceMasks.ExternalSystem, "OPC Interface '{0}' is not supported by server but it is only an optional one.", typeof(T).Name);
}
}
DCOMCallWatchdog.Set();
return comObject;
}
}
/// <summary>
/// Must called if a COM call returns an unexpected exception.
/// </summary>
/// <param name="methodName">Name of the method.</param>
/// <param name="e">The exception.</param>
/// <remarks>Note that some COM calls are expected to return errors.</remarks>
protected void ComCallError(string methodName, Exception e)
{
SafeNativeMethods.TraceComError(e, methodName);
}
/// <summary>
/// Must be called in the finally block after making a COM call.
/// </summary>
/// <param name="methodName">Name of the method.</param>
protected void EndComCall(string methodName)
{
Utils.Trace(Utils.TraceMasks.ExternalSystem, "{0} completed.", methodName);
lock (lock_)
{
outstandingCalls_--;
DCOMCallWatchdog.Reset();
}
}
#endregion
#region Private Methods
/// <summary>
/// Establishes a connection point callback with the COM server.
/// </summary>
private void Advise()
{
if (connection_ == null)
{
connection_ = new ConnectionPoint(server_, typeof(IOPCShutdown).GUID);
connection_.Advise(callback_);
}
}
/// <summary>
/// Closes a connection point callback with the COM server.
/// </summary>
private void Unadvise()
{
if (connection_ != null)
{
if (connection_.Unadvise() == 0)
{
connection_.Dispose();
connection_ = null;
}
}
}
/// <summary>
/// A class that implements the IOPCShutdown interface.
/// </summary>
private class Callback : IOPCShutdown
{
/// <summary>
/// Initializes the object with the containing subscription object.
/// </summary>
public Callback(Server server)
{
m_server = server;
}
/// <summary>
/// An event to receive server shutdown notificiations.
/// </summary>
public event OpcServerShutdownEventHandler ServerShutdown
{
add { lock (lock_) { m_serverShutdown += value; } }
remove { lock (lock_) { m_serverShutdown -= value; } }
}
/// <summary>
/// A table of item identifiers indexed by internal handle.
/// </summary>
private Server m_server = null;
/// <summary>
/// Raised when data changed callbacks arrive.
/// </summary>
private event OpcServerShutdownEventHandler m_serverShutdown = null;
/// <summary>
/// Called when a shutdown event is received.
/// </summary>
public void ShutdownRequest(string reason)
{
try
{
lock (lock_)
{
if (m_serverShutdown != null)
{
m_serverShutdown(reason);
}
}
}
catch (Exception e)
{
var stack = e.StackTrace;
}
}
}
#endregion
}
}

View File

@@ -0,0 +1,320 @@
#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.Net;
using System.Collections;
using System.Runtime.InteropServices;
using Technosoftware.OpcRcw.Comn;
using Technosoftware.DaAeHdaClient.Ae;
using Technosoftware.DaAeHdaClient.Da;
using Technosoftware.DaAeHdaClient.Hda;
#endregion
namespace Technosoftware.DaAeHdaClient.Com
{
/// <summary>
/// A unique identifier for the result of an operation of an item.
/// </summary>
public class ServerEnumerator : IOpcDiscovery
{
//======================================================================
// IDisposable
/// <summary>
/// Frees all unmanaged resources
/// </summary>
public void Dispose() {}
//======================================================================
// IDiscovery
/// <summary>
/// Enumerates hosts that may be accessed for server discovery.
/// </summary>
public string[] EnumerateHosts()
{
return Interop.EnumComputers();
}
/// <summary>
/// Returns a list of servers that support the specified interface specification.
/// </summary>
public OpcServer[] GetAvailableServers(OpcSpecification specification)
{
return GetAvailableServers(specification, null, null);
}
/// <summary>
/// Returns a list of servers that support the specified specification on the specified host.
/// </summary>
public OpcServer[] GetAvailableServers(OpcSpecification specification, string host, OpcConnectData connectData)
{
lock (this)
{
var credentials = (connectData != null)?connectData.GetCredential(null, null):null;
// connect to the server.
m_server = (IOPCServerList2)Interop.CreateInstance(CLSID, host, credentials, connectData?.UseConnectSecurity ?? false);
m_host = host;
try
{
var servers = new ArrayList();
// convert the interface version to a guid.
var catid = new Guid(specification.Id);
// get list of servers in the specified specification.
IOPCEnumGUID enumerator = null;
m_server.EnumClassesOfCategories(
1,
new Guid[] { catid },
0,
null,
out enumerator);
// read clsids.
var clsids = ReadClasses(enumerator);
// release enumerator object.
Interop.ReleaseServer(enumerator);
enumerator = null;
// fetch class descriptions.
foreach (var clsid in clsids)
{
var factory = new Factory();
try
{
var url = CreateUrl(specification, clsid);
OpcServer server = null;
if (specification == OpcSpecification.OPC_DA_30)
{
server = new TsCDaServer(factory, url);
}
else if (specification == OpcSpecification.OPC_DA_20)
{
server = new TsCDaServer(factory, url);
}
else if (specification == OpcSpecification.OPC_AE_10)
{
server = new TsCAeServer(factory, url);
}
else if (specification == OpcSpecification.OPC_HDA_10)
{
server = new TsCHdaServer(factory, url);
}
servers.Add(server);
}
catch (Exception)
{
// ignore bad clsids.
}
}
return (OpcServer[])servers.ToArray(typeof(OpcServer));
}
finally
{
// free the server.
Interop.ReleaseServer(m_server);
m_server = null;
}
}
}
/// <summary>
/// Looks up the CLSID for the specified prog id on a remote host.
/// </summary>
public Guid CLSIDFromProgID(string progID, string host, OpcConnectData connectData)
{
lock (this)
{
var credentials = (connectData != null)?connectData.GetCredential(null, null):null;
// connect to the server.
m_server = (IOPCServerList2)Interop.CreateInstance(CLSID, host, credentials, connectData?.UseConnectSecurity ?? false);
m_host = host;
// lookup prog id.
Guid clsid;
try
{
m_server.CLSIDFromProgID(progID, out clsid);
}
catch
{
clsid = Guid.Empty;
}
finally
{
Interop.ReleaseServer(m_server);
m_server = null;
}
// return empty guid if prog id not found.
return clsid;
}
}
//======================================================================
// Private Members
/// <summary>
/// The server enumerator COM server.
/// </summary>
private IOPCServerList2 m_server = null;
/// <summary>
/// The host where the servers are being enumerated.
/// </summary>
private string m_host = null;
/// <summary>
/// The ProgID for the OPC Server Enumerator.
/// </summary>
private const string ProgID = "OPC.ServerList.1";
/// <summary>
/// The CLSID for the OPC Server Enumerator.
/// </summary>
private static readonly Guid CLSID = new Guid("13486D51-4821-11D2-A494-3CB306C10000");
//======================================================================
// Private Methods
/// <summary>
/// Reads the guids from the enumerator.
/// </summary>
private Guid[] ReadClasses(IOPCEnumGUID enumerator)
{
var guids = new ArrayList();
var count = 10;
// create buffer.
var buffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(Guid))*count);
try
{
int fetched;
do
{
try
{
enumerator.Next(count, buffer, out fetched);
var pPos = buffer;
for (var ii = 0; ii < fetched; ii++)
{
var guid = (Guid)Marshal.PtrToStructure(pPos, typeof(Guid));
guids.Add(guid);
pPos = (IntPtr)(pPos.ToInt64() + Marshal.SizeOf(typeof(Guid)));
}
}
catch
{
break;
}
}
while (fetched > 0);
return (Guid[])guids.ToArray(typeof(Guid));
}
finally
{
Marshal.FreeCoTaskMem(buffer);
}
}
/// <summary>
/// Reads the server details from the enumerator.
/// </summary>
OpcUrl CreateUrl(OpcSpecification specification, Guid clsid)
{
// initialize the server url.
var url = new OpcUrl();
url.HostName = m_host;
url.Port = 0;
url.Path = null;
if (specification == OpcSpecification.OPC_DA_30) { url.Scheme = OpcUrlScheme.DA; }
else if (specification == OpcSpecification.OPC_DA_20) { url.Scheme = OpcUrlScheme.DA; }
else if (specification == OpcSpecification.OPC_DA_10) { url.Scheme = OpcUrlScheme.DA; }
else if (specification == OpcSpecification.OPC_AE_10) { url.Scheme = OpcUrlScheme.AE; }
else if (specification == OpcSpecification.OPC_HDA_10) { url.Scheme = OpcUrlScheme.HDA; }
try
{
// fetch class details from the enumerator.
string progID = null;
string description = null;
string verIndProgID = null;
m_server.GetClassDetails(
ref clsid,
out progID,
out description,
out verIndProgID);
// create the server URL path.
if (verIndProgID != null)
{
url.Path = string.Format("{0}/{1}", verIndProgID, "{" + clsid.ToString() + "}");
}
else if (progID != null)
{
url.Path = string.Format("{0}/{1}", progID, "{" + clsid.ToString() + "}");
}
}
catch (Exception)
{
// bad value in registry.
}
finally
{
// default to the clsid if the prog is not known.
if (url.Path == null)
{
url.Path = string.Format("{0}", "{" + clsid.ToString() + "}");
}
}
// return the server url.
return url;
}
}
}

View File

@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>Technosoftware.DaAeHdaClient.Com</AssemblyName>
<TargetFrameworks>net6.0</TargetFrameworks>
<LangVersion>9.0</LangVersion>
<PackageId>Technosoftware.DaAeHdaSolution.DaAeHdaClient.Com</PackageId>
<Description>OPC DA/AE/HDA Client Solution .NET</Description>
<Platforms>AnyCPU</Platforms>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Runtime.InteropServices" Version="4.3.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\OpcRcw\Technosoftware.OpcRcw.csproj" />
<ProjectReference Include="..\DaAeHdaClient\Technosoftware.DaAeHdaClient.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,390 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Technosoftware.DaAeHdaClient.Utilities;
namespace Technosoftware.DaAeHdaClient.Com.Utilities
{
/// <summary>
/// The result of DCOM watchdog
/// </summary>
public enum DCOMWatchdogResult
{
/// <summary>
/// Watchdog has not been set/there is no result
/// </summary>
None = 0,
/// <summary>
/// The Set/Reset cycle was manually completed i.e. the DCOM call did not timeout
/// </summary>
Completed,
/// <summary>
/// No Reset call occurred with the timeout period thus the current DCOM call was automatically cancelled
/// </summary>
TimedOut,
/// <summary>
/// The current DCOM call was manually cancelled
/// </summary>
ManuallyCancelled
}
/// <summary>
/// Watchdog mechanism to allow for cancellation of DCOM calls. Note that this mechanism will only work for a STA thread apartment - the thread on which
/// the watchdog is Set and DCOM calls are made have to be the same thread and the thread apartment model has to be set to STA.
/// </summary>
public static class DCOMCallWatchdog
{
#region Fields
private const int DEFAULT_TIMEOUT_SECONDS = 10;
private static object watchdogLock_ = new object();
private static uint watchDogThreadID_;
private static bool isCancelled_;
private static TimeSpan timeout_ = TimeSpan.Zero; //disabled by default
private static Task watchdogTask_;
private static DCOMWatchdogResult lastWatchdogResult_ = DCOMWatchdogResult.None;
private static DateTime setStart_;
#endregion
#region Properties
/// <summary>
/// The result of the last watchdog set/reset operation
/// </summary>
public static DCOMWatchdogResult LastWatchdogResult
{
get { return lastWatchdogResult_; }
}
/// <summary>
/// The current native thread ID on which the watchdog has been enabled
/// </summary>
public static uint WatchDogThreadID
{
get => watchDogThreadID_;
}
/// <summary>
/// Indicates if the watchdog mechanism is active or not
/// </summary>
public static bool IsEnabled
{
get => timeout_ != TimeSpan.Zero;
}
/// <summary>
/// Indicates if the watchdog has been set and is busy waiting for a call completion Reset to be called or a timeout to occur.
/// </summary>
public static bool IsSet
{
get => WatchDogThreadID != 0;
}
/// <summary>
/// Indicates if the watchdog was cancelled due to a timeout
/// </summary>
public static bool IsCancelled
{
get => isCancelled_;
}
/// <summary>
/// The watchdog timeout timespan
/// </summary>
public static TimeSpan Timeout
{
get => timeout_;
set
{
Enable(value);
}
}
#endregion
#region Methods
/// <summary>
/// Enables the Watchdog mechanism. This can be called from any thread and does not have to be the DCOM call originator thread.
/// Uses the default call timeout.
/// </summary>
public static void Enable()
{
Enable(TimeSpan.FromSeconds(DEFAULT_TIMEOUT_SECONDS));
}
/// <summary>
/// Enables the Watchdog mechanism. This can be called from any thread and does not have to be the DCOM call originator thread.
/// </summary>
/// <param name="timeout">The maximum time to wait for a DCOM call to succeed before it is cancelled. Note that DCOM will typically timeout
/// between 1-2 minutes, depending on the OS</param>
public static void Enable(TimeSpan timeout)
{
if (timeout == TimeSpan.Zero)
{
timeout = TimeSpan.FromSeconds(DEFAULT_TIMEOUT_SECONDS);
}
lock (watchdogLock_)
{
timeout_ = timeout;
}
watchdogTask_ = Task.Run(() => WatchdogTask());
}
/// <summary>
/// Disables the watchdog mechanism and stops any call cancellations.
/// </summary>
/// <returns>True if enabled and now disabled, otherwise false</returns>
public static bool Disable()
{
lock (watchdogLock_)
{
if (IsEnabled)
{
timeout_ = TimeSpan.Zero;
return true;
}
else
{
return false;
}
}
}
/// <summary>
/// Sets the watchdog timer active on the current thread. If Reset is not called within the timeout period, any current thread DCOM call will be cancelled. The
/// calling thread must be the originator of the DCOM call and must be an STA thread.
/// </summary>
/// <returns>True if the watchdog set succeeds or was already set for the current thread, else false if the watchdog is not enabled.</returns>
public static bool Set()
{
if (IsEnabled)
{
var apartmentState = Thread.CurrentThread.GetApartmentState();
if (apartmentState != ApartmentState.STA)
{
throw new InvalidOperationException("COM calls can only be cancelled on a COM STA apartment thread - use [STAThread] attibute or set the state of the thread on creation");
}
lock (watchdogLock_)
{
var threadId = Interop.GetCurrentThreadId();
if (IsSet)
{
if (threadId != watchDogThreadID_)
{
throw new InvalidOperationException($"Attempt to set call cancellation on different thread [{threadId}] to where it was already enabled [{watchDogThreadID_}]");
}
}
else
{
isCancelled_ = false;
watchDogThreadID_ = 0;
lastWatchdogResult_ = DCOMWatchdogResult.None;
//enable DCOM call cancellation for duration of the watchdog
var hresult = Interop.CoEnableCallCancellation(IntPtr.Zero);
if (hresult == 0)
{
setStart_ = DateTime.UtcNow;
watchDogThreadID_ = threadId;
Utils.Trace(Utils.TraceMasks.Information, $"COM call cancellation on thread [{watchDogThreadID_}] was set with timeout [{timeout_.TotalSeconds} seconds]");
}
else
{
throw new Exception($"Failed to set COM call cancellation (HResult = {hresult})");
}
}
}
return true;
}
else
{
return false;
}
}
/// <summary>
/// Refreshes the watchdog activity timer to now, effectively resetting the time to wait.
/// </summary>
/// <returns>True if the watchdog time was updated, else False if the watchdog timer is not Enabled or Set</returns>
public static bool Update()
{
if (IsEnabled)
{
lock (watchdogLock_)
{
if (IsSet)
{
setStart_ = DateTime.UtcNow;
return true;
}
else
{
return false;
}
}
}
else
{
return false;
}
}
/// <summary>
/// Resets the watchdog timer for the current thread. This should be called after a DCOM call returns to indicate the call succeeded, and thus cancelling the
/// watchdog timer.
/// </summary>
/// <returns>True if the watchdog timer was reset for the current thread, else False if the timer was not set for the thread of the watchdog is not enabled.</returns>
public static bool Reset()
{
if (IsEnabled)
{
lock (watchdogLock_)
{
if (IsSet)
{
var threadId = Interop.GetCurrentThreadId();
if (threadId == watchDogThreadID_)
{
if (!IsCancelled)
{
lastWatchdogResult_ = DCOMWatchdogResult.Completed;
}
watchDogThreadID_ = 0;
isCancelled_ = false;
//disable DCOM call cancellation
var hresult = Interop.CoDisableCallCancellation(IntPtr.Zero);
Utils.Trace(Utils.TraceMasks.Information, $"COM call cancellation on thread [{watchDogThreadID_}] was reset [HRESULT = {hresult}]");
}
else
{
throw new Exception($"COM call cancellation cannot be reset from different thread [{threadId}] it was set on [{watchDogThreadID_}]");
}
}
}
return true;
}
else
{
return false;
}
}
/// <summary>
/// Allows for manual cancellation of the current DCOM call
/// </summary>
/// <returns></returns>
public static bool Cancel()
{
return Cancel(DCOMWatchdogResult.ManuallyCancelled);
}
/// <summary>
/// Cancels the current DCOM call if there is one active
/// </summary>
/// <param name="reason"></param>
/// <returns>The reason for the cancellation</returns>
private static bool Cancel(DCOMWatchdogResult reason)
{
if (IsEnabled)
{
lock (watchdogLock_)
{
if (!IsCancelled && IsSet)
{
isCancelled_ = true;
//cancel the current DCOM call immediately
var hresult = Interop.CoCancelCall(watchDogThreadID_, 0);
Utils.Trace(Utils.TraceMasks.Information, $"COM call on thread [{watchDogThreadID_}] was cancelled [HRESULT = {hresult}]");
lastWatchdogResult_ = reason;
return true;
}
else
{
return false;
}
}
}
else
{
return false;
}
}
/// <summary>
/// The Watchdog Task is a seperate thread that is activated when the Watchdog is enabled. It checks the time since the last Set was called and
/// then cancels the current DCOM call automatically if Reset is not called within the timeout period.
/// </summary>
private static void WatchdogTask()
{
while (IsEnabled)
{
try
{
if (IsSet & !IsCancelled)
{
if (TimeElapsed(setStart_) >= timeout_)
{
Utils.Trace(Utils.TraceMasks.Information, $"Sync call watchdog for thread [{watchDogThreadID_}] timed out - cancelling current call...");
Cancel(DCOMWatchdogResult.TimedOut);
}
}
}
catch (Exception e)
{
Utils.Trace(Utils.TraceMasks.Error, $"Error in Sync call watchdog thread : {e.ToString()}");
}
finally
{
Thread.Sleep(1);
}
}
}
private static TimeSpan TimeElapsed(DateTime startTime)
{
var now = DateTime.UtcNow;
startTime = startTime.ToUniversalTime();
if (startTime > now)
{
return startTime - now;
}
else
{
return now - startTime;
}
}
#endregion
}
}

File diff suppressed because it is too large Load Diff