File: winforms\Managed\System\WinForms\ComponentModel\COM2Interop\COM2Properties.cs
Project: ndp\fx\src\System.Windows.Forms.csproj (System.Windows.Forms)
//------------------------------------------------------------------------------
// <copyright file="COM2Properties.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------
 
namespace System.Windows.Forms.ComponentModel.Com2Interop {
    using System.Runtime.Remoting;
    using System.Runtime.InteropServices;
    using System.ComponentModel;
    using System.Diagnostics;
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using Microsoft.Win32;
 
 
    /// <include file='doc\COM2Properties.uex' path='docs/doc[@for="Com2Properties"]/*' />
    /// <devdoc>
    /// This class is responsible for managing a set or properties for a native object.  It determines
    /// when the properties need to be refreshed, and owns the extended handlers for those properties.
    /// </devdoc>
    internal class Com2Properties {
    
        private static TraceSwitch DbgCom2PropertiesSwitch = new TraceSwitch("DbgCom2Properties", "Com2Properties: debug Com2 properties manager");
        
        
        
        /// <include file='doc\COM2Properties.uex' path='docs/doc[@for="Com2Properties.AGE_THRESHHOLD"]/*' />
        /// <devdoc>
        /// This is the interval that we'll hold props for.  If someone doesn't touch an object
        /// for this amount of time, we'll dump the properties from our cache.
        /// 
        /// 5 minutes -- ticks are 1/10,000,000th of a second
        /// </devdoc>
        private static long AGE_THRESHHOLD = (long)(10000000L * 60L * 5L);
 
        
        /// <include file='doc\COM2Properties.uex' path='docs/doc[@for="Com2Properties.weakObjRef"]/*' />
        /// <devdoc>
        /// This is the object that gave us the properties.  We hold a WeakRef so we don't addref the object.
        /// </devdoc>
        internal WeakReference weakObjRef;
        
        /// <include file='doc\COM2Properties.uex' path='docs/doc[@for="Com2Properties.props"]/*' />
        /// <devdoc>
        /// This is our list of properties.
        /// </devdoc>
        private Com2PropertyDescriptor[] props;
        
        /// <include file='doc\COM2Properties.uex' path='docs/doc[@for="Com2Properties.defaultIndex"]/*' />
        /// <devdoc>
        /// The index of the default property
        /// </devdoc>
        private int           defaultIndex = -1;
        
        
        /// <include file='doc\COM2Properties.uex' path='docs/doc[@for="Com2Properties.touchedTime"]/*' />
        /// <devdoc>
        /// The timestamp of the last operation on this property manager, usually
        /// when the property list was fetched.
        /// </devdoc>
        private long          touchedTime;  
 
        /// <devdoc>
        /// For non-IProvideMultipleClassInfo ITypeInfos, this is the version number on the last
        /// ITypeInfo we looked at.  If this changes, we know we need to dump the cache.
        /// </devdoc>
        private long[]       typeInfoVersions;
       
 
#if DEBUG
        private string        dbgObjName;
        private string        dbgObjClass;
#endif
 
        private int          alwaysValid = 0;
 
        /// <include file='doc\COM2Properties.uex' path='docs/doc[@for="Com2Properties.extendedInterfaces"]/*' />
        /// <devdoc>
        /// These are the interfaces we recognize for extended browsing.
        /// </devdoc>
        private static Type[] extendedInterfaces = new Type[]{
                                                        typeof(NativeMethods.ICategorizeProperties),
                                                        typeof(NativeMethods.IProvidePropertyBuilder),
                                                        typeof(NativeMethods.IPerPropertyBrowsing),
                                                        typeof(NativeMethods.IVsPerPropertyBrowsing),
                                                        typeof(NativeMethods.IManagedPerPropertyBrowsing)};
 
        /// <include file='doc\COM2Properties.uex' path='docs/doc[@for="Com2Properties.extendedInterfaceHandlerTypes"]/*' />
        /// <devdoc>
        /// These are the classes of handlers corresponding to the extended
        /// interfaces above.
        /// </devdoc>
        private static Type[] extendedInterfaceHandlerTypes = new Type[]{
                                                        typeof(Com2ICategorizePropertiesHandler),
                                                        typeof(Com2IProvidePropertyBuilderHandler),
                                                        typeof(Com2IPerPropertyBrowsingHandler),
                                                        typeof(Com2IVsPerPropertyBrowsingHandler),
                                                        typeof(Com2IManagedPerPropertyBrowsingHandler)};
                                                
 
 
