#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; using System.Globalization; using System.Net; using System.Reflection; using System.Runtime.InteropServices; using Technosoftware.DaAeHdaClient.Utilities; #endregion namespace Technosoftware.DaAeHdaClient.Com { /// /// Exposes WIN32 and COM API functions. /// internal class ComUtils { #region NetApi Function Declarations [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] private struct SERVER_INFO_100 { public uint sv100_platform_id; [MarshalAs(UnmanagedType.LPWStr)] public string sv100_name; } private const uint LEVEL_SERVER_INFO_100 = 100; private const uint LEVEL_SERVER_INFO_101 = 101; private const int MAX_PREFERRED_LENGTH = -1; private const uint SV_TYPE_WORKSTATION = 0x00000001; private const uint SV_TYPE_SERVER = 0x00000002; [DllImport("Netapi32.dll")] private static extern int NetServerEnum( IntPtr servername, uint level, out IntPtr bufptr, int prefmaxlen, out int entriesread, out int totalentries, uint servertype, IntPtr domain, IntPtr resume_handle); [DllImport("Netapi32.dll")] private static extern int NetApiBufferFree(IntPtr buffer); /// /// Enumerates computers on the local network. /// public static List EnumComputers() { IntPtr pInfo; int totalEntries; int entriesRead; var result = NetServerEnum( IntPtr.Zero, LEVEL_SERVER_INFO_100, out pInfo, MAX_PREFERRED_LENGTH, out entriesRead, out totalEntries, SV_TYPE_WORKSTATION | SV_TYPE_SERVER, IntPtr.Zero, IntPtr.Zero); if (result != 0) { throw new ApplicationException("NetApi Error = " + string.Format("0x{0:X8}", result)); } var computers = new List(); var pos = pInfo; for (var ii = 0; ii < entriesRead; ii++) { var info = (SERVER_INFO_100)Marshal.PtrToStructure(pos, typeof(SERVER_INFO_100)); computers.Add(info.sv100_name); pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(SERVER_INFO_100))); } NetApiBufferFree(pInfo); return computers; } #endregion #region OLE32 Function/Interface Declarations private const int MAX_MESSAGE_LENGTH = 1024; private const uint FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200; private const uint FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000; [DllImport("Kernel32.dll")] private static extern int FormatMessageW( int dwFlags, IntPtr lpSource, int dwMessageId, int dwLanguageId, IntPtr lpBuffer, int nSize, IntPtr Arguments); [DllImport("Kernel32.dll")] private static extern int GetSystemDefaultLangID(); [DllImport("Kernel32.dll")] private static extern int GetUserDefaultLangID(); /// /// The WIN32 system default locale. /// public const int LOCALE_SYSTEM_DEFAULT = 0x800; /// /// The WIN32 user default locale. /// public const int LOCALE_USER_DEFAULT = 0x400; /// /// The base for the WIN32 FILETIME structure. /// private static readonly DateTime FILETIME_BaseTime = new DateTime(1601, 1, 1); /// /// WIN32 GUID struct declaration. /// [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] private struct GUID { public int Data1; public short Data2; public short Data3; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public byte[] Data4; } /// /// The size, in bytes, of a VARIANT structure. /// private const int VARIANT_SIZE = 0x10; [DllImport("OleAut32.dll")] private static extern int VariantChangeTypeEx( IntPtr pvargDest, IntPtr pvarSrc, int lcid, ushort wFlags, short vt); /// /// Intializes a pointer to a VARIANT. /// [DllImport("oleaut32.dll")] private static extern void VariantInit(IntPtr pVariant); /// /// Frees all memory referenced by a VARIANT stored in unmanaged memory. /// [DllImport("oleaut32.dll")] public static extern void VariantClear(IntPtr pVariant); private const int DISP_E_TYPEMISMATCH = -0x7FFDFFFB; // 0x80020005 private const int DISP_E_OVERFLOW = -0x7FFDFFF6; // 0x8002000A private const int VARIANT_NOVALUEPROP = 0x01; private const int VARIANT_ALPHABOOL = 0x02; // For VT_BOOL to VT_BSTR conversions convert to "True"/"False" instead of [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] private struct SOLE_AUTHENTICATION_SERVICE { public uint dwAuthnSvc; public uint dwAuthzSvc; [MarshalAs(UnmanagedType.LPWStr)] public string pPrincipalName; public int hr; } private const uint RPC_C_AUTHN_NONE = 0; private const uint RPC_C_AUTHN_DCE_PRIVATE = 1; private const uint RPC_C_AUTHN_DCE_PUBLIC = 2; private const uint RPC_C_AUTHN_DEC_PUBLIC = 4; private const uint RPC_C_AUTHN_GSS_NEGOTIATE = 9; private const uint RPC_C_AUTHN_WINNT = 10; private const uint RPC_C_AUTHN_GSS_SCHANNEL = 14; private const uint RPC_C_AUTHN_GSS_KERBEROS = 16; private const uint RPC_C_AUTHN_DPA = 17; private const uint RPC_C_AUTHN_MSN = 18; private const uint RPC_C_AUTHN_DIGEST = 21; private const uint RPC_C_AUTHN_MQ = 100; private const uint RPC_C_AUTHN_DEFAULT = 0xFFFFFFFF; private const uint RPC_C_AUTHZ_NONE = 0; private const uint RPC_C_AUTHZ_NAME = 1; private const uint RPC_C_AUTHZ_DCE = 2; private const uint RPC_C_AUTHZ_DEFAULT = 0xffffffff; private const uint RPC_C_AUTHN_LEVEL_DEFAULT = 0; private const uint RPC_C_AUTHN_LEVEL_NONE = 1; private const uint RPC_C_AUTHN_LEVEL_CONNECT = 2; private const uint RPC_C_AUTHN_LEVEL_CALL = 3; private const uint RPC_C_AUTHN_LEVEL_PKT = 4; private const uint RPC_C_AUTHN_LEVEL_PKT_INTEGRITY = 5; private const uint RPC_C_AUTHN_LEVEL_PKT_PRIVACY = 6; private const uint RPC_C_IMP_LEVEL_ANONYMOUS = 1; private const uint RPC_C_IMP_LEVEL_IDENTIFY = 2; private const uint RPC_C_IMP_LEVEL_IMPERSONATE = 3; private const uint RPC_C_IMP_LEVEL_DELEGATE = 4; private const uint EOAC_NONE = 0x00; private const uint EOAC_MUTUAL_AUTH = 0x01; private const uint EOAC_CLOAKING = 0x10; private const uint EOAC_STATIC_CLOAKING = 0x20; private const uint EOAC_DYNAMIC_CLOAKING = 0x40; private const uint EOAC_SECURE_REFS = 0x02; private const uint EOAC_ACCESS_CONTROL = 0x04; private const uint EOAC_APPID = 0x08; /// If function succeeds, it returns 0(S_OK). Otherwise, it returns an error code. [DllImport("ole32.dll", CharSet = CharSet.Auto, SetLastError = true, CallingConvention = CallingConvention.StdCall)] private static extern int CoInitializeEx( [In, Optional] IntPtr pvReserved, [In] COINIT dwCoInit //DWORD ); [DllImport("ole32.dll")] private static extern int CoInitializeSecurity( IntPtr pSecDesc, int cAuthSvc, SOLE_AUTHENTICATION_SERVICE[] asAuthSvc, IntPtr pReserved1, uint dwAuthnLevel, uint dwImpLevel, IntPtr pAuthList, uint dwCapabilities, IntPtr pReserved3); [DllImport("ole32.dll")] private static extern int CoQueryProxyBlanket( [MarshalAs(UnmanagedType.IUnknown)] object pProxy, ref uint pAuthnSvc, ref uint pAuthzSvc, [MarshalAs(UnmanagedType.LPWStr)] ref string pServerPrincName, ref uint pAuthnLevel, ref uint pImpLevel, ref IntPtr pAuthInfo, ref uint pCapabilities); [DllImport("ole32.dll")] private static extern int CoSetProxyBlanket( [MarshalAs(UnmanagedType.IUnknown)] object pProxy, uint pAuthnSvc, uint pAuthzSvc, IntPtr pServerPrincName, uint pAuthnLevel, uint pImpLevel, IntPtr pAuthInfo, uint pCapabilities); private static readonly IntPtr COLE_DEFAULT_PRINCIPAL = new IntPtr(-1); private static readonly IntPtr COLE_DEFAULT_AUTHINFO = new IntPtr(-1); private enum COINIT : uint //tagCOINIT { COINIT_MULTITHREADED = 0x0, //Initializes the thread for multi-threaded object concurrency. COINIT_APARTMENTTHREADED = 0x2, //Initializes the thread for apartment-threaded object concurrency COINIT_DISABLE_OLE1DDE = 0x4, //Disables DDE for OLE1 support COINIT_SPEED_OVER_MEMORY = 0x8, //Trade memory for speed } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] private struct COSERVERINFO { public uint dwReserved1; [MarshalAs(UnmanagedType.LPWStr)] public string pwszName; public IntPtr pAuthInfo; public uint dwReserved2; }; [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] private struct COAUTHINFO { public uint dwAuthnSvc; public uint dwAuthzSvc; public IntPtr pwszServerPrincName; public uint dwAuthnLevel; public uint dwImpersonationLevel; public IntPtr pAuthIdentityData; public uint dwCapabilities; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] private struct COAUTHIDENTITY { public IntPtr User; public uint UserLength; public IntPtr Domain; public uint DomainLength; public IntPtr Password; public uint PasswordLength; public uint Flags; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] private struct MULTI_QI { public IntPtr iid; [MarshalAs(UnmanagedType.IUnknown)] public object pItf; public uint hr; } private const uint CLSCTX_INPROC_SERVER = 0x1; private const uint CLSCTX_INPROC_HANDLER = 0x2; private const uint CLSCTX_LOCAL_SERVER = 0x4; private const uint CLSCTX_REMOTE_SERVER = 0x10; private const uint CLSCTX_DISABLE_AAA = 0x8000; private static readonly Guid IID_IUnknown = new Guid("00000000-0000-0000-C000-000000000046"); private const uint SEC_WINNT_AUTH_IDENTITY_ANSI = 0x1; private const uint SEC_WINNT_AUTH_IDENTITY_UNICODE = 0x2; [DllImport("ole32.dll")] private static extern void CoCreateInstanceEx( ref Guid clsid, [MarshalAs(UnmanagedType.IUnknown)] object punkOuter, uint dwClsCtx, [In] ref COSERVERINFO pServerInfo, uint dwCount, [In, Out] MULTI_QI[] pResults); [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] private struct LICINFO { public int cbLicInfo; [MarshalAs(UnmanagedType.Bool)] public bool fRuntimeKeyAvail; [MarshalAs(UnmanagedType.Bool)] public bool fLicVerified; } [ComImport] [GuidAttribute("00000001-0000-0000-C000-000000000046")] [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] private interface IClassFactory { void CreateInstance( [MarshalAs(UnmanagedType.IUnknown)] object punkOuter, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, [MarshalAs(UnmanagedType.Interface)] [Out] out object ppvObject); void LockServer( [MarshalAs(UnmanagedType.Bool)] bool fLock); } [ComImport] [GuidAttribute("B196B28F-BAB4-101A-B69C-00AA00341D07")] [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] private interface IClassFactory2 { void CreateInstance( [MarshalAs(UnmanagedType.IUnknown)] object punkOuter, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, [MarshalAs(UnmanagedType.Interface)] [Out] out object ppvObject); void LockServer( [MarshalAs(UnmanagedType.Bool)] bool fLock); void GetLicInfo( [In, Out] ref LICINFO pLicInfo); void RequestLicKey( int dwReserved, [MarshalAs(UnmanagedType.BStr)] string pbstrKey); void CreateInstanceLic( [MarshalAs(UnmanagedType.IUnknown)] object punkOuter, [MarshalAs(UnmanagedType.IUnknown)] object punkReserved, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, [MarshalAs(UnmanagedType.BStr)] string bstrKey, [MarshalAs(UnmanagedType.IUnknown)] [Out] out object ppvObject); } [DllImport("ole32.dll")] private static extern void CoGetClassObject( [MarshalAs(UnmanagedType.LPStruct)] Guid clsid, uint dwClsContext, [In] ref COSERVERINFO pServerInfo, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, [MarshalAs(UnmanagedType.IUnknown)] [Out] out object ppv); private const int LOGON32_PROVIDER_DEFAULT = 0; private const int LOGON32_LOGON_INTERACTIVE = 2; private const int LOGON32_LOGON_NETWORK = 3; private const int SECURITY_ANONYMOUS = 0; private const int SECURITY_IDENTIFICATION = 1; private const int SECURITY_IMPERSONATION = 2; private const int SECURITY_DELEGATION = 3; [DllImport("advapi32.dll", SetLastError = true)] private static extern bool LogonUser( string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken); [DllImport("kernel32.dll", CharSet = CharSet.Auto)] private extern static bool CloseHandle(IntPtr handle); [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] private extern static bool DuplicateToken( IntPtr ExistingTokenHandle, int SECURITY_IMPERSONATION_LEVEL, ref IntPtr DuplicateTokenHandle); #endregion #region ServerInfo Class /// /// A class used to allocate and deallocate the elements of a COSERVERINFO structure. /// class ServerInfo { #region internal interface /// /// Allocates a COSERVERINFO structure. /// public COSERVERINFO Allocate(string hostName, OpcUserIdentity identity) { // initialize server info structure. var serverInfo = new COSERVERINFO(); serverInfo.pwszName = hostName; serverInfo.pAuthInfo = IntPtr.Zero; serverInfo.dwReserved1 = 0; serverInfo.dwReserved2 = 0; // no authentication for default identity if (OpcUserIdentity.IsDefault(identity)) { return serverInfo; } m_hUserName = GCHandle.Alloc(identity.Username, GCHandleType.Pinned); m_hPassword = GCHandle.Alloc(identity.Password, GCHandleType.Pinned); m_hDomain = GCHandle.Alloc(identity.Domain, GCHandleType.Pinned); m_hIdentity = new GCHandle(); // create identity structure. var authIdentity = new COAUTHIDENTITY(); authIdentity.User = m_hUserName.AddrOfPinnedObject(); authIdentity.UserLength = (uint)((identity.Username != null) ? identity.Username.Length : 0); authIdentity.Password = m_hPassword.AddrOfPinnedObject(); authIdentity.PasswordLength = (uint)((identity.Password != null) ? identity.Password.Length : 0); authIdentity.Domain = m_hDomain.AddrOfPinnedObject(); authIdentity.DomainLength = (uint)((identity.Domain != null) ? identity.Domain.Length : 0); authIdentity.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE; m_hIdentity = GCHandle.Alloc(authIdentity, GCHandleType.Pinned); // create authorization info structure. var authInfo = new COAUTHINFO(); authInfo.dwAuthnSvc = RPC_C_AUTHN_WINNT; authInfo.dwAuthzSvc = RPC_C_AUTHZ_NONE; authInfo.pwszServerPrincName = IntPtr.Zero; authInfo.dwAuthnLevel = RPC_C_AUTHN_LEVEL_CONNECT; authInfo.dwImpersonationLevel = RPC_C_IMP_LEVEL_IMPERSONATE; authInfo.pAuthIdentityData = m_hIdentity.AddrOfPinnedObject(); authInfo.dwCapabilities = EOAC_NONE; // EOAC_DYNAMIC_CLOAKING; m_hAuthInfo = GCHandle.Alloc(authInfo, GCHandleType.Pinned); // update server info structure. serverInfo.pAuthInfo = m_hAuthInfo.AddrOfPinnedObject(); return serverInfo; } /// /// Deallocated memory allocated when the COSERVERINFO structure was created. /// public void Deallocate() { if (m_hUserName.IsAllocated) m_hUserName.Free(); if (m_hPassword.IsAllocated) m_hPassword.Free(); if (m_hDomain.IsAllocated) m_hDomain.Free(); if (m_hIdentity.IsAllocated) m_hIdentity.Free(); if (m_hAuthInfo.IsAllocated) m_hAuthInfo.Free(); } #endregion #region Private Members private GCHandle m_hUserName; private GCHandle m_hPassword; private GCHandle m_hDomain; private GCHandle m_hIdentity; private GCHandle m_hAuthInfo; #endregion } #endregion #region Initialization Functions /// /// Initializes COM security. /// public static void InitializeSecurity() { var error = CoInitializeSecurity( IntPtr.Zero, -1, null, IntPtr.Zero, RPC_C_AUTHN_LEVEL_CONNECT, RPC_C_IMP_LEVEL_IMPERSONATE, IntPtr.Zero, EOAC_DYNAMIC_CLOAKING, IntPtr.Zero); // this call will fail in the debugger if the // 'Debug | Enable Visual Studio Hosting Process' // option is checked in the project properties. if (error != 0) { // throw new ExternalException("CoInitializeSecurity: " + GetSystemMessage(error), error); } } /// /// Determines if the host is the local host. /// private static bool IsLocalHost(string hostName) { // lookup requested host. var requestedHost = Dns.GetHostEntry(hostName); if (requestedHost == null || requestedHost.AddressList == null) { return true; } // check for loopback. for (var ii = 0; ii < requestedHost.AddressList.Length; ii++) { var requestedIP = requestedHost.AddressList[ii]; if (requestedIP == null || requestedIP.Equals(IPAddress.Loopback)) { return true; } } // lookup local host. var localHost = Dns.GetHostEntry(Dns.GetHostName()); if (localHost == null || localHost.AddressList == null) { return false; } // check for localhost. for (var ii = 0; ii < requestedHost.AddressList.Length; ii++) { var requestedIP = requestedHost.AddressList[ii]; for (var jj = 0; jj < localHost.AddressList.Length; jj++) { if (requestedIP.Equals(localHost.AddressList[jj])) { return true; } } } // must be remote. return false; } /// /// Creates an instance of a COM server using the specified license key. /// public static object CreateInstance(Guid clsid, string hostName, OpcUserIdentity identity) { return CreateInstance1(clsid, hostName, identity); } /// /// Creates an instance of a COM server. /// public static object CreateInstance1(Guid clsid, string hostName, OpcUserIdentity identity) { var serverInfo = new ServerInfo(); var coserverInfo = serverInfo.Allocate(hostName, identity); var hIID = GCHandle.Alloc(IID_IUnknown, GCHandleType.Pinned); var results = new MULTI_QI[1]; results[0].iid = hIID.AddrOfPinnedObject(); results[0].pItf = null; results[0].hr = 0; try { // check whether connecting locally or remotely. var clsctx = CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER; if (!string.IsNullOrEmpty(hostName) && hostName != "localhost") { clsctx = CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER; } // create an instance. CoCreateInstanceEx( ref clsid, null, clsctx, ref coserverInfo, 1, results); } finally { if (hIID.IsAllocated) hIID.Free(); serverInfo.Deallocate(); } if (results[0].hr != 0) { throw new OpcResultException(new OpcResult(OpcResult.CONNECT_E_NOCONNECTION.Code, OpcResult.FuncCallType.SysFuncCall, null), string.Format("Could not create COM server '{0}' on host '{1}'. Reason: {2}.", clsid, hostName, GetSystemMessage((int)results[0].hr, LOCALE_SYSTEM_DEFAULT))); } return results[0].pItf; } // COM impersonation is a nice feature but variations between behavoirs on different // windows platforms make it virtually impossible to support. This code is left here // in case it becomes a critical requirement in the future. #if COM_IMPERSONATION_SUPPORT /// /// Returns the WindowsIdentity associated with a UserIdentity. /// public static WindowsPrincipal GetPrincipalFromUserIdentity(UserIdentity user) { if (UserIdentity.IsDefault(user)) { return null; } // validate the credentials. IntPtr token = IntPtr.Zero; bool result = LogonUser( user.Username, user.Domain, user.Password, LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT, ref token); if (!result) { throw new OpcResultException(new OpcResult((int)OpcResult.CONNECT_E_NOCONNECTION.Code, OpcResult.FuncCallType.SysFuncCall, null), String.Format("Could not logon as user '{0}'. Reason: {1}.", user.Username, GetSystemMessage(Marshal.GetLastWin32Error(), LOCALE_SYSTEM_DEFAULT))); throw OpcResultException.Create( StatusCodes.BadIdentityTokenRejected, "Could not logon as user '{0}'. Reason: {1}.", user.Username, GetSystemMessage(Marshal.GetLastWin32Error(), LOCALE_SYSTEM_DEFAULT)); } try { // create the windows identity. WindowsIdentity identity = new WindowsIdentity(token); // validate the identity. identity.Impersonate(); // return a principal. return new WindowsPrincipal(identity); } finally { CloseHandle(token); } } /// /// Sets the security settings for the proxy. /// public static void SetProxySecurity(object server, UserIdentity user) { // allocate the GCHandle hUserName = GCHandle.Alloc(user.Username, GCHandleType.Pinned); GCHandle hPassword = GCHandle.Alloc(user.Password, GCHandleType.Pinned); GCHandle hDomain = GCHandle.Alloc(user.Domain, GCHandleType.Pinned); GCHandle hIdentity = new GCHandle(); // create identity structure. COAUTHIDENTITY authIdentity = new COAUTHIDENTITY(); authIdentity.User = hUserName.AddrOfPinnedObject(); authIdentity.UserLength = (uint)((user.Username != null) ? user.Username.Length : 0); authIdentity.Password = hPassword.AddrOfPinnedObject(); authIdentity.PasswordLength = (uint)((user.Password != null) ? user.Password.Length : 0); authIdentity.Domain = hDomain.AddrOfPinnedObject(); authIdentity.DomainLength = (uint)((user.Domain != null) ? user.Domain.Length : 0); authIdentity.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE; hIdentity = GCHandle.Alloc(authIdentity, GCHandleType.Pinned); try { SetProxySecurity(server, hIdentity.AddrOfPinnedObject()); } finally { hUserName.Free(); hPassword.Free(); hDomain.Free(); hIdentity.Free(); } } /// /// Sets the security settings for the proxy. /// public static void SetProxySecurity(object server, IntPtr pAuthInfo) { // get the existing proxy settings. uint pAuthnSvc = 0; uint pAuthzSvc = 0; string pServerPrincName = ""; uint pAuthnLevel = 0; uint pImpLevel = 0; IntPtr pAuthInfo2 = IntPtr.Zero; uint pCapabilities = 0; CoQueryProxyBlanket( server, ref pAuthnSvc, ref pAuthzSvc, ref pServerPrincName, ref pAuthnLevel, ref pImpLevel, ref pAuthInfo2, ref pCapabilities); pAuthnSvc = RPC_C_AUTHN_WINNT; pAuthzSvc = RPC_C_AUTHZ_NONE; pAuthnLevel = RPC_C_AUTHN_LEVEL_CONNECT; pImpLevel = RPC_C_IMP_LEVEL_IMPERSONATE; pCapabilities = EOAC_DYNAMIC_CLOAKING; // update proxy security settings. CoSetProxyBlanket( server, pAuthnSvc, pAuthzSvc, COLE_DEFAULT_PRINCIPAL, pAuthnLevel, pImpLevel, pAuthInfo, pCapabilities); } /// /// Creates an instance of a COM server using the specified license key. /// public static object CreateInstance2(Guid clsid, string hostName, UserIdentity identity) { // validate the host name before proceeding (exception thrown if host is not valid). bool isLocalHost = IsLocalHost(hostName); // allocate the connection info. ServerInfo serverInfo = new ServerInfo(); COSERVERINFO coserverInfo = serverInfo.Allocate(hostName, identity); object instance = null; IClassFactory factory = null; try { // create the factory. object server = null; CoGetClassObject( clsid, (isLocalHost)?CLSCTX_LOCAL_SERVER:CLSCTX_REMOTE_SERVER, ref coserverInfo, IID_IUnknown, out server); // SetProxySecurity(server, coserverInfo.pAuthInfo); factory = (IClassFactory)server; // check for valid factory. if (factory == null) { throw OpcResultException.Create(StatusCodes.BadCommunicationError, "Could not load IClassFactory for COM server '{0}' on host '{1}'.", clsid, hostName); } // SetProxySecurity(factory, coserverInfo.pAuthInfo); factory.CreateInstance(null, IID_IUnknown, out instance); // SetProxySecurity(instance, coserverInfo.pAuthInfo); } finally { serverInfo.Deallocate(); } return instance; } /// /// Creates an instance of a COM server using the specified license key. /// public static object CreateInstanceWithLicenseKey(Guid clsid, string hostName, UserIdentity identity, string licenseKey) { ServerInfo serverInfo = new ServerInfo(); COSERVERINFO coserverInfo = serverInfo.Allocate(hostName, identity); object instance = null; IClassFactory2 factory = null; try { // check whether connecting locally or remotely. uint clsctx = CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER; if (hostName != null && hostName.Length > 0) { clsctx = CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER; } // get the class factory. object server = null; CoGetClassObject( clsid, clsctx, ref coserverInfo, typeof(IClassFactory2).GUID, out server); // SetProxySecurity(server, coserverInfo.pAuthInfo); factory = (IClassFactory2)server; // check for valid factory. if (factory == null) { throw OpcResultException.Create(StatusCodes.BadCommunicationError, "Could not load IClassFactory2 for COM server '{0}' on host '{1}'.", clsid, hostName); } // SetProxySecurity(factory, coserverInfo.pAuthInfo); // create instance. factory.CreateInstanceLic( null, null, IID_IUnknown, licenseKey, out instance); // SetProxySecurity(instance, coserverInfo.pAuthInfo); } finally { serverInfo.Deallocate(); ComUtils.ReleaseServer(factory); } return instance; } #endif #endregion #region Conversion Functions #if TEST /// /// Tests if the specified string matches the specified pattern. /// public static bool Match(string target, string pattern, bool caseSensitive) { // an empty pattern always matches. if (pattern == null || pattern.Length == 0) { return true; } // an empty string never matches. if (target == null || target.Length == 0) { return false; } // check for exact match if (caseSensitive) { if (target == pattern) { return true; } } else { if (target.ToLower() == pattern.ToLower()) { return true; } } char c; char p; char l; int pIndex = 0; int tIndex = 0; while (tIndex < target.Length && pIndex < pattern.Length) { p = ConvertCase(pattern[pIndex++], caseSensitive); if (pIndex > pattern.Length) { return (tIndex >= target.Length); // if end of string true } switch (p) { // match zero or more char. case '*': { while (pIndex < pattern.Length && pattern[pIndex] == '*') { pIndex++; } while (tIndex < target.Length) { if (Match(target.Substring(tIndex++), pattern.Substring(pIndex), caseSensitive)) { return true; } } return Match(target, pattern.Substring(pIndex), caseSensitive); } // match any one char. case '?': { // check if end of string when looking for a single character. if (tIndex >= target.Length) { return false; } // check if end of pattern and still string data left. if (pIndex >= pattern.Length && tIndex < target.Length-1) { return false; } tIndex++; break; } // match char set case '[': { c = ConvertCase(target[tIndex++], caseSensitive); if (tIndex > target.Length) { return false; // syntax } l = '\0'; // match a char if NOT in set [] if (pattern[pIndex] == '!') { ++pIndex; p = ConvertCase(pattern[pIndex++], caseSensitive); while (pIndex < pattern.Length) { if (p == ']') // if end of char set, then { break; // no match found } if (p == '-') { // check a range of chars? p = ConvertCase(pattern[pIndex], caseSensitive); // get high limit of range if (pIndex > pattern.Length || p == ']') { return false; // syntax } if (c >= l && c <= p) { return false; // if in range, return false } } l = p; if (c == p) // if char matches this element { return false; // return false } p = ConvertCase(pattern[pIndex++], caseSensitive); } } // match if char is in set [] else { p = ConvertCase(pattern[pIndex++], caseSensitive); while (pIndex < pattern.Length) { if (p == ']') // if end of char set, then no match found { return false; } if (p == '-') { // check a range of chars? p = ConvertCase(pattern[pIndex], caseSensitive); // get high limit of range if (pIndex > pattern.Length || p == ']') { return false; // syntax } if (c >= l && c <= p) { break; // if in range, move on } } l = p; if (c == p) // if char matches this element move on { break; } p = ConvertCase(pattern[pIndex++], caseSensitive); } while (pIndex < pattern.Length && p != ']') // got a match in char set skip to end of set { p = pattern[pIndex++]; } } break; } // match digit. case '#': { c = target[tIndex++]; if (!Char.IsDigit(c)) { return false; // not a digit } break; } // match exact char. default: { c = ConvertCase(target[tIndex++], caseSensitive); if (c != p) // check for exact char { return false; // not a match } // check if end of pattern and still string data left. if (pIndex >= pattern.Length && tIndex < target.Length-1) { return false; } break; } } } return true; } // ConvertCase private static char ConvertCase(char c, bool caseSensitive) { return (caseSensitive)?c:Char.ToUpper(c); } /// /// Unmarshals and frees an array of HRESULTs. /// public static int[] GetStatusCodes(ref IntPtr pArray, int size, bool deallocate) { if (pArray == IntPtr.Zero || size <= 0) { return null; } // unmarshal HRESULT array. int[] output = new int[size]; Marshal.Copy(pArray, output, 0, size); if (deallocate) { Marshal.FreeCoTaskMem(pArray); pArray = IntPtr.Zero; } return output; } /// /// Unmarshals and frees an array of 32 bit integers. /// public static int[] GetInt32s(ref IntPtr pArray, int size, bool deallocate) { if (pArray == IntPtr.Zero || size <= 0) { return null; } int[] array = new int[size]; Marshal.Copy(pArray, array, 0, size); if (deallocate) { Marshal.FreeCoTaskMem(pArray); pArray = IntPtr.Zero; } return array; } /// /// Unmarshals and frees an array of 32 bit integers. /// public static int[] GetUInt32s(ref IntPtr pArray, int size, bool deallocate) { if (pArray == IntPtr.Zero || size <= 0) { return null; } int[] array = new int[size]; Marshal.Copy(pArray, array, 0, size); if (deallocate) { Marshal.FreeCoTaskMem(pArray); pArray = IntPtr.Zero; } return array; } /// /// Allocates and marshals an array of 32 bit integers. /// public static IntPtr GetInt32s(int[] input) { IntPtr output = IntPtr.Zero; if (input != null) { output = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(Int32))*input.Length); Marshal.Copy(input, 0, output, input.Length); } return output; } /// /// Unmarshals and frees a array of 16 bit integers. /// public static short[] GetInt16s(ref IntPtr pArray, int size, bool deallocate) { if (pArray == IntPtr.Zero || size <= 0) { return null; } short[] array = new short[size]; Marshal.Copy(pArray, array, 0, size); if (deallocate) { Marshal.FreeCoTaskMem(pArray); pArray = IntPtr.Zero; } return array; } /// /// Allocates and marshals an array of 16 bit integers. /// public static IntPtr GetInt16s(short[] input) { IntPtr output = IntPtr.Zero; if (input != null) { output = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(Int16))*input.Length); Marshal.Copy(input, 0, output, input.Length); } return output; } /// /// Marshals an array of strings into a unmanaged memory buffer /// /// The array of strings to marshal /// The pointer to the unmanaged memory buffer public static IntPtr GetUnicodeStrings(string[] values) { int size = (values != null)?values.Length:0; if (size <= 0) { return IntPtr.Zero; } IntPtr pValues = IntPtr.Zero; int[] pointers = new int[size]; for (int ii = 0; ii < size; ii++) { pointers[ii] = (int)Marshal.StringToCoTaskMemUni(values[ii]); } pValues = Marshal.AllocCoTaskMem(values.Length*Marshal.SizeOf(typeof(IntPtr))); Marshal.Copy(pointers, 0, pValues, size); return pValues; } /// /// Unmarshals and frees a array of unicode strings. /// public static string[] GetUnicodeStrings(ref IntPtr pArray, int size, bool deallocate) { if (pArray == IntPtr.Zero || size <= 0) { return null; } IntPtr[] pointers = new IntPtr[size]; Marshal.Copy(pArray, pointers, 0, size); string[] strings = new string[size]; for (int ii = 0; ii < size; ii++) { IntPtr pString = pointers[ii]; strings[ii] = Marshal.PtrToStringUni(pString); if (deallocate) Marshal.FreeCoTaskMem(pString); } if (deallocate) { Marshal.FreeCoTaskMem(pArray); pArray = IntPtr.Zero; } return strings; } /// /// Marshals a DateTime as a WIN32 FILETIME. /// /// The DateTime object to marshal /// The WIN32 FILETIME public static System.Runtime.InteropServices.ComTypes.FILETIME GetFILETIME(DateTime datetime) { System.Runtime.InteropServices.ComTypes.FILETIME filetime; if (datetime <= FILETIME_BaseTime) { filetime.dwHighDateTime = 0; filetime.dwLowDateTime = 0; return filetime; } // adjust for WIN32 FILETIME base. long ticks = 0; ticks = datetime.Subtract(new TimeSpan(FILETIME_BaseTime.Ticks)).Ticks; filetime.dwHighDateTime = (int)((ticks>>32) & 0xFFFFFFFF); filetime.dwLowDateTime = (int)(ticks & 0xFFFFFFFF); return filetime; } /// /// Unmarshals a WIN32 FILETIME from a pointer. /// /// A pointer to a FILETIME structure. /// A DateTime object. public static DateTime GetDateTime(IntPtr pFiletime) { if (pFiletime == IntPtr.Zero) { return DateTime.MinValue; } return GetDateTime((System.Runtime.InteropServices.ComTypes.FILETIME)Marshal.PtrToStructure(pFiletime, typeof(System.Runtime.InteropServices.ComTypes.FILETIME))); } /// /// Unmarshals a WIN32 FILETIME. /// public static DateTime GetDateTime(System.Runtime.InteropServices.ComTypes.FILETIME filetime) { // check for invalid value. if (filetime.dwHighDateTime < 0) { return DateTime.MinValue; } // convert FILETIME structure to a 64 bit integer. long buffer = (long)filetime.dwHighDateTime; if (buffer < 0) { buffer += ((long)UInt32.MaxValue+1); } long ticks = (buffer<<32); buffer = (long)filetime.dwLowDateTime; if (buffer < 0) { buffer += ((long)UInt32.MaxValue+1); } ticks += buffer; // check for invalid value. if (ticks == 0) { return DateTime.MinValue; } // adjust for WIN32 FILETIME base. return FILETIME_BaseTime.Add(new TimeSpan(ticks)); } /// /// Marshals an array of DateTimes into an unmanaged array of FILETIMEs /// /// The array of DateTimes to marshal /// The IntPtr array of FILETIMEs public static IntPtr GetFILETIMEs(DateTime[] datetimes) { int count = (datetimes != null)?datetimes.Length:0; if (count <= 0) { return IntPtr.Zero; } IntPtr pFiletimes = Marshal.AllocCoTaskMem(count*Marshal.SizeOf(typeof(System.Runtime.InteropServices.ComTypes.FILETIME))); IntPtr pos = pFiletimes; for (int ii = 0; ii < count; ii++) { Marshal.StructureToPtr(GetFILETIME(datetimes[ii]), pos, false); pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(System.Runtime.InteropServices.ComTypes.FILETIME))); } return pFiletimes; } /// /// Unmarshals an array of WIN32 FILETIMEs as DateTimes. /// public static DateTime[] GetDateTimes(ref IntPtr pArray, int size, bool deallocate) { if (pArray == IntPtr.Zero || size <= 0) { return null; } DateTime[] datetimes = new DateTime[size]; IntPtr pos = pArray; for (int ii = 0; ii < size; ii++) { datetimes[ii] = GetDateTime(pos); pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(System.Runtime.InteropServices.ComTypes.FILETIME))); } if (deallocate) { Marshal.FreeCoTaskMem(pArray); pArray = IntPtr.Zero; } return datetimes; } /// /// Unmarshals an array of WIN32 GUIDs as Guid. /// public static Guid[] GetGUIDs(ref IntPtr pInput, int size, bool deallocate) { if (pInput == IntPtr.Zero || size <= 0) { return null; } Guid[] guids = new Guid[size]; IntPtr pos = pInput; for (int ii = 0; ii < size; ii++) { GUID input = (GUID)Marshal.PtrToStructure(pInput, typeof(GUID)); guids[ii] = new Guid(input.Data1, input.Data2, input.Data3, input.Data4); pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(GUID))); } if (deallocate) { Marshal.FreeCoTaskMem(pInput); pInput = IntPtr.Zero; } return guids; } /// /// Converts an object into a value that can be marshalled to a VARIANT. /// /// The object to convert. /// The converted object. public static object GetVARIANT(object source) { // check for invalid args. if (source == null) { return null; } return GetVARIANT(source, TypeInfo.Construct(source)); } /// /// Converts an object into a value that can be marshalled to a VARIANT. /// /// The object to convert. /// The converted object. public static object GetVARIANT(Variant source) { return GetVARIANT(source.Value, source.TypeInfo); } /// /// Converts an object into a value that can be marshalled to a VARIANT. /// /// The object to convert. /// The type info. /// The converted object. public static object GetVARIANT(object source, TypeInfo typeInfo) { // check for invalid args. if (source == null) { return null; } try { switch (typeInfo.BuiltInType) { case BuiltInType.Boolean: case BuiltInType.Byte: case BuiltInType.Int16: case BuiltInType.UInt16: case BuiltInType.Int32: case BuiltInType.UInt32: case BuiltInType.Int64: case BuiltInType.UInt64: case BuiltInType.Float: case BuiltInType.Double: case BuiltInType.String: case BuiltInType.DateTime: { return source; } case BuiltInType.ByteString: { if (typeInfo.ValueRank < 0) { return source; } return VariantToObjectArray((Array)source); } case BuiltInType.Guid: case BuiltInType.LocalizedText: case BuiltInType.QualifiedName: case BuiltInType.NodeId: case BuiltInType.ExpandedNodeId: case BuiltInType.XmlElement: { return TypeInfo.Cast(source, typeInfo, BuiltInType.String); } case BuiltInType.StatusCode: { return TypeInfo.Cast(source, typeInfo, BuiltInType.UInt32); } case BuiltInType.Variant: { if (typeInfo.ValueRank < 0) { return GetVARIANT(((Variant)source).Value); } return VariantToObjectArray((Array)source); } case BuiltInType.ExtensionObject: { if (typeInfo.ValueRank < 0) { byte[] body = null; ExtensionObject extension = (ExtensionObject)source; switch (extension.Encoding) { case ExtensionObjectEncoding.Binary: { body = (byte[])extension.Body; break; } case ExtensionObjectEncoding.Xml: { body = new UTF8Encoding().GetBytes(((XmlElement)extension.Body).OuterXml); break; } case ExtensionObjectEncoding.EncodeableObject: { BinaryEncoder encoder = new BinaryEncoder(ServiceMessageContext.GlobalContext); encoder.WriteEncodeable(null, (IEncodeable)extension.Body, null); body = encoder.CloseAndReturnBuffer(); break; } } return body; } return VariantToObjectArray((Array)source); } case BuiltInType.DataValue: case BuiltInType.DiagnosticInfo: { return "(unsupported)"; } } } catch (Exception e) { return e.Message; } // no conversion required. return source; } /// /// Converts a Variant array to an Object array. /// private static Array VariantToObjectArray(Array input) { int[] dimensions = new int[input.Rank]; for (int ii = 0; ii < dimensions.Length; ii++) { dimensions[ii] = input.GetLength(ii); } Array output = Array.CreateInstance(typeof(object), dimensions); int length = output.Length; int[] indexes = new int[dimensions.Length]; for (int ii = 0; ii < length; ii++) { int divisor = output.Length; for (int jj = 0; jj < indexes.Length; jj++) { divisor /= dimensions[jj]; indexes[jj] = (ii/divisor)%dimensions[jj]; } object value = input.GetValue(indexes); output.SetValue(GetVARIANT(value), indexes); } return output; } /// /// Marshals an array objects into an unmanaged array of VARIANTs. /// /// An array of the objects to be marshalled /// Whether the objects should have troublesome types removed before marhalling. /// An pointer to the array in unmanaged memory public static IntPtr GetVARIANTs(object[] values, bool preprocess) { int count = (values != null)?values.Length:0; if (count <= 0) { return IntPtr.Zero; } IntPtr pValues = Marshal.AllocCoTaskMem(count*VARIANT_SIZE); IntPtr pos = pValues; for (int ii = 0; ii < count; ii++) { if (preprocess) { Marshal.GetNativeVariantForObject(GetVARIANT(values[ii]), pos); } else { Marshal.GetNativeVariantForObject(values[ii], pos); } pos = (IntPtr)(pos.ToInt64() + VARIANT_SIZE); } return pValues; } /// /// Unmarshals an array of VARIANTs as objects. /// public static object[] GetVARIANTs(ref IntPtr pArray, int size, bool deallocate) { // this method unmarshals VARIANTs one at a time because a single bad value throws // an exception with GetObjectsForNativeVariants(). This approach simply sets the // offending value to null. if (pArray == IntPtr.Zero || size <= 0) { return null; } object[] values = new object[size]; IntPtr pos = pArray; for (int ii = 0; ii < size; ii++) { try { values[ii] = Marshal.GetObjectForNativeVariant(pos); if (deallocate) VariantClear(pos); } catch (Exception) { values[ii] = null; } pos = (IntPtr)(pos.ToInt64() + VARIANT_SIZE); } if (deallocate) { Marshal.FreeCoTaskMem(pArray); pArray = IntPtr.Zero; } return values; } /// /// Converts a LCID to a Locale string. /// public static string GetLocale(int input) { try { if (input == LOCALE_SYSTEM_DEFAULT || input == LOCALE_USER_DEFAULT || input == 0) { return CultureInfo.InvariantCulture.Name; } return new CultureInfo(input).Name; } catch (Exception e) { throw new OpcResultException(StatusCodes.Bad, "Unrecognized locale provided.", e); } } /// /// Converts a Locale string to a LCID. /// public static int GetLocale(string input) { // check for the default culture. if (input == null || input == "") { return 0; } CultureInfo locale = null; try { locale = new CultureInfo(input); } catch { locale = CultureInfo.CurrentCulture; } return locale.LCID; } /// /// Converts the VARTYPE to a UA Data Type ID. /// public static NodeId GetDataTypeId(short input) { switch ((VarEnum)Enum.ToObject(typeof(VarEnum), (input & ~(short)VarEnum.VT_ARRAY))) { case VarEnum.VT_I1: return DataTypes.SByte; case VarEnum.VT_UI1: return DataTypes.Byte; case VarEnum.VT_I2: return DataTypes.Int16; case VarEnum.VT_UI2: return DataTypes.UInt16; case VarEnum.VT_I4: return DataTypes.Int32; case VarEnum.VT_UI4: return DataTypes.UInt32; case VarEnum.VT_I8: return DataTypes.Int64; case VarEnum.VT_UI8: return DataTypes.UInt64; case VarEnum.VT_R4: return DataTypes.Float; case VarEnum.VT_R8: return DataTypes.Double; case VarEnum.VT_BOOL: return DataTypes.Boolean; case VarEnum.VT_DATE: return DataTypes.DateTime; case VarEnum.VT_BSTR: return DataTypes.String; case VarEnum.VT_CY: return DataTypes.String; case VarEnum.VT_EMPTY: return DataTypes.BaseDataType; case VarEnum.VT_VARIANT: { return DataTypes.BaseDataType; } } return NodeId.Null; } /// /// Converts the VARTYPE to a UA ValueRank. /// public static int GetValueRank(short input) { if ((((short)VarEnum.VT_ARRAY & input) != 0)) { return ValueRanks.OneDimension; } return ValueRanks.Scalar; } /// /// Converts the VARTYPE to a UA Data Type ID. /// public static NodeId GetDataTypeId(short input, out bool isArray) { isArray = (((short)VarEnum.VT_ARRAY & input) != 0); return GetDataTypeId(input); } /// /// Converts the VARTYPE to a SystemType /// public static Type GetSystemType(short input) { bool isArray = (((short)VarEnum.VT_ARRAY & input) != 0); VarEnum varType = (VarEnum)Enum.ToObject(typeof(VarEnum), (input & ~(short)VarEnum.VT_ARRAY)); if (!isArray) { switch (varType) { case VarEnum.VT_I1: return typeof(sbyte); case VarEnum.VT_UI1: return typeof(byte); case VarEnum.VT_I2: return typeof(short); case VarEnum.VT_UI2: return typeof(ushort); case VarEnum.VT_I4: return typeof(int); case VarEnum.VT_UI4: return typeof(uint); case VarEnum.VT_I8: return typeof(long); case VarEnum.VT_UI8: return typeof(ulong); case VarEnum.VT_R4: return typeof(float); case VarEnum.VT_R8: return typeof(double); case VarEnum.VT_BOOL: return typeof(bool); case VarEnum.VT_DATE: return typeof(DateTime); case VarEnum.VT_BSTR: return typeof(string); case VarEnum.VT_CY: return typeof(decimal); case VarEnum.VT_EMPTY: return typeof(object); case VarEnum.VT_VARIANT: return typeof(object); } } else { switch (varType) { case VarEnum.VT_I1: return typeof(sbyte[]); case VarEnum.VT_UI1: return typeof(byte[]); case VarEnum.VT_I2: return typeof(short[]); case VarEnum.VT_UI2: return typeof(ushort[]); case VarEnum.VT_I4: return typeof(int[]); case VarEnum.VT_UI4: return typeof(uint[]); case VarEnum.VT_I8: return typeof(long[]); case VarEnum.VT_UI8: return typeof(ulong[]); case VarEnum.VT_R4: return typeof(float[]); case VarEnum.VT_R8: return typeof(double[]); case VarEnum.VT_BOOL: return typeof(bool[]); case VarEnum.VT_DATE: return typeof(DateTime[]); case VarEnum.VT_BSTR: return typeof(string[]); case VarEnum.VT_CY: return typeof(decimal[]); case VarEnum.VT_EMPTY: return typeof(object[]); case VarEnum.VT_VARIANT: return typeof(object[]); } } return null; } /// /// Returns the VARTYPE for the value. /// public static VarEnum GetVarType(object input) { if (input == null) { return VarEnum.VT_EMPTY; } return GetVarType(input.GetType()); } /// /// Converts the system type to a VARTYPE. /// public static VarEnum GetVarType(System.Type type) { if (type == null) { return VarEnum.VT_EMPTY; } if (type == null) return VarEnum.VT_EMPTY; if (type == typeof(sbyte)) return VarEnum.VT_I1; if (type == typeof(byte)) return VarEnum.VT_UI1; if (type == typeof(short)) return VarEnum.VT_I2; if (type == typeof(ushort)) return VarEnum.VT_UI2; if (type == typeof(int)) return VarEnum.VT_I4; if (type == typeof(uint)) return VarEnum.VT_UI4; if (type == typeof(long)) return VarEnum.VT_I8; if (type == typeof(ulong)) return VarEnum.VT_UI8; if (type == typeof(float)) return VarEnum.VT_R4; if (type == typeof(double)) return VarEnum.VT_R8; if (type == typeof(decimal)) return VarEnum.VT_CY; if (type == typeof(bool)) return VarEnum.VT_BOOL; if (type == typeof(DateTime)) return VarEnum.VT_DATE; if (type == typeof(string)) return VarEnum.VT_BSTR; if (type == typeof(sbyte[])) return VarEnum.VT_ARRAY | VarEnum.VT_I1; if (type == typeof(byte[])) return VarEnum.VT_ARRAY | VarEnum.VT_UI1; if (type == typeof(short[])) return VarEnum.VT_ARRAY | VarEnum.VT_I2; if (type == typeof(ushort[])) return VarEnum.VT_ARRAY | VarEnum.VT_UI2; if (type == typeof(int[])) return VarEnum.VT_ARRAY | VarEnum.VT_I4; if (type == typeof(uint[])) return VarEnum.VT_ARRAY | VarEnum.VT_UI4; if (type == typeof(long[])) return VarEnum.VT_ARRAY | VarEnum.VT_I8; if (type == typeof(ulong[])) return VarEnum.VT_ARRAY | VarEnum.VT_UI8; if (type == typeof(float[])) return VarEnum.VT_ARRAY | VarEnum.VT_R4; if (type == typeof(double[])) return VarEnum.VT_ARRAY | VarEnum.VT_R8; if (type == typeof(decimal[])) return VarEnum.VT_ARRAY | VarEnum.VT_CY; if (type == typeof(bool[])) return VarEnum.VT_ARRAY | VarEnum.VT_BOOL; if (type == typeof(DateTime[])) return VarEnum.VT_ARRAY | VarEnum.VT_DATE; if (type == typeof(string[])) return VarEnum.VT_ARRAY | VarEnum.VT_BSTR; if (type == typeof(object[])) return VarEnum.VT_ARRAY | VarEnum.VT_VARIANT; return VarEnum.VT_EMPTY; } /// /// Converts the TypeInfo to a VARTYPE. /// public static VarEnum GetVarType(TypeInfo typeInfo) { if (typeInfo == null) { return VarEnum.VT_EMPTY; } VarEnum vtType = VarEnum.VT_EMPTY; switch (typeInfo.BuiltInType) { case BuiltInType.Boolean: { vtType = VarEnum.VT_BOOL; break; } case BuiltInType.SByte: { vtType = VarEnum.VT_I1; break; } case BuiltInType.Byte: { vtType = VarEnum.VT_UI1; break; } case BuiltInType.Int16: { vtType = VarEnum.VT_I2; break; } case BuiltInType.UInt16: { vtType = VarEnum.VT_UI2; break; } case BuiltInType.Int32: { vtType = VarEnum.VT_I4; break; } case BuiltInType.UInt32: { vtType = VarEnum.VT_UI4; break; } case BuiltInType.Int64: { vtType = VarEnum.VT_I8; break; } case BuiltInType.UInt64: { vtType = VarEnum.VT_UI8; break; } case BuiltInType.Float: { vtType = VarEnum.VT_R4; break; } case BuiltInType.Double: { vtType = VarEnum.VT_R8; break; } case BuiltInType.String: { vtType = VarEnum.VT_BSTR; break; } case BuiltInType.DateTime: { vtType = VarEnum.VT_DATE; break; } case BuiltInType.Guid: { vtType = VarEnum.VT_BSTR; break; } case BuiltInType.ByteString: { vtType = VarEnum.VT_ARRAY | VarEnum.VT_UI1; break; } case BuiltInType.XmlElement: { vtType = VarEnum.VT_BSTR; break; } case BuiltInType.NodeId: { vtType = VarEnum.VT_BSTR; break; } case BuiltInType.ExpandedNodeId: { vtType = VarEnum.VT_BSTR; break; } case BuiltInType.QualifiedName: { vtType = VarEnum.VT_BSTR; break; } case BuiltInType.LocalizedText: { vtType = VarEnum.VT_BSTR; break; } case BuiltInType.StatusCode: { vtType = VarEnum.VT_UI4; break; } case BuiltInType.ExtensionObject: { vtType = VarEnum.VT_ARRAY | VarEnum.VT_UI1; break; } case BuiltInType.Enumeration: { vtType = VarEnum.VT_I4; break; } case BuiltInType.Number: { vtType = VarEnum.VT_R8; break; } case BuiltInType.Integer: { vtType = VarEnum.VT_I8; break; } case BuiltInType.UInteger: { vtType = VarEnum.VT_UI8; break; } case BuiltInType.Variant: { if (typeInfo.ValueRank == ValueRanks.Scalar) { return VarEnum.VT_EMPTY; } vtType = VarEnum.VT_VARIANT; break; } default: { return VarEnum.VT_EMPTY; } } if (typeInfo.ValueRank > 0) { vtType |= VarEnum.VT_ARRAY; } return vtType; } /// /// Converts a value to the specified type using COM conversion rules. /// public static int ChangeTypeForCOM(object source, VarEnum targetType, out object target) { return ChangeTypeForCOM(source, GetVarType(source), targetType, out target); } /// /// Converts a value to the specified type using COM conversion rules. /// public static int ChangeTypeForCOM(object source, VarEnum sourceType, VarEnum targetType, out object target) { target = source; // check for trivial case. if (sourceType == targetType) { return ResultIds.S_OK; } // check for conversions to date time from string. string stringValue = source as string; if (stringValue != null && targetType == VarEnum.VT_DATE) { try { target = System.Convert.ToDateTime(stringValue); return ResultIds.S_OK; } catch { target = null; return ResultIds.DISP_E_OVERFLOW; } } // check for conversions from date time to boolean. if (sourceType == VarEnum.VT_DATE && targetType == VarEnum.VT_BOOL) { target = !(new DateTime(1899, 12, 30, 0, 0, 0).Equals((DateTime)source)); return ResultIds.S_OK; } // check for conversions from float to double. if (sourceType == VarEnum.VT_R4 && targetType == VarEnum.VT_R8) { target = System.Convert.ToDouble((float)source); return ResultIds.S_OK; } // check for array conversion. Array array = source as Array; bool targetIsArray = ((targetType & VarEnum.VT_ARRAY) != 0); if (array != null && targetIsArray) { VarEnum elementType = (VarEnum)((short)targetType & ~(short)VarEnum.VT_ARRAY); Array convertedArray = Array.CreateInstance(GetSystemType((short)elementType), array.Length); for (int ii = 0; ii < array.Length; ii++) { object elementValue = null; int error = ChangeTypeForCOM(array.GetValue(ii), elementType, out elementValue); if (error < 0) { target = null; return ResultIds.DISP_E_OVERFLOW; } convertedArray.SetValue(elementValue, ii); } target = convertedArray; return ResultIds.S_OK; } else if (array == null && !targetIsArray) { IntPtr pvargDest = Marshal.AllocCoTaskMem(16); IntPtr pvarSrc = Marshal.AllocCoTaskMem(16); VariantInit(pvargDest); VariantInit(pvarSrc); Marshal.GetNativeVariantForObject(source, pvarSrc); try { // change type. int error = VariantChangeTypeEx( pvargDest, pvarSrc, Thread.CurrentThread.CurrentCulture.LCID, VARIANT_NOVALUEPROP | VARIANT_ALPHABOOL, (short)targetType); // check error code. if (error != 0) { target = null; return error; } // unmarshal result. object result = Marshal.GetObjectForNativeVariant(pvargDest); // check for invalid unsigned <=> signed conversions. switch (targetType) { case VarEnum.VT_I1: case VarEnum.VT_I2: case VarEnum.VT_I4: case VarEnum.VT_I8: case VarEnum.VT_UI1: case VarEnum.VT_UI2: case VarEnum.VT_UI4: case VarEnum.VT_UI8: { // ignore issue for conversions from boolean. if (sourceType == VarEnum.VT_BOOL) { break; } decimal sourceAsDecimal = 0; decimal resultAsDecimal = System.Convert.ToDecimal(result); try { sourceAsDecimal = System.Convert.ToDecimal(source); } catch { sourceAsDecimal = 0; } if ((sourceAsDecimal < 0 && resultAsDecimal > 0) || (sourceAsDecimal > 0 && resultAsDecimal < 0)) { target = null; return ResultIds.E_RANGE; } // conversion from datetime should have failed. if (sourceType == VarEnum.VT_DATE) { if (resultAsDecimal == 0) { target = null; return ResultIds.E_RANGE; } } break; } case VarEnum.VT_R8: { // fix precision problem introduced with conversion from float to double. if (sourceType == VarEnum.VT_R4) { result = System.Convert.ToDouble(source.ToString()); } break; } } target = result; return ResultIds.S_OK; } finally { VariantClear(pvargDest); VariantClear(pvarSrc); Marshal.FreeCoTaskMem(pvargDest); Marshal.FreeCoTaskMem(pvarSrc); } } else if (array != null && targetType == VarEnum.VT_BSTR) { int count = ((Array)source).Length; StringBuilder buffer = new StringBuilder(); buffer.Append("{"); foreach (object element in (Array)source) { object elementValue = null; int error = ChangeTypeForCOM(element, VarEnum.VT_BSTR, out elementValue); if (error < 0) { target = null; return error; } buffer.Append((string)elementValue); count--; if (count > 0) { buffer.Append(" | "); } } buffer.Append("}"); target = buffer.ToString(); return ResultIds.S_OK; } // no conversions between scalar and array types allowed. target = null; return ResultIds.E_BADTYPE; } /// /// Returns true is the object is a valid COM type. /// public static bool IsValidComType(object input) { Array array = input as Array; if (array != null) { foreach (object value in array) { if (!IsValidComType(value)) { return false; } } return true; } return GetVarType(input) != VarEnum.VT_EMPTY; } /// /// Converts a COM value to something that UA clients can deal with. /// public static object ProcessComValue(object value) { // flatten any multi-dimensional array. Array array = value as Array; if (array != null) { if (array.Rank > 1) { value = array = Utils.FlattenArray(array); } // convert array of decimal to strings. if (array != null && array.GetType().GetElementType() == typeof(decimal)) { string[] clone = new string[array.Length]; for (int ii = 0; ii < array.Length; ii++) { clone[ii] = Convert.ToString(array.GetValue(ii)); } value = clone; } } // convert scalar decimal to a string. if (value is decimal) { value = Convert.ToString(value); } return value; } /// /// Converts a DA quality code to a status code. /// public static StatusCode GetQualityCode(short quality) { StatusCode code = 0; // convert quality status. switch ((short)(quality & 0x00FC)) { case OpcRcw.Da.Qualities.OPC_QUALITY_GOOD: { code = StatusCodes.Good; break; } case OpcRcw.Da.Qualities.OPC_QUALITY_LOCAL_OVERRIDE: { code = StatusCodes.GoodLocalOverride; break; } case OpcRcw.Da.Qualities.OPC_QUALITY_UNCERTAIN: { code = StatusCodes.Uncertain; break; } case OpcRcw.Da.Qualities.OPC_QUALITY_SUB_NORMAL: { code = StatusCodes.UncertainSubNormal; break; } case OpcRcw.Da.Qualities.OPC_QUALITY_SENSOR_CAL: { code = StatusCodes.UncertainSensorNotAccurate; break; } case OpcRcw.Da.Qualities.OPC_QUALITY_EGU_EXCEEDED: { code = StatusCodes.UncertainEngineeringUnitsExceeded; break; } case OpcRcw.Da.Qualities.OPC_QUALITY_LAST_USABLE: { code = StatusCodes.UncertainLastUsableValue; break; } case OpcRcw.Da.Qualities.OPC_QUALITY_BAD: { code = StatusCodes.Bad; break; } case OpcRcw.Da.Qualities.OPC_QUALITY_CONFIG_ERROR: { code = StatusCodes.BadConfigurationError; break; } case OpcRcw.Da.Qualities.OPC_QUALITY_NOT_CONNECTED: { code = StatusCodes.BadNotConnected; break; } case OpcRcw.Da.Qualities.OPC_QUALITY_COMM_FAILURE: { code = StatusCodes.BadNoCommunication; break; } case OpcRcw.Da.Qualities.OPC_QUALITY_DEVICE_FAILURE: { code = StatusCodes.BadDeviceFailure; break; } case OpcRcw.Da.Qualities.OPC_QUALITY_SENSOR_FAILURE: { code = StatusCodes.BadSensorFailure; break; } case OpcRcw.Da.Qualities.OPC_QUALITY_LAST_KNOWN: { code = StatusCodes.BadOutOfService; break; } case OpcRcw.Da.Qualities.OPC_QUALITY_OUT_OF_SERVICE: { code = StatusCodes.BadOutOfService; break; } case OpcRcw.Da.Qualities.OPC_QUALITY_WAITING_FOR_INITIAL_DATA: { code = StatusCodes.BadWaitingForInitialData; break; } } // convert the limit status. switch ((short)(quality & 0x0003)) { case OpcRcw.Da.Qualities.OPC_LIMIT_LOW: { code.LimitBits = LimitBits.Low; break; } case OpcRcw.Da.Qualities.OPC_LIMIT_HIGH: { code.LimitBits = LimitBits.High; break; } case OpcRcw.Da.Qualities.OPC_LIMIT_CONST: { code.LimitBits = LimitBits.Constant; break; } } // return the combined code. return code; } /// /// Converts a UA status code to a DA quality code. /// public static short GetQualityCode(StatusCode input) { short code = 0; // convert quality status. switch (input.CodeBits) { case StatusCodes.Good: { code = OpcRcw.Da.Qualities.OPC_QUALITY_GOOD; break; } case StatusCodes.GoodLocalOverride: { code = OpcRcw.Da.Qualities.OPC_QUALITY_LOCAL_OVERRIDE; break; } case StatusCodes.Uncertain: { code = OpcRcw.Da.Qualities.OPC_QUALITY_UNCERTAIN; break; } case StatusCodes.UncertainSubNormal: { code = OpcRcw.Da.Qualities.OPC_QUALITY_SUB_NORMAL; break; } case StatusCodes.UncertainSensorNotAccurate: { code = OpcRcw.Da.Qualities.OPC_QUALITY_SENSOR_CAL; break; } case StatusCodes.UncertainEngineeringUnitsExceeded: { code = OpcRcw.Da.Qualities.OPC_QUALITY_EGU_EXCEEDED; break; } case StatusCodes.UncertainLastUsableValue: { code = OpcRcw.Da.Qualities.OPC_QUALITY_LAST_USABLE; break; } case StatusCodes.Bad: { code = OpcRcw.Da.Qualities.OPC_QUALITY_BAD; break; } case StatusCodes.BadConfigurationError: { code = OpcRcw.Da.Qualities.OPC_QUALITY_CONFIG_ERROR; break; } case StatusCodes.BadNotConnected: { code = OpcRcw.Da.Qualities.OPC_QUALITY_NOT_CONNECTED; break; } case StatusCodes.BadNoCommunication: { code = OpcRcw.Da.Qualities.OPC_QUALITY_COMM_FAILURE; break; } case StatusCodes.BadOutOfService: { code = OpcRcw.Da.Qualities.OPC_QUALITY_OUT_OF_SERVICE; break; } case StatusCodes.BadDeviceFailure: { code = OpcRcw.Da.Qualities.OPC_QUALITY_DEVICE_FAILURE; break; } case StatusCodes.BadSensorFailure: { code = OpcRcw.Da.Qualities.OPC_QUALITY_SENSOR_FAILURE; break; } case StatusCodes.BadWaitingForInitialData: { code = OpcRcw.Da.Qualities.OPC_QUALITY_WAITING_FOR_INITIAL_DATA; break; } default: { if (StatusCode.IsBad(input)) { code = OpcRcw.Da.Qualities.OPC_QUALITY_BAD; break; } if (StatusCode.IsUncertain(input)) { code = OpcRcw.Da.Qualities.OPC_QUALITY_UNCERTAIN; break; } code = OpcRcw.Da.Qualities.OPC_QUALITY_GOOD; break; } } // convert the limit status. switch (input.LimitBits) { case LimitBits.Low: { code |= OpcRcw.Da.Qualities.OPC_LIMIT_LOW; break; } case LimitBits.High: { code |= OpcRcw.Da.Qualities.OPC_LIMIT_HIGH; break; } case LimitBits.Constant: { code |= OpcRcw.Da.Qualities.OPC_LIMIT_CONST; break; } } // return the combined code. return code; } /// /// Converts a HDA quality code to a StatusCode. /// public static StatusCode GetHdaQualityCode(uint quality) { uint hdaCode = quality & 0xFFFF0000; // check for bits indicating an out right error. if ((hdaCode & OpcRcw.Hda.Constants.OPCHDA_NOBOUND) != 0) { return StatusCodes.BadBoundNotFound; } if ((hdaCode & OpcRcw.Hda.Constants.OPCHDA_NODATA) != 0) { return StatusCodes.BadNoData; } if ((hdaCode & OpcRcw.Hda.Constants.OPCHDA_DATALOST) != 0) { return StatusCodes.BadDataLost; } if ((hdaCode & OpcRcw.Hda.Constants.OPCHDA_CONVERSION) != 0) { return StatusCodes.BadTypeMismatch; } // Get DA part (lower 2 bytes). StatusCode code = GetQualityCode((short)(quality & 0x0000FFFF)); // check for bits that are placed in the info bits. AggregateBits aggregateBits = 0; if ((hdaCode & OpcRcw.Hda.Constants.OPCHDA_EXTRADATA) != 0) { aggregateBits |= AggregateBits.ExtraData; } if ((hdaCode & OpcRcw.Hda.Constants.OPCHDA_INTERPOLATED) != 0) { aggregateBits |= AggregateBits.Interpolated; } if ((hdaCode & OpcRcw.Hda.Constants.OPCHDA_RAW) != 0) { aggregateBits |= AggregateBits.Raw; } if ((hdaCode & OpcRcw.Hda.Constants.OPCHDA_CALCULATED) != 0) { aggregateBits |= AggregateBits.Calculated; } if ((hdaCode & OpcRcw.Hda.Constants.OPCHDA_PARTIAL) != 0) { aggregateBits |= AggregateBits.Partial; } return code.SetAggregateBits(aggregateBits); } /// /// Converts a UA status code to a HDA quality code. /// public static uint GetHdaQualityCode(StatusCode input) { // check for fatal errors. switch (input.CodeBits) { case StatusCodes.BadBoundNotFound: { return OpcRcw.Hda.Constants.OPCHDA_NOBOUND; } case StatusCodes.BadBoundNotSupported: { return OpcRcw.Hda.Constants.OPCHDA_NOBOUND; } case StatusCodes.BadNoData: { return OpcRcw.Hda.Constants.OPCHDA_NODATA; } case StatusCodes.BadDataLost: { return OpcRcw.Hda.Constants.OPCHDA_DATALOST; } } // handle normal case. uint code = Utils.ToUInt32(GetQualityCode(input)); // check for bits that are placed in the info bits. AggregateBits aggregateBits = input.AggregateBits; if ((aggregateBits & AggregateBits.ExtraData) != 0) { code |= OpcRcw.Hda.Constants.OPCHDA_EXTRADATA; } // set the source of the data. if ((aggregateBits & AggregateBits.Interpolated) != 0) { code |= OpcRcw.Hda.Constants.OPCHDA_INTERPOLATED; } else if ((aggregateBits & AggregateBits.Calculated) != 0) { code |= OpcRcw.Hda.Constants.OPCHDA_CALCULATED; } else if ((aggregateBits & AggregateBits.Partial) != 0) { code |= OpcRcw.Hda.Constants.OPCHDA_PARTIAL; } else { code |= OpcRcw.Hda.Constants.OPCHDA_RAW; } // return the combined code. return code; } #endif /// /// Returns the symbolic name for the specified error. /// public static string GetErrorText(Type type, int error) { var fields = type.GetFields(BindingFlags.Public | BindingFlags.Static); foreach (var field in fields) { if (error == (int)field.GetValue(type)) { return field.Name; } } return string.Format("0x{0:X8}", error); } /// /// Gets the error code for the exception. /// /// The exception. /// The default code. /// The error code /// This method ignores the exception but makes it possible to keep track of ignored exceptions. public static int GetErrorCode(Exception e, int defaultCode) { return defaultCode; } /// /// Releases the server if it is a true COM server. /// public static void ReleaseServer(object server) { if (server != null && server.GetType().IsCOMObject) { Marshal.ReleaseComObject(server); } } /// /// Retrieves the system message text for the specified error. /// public static string GetSystemMessage(int error, int localeId) { int langId; switch (localeId) { case LOCALE_SYSTEM_DEFAULT: { langId = GetSystemDefaultLangID(); break; } case LOCALE_USER_DEFAULT: { langId = GetUserDefaultLangID(); break; } default: { langId = (0xFFFF & localeId); break; } } var buffer = Marshal.AllocCoTaskMem(MAX_MESSAGE_LENGTH); var result = FormatMessageW( (int)FORMAT_MESSAGE_FROM_SYSTEM, IntPtr.Zero, error, langId, buffer, MAX_MESSAGE_LENGTH - 1, IntPtr.Zero); if (result > 0) { var msg = Marshal.PtrToStringUni(buffer); Marshal.FreeCoTaskMem(buffer); if (msg != null && msg.Length > 0) { return msg.Trim(); } } return string.Format("0x{0:X8}", error); } /// /// Converts an exception to an exception that returns a COM error code. /// public static Exception CreateComException(Exception e, int errorId) { return new COMException(e.Message, errorId); } /// /// Creates a COM exception. /// public static Exception CreateComException(string message, int errorId) { return new COMException(message, errorId); } /// /// Converts an exception to an exception that returns a COM error code. /// public static Exception CreateComException(int errorId) { return new COMException(string.Format("0x{0:X8}", errorId), errorId); } /// /// Converts an exception to an exception that returns a COM error code. /// public static Exception CreateComException(Exception e) { // nothing special required for external exceptions. if (e is COMException) { return e; } // convert other exceptions to E_FAIL. return new COMException(e.Message, OpcResult.E_FAIL.Code); } /// /// Creates an error message for a failed COM function call. /// public static Exception CreateException(Exception e, string function) { return new OpcResultException(new OpcResult((int)OpcResult.E_NETWORK_ERROR.Code, OpcResult.FuncCallType.SysFuncCall, null), string.Format("Call to {0} failed. Error: {1}.", function, GetSystemMessage(Marshal.GetHRForException(e), LOCALE_SYSTEM_DEFAULT))); } /// /// Checks if the error is an RPC error. /// public static bool IsRpcError(Exception e) { var error = Marshal.GetHRForException(e); // Assume that any 0x8007 is a fatal communication error. // May need to update this check if the assumption proves to be incorrect. if ((error & 0xFFFF0000) == 0x80070000) { error &= 0xFFFF; // check the RPC error range define in WinError.h if (error >= 1700 && error < 1918) { return true; } } return false; } /// /// Checks if the error for the exception is one of the recognized errors. /// public static bool IsUnknownError(Exception e, params int[] knownErrors) { var error = Marshal.GetHRForException(e); if (knownErrors != null) { for (var ii = 0; ii < knownErrors.Length; ii++) { if (knownErrors[ii] == error) { return false; } } } return true; } #endregion #region Utility Functions /// /// Compares a string locale to a WIN32 localeId /// public static bool CompareLocales(int localeId, string locale, bool ignoreRegion) { // parse locale. CultureInfo culture; try { culture = new CultureInfo(locale); } catch (Exception) { return false; } // only match the language portion of the locale id. if (ignoreRegion) { if ((localeId & culture.LCID & 0x3FF) == (localeId & 0x3FF)) { return true; } } // check for exact match. else { if (localeId == culture.LCID) { return true; } } return false; } /// /// Reports an unexpected exception during a COM operation. /// public static void TraceComError(Exception e, string format, params object[] args) { var message = Utils.Format(format, args); var code = Marshal.GetHRForException(e); var error = code.ToString(); if (error == null) { return; } } #endregion } }