/* * Greenshot - a free and open source screenshot tool * Copyright (C) 2007-2013 Thomas Braun, Jens Klingen, Robin Krom * * For more information see: http://getgreenshot.org/ * The Greenshot project is hosted on Sourceforge: http://sourceforge.net/projects/greenshot/ * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 1 of the License, or * (at your option) any later version. * * This program 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. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ using GreenshotPlugin; using GreenshotPlugin.Core; using System; using System.Reflection; using System.Runtime.InteropServices; using System.Runtime.Remoting; using System.Runtime.Remoting.Messaging; using System.Runtime.Remoting.Proxies; using System.Windows.Forms; namespace Greenshot.Interop { /// /// Wraps a late-bound COM server. /// public sealed class COMWrapper : RealProxy, IDisposable, IRemotingTypeInfo { private const int MK_E_UNAVAILABLE = -2147221021; private const int CO_E_CLASSSTRING = -2147221005; public const int RPC_E_CALL_REJECTED = unchecked((int)0x80010001); public const int RPC_E_FAIL = unchecked((int)0x80004005); #region Private Data /// /// Holds reference to the actual COM object which is wrapped by this proxy /// private object _COMObject; /// /// Type of the COM object, set on constructor after getting the COM reference /// private Type _COMType; /// /// The type of which method calls are intercepted and executed on the COM object. /// private Type _InterceptType; /// /// The humanly readable target name /// private string _TargetName; #endregion Private Data [DllImport("ole32.dll")] private static extern int ProgIDFromCLSID([In] ref Guid clsid, [MarshalAs(UnmanagedType.LPWStr)] out string lplpszProgID); // Converts failure HRESULTs to exceptions: [DllImport("oleaut32", PreserveSig = false)] private static extern void GetActiveObject(ref Guid rclsid, IntPtr pvReserved, [MarshalAs(UnmanagedType.IUnknown)] out Object ppunk); #region Construction /// /// Gets a COM object and returns the transparent proxy which intercepts all calls to the object /// /// Interface which defines the method and properties to intercept /// Transparent proxy to the real proxy for the object /// The must be an interface decorated with the attribute. public static T GetInstance() { Type type = typeof(T); if (null == type) { throw new ArgumentNullException("type"); } if (!type.IsInterface) { throw new ArgumentException("The specified type must be an interface.", "type"); } ComProgIdAttribute progIDAttribute = ComProgIdAttribute.GetAttribute(type); if (null == progIDAttribute || null == progIDAttribute.Value || 0 == progIDAttribute.Value.Length) { throw new ArgumentException("The specified type must define a ComProgId attribute.", "type"); } string progId = progIDAttribute.Value; object comObject = null; // Convert from clsid to Prog ID, if needed if (progId.StartsWith("clsid:")) { Guid guid = new Guid(progId.Substring(6)); int result = ProgIDFromCLSID(ref guid, out progId); if (result != 0) { // Restore progId, as it's overwritten progId = progIDAttribute.Value; try { GetActiveObject(ref guid, IntPtr.Zero, out comObject); } catch (Exception) { LOG.WarnFormat("Error {0} getting instance for class id {1}", result, progIDAttribute.Value); } if (comObject == null) { LOG.WarnFormat("Error {0} getting progId {1}", result, progIDAttribute.Value); } } else { LOG.InfoFormat("Mapped {0} to progId {1}", progIDAttribute.Value, progId); } } if (comObject == null) { try { comObject = Marshal.GetActiveObject(progId); } catch (COMException comE) { if (comE.ErrorCode == MK_E_UNAVAILABLE) { LOG.DebugFormat("No current instance of {0} object available.", progId); } else if (comE.ErrorCode == CO_E_CLASSSTRING) { LOG.WarnFormat("Unknown progId {0}", progId); } else { LOG.Warn("Error getting active object for " + progIDAttribute.Value, comE); } } catch (Exception e) { LOG.Warn("Error getting active object for " + progIDAttribute.Value, e); } } if (comObject != null) { if (comObject is IDispatch) { COMWrapper wrapper = new COMWrapper(comObject, type, progIDAttribute.Value); return (T)wrapper.GetTransparentProxy(); } else { return (T)comObject; } } return default(T); } /// /// A simple create instance, doesn't create a wrapper!! /// /// T public static T CreateInstance() { Type type = typeof(T); if (null == type) { throw new ArgumentNullException("type"); } if (!type.IsInterface) { throw new ArgumentException("The specified type must be an interface.", "type"); } ComProgIdAttribute progIDAttribute = ComProgIdAttribute.GetAttribute(type); if (null == progIDAttribute || null == progIDAttribute.Value || 0 == progIDAttribute.Value.Length) { throw new ArgumentException("The specified type must define a ComProgId attribute.", "type"); } string progId = progIDAttribute.Value; Type comType = null; if (progId.StartsWith("clsid:")) { Guid guid = new Guid(progId.Substring(6)); try { comType = Type.GetTypeFromCLSID(guid); } catch (Exception ex) { LOG.WarnFormat("Error {1} type for {0}", progId, ex.Message); } } else { try { comType = Type.GetTypeFromProgID(progId, true); } catch (Exception ex) { LOG.WarnFormat("Error {1} type for {0}", progId, ex.Message); } } object comObject = null; if (comType != null) { try { comObject = Activator.CreateInstance(comType); if (comObject != null) { LOG.DebugFormat("Created new instance of {0} object.", progId); } } catch (Exception e) { LOG.WarnFormat("Error {1} creating object for {0}", progId, e.Message); } } if (comObject != null) { return (T)comObject; } return default(T); } /// /// Gets or creates a COM object and returns the transparent proxy which intercepts all calls to the object /// The ComProgId can be a normal ComProgId or a GUID prefixed with "clsid:" /// /// Interface which defines the method and properties to intercept /// Transparent proxy to the real proxy for the object /// The must be an interface decorated with the attribute. public static T GetOrCreateInstance() { Type type = typeof(T); if (null == type) { throw new ArgumentNullException("type"); } if (!type.IsInterface) { throw new ArgumentException("The specified type must be an interface.", "type"); } ComProgIdAttribute progIDAttribute = ComProgIdAttribute.GetAttribute(type); if (null == progIDAttribute || null == progIDAttribute.Value || 0 == progIDAttribute.Value.Length) { throw new ArgumentException("The specified type must define a ComProgId attribute.", "type"); } object comObject = null; Type comType = null; string progId = progIDAttribute.Value; Guid guid = Guid.Empty; // Convert from clsid to Prog ID, if needed if (progId.StartsWith("clsid:")) { guid = new Guid(progId.Substring(6)); int result = ProgIDFromCLSID(ref guid, out progId); if (result != 0) { // Restore progId, as it's overwritten progId = progIDAttribute.Value; try { GetActiveObject(ref guid, IntPtr.Zero, out comObject); } catch (Exception) { LOG.WarnFormat("Error {0} getting instance for class id {1}", result, progIDAttribute.Value); } if (comObject == null) { LOG.WarnFormat("Error {0} getting progId {1}", result, progIDAttribute.Value); } } else { LOG.InfoFormat("Mapped {0} to progId {1}", progIDAttribute.Value, progId); } } if (comObject == null) { if (!progId.StartsWith("clsid:")) { try { comObject = Marshal.GetActiveObject(progId); } catch (COMException comE) { if (comE.ErrorCode == MK_E_UNAVAILABLE) { LOG.DebugFormat("No current instance of {0} object available.", progId); } else if (comE.ErrorCode == CO_E_CLASSSTRING) { LOG.WarnFormat("Unknown progId {0} (application not installed)", progId); return default(T); } else { LOG.Warn("Error getting active object for " + progId, comE); } } catch (Exception e) { LOG.Warn("Error getting active object for " + progId, e); } } } // Did we get the current instance? If not, try to create a new if (comObject == null) { try { comType = Type.GetTypeFromProgID(progId, true); } catch (Exception ex) { if (Guid.Empty != guid) { comType = Type.GetTypeFromCLSID(guid); } else { LOG.Warn("Error type for " + progId, ex); } } if (comType != null) { try { comObject = Activator.CreateInstance(comType); if (comObject != null) { LOG.DebugFormat("Created new instance of {0} object.", progId); } } catch (Exception e) { LOG.Warn("Error creating object for " + progId, e); } } } if (comObject != null) { if (comObject is IDispatch) { COMWrapper wrapper = new COMWrapper(comObject, type, progIDAttribute.Value); return (T)wrapper.GetTransparentProxy(); } else { return (T)comObject; } } return default(T); } /// /// Wrap an object and return the transparent proxy which intercepts all calls to the object /// /// An object to intercept /// Interface which defines the method and properties to intercept /// Transparent proxy to the real proxy for the object private static object Wrap(object comObject, Type type, string targetName) { if (null == comObject) { throw new ArgumentNullException("comObject"); } if (null == type) { throw new ArgumentNullException("type"); } COMWrapper wrapper = new COMWrapper(comObject, type, targetName); return wrapper.GetTransparentProxy(); } /// /// Constructor /// /// /// The COM object to wrap. /// /// /// The interface type to impersonate. /// private COMWrapper(object comObject, Type type, string targetName) : base(type) { _COMObject = comObject; _COMType = comObject.GetType(); _InterceptType = type; _TargetName = targetName; } #endregion Construction #region Clean up /// /// If is not called, we need to make /// sure that the COM object is still cleaned up. /// ~COMWrapper() { LOG.DebugFormat("Finalize {0}", _InterceptType.ToString()); Dispose(false); } /// /// Cleans up the COM object. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Release the COM reference /// /// /// if this was called from the /// interface. /// private void Dispose(bool disposing) { if (null != _COMObject) { LOG.DebugFormat("Disposing {0}", _InterceptType.ToString()); if (Marshal.IsComObject(_COMObject)) { try { while (Marshal.ReleaseComObject(_COMObject) > 0) ; } catch (Exception ex) { LOG.WarnFormat("Problem releasing {0}", _COMType); LOG.Warn("Error: ", ex); } } _COMObject = null; } } #endregion Clean up #region Object methods /// /// Returns a string representing the wrapped object. /// /// /// The full name of the intercepted type. /// public override string ToString() { return _InterceptType.FullName; } /// /// Returns the hash code of the wrapped object. /// /// /// The hash code of the wrapped object. /// public override int GetHashCode() { return _COMObject.GetHashCode(); } /// /// Compares this object to another. /// /// /// The value to compare to. /// /// /// if the objects are equal. /// public override bool Equals(object value) { if (null != value && RemotingServices.IsTransparentProxy(value)) { COMWrapper wrapper = RemotingServices.GetRealProxy(value) as COMWrapper; if (null != wrapper) { return _COMObject == wrapper._COMObject; } } return base.Equals(value); } /// /// Returns the base type for a reference type. /// /// /// The reference type. /// /// /// The base value type. /// /// /// is . /// private static Type GetByValType(Type byRefType) { if (null == byRefType) { throw new ArgumentNullException("byRefType"); } if (byRefType.IsByRef) { string name = byRefType.FullName; name = name.Substring(0, name.Length - 1); byRefType = byRefType.Assembly.GetType(name, true); } return byRefType; } #endregion Object methods /// /// Use this static method to cast a wrapped proxy to a new wrapper proxy of the supplied type. /// In English, use this to cast you base "COM" interface to a specialized interface. /// E.G. Outlook Item -> MailItem /// /// the type you want to cast to /// The wrapper interface, e.g. something you got back from calling GetItem /// A new wrapper proxy for the specified type public static T Cast(object wrapperProxy) { if (wrapperProxy == null) { return default(T); } Type newType = typeof(T); COMWrapper oldWrapper = RemotingServices.GetRealProxy(wrapperProxy) as COMWrapper; if (oldWrapper == null) { throw new ArgumentException("wrapper proxy was no COMWrapper"); } if (oldWrapper._InterceptType.IsAssignableFrom(newType)) { COMWrapper newWrapper = new COMWrapper(oldWrapper._COMObject, newType, oldWrapper._TargetName); return (T)newWrapper.GetTransparentProxy(); } throw new InvalidCastException(string.Format("{0} is not assignable from {1}", oldWrapper._InterceptType, newType)); } /// /// Returns the "com" type of the wrapperproxy, making it possible to perform reflection on it. /// /// wrapperProxy to get the type from /// Type public static Type GetUnderlyingTypeForWrapper(object wrapperProxy) { Type returnType = null; COMWrapper wrapper = RemotingServices.GetRealProxy(wrapperProxy) as COMWrapper; if (wrapper != null) { IDispatch dispatch = wrapper._COMObject as IDispatch; if (dispatch != null) { int result = dispatch.GetTypeInfo(0, 0, out returnType); if (result != 0) { LOG.DebugFormat("GetTypeInfo : 0x{0} ({1})", result.ToString("X"), result); } } } return returnType; } /// /// Return the Type of a IDispatch /// /// IDispatch to get the type object for /// Type of the IDispatch public static Type GetUnderlyingType(IDispatch dispatch) { Type returnType = null; if (dispatch != null) { int result = dispatch.GetTypeInfo(0, 0, out returnType); if (result != 0) { LOG.DebugFormat("GetTypeInfo : 0x{0} ({1})", result.ToString("X"), result); } } return returnType; } /// /// Dump the Type-Information for the Type to the log, this uses reflection /// /// Type to inspect public static void DumpTypeInfo(Type type) { LOG.InfoFormat("Type information for Type with name: {0}", type.Name); try { foreach (MemberInfo memberInfo in type.GetMembers()) { LOG.InfoFormat("Member: {0};", memberInfo.ToString()); } } catch (Exception memberException) { LOG.Error(memberException); } try { foreach (PropertyInfo propertyInfo in type.GetProperties()) { LOG.InfoFormat("Property: {0};", propertyInfo.ToString()); } } catch (Exception propertyException) { LOG.Error(propertyException); } try { foreach (FieldInfo fieldInfo in type.GetFields()) { LOG.InfoFormat("Field: {0};", fieldInfo.ToString()); } } catch (Exception fieldException) { LOG.Error(fieldException); } LOG.InfoFormat("Type information end."); } /// /// Intercept method calls /// /// /// Contains information about the method being called /// /// /// A . /// public override IMessage Invoke(IMessage myMessage) { IMethodCallMessage callMessage = myMessage as IMethodCallMessage; if (null == callMessage) { LOG.DebugFormat("Message type not implemented: {0}", myMessage.GetType().ToString()); return null; } MethodInfo method = callMessage.MethodBase as MethodInfo; if (null == method) { LOG.DebugFormat("Unrecognized Invoke call: {0}", callMessage.MethodBase.ToString()); return null; } object returnValue = null; object[] outArgs = null; int outArgsCount = 0; string methodName = method.Name; Type returnType = method.ReturnType; BindingFlags flags = BindingFlags.InvokeMethod; int argCount = callMessage.ArgCount; object invokeObject; Type invokeType; Type byValType; object[] args; object arg; COMWrapper[] originalArgs; COMWrapper wrapper; ParameterModifier[] argModifiers = null; ParameterInfo[] parameters = null; ParameterInfo parameter; if ("Dispose" == methodName && 0 == argCount && typeof(void) == returnType) { Dispose(); } else if ("ToString" == methodName && 0 == argCount && typeof(string) == returnType) { returnValue = ToString(); } else if ("GetType" == methodName && 0 == argCount && typeof(Type) == returnType) { returnValue = _InterceptType; } else if ("GetHashCode" == methodName && 0 == argCount && typeof(int) == returnType) { returnValue = GetHashCode(); } else if ("Equals" == methodName && 1 == argCount && typeof(bool) == returnType) { returnValue = Equals(callMessage.Args[0]); } else if (1 == argCount && typeof(void) == returnType && (methodName.StartsWith("add_") || methodName.StartsWith("remove_"))) { bool removeHandler = methodName.StartsWith("remove_"); methodName = methodName.Substring(removeHandler ? 7 : 4); Delegate handler = callMessage.InArgs[0] as Delegate; if (null == handler) { return new ReturnMessage(new ArgumentNullException("handler"), callMessage); } } else { invokeObject = _COMObject; invokeType = _COMType; if (methodName.StartsWith("get_")) { // Property Get methodName = methodName.Substring(4); flags = BindingFlags.GetProperty; args = callMessage.InArgs; } else if (methodName.StartsWith("set_")) { // Property Set methodName = methodName.Substring(4); flags = BindingFlags.SetProperty; args = callMessage.InArgs; } else { args = callMessage.Args; if (null != args && 0 != args.Length) { // Modifiers for ref / out parameters argModifiers = new ParameterModifier[1]; argModifiers[0] = new ParameterModifier(args.Length); parameters = method.GetParameters(); for (int i = 0; i < parameters.Length; i++) { parameter = parameters[i]; if (parameter.IsOut || parameter.ParameterType.IsByRef) { argModifiers[0][i] = true; outArgsCount++; } } if (0 == outArgsCount) { argModifiers = null; } } } // Un-wrap wrapped COM objects before passing to the method if (null == args || 0 == args.Length) { originalArgs = null; } else { originalArgs = new COMWrapper[args.Length]; for (int i = 0; i < args.Length; i++) { if (null != args[i] && RemotingServices.IsTransparentProxy(args[i])) { wrapper = RemotingServices.GetRealProxy(args[i]) as COMWrapper; if (null != wrapper) { originalArgs[i] = wrapper; args[i] = wrapper._COMObject; } } else if (0 != outArgsCount && argModifiers[0][i]) { byValType = GetByValType(parameters[i].ParameterType); if (byValType.IsInterface) { // If we're passing a COM object by reference, and // the parameter is null, we need to pass a // DispatchWrapper to avoid a type mismatch exception. if (null == args[i]) { args[i] = new DispatchWrapper(null); } } else if (typeof(Decimal) == byValType) { // If we're passing a decimal value by reference, // we need to pass a CurrencyWrapper to avoid a // type mismatch exception. // http://support.microsoft.com/?kbid=837378 args[i] = new CurrencyWrapper(args[i]); } } } } do { try { returnValue = invokeType.InvokeMember(methodName, flags, null, invokeObject, args, argModifiers, null, null); break; } catch (Exception ex) { // Test for rejected COMException comEx = ex as COMException; if (comEx == null) { comEx = ex.InnerException as COMException; } if (comEx != null && (comEx.ErrorCode == RPC_E_CALL_REJECTED || comEx.ErrorCode == RPC_E_FAIL)) { string destinationName = _TargetName; // Try to find a "catchy" name for the rejecting application if (destinationName != null && destinationName.Contains(".")) { destinationName = destinationName.Substring(0, destinationName.IndexOf(".")); } if (destinationName == null) { destinationName = _InterceptType.FullName; } DialogResult result = MessageBox.Show(Language.GetFormattedString("com_rejected", destinationName), Language.GetString("com_rejected_title"), MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation); if (result == DialogResult.OK) { continue; } } // Not rejected OR pressed cancel return new ReturnMessage(ex, callMessage); } } while (true); // Handle enum and interface return types if (null != returnValue) { if (returnType.IsInterface) { // Wrap the returned value in an intercepting COM wrapper if (Marshal.IsComObject(returnValue)) { returnValue = Wrap(returnValue, returnType, _TargetName); } } else if (returnType.IsEnum) { // Convert to proper Enum type returnValue = Enum.Parse(returnType, returnValue.ToString()); } } // Handle out args if (0 != outArgsCount) { outArgs = new object[args.Length]; for (int i = 0; i < parameters.Length; i++) { if (!argModifiers[0][i]) { continue; } arg = args[i]; if (null == arg) { continue; } parameter = parameters[i]; wrapper = null; byValType = GetByValType(parameter.ParameterType); if (typeof(Decimal) == byValType) { if (arg is CurrencyWrapper) { arg = ((CurrencyWrapper)arg).WrappedObject; } } else if (byValType.IsEnum) { arg = Enum.Parse(byValType, arg.ToString()); } else if (byValType.IsInterface) { if (Marshal.IsComObject(arg)) { wrapper = originalArgs[i]; if (null != wrapper && wrapper._COMObject != arg) { wrapper.Dispose(); wrapper = null; } if (null == wrapper) { wrapper = new COMWrapper(arg, byValType, _TargetName); } arg = wrapper.GetTransparentProxy(); } } outArgs[i] = arg; } } } return new ReturnMessage(returnValue, outArgs, outArgsCount, callMessage.LogicalCallContext, callMessage); } /// /// Implementation for the interface IRemotingTypeInfo /// This makes it possible to cast the COMWrapper /// /// Type to cast to /// object to cast /// public bool CanCastTo(Type toType, object o) { bool returnValue = _InterceptType.IsAssignableFrom(toType); return returnValue; } /// /// Implementation for the interface IRemotingTypeInfo /// public string TypeName { get { throw new NotSupportedException(); } set { throw new NotSupportedException(); } } } }