        public event EventHandler Disposed;
 
 
        /// <include file='doc\COM2Properties.uex' path='docs/doc[@for="Com2Properties.Com2Properties"]/*' />
        /// <devdoc>
        /// Default ctor.
        /// </devdoc>
        public Com2Properties(object obj, Com2PropertyDescriptor[] props, int defaultIndex) {
#if DEBUG
            ComNativeDescriptor cnd = new ComNativeDescriptor();
            this.dbgObjName = cnd.GetName(obj);
            if (this.dbgObjName == null) {
                this.dbgObjName = "(null)";
            }
            this.dbgObjClass = cnd.GetClassName(obj);
            if (this.dbgObjClass == null) {
                this.dbgObjClass = "(null)";
            }
            if (DbgCom2PropertiesSwitch.TraceVerbose) Debug.WriteLine("Creating Com2Properties for object " + dbgObjName + ", class=" + dbgObjClass);
#endif
            
            // set up our variables
            SetProps(props);
            weakObjRef = new WeakReference(obj);
 
            this.defaultIndex = defaultIndex;
 
            typeInfoVersions = GetTypeInfoVersions(obj);
 
            touchedTime = DateTime.Now.Ticks;
 
        }
 
        internal bool AlwaysValid {
            get {
                return this.alwaysValid > 0;
            }
            set {
                if (value) {
                    if (alwaysValid == 0 && !CheckValid()) {
                        return;
                    }
                    this.alwaysValid++;
                }
                else {
                    if (alwaysValid > 0) {
                        this.alwaysValid--;
                    }
                }
            }
        }
 
        /// <include file='doc\COM2Properties.uex' path='docs/doc[@for="Com2Properties.DefaultProperty"]/*' />
        /// <devdoc>
        /// Retrieve the default property.
        /// </devdoc>
        public Com2PropertyDescriptor DefaultProperty{
            get{
                if (!CheckValid(true)) {
                    return null;
                }
                if (defaultIndex == -1) {
                    if (props.Length > 0) {
                        return props[0];
                    }
                    else {
                        return null;
                    }
                }
                Debug.Assert(defaultIndex < props.Length, "Whoops! default index is > props.Length");
                return props[defaultIndex];
            }
        }
 
        /// <include file='doc\COM2Properties.uex' path='docs/doc[@for="Com2Properties.TargetObject"]/*' />
        /// <devdoc>
        /// The object that created the list of properties.  This will
        /// return null if the timeout has passed or the ref has died.
        /// </devdoc>
        public object TargetObject{
            get{
                if (!CheckValid(false) || touchedTime == 0) {
#if DEBUG
                    if (DbgCom2PropertiesSwitch.TraceVerbose) Debug.WriteLine("CheckValid called on dead object!");
#endif
                    return null;
                }
                return weakObjRef.Target;
            }
        }
 
        /// <include file='doc\COM2Properties.uex' path='docs/doc[@for="Com2Properties.TicksSinceTouched"]/*' />
        /// <devdoc>
        /// How long since these props have been queried.
        /// </devdoc>
        public long TicksSinceTouched{
            get{
                if (touchedTime == 0) {
                    return 0;
                }
                return DateTime.Now.Ticks - touchedTime;
            }
        }
 
        /// <include file='doc\COM2Properties.uex' path='docs/doc[@for="Com2Properties.Properties"]/*' />
        /// <devdoc>
        /// Returns the list of properties
        /// </devdoc>
        public Com2PropertyDescriptor[] Properties{
            get{
                CheckValid(true);
                if (touchedTime == 0 || props == null) {
                    return null;
                }
                touchedTime = DateTime.Now.Ticks;
 
                // refresh everyone!
                for (int i = 0; i < props.Length; i++) {
                    props[i].SetNeedsRefresh(Com2PropertyDescriptorRefresh.All, true);
                }
 
#if DEBUG
                if (DbgCom2PropertiesSwitch.TraceVerbose) Debug.WriteLine("Returning prop array for object " + dbgObjName + ", class=" + dbgObjClass);
#endif
                return props;
            }
        }
 
        /// <include file='doc\COM2Properties.uex' path='docs/doc[@for="Com2Properties.TooOld"]/*' />
        /// <devdoc>
        /// Should this guy be refreshed because of old age?
        /// </devdoc>
        public bool TooOld{
            get{
                // check if the property is valid but don't dispose it if it's not
                CheckValid(false, false);
                if (touchedTime == 0) {
                    return false;
                }
                return TicksSinceTouched > AGE_THRESHHOLD;
            }
        }
 
