Fix
This commit is contained in:
876
Technosoftware/DaAeHdaClient.Com/Ae/Interop.cs
Normal file
876
Technosoftware/DaAeHdaClient.Com/Ae/Interop.cs
Normal 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;
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
51
Technosoftware/DaAeHdaClient.Com/Ae/Result.cs
Normal file
51
Technosoftware/DaAeHdaClient.Com/Ae/Result.cs
Normal 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
|
||||
}
|
||||
}
|
||||
1575
Technosoftware/DaAeHdaClient.Com/Ae/Server.cs
Normal file
1575
Technosoftware/DaAeHdaClient.Com/Ae/Server.cs
Normal file
File diff suppressed because it is too large
Load Diff
610
Technosoftware/DaAeHdaClient.Com/Ae/Subscription.cs
Normal file
610
Technosoftware/DaAeHdaClient.Com/Ae/Subscription.cs
Normal 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
|
||||
}
|
||||
}
|
||||
97
Technosoftware/DaAeHdaClient.Com/ApplicationInstance.cs
Normal file
97
Technosoftware/DaAeHdaClient.Com/ApplicationInstance.cs
Normal 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
|
||||
}
|
||||
}
|
||||
2792
Technosoftware/DaAeHdaClient.Com/ComUtils.cs
Normal file
2792
Technosoftware/DaAeHdaClient.Com/ComUtils.cs
Normal file
File diff suppressed because it is too large
Load Diff
107
Technosoftware/DaAeHdaClient.Com/ConnectionPoint.cs
Normal file
107
Technosoftware/DaAeHdaClient.Com/ConnectionPoint.cs
Normal 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_;
|
||||
}
|
||||
}
|
||||
}
|
||||
60
Technosoftware/DaAeHdaClient.Com/Da/BrowsePosition.cs
Normal file
60
Technosoftware/DaAeHdaClient.Com/Da/BrowsePosition.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
906
Technosoftware/DaAeHdaClient.Com/Da/Interop.cs
Normal file
906
Technosoftware/DaAeHdaClient.Com/Da/Interop.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
128
Technosoftware/DaAeHdaClient.Com/Da/Result.cs
Normal file
128
Technosoftware/DaAeHdaClient.Com/Da/Result.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
984
Technosoftware/DaAeHdaClient.Com/Da/Server.cs
Normal file
984
Technosoftware/DaAeHdaClient.Com/Da/Server.cs
Normal 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
|
||||
}
|
||||
}
|
||||
2942
Technosoftware/DaAeHdaClient.Com/Da/Subscription.cs
Normal file
2942
Technosoftware/DaAeHdaClient.Com/Da/Subscription.cs
Normal file
File diff suppressed because it is too large
Load Diff
1649
Technosoftware/DaAeHdaClient.Com/Da20/Server.cs
Normal file
1649
Technosoftware/DaAeHdaClient.Com/Da20/Server.cs
Normal file
File diff suppressed because it is too large
Load Diff
586
Technosoftware/DaAeHdaClient.Com/Da20/Subscription.cs
Normal file
586
Technosoftware/DaAeHdaClient.Com/Da20/Subscription.cs
Normal 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
|
||||
}
|
||||
}
|
||||
128
Technosoftware/DaAeHdaClient.Com/EnumString.cs
Normal file
128
Technosoftware/DaAeHdaClient.Com/EnumString.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
44
Technosoftware/DaAeHdaClient.Com/FILETIME.cs
Normal file
44
Technosoftware/DaAeHdaClient.Com/FILETIME.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
290
Technosoftware/DaAeHdaClient.Com/Factory.cs
Normal file
290
Technosoftware/DaAeHdaClient.Com/Factory.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
441
Technosoftware/DaAeHdaClient.Com/Hda/Browser.cs
Normal file
441
Technosoftware/DaAeHdaClient.Com/Hda/Browser.cs
Normal 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
|
||||
}
|
||||
}
|
||||
591
Technosoftware/DaAeHdaClient.Com/Hda/DataCallback.cs
Normal file
591
Technosoftware/DaAeHdaClient.Com/Hda/DataCallback.cs
Normal 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
|
||||
}
|
||||
}
|
||||
441
Technosoftware/DaAeHdaClient.Com/Hda/Interop.cs
Normal file
441
Technosoftware/DaAeHdaClient.Com/Hda/Interop.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
404
Technosoftware/DaAeHdaClient.Com/Hda/Request.cs
Normal file
404
Technosoftware/DaAeHdaClient.Com/Hda/Request.cs
Normal 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
|
||||
}
|
||||
}
|
||||
68
Technosoftware/DaAeHdaClient.Com/Hda/Result.cs
Normal file
68
Technosoftware/DaAeHdaClient.Com/Hda/Result.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
3473
Technosoftware/DaAeHdaClient.Com/Hda/Server.cs
Normal file
3473
Technosoftware/DaAeHdaClient.Com/Hda/Server.cs
Normal file
File diff suppressed because it is too large
Load Diff
1515
Technosoftware/DaAeHdaClient.Com/Interop.cs
Normal file
1515
Technosoftware/DaAeHdaClient.Com/Interop.cs
Normal file
File diff suppressed because it is too large
Load Diff
204
Technosoftware/DaAeHdaClient.Com/OpcDiscovery.cs
Normal file
204
Technosoftware/DaAeHdaClient.Com/OpcDiscovery.cs
Normal 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
|
||||
|
||||
}
|
||||
}
|
||||
1883
Technosoftware/DaAeHdaClient.Com/SafeNativeMethods.cs
Normal file
1883
Technosoftware/DaAeHdaClient.Com/SafeNativeMethods.cs
Normal file
File diff suppressed because it is too large
Load Diff
599
Technosoftware/DaAeHdaClient.Com/Server.cs
Normal file
599
Technosoftware/DaAeHdaClient.Com/Server.cs
Normal 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
|
||||
}
|
||||
}
|
||||
320
Technosoftware/DaAeHdaClient.Com/ServerEnumerator.cs
Normal file
320
Technosoftware/DaAeHdaClient.Com/ServerEnumerator.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
390
Technosoftware/DaAeHdaClient.Com/Utilities/DCOMCallWatchdog.cs
Normal file
390
Technosoftware/DaAeHdaClient.Com/Utilities/DCOMCallWatchdog.cs
Normal 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
|
||||
|
||||
}
|
||||
}
|
||||
1969
Technosoftware/DaAeHdaClient.Com/Utilities/Interop.cs
Normal file
1969
Technosoftware/DaAeHdaClient.Com/Utilities/Interop.cs
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user