#region Using Directives
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Security.Permissions;
#endregion
namespace TestDispatchUtility
{
	/// 
	/// Provides helper methods for working with COM IDispatch objects that have a registered type library.
	/// 
	public static class DispatchUtility
	{
		#region Private Constants
		private const int S_OK = 0; //From WinError.h
		private const int LOCALE_SYSTEM_DEFAULT = 2 << 10; //From WinNT.h == 2048 == 0x800
		#endregion
		#region Public Methods
		/// 
		/// Gets whether the specified object implements IDispatch.
		/// 
		/// An object to check.
		/// True if the object implements IDispatch.  False otherwise.
		public static bool ImplementsIDispatch(object obj)
		{
			bool result = obj is IDispatchInfo;
			return result;
		}
		/// 
		/// Gets a Type that can be used with reflection.
		/// 
		/// An object that implements IDispatch.
		/// Whether an exception should be thrown if a Type can't be obtained.
		/// A .NET Type that can be used with reflection.
		/// If  doesn't implement IDispatch.
		[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
		public static Type GetType(object obj, bool throwIfNotFound)
		{
			RequireReference(obj, "obj");
			Type result = GetType((IDispatchInfo)obj, throwIfNotFound);
			return result;
		}
		/// 
		/// Tries to get the DISPID for the requested member name.
		/// 
		/// An object that implements IDispatch.
		/// The name of a member to lookup.
		/// If the method returns true, this holds the DISPID on output.
		/// If the method returns false, this value should be ignored.
		/// True if the member was found and resolved to a DISPID.  False otherwise.
		/// If  doesn't implement IDispatch.
		[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
		public static bool TryGetDispId(object obj, string name, out int dispId)
		{
			RequireReference(obj, "obj");
			bool result = TryGetDispId((IDispatchInfo)obj, name, out dispId);
			return result;
		}
		/// 
		/// Invokes a member by DISPID.
		/// 
		/// An object that implements IDispatch.
		/// The DISPID of a member.  This can be obtained using
		/// .
		/// The arguments to pass to the member.
		/// The member's return value.
		/// 
		/// This can invoke a method or a property get accessor.
		/// 
		public static object Invoke(object obj, int dispId, object[] args)
		{
			string memberName = "[DispId=" + dispId + "]";
			object result = Invoke(obj, memberName, args);
			return result;
		}
		/// 
		/// Invokes a member by name.
		/// 
		/// An object.
		/// The name of the member to invoke.
		/// The arguments to pass to the member.
		/// The member's return value.
		/// 
		/// This can invoke a method or a property get accessor.
		/// 
		public static object Invoke(object obj, string memberName, object[] args)
		{
			RequireReference(obj, "obj");
			Type type = obj.GetType();
			object result = type.InvokeMember(memberName, BindingFlags.InvokeMethod | BindingFlags.GetProperty,
				null, obj, args, null);
			return result;
		}
		#endregion
		#region Private Methods
		/// 
		/// Requires that the value is non-null.
		/// 
		/// The type of the value.
		/// The value to check.
		/// The name of the value.
		private static void RequireReference(T value, string name) where T : class
		{
			if (value == null)
			{
				throw new ArgumentNullException(name);
			}
		}
		/// 
		/// Gets a Type that can be used with reflection.
		/// 
		/// An object that implements IDispatch.
		/// Whether an exception should be thrown if a Type can't be obtained.
		/// A .NET Type that can be used with reflection.
		private static Type GetType(IDispatchInfo dispatch, bool throwIfNotFound)
		{
			RequireReference(dispatch, "dispatch");
			Type result = null;
			int typeInfoCount;
			int hr = dispatch.GetTypeInfoCount(out typeInfoCount);
			if (hr == S_OK && typeInfoCount > 0)
			{
				// Type info isn't usually culture-aware for IDispatch, so we might as well pass
				// the default locale instead of looking up the current thread's LCID each time
				// (via CultureInfo.CurrentCulture.LCID).
				dispatch.GetTypeInfo(0, LOCALE_SYSTEM_DEFAULT, out result);
			}
			if (result == null && throwIfNotFound)
			{
				// If the GetTypeInfoCount called failed, throw an exception for that.
				Marshal.ThrowExceptionForHR(hr);
				// Otherwise, throw the same exception that Type.GetType would throw.
				throw new TypeLoadException();
			}
			return result;
		}
		/// 
		/// Tries to get the DISPID for the requested member name.
		/// 
		/// An object that implements IDispatch.
		/// The name of a member to lookup.
		/// If the method returns true, this holds the DISPID on output.
		/// If the method returns false, this value should be ignored.
		/// True if the member was found and resolved to a DISPID.  False otherwise.
		private static bool TryGetDispId(IDispatchInfo dispatch, string name, out int dispId)
		{
			RequireReference(dispatch, "dispatch");
			RequireReference(name, "name");
			bool result = false;
			// Members names aren't usually culture-aware for IDispatch, so we might as well
			// pass the default locale instead of looking up the current thread's LCID each time
			// (via CultureInfo.CurrentCulture.LCID).
			Guid iidNull = Guid.Empty;
			int hr = dispatch.GetDispId(ref iidNull, ref name, 1, LOCALE_SYSTEM_DEFAULT, out dispId);
			const int DISP_E_UNKNOWNNAME = unchecked((int)0x80020006); //From WinError.h
			const int DISPID_UNKNOWN = -1; //From OAIdl.idl
			if (hr == S_OK)
			{
				result = true;
			}
			else if (hr == DISP_E_UNKNOWNNAME && dispId == DISPID_UNKNOWN)
			{
				// This is the only supported "error" case because it means IDispatch
				// is saying it doesn't know the member we asked about.
				result = false;
			}
			else
			{
				// The other documented result codes are all errors.
				Marshal.ThrowExceptionForHR(hr);
			}
			return result;
		}
		#endregion
		#region Private Interfaces
		/// 
		/// A partial declaration of IDispatch used to lookup Type information and DISPIDs.
		/// 
		/// 
		/// This interface only declares the first three methods of IDispatch.  It omits the
		/// fourth method (Invoke) because there are already plenty of ways to do dynamic
		/// invocation in .NET.  But the first three methods provide dynamic type metadata
		/// discovery, which .NET doesn't provide normally if you have a System.__ComObject
		/// RCW instead of a strongly-typed RCW.
		/// 
		/// Note: The original declaration of IDispatch is in OAIdl.idl.
		/// 
		[ComImport]
		[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
		[Guid("00020400-0000-0000-C000-000000000046")]
		private interface IDispatchInfo
		{
			/// 
			/// Gets the number of Types that the object provides (0 or 1).
			/// 
			/// Returns 0 or 1 for the number of Types provided by .
			/// 
			/// http://msdn.microsoft.com/en-us/library/da876d53-cb8a-465c-a43e-c0eb272e2a12(VS.85)
			/// 
			[PreserveSig]
			int GetTypeInfoCount(out int typeInfoCount);
			/// 
			/// Gets the Type information for an object if  returned 1.
			/// 
			/// Must be 0.
			/// Typically, LOCALE_SYSTEM_DEFAULT (2048).
			/// Returns the object's Type information.
			/// 
			/// http://msdn.microsoft.com/en-us/library/cc1ec9aa-6c40-4e70-819c-a7c6dd6b8c99(VS.85)
			/// 
			void GetTypeInfo(int typeInfoIndex, int lcid, [MarshalAs(UnmanagedType.CustomMarshaler,
				MarshalTypeRef = typeof(System.Runtime.InteropServices.CustomMarshalers.TypeToTypeInfoMarshaler))] out Type typeInfo);
			/// 
			/// Gets the DISPID of the specified member name.
			/// 
			/// Must be IID_NULL.  Pass a copy of Guid.Empty.
			/// The name of the member to look up.
			/// Must be 1.
			/// Typically, LOCALE_SYSTEM_DEFAULT (2048).
			/// If a member with the requested 
			/// is found, this returns its DISPID and the method's return value is 0.
			/// If the method returns a non-zero value, then this parameter's output value is
			/// undefined.
			/// Zero for success. Non-zero for failure.
			/// 
			/// http://msdn.microsoft.com/en-us/library/6f6cf233-3481-436e-8d6a-51f93bf91619(VS.85)
			/// 
			[PreserveSig]
			int GetDispId(ref Guid riid, ref string name, int nameCount, int lcid, out int dispId);
			// NOTE: The real IDispatch also has an Invoke method next, but we don't need it.
			// We can invoke methods using .NET's Type.InvokeMember method with the special
			// [DISPID=n] syntax for member "names", or we can get a .NET Type using GetTypeInfo
			// and invoke methods on that through reflection.
			// Type.InvokeMember: http://msdn.microsoft.com/en-us/library/de3dhzwy.aspx
		}
		#endregion
	}
}