        /// <include file='doc\COM2Properties.uex' path='docs/doc[@for="Com2Properties.AddExtendedBrowsingHandlers"]/*' />
        /// <devdoc>
        /// Checks the source object for eache extended browsing inteface
        /// listed in extendedInterfaces and creates a handler from extendedInterfaceHandlerTypes
        /// to handle it.
        /// </devdoc>
        public void AddExtendedBrowsingHandlers(Hashtable handlers) {
 
            object target = this.TargetObject;
            if (target == null) {
                return;
            }
 
 
            // process all our registered types
            Type t;
            for (int i = 0; i < extendedInterfaces.Length; i++) {
                t = extendedInterfaces[i];
                
                // is this object an implementor of the interface?
                //
                if (t.IsInstanceOfType(target)) {
                
                    // since handlers must be stateless, check to see if we've already
                    // created one of this type
                    //
                    Com2ExtendedBrowsingHandler handler = (Com2ExtendedBrowsingHandler)handlers[t];
                    if (handler == null) {
                        handler = (Com2ExtendedBrowsingHandler)Activator.CreateInstance(extendedInterfaceHandlerTypes[i]);
                        handlers[t] = handler;
                    }
                    
                    // make sure we got the right one
                    //
                    if (t.IsAssignableFrom(handler.Interface)) {
#if DEBUG
                        if (DbgCom2PropertiesSwitch.TraceVerbose) Debug.WriteLine("Adding browsing handler type " + handler.Interface.Name + " to object " + dbgObjName + ", class=" + dbgObjClass);
#endif
                        // allow the handler to attach itself to the appropriate properties
                        //
                        handler.SetupPropertyHandlers(props);
                    }
                    else {
                        throw new ArgumentException(SR.GetString(SR.COM2BadHandlerType, t.Name, handler.Interface.Name));
                    }
                }
            }
        }
 
       
        public void Dispose() {
#if DEBUG
            if (DbgCom2PropertiesSwitch.TraceVerbose) Debug.WriteLine("Disposing property manager for " + dbgObjName + ", class=" + dbgObjClass);
#endif
 
           if (props != null) {
 
                if (Disposed != null) {

                    Disposed(this, EventArgs.Empty);
                }
            
                weakObjRef = null;
                props = null;
                touchedTime = 0;
           }
        }
 
        public bool CheckValid() {
            return CheckValid(false);
        }
 
        /// <include file='doc\COM2Properties.uex' path='docs/doc[@for="Com2Properties.CheckValid"]/*' />
        /// <devdoc>
        /// Make sure this property list is still valid.
        ///
        /// 1) WeakRef is still alive
        /// 2) Our timeout hasn't passed
        /// </devdoc>
        public bool CheckValid(bool checkVersions) {
            return CheckValid(checkVersions, true);
        }
 
        
        /// <devdoc>
        /// Gets a list of version longs for each type info in the COM object
        /// representing hte current version stamp, function and variable count.
        /// If any of these things change, we'll re-fetch the properties.
        /// </devdoc>
        private long[] GetTypeInfoVersions(object comObject) {
 
            // get type infos
            //
            UnsafeNativeMethods.ITypeInfo[] pTypeInfos = Com2TypeInfoProcessor.FindTypeInfos(comObject, false);
 
            // build up the info.
            //
            long[] versions = new long[pTypeInfos.Length];
            for (int i = 0; i < pTypeInfos.Length; i++) {
                versions [i] = GetTypeInfoVersion(pTypeInfos[i]);
            }           
            return versions;
        }
 
        private static int countOffset = -1;
        private static int versionOffset = -1;
 
        // we define a struct here so we can use unsafe code to marshal it
        // as a blob of memory.  This is here as a reference to how big and where the members are.
        //
        /*private struct tagTYPEATTR {
            public Guid guid;                       //16
            public   int lcid;                      // 4
            public   int dwReserved;                // 4
            public   int memidConstructor;          // 4
            public   int memidDestructor;           // 4
            public   IntPtr lpstrSchema;            // platform
            public   int cbSizeInstance;            // 4
            public    int typekind;                 // 4
            public   short cFuncs;                  // 2
            public   short cVars;                   // 2
            public   short cImplTypes;              // 2
            public   short cbSizeVft;               // 2
            public   short cbAlignment;             // 2
            public   short wTypeFlags;              // 2
            public   short wMajorVerNum;            // 2
            public   short wMinorVerNum;            // 2
            
            public   int tdescAlias_unionMember;            
            public   short tdescAlias_vt;            
            public   int idldescType_dwReserved;            
            public   short idldescType_wIDLFlags;
        
        }*/
 
 
        // the offset of the cFunc member in the TYPEATTR structure.
        //        
        private static int CountMemberOffset {
 
                get {
                    if (countOffset == -1) {
                        countOffset = Marshal.SizeOf(typeof(Guid)) + IntPtr.Size + 24;
                    }
                    return countOffset;
                }
        }
 
        // the offset of the cMajorVerNum member in the TYPEATTR structure.
        //
        private static int VersionOffset {
                get {
                    if (versionOffset == -1) {
                        versionOffset = CountMemberOffset + 12;
                    }
                    return versionOffset;
                }
                    
        }
 
        private unsafe long GetTypeInfoVersion(UnsafeNativeMethods.ITypeInfo pTypeInfo) {
 
 
            IntPtr pTypeAttr = IntPtr.Zero;
            int hr = pTypeInfo.GetTypeAttr(ref pTypeAttr);
            if (!NativeMethods.Succeeded(hr)) {
                return 0;
            }
 
            System.Runtime.InteropServices.ComTypes.TYPEATTR pTAStruct;
            try {
                try {
                    // just access directly...no marshalling needed!
                    //
                    pTAStruct = *(System.Runtime.InteropServices.ComTypes.TYPEATTR*)pTypeAttr;
                }
                catch {
 
                    return 0;
                }
 
                long result = 0;
 
                // we pull two things out of the struct: the 
                // number of functions and variables, and the version.
                // since they are next to each other, we just pull the memory directly.
                //
                // the cFuncs and cVars are both shorts, so we read them as one block of ints.
                //
                //
                int* pResult = (int*)&result;
                byte* pbStruct = (byte*)&pTAStruct;
 
                // in the low byte, pull the number of props.
                //
                *pResult = *(int*)(pbStruct + CountMemberOffset);
 
                // move up to the high word of the long.
                //                
                pResult++;
 
                // now pull out the version info.
                //
                *pResult = *(int*)(pbStruct + VersionOffset);
 
                // return that composite long.
                //
                return result;
            }
            finally {
               pTypeInfo.ReleaseTypeAttr(pTypeAttr);
            }
        }
 
        internal bool CheckValid(bool checkVersions, bool callDispose) {
 
            if (this.AlwaysValid) {
                return true;
            }
 
            bool valid = weakObjRef != null && weakObjRef.IsAlive;
 
            // check the version information for each ITypeInfo the object exposes.
            //
            if (valid && checkVersions) {
 
               // 
               long[] newTypeInfoVersions = GetTypeInfoVersions(weakObjRef.Target);
 
               if (newTypeInfoVersions.Length != typeInfoVersions.Length) {
                   valid = false;
               } else {
                   // compare each version number to the old one.
                   //
                   for (int i = 0; i < newTypeInfoVersions.Length; i++) {
 
                        if (newTypeInfoVersions[i] != typeInfoVersions[i]) {                            
                            valid = false;
                            break;
                        }
                   }
               }
 
               if (!valid) {
 
                   // update to the new version list we have.
                   // 
                   typeInfoVersions = newTypeInfoVersions;
               }
            }
            
            if (!valid && callDispose) {
                // weak ref has died, so remove this from the hash table
                //
#if DEBUG
                if (DbgCom2PropertiesSwitch.TraceVerbose) Debug.WriteLine("Disposing reference to object " + dbgObjName + ", class=" + dbgObjClass + " (weakRef " + (weakObjRef == null ? "null" : "dead") + ")");
#endif
 
                Dispose();
            }
 
            return valid;
        }
 
        /// <include file='doc\COM2Properties.uex' path='docs/doc[@for="Com2Properties.SetProps"]/*' />
        /// <devdoc>
        /// Set the props for this object, and notify each property
        /// that we are now it's manager
        /// </devdoc>
        internal void SetProps(Com2PropertyDescriptor[] props) {
            this.props = props;
            if (props != null) {
                for (int i = 0; i < props.Length; i++) {
                    props[i].PropertyManager = this;
                }
            }
        }        
    }
}