diff --git a/RetailCoder.VBE/App.cs b/RetailCoder.VBE/App.cs index a3d71d721a..9a871d8f2c 100644 --- a/RetailCoder.VBE/App.cs +++ b/RetailCoder.VBE/App.cs @@ -1,6 +1,9 @@ -using System.IO; +using System.Collections.Generic; +using System.IO; +using System.Reflection; using Infralution.Localization.Wpf; using NLog; +using NLog.Fluent; using Rubberduck.Common; using Rubberduck.Parsing; using Rubberduck.Parsing.Symbols; @@ -145,6 +148,7 @@ private void UpdateLoggingLevel() public void Startup() { EnsureLogFolderPathExists(); + LogRubberduckSart(); LoadConfig(); CheckForLegacyIndenterSettings(); _appMenus.Initialize(); @@ -223,6 +227,21 @@ private void CheckForLegacyIndenterSettings() } } + private void LogRubberduckSart() + { + var version = GetType().Assembly.GetName().Version.ToString(); + GlobalDiagnosticsContext.Set("RubberduckVersion", version); + var headers = new List + { + string.Format("Rubberduck version {0} loading:", version), + string.Format("\tOperating System: {0} {1}", Environment.OSVersion.VersionString, Environment.Is64BitOperatingSystem ? "x64" : "x86"), + string.Format("\tHost Product: {0} {1}", Application.ProductName, Environment.Is64BitProcess ? "x64" : "x86"), + string.Format("\tHost Version: {0}", Application.ProductVersion), + string.Format("\tHost Executable: {0}", Path.GetFileName(Application.ExecutablePath)), + }; + Logger.Log(LogLevel.Info, string.Join(Environment.NewLine, headers)); + } + private bool _disposed; public void Dispose() { diff --git a/RetailCoder.VBE/Inspections/UnassignedVariableUsageInspection.cs b/RetailCoder.VBE/Inspections/UnassignedVariableUsageInspection.cs index 41cbaaf75f..a7cb3a8a95 100644 --- a/RetailCoder.VBE/Inspections/UnassignedVariableUsageInspection.cs +++ b/RetailCoder.VBE/Inspections/UnassignedVariableUsageInspection.cs @@ -30,8 +30,9 @@ public override IEnumerable GetInspectionResults() && !declaration.IsSelfAssigned && !declaration.References.Any(reference => reference.IsAssignment)); - var lenFunction = BuiltInDeclarations.SingleOrDefault(s => s.Scope == "VBE7.DLL;VBA.Strings.Len"); - var lenbFunction = BuiltInDeclarations.SingleOrDefault(s => s.Scope == "VBE7.DLL;VBA.Strings.LenB"); + //The parameter scoping was apparently incorrect before - need to filter for the actual function. + var lenFunction = BuiltInDeclarations.SingleOrDefault(s => s.DeclarationType == DeclarationType.Function && s.Scope.Equals("VBE7.DLL;VBA.Strings.Len")); + var lenbFunction = BuiltInDeclarations.SingleOrDefault(s => s.DeclarationType == DeclarationType.Function && s.Scope.Equals("VBE7.DLL;VBA.Strings.Len")); return from issue in declarations where issue.References.Any() diff --git a/RetailCoder.VBE/NLog.dll.nlog b/RetailCoder.VBE/NLog.dll.nlog index 1f855f436c..030cd2140c 100644 --- a/RetailCoder.VBE/NLog.dll.nlog +++ b/RetailCoder.VBE/NLog.dll.nlog @@ -7,7 +7,7 @@ xsi:type="File" name="file" fileName="${specialfolder:folder=ApplicationData}/Rubberduck/Logs/RubberduckLog.txt" - layout="${longdate};${uppercase:${level}};${logger};${message};${exception:format=tostring}" + layout="${longdate};${uppercase:${level}}-${gdc:item=RubberduckVersion};${logger};${message};${exception:format=tostring}" archiveFileName="${specialfolder:folder=ApplicationData}/Rubberduck/Logs/archives/RubberduckLog.{#}.txt" archiveAboveSize="5242880" archiveNumbering="Sequence" diff --git a/RetailCoder.VBE/Settings/WindowSettings.cs b/RetailCoder.VBE/Settings/WindowSettings.cs index 8fb6fd39ba..e841ddb32e 100644 --- a/RetailCoder.VBE/Settings/WindowSettings.cs +++ b/RetailCoder.VBE/Settings/WindowSettings.cs @@ -1,4 +1,11 @@ using System.Xml.Serialization; +using Rubberduck.Properties; +using Rubberduck.UI; +using Rubberduck.UI.CodeExplorer; +using Rubberduck.UI.Inspections; +using Rubberduck.UI.SourceControl; +using Rubberduck.UI.ToDoItems; +using Rubberduck.UI.UnitTesting; namespace Rubberduck.Settings { @@ -9,6 +16,8 @@ public interface IWindowSettings bool SourceControlVisibleOnStartup { get; set; } bool TestExplorerVisibleOnStartup { get; set; } bool TodoExplorerVisibleOnStartup { get; set; } + + bool IsWindowVisible(DockableToolwindowPresenter candidate); } [XmlType(AnonymousType = true)] @@ -34,5 +43,33 @@ public WindowSettings(bool codeExplorerVisibleOnStartup, bool codeInspectionsVis public bool SourceControlVisibleOnStartup { get; set; } public bool TestExplorerVisibleOnStartup { get; set; } public bool TodoExplorerVisibleOnStartup { get; set; } + + public bool IsWindowVisible(DockableToolwindowPresenter candidate) + { + //I'm sure there's a better way to do this, because this is a lazy-ass way to do it. + //We're injecting into the base class, so check the derived class: + if (candidate is CodeExplorerDockablePresenter) + { + return CodeExplorerVisibleOnStartup; + } + if (candidate is CodeInspectionsDockablePresenter) + { + return CodeInspectionsVisibleOnStartup; + } + if (candidate is SourceControlDockablePresenter) + { + return SourceControlVisibleOnStartup; + } + if (candidate is TestExplorerDockablePresenter) + { + return TestExplorerVisibleOnStartup; + } + if (candidate is ToDoExplorerDockablePresenter) + { + return TodoExplorerVisibleOnStartup; + } + //Oh. Hello. I have no clue who you are... + return false; + } } } diff --git a/RetailCoder.VBE/UI/CodeExplorer/CodeExplorerDockablePresenter.cs b/RetailCoder.VBE/UI/CodeExplorer/CodeExplorerDockablePresenter.cs index 34eabae1f2..4bc79bed61 100644 --- a/RetailCoder.VBE/UI/CodeExplorer/CodeExplorerDockablePresenter.cs +++ b/RetailCoder.VBE/UI/CodeExplorer/CodeExplorerDockablePresenter.cs @@ -1,11 +1,13 @@ -using Rubberduck.VBEditor.SafeComWrappers.Abstract; +using Rubberduck.Settings; +using Rubberduck.SettingsProvider; +using Rubberduck.VBEditor.SafeComWrappers.Abstract; namespace Rubberduck.UI.CodeExplorer { public class CodeExplorerDockablePresenter : DockableToolwindowPresenter { - public CodeExplorerDockablePresenter(IVBE vbe, IAddIn addIn, CodeExplorerWindow view) - : base(vbe, addIn, view) + public CodeExplorerDockablePresenter(IVBE vbe, IAddIn addIn, CodeExplorerWindow view, IConfigProvider settings) + : base(vbe, addIn, view, settings) { } } diff --git a/RetailCoder.VBE/UI/Controls/SearchResultsDockablePresenter.cs b/RetailCoder.VBE/UI/Controls/SearchResultsDockablePresenter.cs index 2d70723701..e3099835b8 100644 --- a/RetailCoder.VBE/UI/Controls/SearchResultsDockablePresenter.cs +++ b/RetailCoder.VBE/UI/Controls/SearchResultsDockablePresenter.cs @@ -4,8 +4,8 @@ namespace Rubberduck.UI.Controls { public class SearchResultsDockablePresenter : DockableToolwindowPresenter { - public SearchResultsDockablePresenter(IVBE vbe, IAddIn addin, IDockableUserControl view) - : base(vbe, addin, view) + public SearchResultsDockablePresenter(IVBE vbe, IAddIn addin, IDockableUserControl view) + : base(vbe, addin, view, null) { } diff --git a/RetailCoder.VBE/UI/DockableToolwindowPresenter.cs b/RetailCoder.VBE/UI/DockableToolwindowPresenter.cs index 11103d10ad..ba7f1f7b3c 100644 --- a/RetailCoder.VBE/UI/DockableToolwindowPresenter.cs +++ b/RetailCoder.VBE/UI/DockableToolwindowPresenter.cs @@ -1,7 +1,10 @@ using System; +using System.Configuration; using System.Runtime.InteropServices; using System.Windows.Forms; using NLog; +using Rubberduck.Settings; +using Rubberduck.SettingsProvider; using Rubberduck.VBEditor.SafeComWrappers.Abstract; namespace Rubberduck.UI @@ -23,13 +26,18 @@ public abstract class DockableToolwindowPresenter : IDockablePresenter, IDisposa private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private readonly IWindow _window; private readonly UserControl _userControl; + private readonly WindowSettings _settings; //Storing this really doesn't matter - it's only checked on startup and never persisted. - protected DockableToolwindowPresenter(IVBE vbe, IAddIn addin, IDockableUserControl view) + protected DockableToolwindowPresenter(IVBE vbe, IAddIn addin, IDockableUserControl view, IConfigProvider settingsProvider) { _vbe = vbe; _addin = addin; Logger.Trace(string.Format("Initializing Dockable Panel ({0})", GetType().Name)); _userControl = view as UserControl; + if (settingsProvider != null) + { + _settings = settingsProvider.Create(); + } _window = CreateToolWindow(view); } @@ -69,7 +77,7 @@ private IWindow CreateToolWindow(IDockableUserControl control) EnsureMinimumWindowSize(toolWindow); - toolWindow.IsVisible = false; //hide it again + toolWindow.IsVisible = _settings != null && _settings.IsWindowVisible(this); userControlHost.AddUserControl(control as UserControl, new IntPtr(_vbe.MainWindow.HWnd)); return toolWindow; diff --git a/RetailCoder.VBE/UI/IdentifierReferences/IdentifierReferencesListDockablePresenter.cs b/RetailCoder.VBE/UI/IdentifierReferences/IdentifierReferencesListDockablePresenter.cs index 7fa0b98150..946e401bd6 100644 --- a/RetailCoder.VBE/UI/IdentifierReferences/IdentifierReferencesListDockablePresenter.cs +++ b/RetailCoder.VBE/UI/IdentifierReferences/IdentifierReferencesListDockablePresenter.cs @@ -7,7 +7,7 @@ namespace Rubberduck.UI.IdentifierReferences public class IdentifierReferencesListDockablePresenter : DockableToolwindowPresenter { public IdentifierReferencesListDockablePresenter(IVBE vbe, IAddIn addin, SimpleListControl control, Declaration target) - : base(vbe, addin, control) + : base(vbe, addin, control, null) { BindTarget(target); } diff --git a/RetailCoder.VBE/UI/IdentifierReferences/ImplementationsListDockablePresenter.cs b/RetailCoder.VBE/UI/IdentifierReferences/ImplementationsListDockablePresenter.cs index 60f34ea64f..617d6206c8 100644 --- a/RetailCoder.VBE/UI/IdentifierReferences/ImplementationsListDockablePresenter.cs +++ b/RetailCoder.VBE/UI/IdentifierReferences/ImplementationsListDockablePresenter.cs @@ -9,7 +9,7 @@ namespace Rubberduck.UI.IdentifierReferences public class ImplementationsListDockablePresenter : DockableToolwindowPresenter { public ImplementationsListDockablePresenter(IVBE vbe, IAddIn addin, IDockableUserControl control, IEnumerable implementations) - : base(vbe, addin, control) + : base(vbe, addin, control, null) { BindTarget(implementations); } diff --git a/RetailCoder.VBE/UI/Inspections/CodeInspectionsDockablePresenter.cs b/RetailCoder.VBE/UI/Inspections/CodeInspectionsDockablePresenter.cs index d67207936e..2101db325b 100644 --- a/RetailCoder.VBE/UI/Inspections/CodeInspectionsDockablePresenter.cs +++ b/RetailCoder.VBE/UI/Inspections/CodeInspectionsDockablePresenter.cs @@ -1,11 +1,13 @@ -using Rubberduck.VBEditor.SafeComWrappers.Abstract; +using Rubberduck.Settings; +using Rubberduck.SettingsProvider; +using Rubberduck.VBEditor.SafeComWrappers.Abstract; namespace Rubberduck.UI.Inspections { public class CodeInspectionsDockablePresenter : DockableToolwindowPresenter { - public CodeInspectionsDockablePresenter(IVBE vbe, IAddIn addin, CodeInspectionsWindow window) - :base(vbe, addin, window) + public CodeInspectionsDockablePresenter(IVBE vbe, IAddIn addin, CodeInspectionsWindow window, IConfigProvider settings) + : base(vbe, addin, window, settings) { } } diff --git a/RetailCoder.VBE/UI/ParserErrors/ParserErrorsPresenter.cs b/RetailCoder.VBE/UI/ParserErrors/ParserErrorsPresenter.cs index 4d9584ee24..e7a7dce4ab 100644 --- a/RetailCoder.VBE/UI/ParserErrors/ParserErrorsPresenter.cs +++ b/RetailCoder.VBE/UI/ParserErrors/ParserErrorsPresenter.cs @@ -17,7 +17,7 @@ public interface IParserErrorsPresenter public class ParserErrorsPresenter : DockableToolwindowPresenter, IParserErrorsPresenter { public ParserErrorsPresenter(IVBE vbe, IAddIn addin) - : base(vbe, addin, new SimpleListControl(RubberduckUI.ParseErrors_Caption)) + : base(vbe, addin, new SimpleListControl(RubberduckUI.ParseErrors_Caption), null) { _source = new BindingList(); var control = UserControl as SimpleListControl; diff --git a/RetailCoder.VBE/UI/SourceControl/SourceControlDockablePresenter.cs b/RetailCoder.VBE/UI/SourceControl/SourceControlDockablePresenter.cs index eaa5fcb19e..f851c03569 100644 --- a/RetailCoder.VBE/UI/SourceControl/SourceControlDockablePresenter.cs +++ b/RetailCoder.VBE/UI/SourceControl/SourceControlDockablePresenter.cs @@ -1,4 +1,6 @@ using System.Diagnostics; +using Rubberduck.Settings; +using Rubberduck.SettingsProvider; using Rubberduck.VBEditor.SafeComWrappers.Abstract; namespace Rubberduck.UI.SourceControl @@ -8,8 +10,8 @@ namespace Rubberduck.UI.SourceControl /// public class SourceControlDockablePresenter : DockableToolwindowPresenter { - public SourceControlDockablePresenter(IVBE vbe, IAddIn addin, SourceControlPanel window) - : base(vbe, addin, window) + public SourceControlDockablePresenter(IVBE vbe, IAddIn addin, SourceControlPanel window, IConfigProvider settings) + : base(vbe, addin, window, settings) { } diff --git a/RetailCoder.VBE/UI/ToDoItems/ToDoExplorerDockablePresenter.cs b/RetailCoder.VBE/UI/ToDoItems/ToDoExplorerDockablePresenter.cs index 7f65b0b2e3..b23fdcb13c 100644 --- a/RetailCoder.VBE/UI/ToDoItems/ToDoExplorerDockablePresenter.cs +++ b/RetailCoder.VBE/UI/ToDoItems/ToDoExplorerDockablePresenter.cs @@ -1,4 +1,6 @@ -using Rubberduck.VBEditor.SafeComWrappers.Abstract; +using Rubberduck.Settings; +using Rubberduck.SettingsProvider; +using Rubberduck.VBEditor.SafeComWrappers.Abstract; namespace Rubberduck.UI.ToDoItems { @@ -7,8 +9,8 @@ namespace Rubberduck.UI.ToDoItems /// public class ToDoExplorerDockablePresenter : DockableToolwindowPresenter { - public ToDoExplorerDockablePresenter(IVBE vbe, IAddIn addin, ToDoExplorerWindow window) - : base(vbe, addin, window) + public ToDoExplorerDockablePresenter(IVBE vbe, IAddIn addin, ToDoExplorerWindow window, IConfigProvider settings) + : base(vbe, addin, window, settings) { } } diff --git a/RetailCoder.VBE/UI/UnitTesting/TestExplorerDockablePresenter.cs b/RetailCoder.VBE/UI/UnitTesting/TestExplorerDockablePresenter.cs index ddaa53ec33..a69eeef3d6 100644 --- a/RetailCoder.VBE/UI/UnitTesting/TestExplorerDockablePresenter.cs +++ b/RetailCoder.VBE/UI/UnitTesting/TestExplorerDockablePresenter.cs @@ -1,11 +1,13 @@ -using Rubberduck.VBEditor.SafeComWrappers.Abstract; +using Rubberduck.Settings; +using Rubberduck.SettingsProvider; +using Rubberduck.VBEditor.SafeComWrappers.Abstract; namespace Rubberduck.UI.UnitTesting { public class TestExplorerDockablePresenter : DockableToolwindowPresenter { - public TestExplorerDockablePresenter(IVBE vbe, IAddIn addin, TestExplorerWindow view) - : base(vbe, addin, view) + public TestExplorerDockablePresenter(IVBE vbe, IAddIn addin, TestExplorerWindow view, IConfigProvider settings) + : base(vbe, addin, view, settings) { } } diff --git a/Rubberduck.Parsing/ComReflection/ComBase.cs b/Rubberduck.Parsing/ComReflection/ComBase.cs new file mode 100644 index 0000000000..22ff07b344 --- /dev/null +++ b/Rubberduck.Parsing/ComReflection/ComBase.cs @@ -0,0 +1,52 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices.ComTypes; +using Rubberduck.Parsing.Symbols; +using FUNCDESC = System.Runtime.InteropServices.ComTypes.FUNCDESC; + +namespace Rubberduck.Parsing.ComReflection +{ + public interface IComBase + { + Guid Guid { get; } + int Index { get; } + ComDocumentation Documentation { get; } + string Name { get; } + DeclarationType Type { get; } + } + + public abstract class ComBase : IComBase + { + public Guid Guid { get; protected set; } + public int Index { get; protected set; } + public ComDocumentation Documentation { get; protected set; } + public string Name + { + get { return Documentation == null ? string.Empty : Documentation.Name; } + } + + public DeclarationType Type { get; protected set; } + + protected ComBase(ITypeLib typeLib, int index) + { + Index = index; + Documentation = new ComDocumentation(typeLib, index); + } + + protected ComBase(ITypeInfo info) + { + ITypeLib typeLib; + int index; + info.GetContainingTypeLib(out typeLib, out index); + Index = index; + Debug.Assert(typeLib != null); + Documentation = new ComDocumentation(typeLib, index); + } + + protected ComBase(ITypeInfo info, FUNCDESC funcDesc) + { + Index = funcDesc.memid; + Documentation = new ComDocumentation(info, funcDesc.memid); + } + } +} diff --git a/Rubberduck.Parsing/ComReflection/ComCoClass.cs b/Rubberduck.Parsing/ComReflection/ComCoClass.cs new file mode 100644 index 0000000000..9548227618 --- /dev/null +++ b/Rubberduck.Parsing/ComReflection/ComCoClass.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using Rubberduck.Parsing.Symbols; +using TYPEATTR = System.Runtime.InteropServices.ComTypes.TYPEATTR; +using IMPLTYPEFLAGS = System.Runtime.InteropServices.ComTypes.IMPLTYPEFLAGS; + +namespace Rubberduck.Parsing.ComReflection +{ + public class ComCoClass : ComType, IComTypeWithMembers + { + private readonly Dictionary _interfaces = new Dictionary(); + private readonly List _events = new List(); + + public ComInterface DefaultInterface { get; private set; } + + public IEnumerable EventInterfaces + { + get { return _events; } + } + public IEnumerable ImplementedInterfaces + { + get { return _interfaces.Keys; } + } + + public IEnumerable VisibleInterfaces + { + get { return _interfaces.Where(i => !i.Value).Select(i => i.Key); } + } + + public IEnumerable Members + { + get { return ImplementedInterfaces.SelectMany(i => i.Members); } + } + + public bool WithEvents + { + get { return _events.Count > 0; } + } + + public ComCoClass(ITypeLib typeLib, ITypeInfo info, TYPEATTR attrib, int index) : base (typeLib, attrib, index) + { + Type = DeclarationType.ClassModule; + GetImplementedInterfaces(info, attrib); + Debug.Assert(attrib.cFuncs == 0); + } + + private void GetImplementedInterfaces(ITypeInfo info, TYPEATTR typeAttr) + { + for (var implIndex = 0; implIndex < typeAttr.cImplTypes; implIndex++) + { + int href; + info.GetRefTypeOfImplType(implIndex, out href); + + ITypeInfo implemented; + info.GetRefTypeInfo(href, out implemented); + + IntPtr attribPtr; + implemented.GetTypeAttr(out attribPtr); + var attribs = (TYPEATTR)Marshal.PtrToStructure(attribPtr, typeof(TYPEATTR)); + + ComType inherited; + ComProject.KnownTypes.TryGetValue(attribs.guid, out inherited); + var intface = inherited as ComInterface ?? new ComInterface(implemented, attribs); + ComProject.KnownTypes.TryAdd(attribs.guid, intface); + + IMPLTYPEFLAGS flags = 0; + try + { + info.GetImplTypeFlags(implIndex, out flags); + } + catch (COMException) { } + + DefaultInterface = flags.HasFlag(IMPLTYPEFLAGS.IMPLTYPEFLAG_FDEFAULT) ? intface : DefaultInterface; + if (flags.HasFlag(IMPLTYPEFLAGS.IMPLTYPEFLAG_FSOURCE)) + { + _events.Add(intface); + } + _interfaces.Add(intface, flags.HasFlag(IMPLTYPEFLAGS.IMPLTYPEFLAG_FRESTRICTED)); + info.ReleaseTypeAttr(attribPtr); + } + } + } +} diff --git a/Rubberduck.Parsing/ComReflection/ComDocumentation.cs b/Rubberduck.Parsing/ComReflection/ComDocumentation.cs new file mode 100644 index 0000000000..5b3f6b9a1f --- /dev/null +++ b/Rubberduck.Parsing/ComReflection/ComDocumentation.cs @@ -0,0 +1,45 @@ +using System.Runtime.InteropServices.ComTypes; + +namespace Rubberduck.Parsing.ComReflection +{ + public class ComDocumentation + { + public string Name { get; private set; } + public string DocString { get; private set; } + public string HelpFile { get; private set; } + public int HelpContext { get; private set; } + + public ComDocumentation(ITypeLib typeLib, int index) + { + LoadDocumentation(typeLib, null, index); + } + + public ComDocumentation(ITypeInfo info, int index) + { + LoadDocumentation(null, info, index); + } + + private void LoadDocumentation(ITypeLib typeLib, ITypeInfo info, int index) + { + string name; + string docString; + int helpContext; + string helpFile; + + if (info == null) + { + typeLib.GetDocumentation(index, out name, out docString, out helpContext, out helpFile); + } + else + { + info.GetDocumentation(index, out name, out docString, out helpContext, out helpFile); + } + + //See http://chat.stackexchange.com/transcript/message/30119269#30119269 + Name = name.Equals("LONG_PTR") ? "LongPtr" : name; + DocString = docString; + HelpContext = helpContext; + HelpFile = helpFile; + } + } +} diff --git a/Rubberduck.Parsing/ComReflection/ComEnumeration.cs b/Rubberduck.Parsing/ComReflection/ComEnumeration.cs new file mode 100644 index 0000000000..92eb7ec88c --- /dev/null +++ b/Rubberduck.Parsing/ComReflection/ComEnumeration.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using Rubberduck.Parsing.Symbols; +using TYPEATTR = System.Runtime.InteropServices.ComTypes.TYPEATTR; +using VARDESC = System.Runtime.InteropServices.ComTypes.VARDESC; + +namespace Rubberduck.Parsing.ComReflection +{ + public class ComEnumeration : ComType + { + public List Members { get; set; } + + public ComEnumeration(ITypeLib typeLib, ITypeInfo info, TYPEATTR attrib, int index) : base(typeLib, attrib, index) + { + Members = new List(); + Type = DeclarationType.Enumeration; + GetEnumerationMembers(info, attrib); + ComProject.KnownEnumerations.TryAdd(Guid, this); + } + + private void GetEnumerationMembers(ITypeInfo info, TYPEATTR attrib) + { + var count = attrib.cVars; + for (var index = 0; index < count; index++) + { + IntPtr varPtr; + info.GetVarDesc(index, out varPtr); + var desc = (VARDESC)Marshal.PtrToStructure(varPtr, typeof(VARDESC)); + Members.Add(new ComEnumerationMember(info, desc)); + info.ReleaseVarDesc(varPtr); + } + } + } +} diff --git a/Rubberduck.Parsing/ComReflection/ComEnumerationMember.cs b/Rubberduck.Parsing/ComReflection/ComEnumerationMember.cs new file mode 100644 index 0000000000..66de6d403b --- /dev/null +++ b/Rubberduck.Parsing/ComReflection/ComEnumerationMember.cs @@ -0,0 +1,28 @@ +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using VARDESC = System.Runtime.InteropServices.ComTypes.VARDESC; + +namespace Rubberduck.Parsing.ComReflection +{ + [DebuggerDisplay("{Name} = {Value} ({ValueType})")] + public class ComEnumerationMember + { + public string Name { get; private set; } + public int Value { get; private set; } + public VarEnum ValueType { get; private set; } + + public ComEnumerationMember(ITypeInfo info, VARDESC varDesc) + { + var value = new ComVariant(varDesc.desc.lpvarValue); + Value = (int)value.Value; + ValueType = value.VariantType; + + var names = new string[255]; + int count; + info.GetNames(varDesc.memid, names, 1, out count); + Debug.Assert(count == 1); + Name = names[0]; + } + } +} diff --git a/Rubberduck.Parsing/ComReflection/ComField.cs b/Rubberduck.Parsing/ComReflection/ComField.cs new file mode 100644 index 0000000000..b526c48e43 --- /dev/null +++ b/Rubberduck.Parsing/ComReflection/ComField.cs @@ -0,0 +1,44 @@ +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using System.Xml.Schema; +using Rubberduck.Parsing.Symbols; +using VARDESC = System.Runtime.InteropServices.ComTypes.VARDESC; +using VARFLAGS = System.Runtime.InteropServices.ComTypes.VARFLAGS; + +namespace Rubberduck.Parsing.ComReflection +{ + [DebuggerDisplay("{Name}")] + public class ComField + { + public string Name { get; private set; } + public int Index { get; private set; } + public DeclarationType Type { get; private set; } + public object DefaultValue { get; set; } + public string ValueType { get; set; } + public VARFLAGS Flags { get; set; } + + public ComField(string name, VARDESC varDesc, int index, DeclarationType type) + { + Name = name; + Index = index; + Type = type; + + Flags = (VARFLAGS)varDesc.wVarFlags; + + if (Type == DeclarationType.Constant) + { + var value = new ComVariant(varDesc.desc.lpvarValue); + DefaultValue = value.Value; + string typeName; + ValueType = ComVariant.TypeNames.TryGetValue(value.VariantType, out typeName) ? typeName : "Object"; + } + else + { + Debug.Assert(varDesc.varkind == VARKIND.VAR_PERINSTANCE); + string typeName; + ValueType = ComVariant.TypeNames.TryGetValue((VarEnum)varDesc.elemdescVar.tdesc.vt, out typeName) ? typeName : "Object"; + } + } + } +} diff --git a/Rubberduck.Parsing/ComReflection/ComInterface.cs b/Rubberduck.Parsing/ComReflection/ComInterface.cs new file mode 100644 index 0000000000..46ff32a177 --- /dev/null +++ b/Rubberduck.Parsing/ComReflection/ComInterface.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using Rubberduck.Parsing.Symbols; +using TYPEATTR = System.Runtime.InteropServices.ComTypes.TYPEATTR; +using FUNCDESC = System.Runtime.InteropServices.ComTypes.FUNCDESC; +using CALLCONV = System.Runtime.InteropServices.ComTypes.CALLCONV; + +namespace Rubberduck.Parsing.ComReflection +{ + [DebuggerDisplay("{Name}")] + public class ComInterface : ComType, IComTypeWithMembers + { + private readonly List _inherited = new List(); + private readonly List _members = new List(); + + public IEnumerable InheritedInterfaces + { + get { return _inherited; } + } + + public IEnumerable Members + { + get { return _members; } + } + + public ComInterface(ITypeInfo info, TYPEATTR attrib) : base(info, attrib) + { + GetImplementedInterfaces(info, attrib); + GetComMembers(info, attrib); + } + + public ComInterface(ITypeLib typeLib, ITypeInfo info, TYPEATTR attrib, int index) : base(typeLib, attrib, index) + { + Type = DeclarationType.ClassModule; + GetImplementedInterfaces(info, attrib); + GetComMembers(info, attrib); + } + + private void GetImplementedInterfaces(ITypeInfo info, TYPEATTR typeAttr) + { + for (var implIndex = 0; implIndex < typeAttr.cImplTypes; implIndex++) + { + int href; + info.GetRefTypeOfImplType(implIndex, out href); + + ITypeInfo implemented; + info.GetRefTypeInfo(href, out implemented); + + IntPtr attribPtr; + implemented.GetTypeAttr(out attribPtr); + var attribs = (TYPEATTR)Marshal.PtrToStructure(attribPtr, typeof(TYPEATTR)); + + ComType inherited; + ComProject.KnownTypes.TryGetValue(attribs.guid, out inherited); + var intface = inherited as ComInterface ?? new ComInterface(implemented, attribs); + _inherited.Add(intface); + ComProject.KnownTypes.TryAdd(attribs.guid, intface); + + info.ReleaseTypeAttr(attribPtr); + } + } + + private void GetComMembers(ITypeInfo info, TYPEATTR attrib) + { + for (var index = 0; index < attrib.cFuncs; index++) + { + IntPtr memberPtr; + info.GetFuncDesc(index, out memberPtr); + var member = (FUNCDESC)Marshal.PtrToStructure(memberPtr, typeof(FUNCDESC)); + if (member.callconv != CALLCONV.CC_STDCALL) + { + continue; + } + _members.Add(new ComMember(info, member)); + info.ReleaseFuncDesc(memberPtr); + } + } + } +} diff --git a/Rubberduck.Parsing/ComReflection/ComMember.cs b/Rubberduck.Parsing/ComReflection/ComMember.cs new file mode 100644 index 0000000000..8e011aaa6b --- /dev/null +++ b/Rubberduck.Parsing/ComReflection/ComMember.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using Rubberduck.Parsing.Symbols; +using ELEMDESC = System.Runtime.InteropServices.ComTypes.ELEMDESC; +using FUNCDESC = System.Runtime.InteropServices.ComTypes.FUNCDESC; +using INVOKEKIND = System.Runtime.InteropServices.ComTypes.INVOKEKIND; +using FUNCFLAGS = System.Runtime.InteropServices.ComTypes.FUNCFLAGS; + +namespace Rubberduck.Parsing.ComReflection +{ + [DebuggerDisplay("{MemberDeclaration}")] + public class ComMember : ComBase + { + public bool IsHidden { get; private set; } + public bool IsRestricted { get; private set; } + public bool IsEventHandler { get; private set; } + public bool IsDefault { get; private set; } + public bool IsEnumerator { get; private set; } + public ComParameter ReturnType { get; private set; } + public List Parameters { get; set; } + + public ComMember(ITypeInfo info, FUNCDESC funcDesc) : base(info, funcDesc) + { + LoadParameters(funcDesc, info); + var flags = (FUNCFLAGS)funcDesc.wFuncFlags; + IsHidden = flags.HasFlag(FUNCFLAGS.FUNCFLAG_FHIDDEN); + IsRestricted = flags.HasFlag(FUNCFLAGS.FUNCFLAG_FRESTRICTED); + IsEventHandler = flags.HasFlag(FUNCFLAGS.FUNCFLAG_FSOURCE); + IsDefault = flags.HasFlag(FUNCFLAGS.FUNCFLAG_FUIDEFAULT); + IsEnumerator = flags.HasFlag(FUNCFLAGS.FUNCFLAG_FNONBROWSABLE) && Name.Equals("_NewEnum"); + SetDeclarationType(funcDesc, info); + } + + private void SetDeclarationType(FUNCDESC funcDesc, ITypeInfo info) + { + if (IsEventHandler) + { + Type = DeclarationType.Event; + } + if (funcDesc.invkind.HasFlag(INVOKEKIND.INVOKE_PROPERTYGET)) + { + Type = DeclarationType.PropertyGet; + + } + else if (funcDesc.invkind.HasFlag(INVOKEKIND.INVOKE_PROPERTYPUT)) + { + Type = DeclarationType.PropertyLet; + } + else if (funcDesc.invkind.HasFlag(INVOKEKIND.INVOKE_PROPERTYPUTREF)) + { + Type = DeclarationType.PropertySet; + } + else if ((VarEnum)funcDesc.elemdescFunc.tdesc.vt == VarEnum.VT_VOID) + { + Type = DeclarationType.Procedure; + } + else + { + Type = DeclarationType.Function; + } + + if (Type == DeclarationType.Function || Type == DeclarationType.PropertyGet) + { + ReturnType = new ComParameter(funcDesc.elemdescFunc, info, string.Empty); + } + } + + private void LoadParameters(FUNCDESC funcDesc, ITypeInfo info) + { + Parameters = new List(); + var names = new string[255]; + int count; + info.GetNames(Index, names, 255, out count); + + for (var index = 0; index < count - 1; index++) + { + var paramPtr = new IntPtr(funcDesc.lprgelemdescParam.ToInt64() + Marshal.SizeOf(typeof(ELEMDESC)) * index); + var elemDesc = (ELEMDESC)Marshal.PtrToStructure(paramPtr, typeof(ELEMDESC)); + var param = new ComParameter(elemDesc, info, names[index + 1]); + Parameters.Add(param); + } + if (Parameters.Any() && funcDesc.cParamsOpt == -1) + { + Parameters.Last().IsParamArray = true; + } + } + + // ReSharper disable once UnusedMember.Local + private string MemberDeclaration + { + get + { + var type = string.Empty; + switch (Type) + { + case DeclarationType.Function: + type = "Function"; + break; + case DeclarationType.Procedure: + type = "Sub"; + break; + case DeclarationType.PropertyGet: + type = "Property Get"; + break; + case DeclarationType.PropertyLet: + type = "Property Let"; + break; + case DeclarationType.PropertySet: + type = "Property Set"; + break; + case DeclarationType.Event: + type = "Event"; + break; + } + return string.Format("{0} {1} {2}{3}{4}", + IsHidden || IsRestricted ? "Private" : "Public", + type, + Name, + ReturnType == null ? string.Empty : " As ", + ReturnType == null ? string.Empty : ReturnType.TypeName); + } + } + } +} diff --git a/Rubberduck.Parsing/ComReflection/ComModule.cs b/Rubberduck.Parsing/ComReflection/ComModule.cs new file mode 100644 index 0000000000..7758b5bed8 --- /dev/null +++ b/Rubberduck.Parsing/ComReflection/ComModule.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using Rubberduck.Parsing.Symbols; +using FUNCDESC = System.Runtime.InteropServices.ComTypes.FUNCDESC; +using TYPEATTR = System.Runtime.InteropServices.ComTypes.TYPEATTR; +using VARDESC = System.Runtime.InteropServices.ComTypes.VARDESC; +using CALLCONV = System.Runtime.InteropServices.ComTypes.CALLCONV; + +namespace Rubberduck.Parsing.ComReflection +{ + public class ComModule : ComType, IComTypeWithMembers, IComTypeWithFields + { + private readonly List _members = new List(); + public IEnumerable Members + { + get { return _members; } + } + + private readonly List _fields = new List(); + public IEnumerable Fields + { + get { return _fields; } + } + + public ComModule(ITypeLib typeLib, ITypeInfo info, TYPEATTR attrib, int index) : base(typeLib, attrib, index) + { + Type = DeclarationType.ProceduralModule; + if (attrib.cFuncs > 0) + { + Debug.Assert(attrib.cVars == 0); + GetComMembers(info, attrib); + } + else + { + Debug.Assert(attrib.cVars > 0); + GetComFields(info, attrib); + } + } + + private void GetComFields(ITypeInfo info, TYPEATTR attrib) + { + var names = new string[255]; + for (var index = 0; index < attrib.cVars; index++) + { + IntPtr varPtr; + info.GetVarDesc(index, out varPtr); + var desc = (VARDESC)Marshal.PtrToStructure(varPtr, typeof(VARDESC)); + int length; + info.GetNames(desc.memid, names, 255, out length); + Debug.Assert(length == 1); + + _fields.Add(new ComField(names[0], desc, index, DeclarationType.Constant)); + info.ReleaseVarDesc(varPtr); + } + } + + private void GetComMembers(ITypeInfo info, TYPEATTR attrib) + { + for (var index = 0; index < attrib.cFuncs; index++) + { + IntPtr memberPtr; + info.GetFuncDesc(index, out memberPtr); + var member = (FUNCDESC)Marshal.PtrToStructure(memberPtr, typeof(FUNCDESC)); + if (member.callconv != CALLCONV.CC_STDCALL) + { + continue; + } + _members.Add(new ComMember(info, member)); + info.ReleaseFuncDesc(memberPtr); + } + } + } +} diff --git a/Rubberduck.Parsing/ComReflection/ComParameter.cs b/Rubberduck.Parsing/ComReflection/ComParameter.cs new file mode 100644 index 0000000000..343e05daaf --- /dev/null +++ b/Rubberduck.Parsing/ComReflection/ComParameter.cs @@ -0,0 +1,136 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using ELEMDESC = System.Runtime.InteropServices.ComTypes.ELEMDESC; +using PARAMFLAG = System.Runtime.InteropServices.ComTypes.PARAMFLAG; +using TYPEATTR = System.Runtime.InteropServices.ComTypes.TYPEATTR; +using TYPEDESC = System.Runtime.InteropServices.ComTypes.TYPEDESC; +using TYPEKIND = System.Runtime.InteropServices.ComTypes.TYPEKIND; + +namespace Rubberduck.Parsing.ComReflection +{ + [DebuggerDisplay("{DeclarationName}")] + public class ComParameter + { + public string Name { get; private set; } + + public string DeclarationName + { + get + { + return string.Format("{0}{1} {2} As {3}{4}{5}", + IsOptional ? "Optional " : string.Empty, + IsByRef ? "ByRef" : "ByVal", + Name, + TypeName, + IsOptional && DefaultValue != null ? " = " : string.Empty, + IsOptional && DefaultValue != null ? + IsEnumMember ? DefaultAsEnum : DefaultValue + : string.Empty); + } + } + + public bool IsArray { get; private set; } + public bool IsByRef { get; private set; } + public bool IsOptional { get; private set; } + public bool IsParamArray { get; set; } + + + private Guid _enumGuid = Guid.Empty; + public bool IsEnumMember + { + get { return !_enumGuid.Equals(Guid.Empty); } + } + public object DefaultValue { get; private set; } + public string DefaultAsEnum { get; private set; } + + private string _type = "Object"; + public string TypeName + { + get + { + return IsArray ? _type + "()" : _type; + } + } + + public ComParameter(ELEMDESC elemDesc, ITypeInfo info, string name) + { + Name = name; + var paramDesc = elemDesc.desc.paramdesc; + GetParameterType(elemDesc.tdesc, info); + IsOptional = paramDesc.wParamFlags.HasFlag(PARAMFLAG.PARAMFLAG_FOPT); + if (!paramDesc.wParamFlags.HasFlag(PARAMFLAG.PARAMFLAG_FHASDEFAULT) || string.IsNullOrEmpty(name)) + { + DefaultAsEnum = string.Empty; + return; + } + + //lpVarValue points to a PARAMDESCEX structure, but we don't care about the cBytes here at all. + //Offset and dereference the VARIANTARG directly. + var defValue = new ComVariant(paramDesc.lpVarValue + Marshal.SizeOf(typeof(ulong))); + DefaultValue = defValue.Value; + + ComEnumeration enumType; + if (!IsEnumMember || !ComProject.KnownEnumerations.TryGetValue(_enumGuid, out enumType)) + { + return; + } + var member = enumType.Members.FirstOrDefault(m => m.Value == (int)DefaultValue); + DefaultAsEnum = member != null ? member.Name : string.Empty; + } + + private void GetParameterType(TYPEDESC desc, ITypeInfo info) + { + var vt = (VarEnum)desc.vt; + TYPEDESC tdesc; + + switch (vt) + { + case VarEnum.VT_PTR: + tdesc = (TYPEDESC)Marshal.PtrToStructure(desc.lpValue, typeof(TYPEDESC)); + GetParameterType(tdesc, info); + IsByRef = true; + break; + case VarEnum.VT_USERDEFINED: + int href; + unchecked + { + href = (int)(desc.lpValue.ToInt64() & 0xFFFFFFFF); + } + try + { + ITypeInfo refTypeInfo; + info.GetRefTypeInfo(href, out refTypeInfo); + + IntPtr attribPtr; + refTypeInfo.GetTypeAttr(out attribPtr); + var attribs = (TYPEATTR)Marshal.PtrToStructure(attribPtr, typeof(TYPEATTR)); + if (attribs.typekind == TYPEKIND.TKIND_ENUM) + { + _enumGuid = attribs.guid; + } + _type = new ComDocumentation(refTypeInfo, -1).Name; + refTypeInfo.ReleaseTypeAttr(attribPtr); + } + catch (COMException) { } + break; + case VarEnum.VT_SAFEARRAY: + case VarEnum.VT_CARRAY: + case VarEnum.VT_ARRAY: + tdesc = (TYPEDESC)Marshal.PtrToStructure(desc.lpValue, typeof(TYPEDESC)); + GetParameterType(tdesc, info); + IsArray = true; + break; + default: + string result; + if (ComVariant.TypeNames.TryGetValue(vt, out result)) + { + _type = result; + } + break; + } + } + } +} diff --git a/Rubberduck.Parsing/ComReflection/ComProject.cs b/Rubberduck.Parsing/ComReflection/ComProject.cs new file mode 100644 index 0000000000..ff16048b8d --- /dev/null +++ b/Rubberduck.Parsing/ComReflection/ComProject.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using TYPEATTR = System.Runtime.InteropServices.ComTypes.TYPEATTR; +using TYPEKIND = System.Runtime.InteropServices.ComTypes.TYPEKIND; +using TYPELIBATTR = System.Runtime.InteropServices.ComTypes.TYPELIBATTR; + +namespace Rubberduck.Parsing.ComReflection +{ + [DebuggerDisplay("{Name}")] + public class ComProject : ComBase + { + public static ConcurrentDictionary KnownTypes = new ConcurrentDictionary(); + public static ConcurrentDictionary KnownEnumerations = new ConcurrentDictionary(); + + public string Path { get; set; } + public long MajorVersion { get; private set; } + public long MinorVersion { get; private set; } + + // YGNI... + // ReSharper disable once NotAccessedField.Local + private TypeLibTypeFlags _flags; + + private readonly List _interfaces = new List(); + public IEnumerable Interfaces + { + get { return _interfaces; } + } + + private readonly List _enumerations = new List(); + public IEnumerable Enumerations + { + get { return _enumerations; } + } + + private readonly List _classes = new List(); + public IEnumerable CoClasses + { + get { return _classes; } + } + + private readonly List _modules = new List(); + public IEnumerable Modules + { + get { return _modules; } + } + + private readonly List _structs = new List(); + public IEnumerable Structs + { + get { return _structs; } + } + + public IEnumerable Members + { + get + { + //Note - Enums and Types should enumerate *last*. That will prevent a duplicate module in the unlikely(?) + //instance where the TypeLib defines a module named "Enums" or "Types". + return _modules.Cast() + .Union(_interfaces) + .Union(_classes) + .Union(_enumerations) + .Union(_structs); + } + } + + public ComProject(ITypeLib typeLibrary) : base(typeLibrary, -1) + { + ProcessLibraryAttributes(typeLibrary); + LoadModules(typeLibrary); + } + + private void ProcessLibraryAttributes(ITypeLib typeLibrary) + { + try + { + IntPtr attribPtr; + typeLibrary.GetLibAttr(out attribPtr); + var typeAttr = (TYPELIBATTR)Marshal.PtrToStructure(attribPtr, typeof(TYPELIBATTR)); + + MajorVersion = typeAttr.wMajorVerNum; + MinorVersion = typeAttr.wMinorVerNum; + _flags = (TypeLibTypeFlags)typeAttr.wLibFlags; + Guid = typeAttr.guid; + } + catch (COMException) { } + } + + private void LoadModules(ITypeLib typeLibrary) + { + var typeCount = typeLibrary.GetTypeInfoCount(); + for (var index = 0; index < typeCount; index++) + { + try + { + ITypeInfo info; + typeLibrary.GetTypeInfo(index, out info); + IntPtr typeAttributesPointer; + info.GetTypeAttr(out typeAttributesPointer); + var typeAttributes = (TYPEATTR)Marshal.PtrToStructure(typeAttributesPointer, typeof(TYPEATTR)); + + ComType type; + KnownTypes.TryGetValue(typeAttributes.guid, out type); + + switch (typeAttributes.typekind) + { + case TYPEKIND.TKIND_ENUM: + var enumeration = type ?? new ComEnumeration(typeLibrary, info, typeAttributes, index); + _enumerations.Add(enumeration as ComEnumeration); + if (type != null) KnownTypes.TryAdd(typeAttributes.guid, enumeration); + break; + case TYPEKIND.TKIND_COCLASS: + var coclass = type ?? new ComCoClass(typeLibrary, info, typeAttributes, index); + _classes.Add(coclass as ComCoClass); + if (type != null) KnownTypes.TryAdd(typeAttributes.guid, coclass); + break; + case TYPEKIND.TKIND_ALIAS: + //The current handling of this is wrong - these don't have to be classes or interfaces. In the VBE module for example, + //"LongPtr" is defined as an alias to "Long" (at least on a 32 bit system) - RD is currently treating is like a class. + //Unclear if these can *also* define alternative names for interfaces as well, but all the ones I've seen have been basically + //a C typedef. So... this needs work. Don't make any assumptions about these elsewhere in the code until this is nailed down. + case TYPEKIND.TKIND_DISPATCH: + case TYPEKIND.TKIND_INTERFACE: + var intface = type ?? new ComInterface(typeLibrary, info, typeAttributes, index); + _interfaces.Add(intface as ComInterface); + if (type != null) KnownTypes.TryAdd(typeAttributes.guid, intface); + break; + case TYPEKIND.TKIND_RECORD: + var structure = new ComStruct(typeLibrary, info, typeAttributes, index); + _structs.Add(structure); + break; + case TYPEKIND.TKIND_MODULE: + var module = type ?? new ComModule(typeLibrary, info, typeAttributes, index); + _modules.Add(module as ComModule); + if (type != null) KnownTypes.TryAdd(typeAttributes.guid, module); + break; + default: + throw new NotImplementedException(string.Format("Didn't expect a TYPEATTR with multiple typekind flags set in {0}.", Path)); + } + info.ReleaseTypeAttr(typeAttributesPointer); + } + catch (COMException) { } + } + } + } +} diff --git a/Rubberduck.Parsing/ComReflection/ComStruct.cs b/Rubberduck.Parsing/ComReflection/ComStruct.cs new file mode 100644 index 0000000000..1ee9aac0c4 --- /dev/null +++ b/Rubberduck.Parsing/ComReflection/ComStruct.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using Rubberduck.Parsing.Symbols; +using TYPEATTR = System.Runtime.InteropServices.ComTypes.TYPEATTR; +using VARDESC = System.Runtime.InteropServices.ComTypes.VARDESC; + +namespace Rubberduck.Parsing.ComReflection +{ + public class ComStruct : ComType, IComTypeWithFields + { + private readonly List _fields = new List(); + public IEnumerable Fields + { + get { return _fields; } + } + + public ComStruct(ITypeLib typeLib, ITypeInfo info, TYPEATTR attrib, int index) + : base(typeLib, attrib, index) + { + _fields = new List(); + Type = DeclarationType.UserDefinedType; + GetFields(info, attrib); + } + + private void GetFields(ITypeInfo info, TYPEATTR attrib) + { + var names = new string[255]; + for (var index = 0; index < attrib.cVars; index++) + { + IntPtr varPtr; + info.GetVarDesc(index, out varPtr); + var desc = (VARDESC)Marshal.PtrToStructure(varPtr, typeof(VARDESC)); + int length; + info.GetNames(desc.memid, names, 255, out length); + Debug.Assert(length == 1); + + _fields.Add(new ComField(names[0], desc, index, DeclarationType.UserDefinedTypeMember)); + info.ReleaseVarDesc(varPtr); + } + } + } +} diff --git a/Rubberduck.Parsing/ComReflection/ComType.cs b/Rubberduck.Parsing/ComReflection/ComType.cs new file mode 100644 index 0000000000..e28cf8c112 --- /dev/null +++ b/Rubberduck.Parsing/ComReflection/ComType.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices.ComTypes; + +namespace Rubberduck.Parsing.ComReflection +{ + public interface IComType : IComBase + { + bool IsAppObject { get; } + bool IsPreDeclared { get; } + bool IsHidden { get; } + bool IsRestricted { get; } + } + + public interface IComTypeWithMembers : IComType + { + IEnumerable Members { get; } + } + + public interface IComTypeWithFields : IComType + { + IEnumerable Fields { get; } + } + + [DebuggerDisplay("{Name}")] + public abstract class ComType : ComBase, IComType + { + public bool IsAppObject { get; private set; } + public bool IsPreDeclared { get; private set; } + public bool IsHidden { get; private set; } + public bool IsRestricted { get; private set; } + + protected ComType(ITypeInfo info, TYPEATTR attrib) + : base(info) + { + SetFlagsFromTypeAttr(attrib); + } + + protected ComType(ITypeLib typeLib, TYPEATTR attrib, int index) + : base(typeLib, index) + { + Index = index; + SetFlagsFromTypeAttr(attrib); + } + + private void SetFlagsFromTypeAttr(TYPEATTR attrib) + { + Guid = attrib.guid; + IsAppObject = attrib.wTypeFlags.HasFlag(TYPEFLAGS.TYPEFLAG_FAPPOBJECT); + IsPreDeclared = attrib.wTypeFlags.HasFlag(TYPEFLAGS.TYPEFLAG_FPREDECLID); + IsHidden = attrib.wTypeFlags.HasFlag(TYPEFLAGS.TYPEFLAG_FHIDDEN); + IsRestricted = attrib.wTypeFlags.HasFlag(TYPEFLAGS.TYPEFLAG_FRESTRICTED); + } + } +} diff --git a/Rubberduck.Parsing/ComReflection/ComVariant.cs b/Rubberduck.Parsing/ComReflection/ComVariant.cs new file mode 100644 index 0000000000..d53e663eed --- /dev/null +++ b/Rubberduck.Parsing/ComReflection/ComVariant.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace Rubberduck.Parsing.ComReflection +{ + //See https://limbioliong.wordpress.com/2011/09/04/using-variants-in-managed-code-part-1/ + public class ComVariant + { + internal static readonly IDictionary TypeNames = new Dictionary + { + {VarEnum.VT_DISPATCH, "Object"}, + {VarEnum.VT_VOID, string.Empty}, + {VarEnum.VT_VARIANT, "Variant"}, + {VarEnum.VT_BLOB_OBJECT, "Object"}, + {VarEnum.VT_STORED_OBJECT, "Object"}, + {VarEnum.VT_STREAMED_OBJECT, "Object"}, + {VarEnum.VT_BOOL, "Boolean"}, + {VarEnum.VT_BSTR, "String"}, + {VarEnum.VT_LPSTR, "String"}, + {VarEnum.VT_LPWSTR, "String"}, + {VarEnum.VT_I1, "Variant"}, // no signed byte type in VBA + {VarEnum.VT_UI1, "Byte"}, + {VarEnum.VT_I2, "Integer"}, + {VarEnum.VT_UI2, "Variant"}, // no unsigned integer type in VBA + {VarEnum.VT_I4, "Long"}, + {VarEnum.VT_UI4, "Variant"}, // no unsigned long integer type in VBA + {VarEnum.VT_I8, "Variant"}, // LongLong on 64-bit VBA + {VarEnum.VT_UI8, "Variant"}, // no unsigned LongLong integer type in VBA + {VarEnum.VT_INT, "Long"}, // same as I4 + {VarEnum.VT_UINT, "Variant"}, // same as UI4 + {VarEnum.VT_DATE, "Date"}, + {VarEnum.VT_CY, "Currency"}, + {VarEnum.VT_DECIMAL, "Currency"}, // best match? + {VarEnum.VT_EMPTY, "Empty"}, + {VarEnum.VT_R4, "Single"}, + {VarEnum.VT_R8, "Double"}, + }; + + + [StructLayout(LayoutKind.Sequential)] + private struct Variant + { + public readonly ushort vt; + private readonly ushort wReserved1; + private readonly ushort wReserved2; + private readonly ushort wReserved3; + private readonly int data01; + private readonly int data02; + } + + public VarEnum VariantType { get; private set; } + public object Value { get; private set; } + + public ComVariant(IntPtr variant) + { + Value = Marshal.GetObjectForNativeVariant(variant); + var members = (Variant)Marshal.PtrToStructure(variant, typeof(Variant)); + VariantType = (VarEnum)members.vt; + if (Value == null && VariantType == VarEnum.VT_BSTR) + { + Value = string.Empty; + } + } + } +} diff --git a/Rubberduck.Parsing/Rubberduck.Parsing.csproj b/Rubberduck.Parsing/Rubberduck.Parsing.csproj index 542f681795..e30ba0b8e8 100644 --- a/Rubberduck.Parsing/Rubberduck.Parsing.csproj +++ b/Rubberduck.Parsing/Rubberduck.Parsing.csproj @@ -110,6 +110,15 @@ + + + + + + + + + @@ -130,10 +139,13 @@ + + + + - - + diff --git a/Rubberduck.Parsing/Symbols/ClassModuleDeclaration.cs b/Rubberduck.Parsing/Symbols/ClassModuleDeclaration.cs index 9d346457dd..c837ec0dc3 100644 --- a/Rubberduck.Parsing/Symbols/ClassModuleDeclaration.cs +++ b/Rubberduck.Parsing/Symbols/ClassModuleDeclaration.cs @@ -1,4 +1,5 @@ using Rubberduck.Parsing.Annotations; +using Rubberduck.Parsing.ComReflection; using Rubberduck.Parsing.VBA; using Rubberduck.VBEditor; using System.Collections.Generic; @@ -47,6 +48,47 @@ public ClassModuleDeclaration( _subtypes = new HashSet(); } + // skip IDispatch.. just about everything implements it and RD doesn't need to care about it; don't care about IUnknown either + private static readonly HashSet IgnoredInterfaces = new HashSet(new[] { "IDispatch", "IUnknown" }); + + public ClassModuleDeclaration(ComCoClass coClass, Declaration parent, QualifiedModuleName module, + Attributes attributes) + : base( + module.QualifyMemberName(coClass.Name), + parent, + parent, + coClass.Name, + null, + false, + coClass.EventInterfaces.Any(), + Accessibility.Public, + DeclarationType.ClassModule, + null, + Selection.Home, + false, + null, + true, + new List(), + attributes) + { + _supertypeNames = + coClass.ImplementedInterfaces.Where(i => !i.IsRestricted && !IgnoredInterfaces.Contains(i.Name)) + .Select(i => i.Name) + .ToList(); + _supertypes = new HashSet(); + _subtypes = new HashSet(); + } + + public ClassModuleDeclaration(ComInterface intrface, Declaration parent, QualifiedModuleName module, + Attributes attributes) + : this( + module.QualifyMemberName(intrface.Name), + parent, + intrface.Name, + true, + new List(), + attributes) { } + public static IEnumerable GetSupertypes(Declaration type) { if (type.DeclarationType != DeclarationType.ClassModule) diff --git a/Rubberduck.Parsing/Symbols/ComInformation.cs b/Rubberduck.Parsing/Symbols/ComInformation.cs deleted file mode 100644 index d81bf17ea7..0000000000 --- a/Rubberduck.Parsing/Symbols/ComInformation.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Runtime.InteropServices.ComTypes; -using Rubberduck.VBEditor; - -namespace Rubberduck.Parsing.Symbols -{ - public class ComInformation - { - public ComInformation(TYPEATTR typeAttributes, IMPLTYPEFLAGS implTypeFlags, ITypeInfo typeInfo, string typeName, QualifiedModuleName typeModuleName, Declaration moduleDeclaration, DeclarationType typeDeclarationType) - { - TypeAttributes = typeAttributes; - ImplTypeFlags = implTypeFlags; - TypeInfo = typeInfo; - TypeName = typeName; - TypeQualifiedModuleName = typeModuleName; - ModuleDeclaration = moduleDeclaration; - TypeDeclarationType = typeDeclarationType; - } - - public TYPEATTR TypeAttributes { get; internal set; } - public IMPLTYPEFLAGS ImplTypeFlags { get; internal set; } - public ITypeInfo TypeInfo { get; internal set; } - - public string TypeName { get; internal set; } - public QualifiedModuleName TypeQualifiedModuleName { get; internal set; } - public Declaration ModuleDeclaration { get; internal set; } - public DeclarationType TypeDeclarationType { get; internal set; } - - public override string ToString() - { - return ModuleDeclaration.IdentifierName; - } - } -} \ No newline at end of file diff --git a/Rubberduck.Parsing/Symbols/ComParameter.cs b/Rubberduck.Parsing/Symbols/ComParameter.cs deleted file mode 100644 index 4d756ea6ca..0000000000 --- a/Rubberduck.Parsing/Symbols/ComParameter.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Rubberduck.Parsing.Symbols -{ - public class ComParameter - { - public bool IsArray { get; set; } - public bool IsByRef { get; set;} - public string Name { get; set;} - - public ComParameter(string name, bool byRef) - { - Name = name; - IsByRef = byRef; - } - } -} diff --git a/Rubberduck.Parsing/Symbols/Declaration.cs b/Rubberduck.Parsing/Symbols/Declaration.cs index 0291c06474..7c23b61452 100644 --- a/Rubberduck.Parsing/Symbols/Declaration.cs +++ b/Rubberduck.Parsing/Symbols/Declaration.cs @@ -1,5 +1,6 @@ using Antlr4.Runtime; using Rubberduck.Parsing.Annotations; +using Rubberduck.Parsing.ComReflection; using Rubberduck.Parsing.Grammar; using Rubberduck.Parsing.VBA; using Rubberduck.VBEditor; @@ -150,7 +151,7 @@ public Declaration( _accessibility = accessibility; _declarationType = declarationType; _selection = selection; - _context = context; + Context = context; _isBuiltIn = isBuiltIn; _annotations = annotations; _attributes = attributes ?? new Attributes(); @@ -163,7 +164,75 @@ public Declaration( _typeHint = typeHint; } - private string FolderFromAnnotations() + public Declaration(ComEnumeration enumeration, Declaration parent, QualifiedModuleName module) : this( + module.QualifyMemberName(enumeration.Name), + parent, + parent, + "Long", //Match the VBA default type declaration. Technically these *can* be a LongLong on 64 bit systems, but would likely crash the VBE... + null, + false, + false, + Accessibility.Global, + DeclarationType.Enumeration, + null, + Selection.Home, + false, + null, + true, + null, + new Attributes()) { } + + public Declaration(ComStruct structure, Declaration parent, QualifiedModuleName module) + : this( + module.QualifyMemberName(structure.Name), + parent, + parent, + structure.Name, + null, + false, + false, + Accessibility.Global, + DeclarationType.UserDefinedType, + null, + Selection.Home, + false, + null, + true, + null, + new Attributes()) { } + + public Declaration(ComEnumerationMember member, Declaration parent, QualifiedModuleName module) : this( + module.QualifyMemberName(member.Name), + parent, + parent, + parent.IdentifierName, + null, + false, + false, + Accessibility.Global, + DeclarationType.EnumerationMember, + null, + Selection.Home, + false, + null) { } + + public Declaration(ComField field, Declaration parent, QualifiedModuleName module) + : this( + module.QualifyMemberName(field.Name), + parent, + parent, + field.ValueType, + null, + false, + false, + Accessibility.Global, + field.Type, + null, + Selection.Home, + false, + null) { } + + private string FolderFromAnnotations() { var @namespace = Annotations.FirstOrDefault(annotation => annotation.AnnotationType == AnnotationType.Folder); string result; @@ -233,18 +302,7 @@ public bool IsTypeSpecified private readonly QualifiedMemberName _qualifiedName; public QualifiedMemberName QualifiedName { get { return _qualifiedName; } } - private ParserRuleContext _context; - public ParserRuleContext Context - { - get - { - return _context; - } - set - { - _context = value; - } - } + public ParserRuleContext Context { get; set; } private ConcurrentBag _references = new ConcurrentBag(); public IEnumerable References @@ -339,24 +397,6 @@ public void AddReference( annotations)); } - //public void AddReference(IdentifierReference reference) - //{ - // if (reference == null || reference.Declaration.Context == reference.Context) - // { - // return; - // } - // if (reference.Context.Parent != _context - // && !_references.Select(r => r.Context).Contains(reference.Context.Parent) - // && !_references.Any(r => r.QualifiedModuleName == reference.QualifiedModuleName - // && r.Selection.StartLine == reference.Selection.StartLine - // && r.Selection.EndLine == reference.Selection.EndLine - // && r.Selection.StartColumn == reference.Selection.StartColumn - // && r.Selection.EndColumn == reference.Selection.EndColumn)) - // { - // _references.Add(reference); - // } - //} - public void AddMemberCall(IdentifierReference reference) { if (reference == null || reference.Declaration == null || reference.Declaration.Context == reference.Context) @@ -407,7 +447,7 @@ public string ProjectName public object[] ToArray() { - return new object[] { this.ProjectName, this.CustomFolder, this.ComponentName, this.DeclarationType.ToString(), this.Scope, this.IdentifierName, this.AsTypeName }; + return new object[] { ProjectName, CustomFolder, ComponentName, DeclarationType.ToString(), Scope, IdentifierName, AsTypeName }; } @@ -496,7 +536,7 @@ internal set DeclarationType.PropertyLet, DeclarationType.PropertyLet, DeclarationType.UserDefinedType, - DeclarationType.Constant, + DeclarationType.Constant }; public bool IsSelected(QualifiedSelection selection) diff --git a/Rubberduck.Parsing/Symbols/FunctionDeclaration.cs b/Rubberduck.Parsing/Symbols/FunctionDeclaration.cs index bebe9ba546..3d20f41795 100644 --- a/Rubberduck.Parsing/Symbols/FunctionDeclaration.cs +++ b/Rubberduck.Parsing/Symbols/FunctionDeclaration.cs @@ -1,5 +1,6 @@ using Antlr4.Runtime; using Rubberduck.Parsing.Annotations; +using Rubberduck.Parsing.ComReflection; using Rubberduck.Parsing.Grammar; using Rubberduck.Parsing.VBA; using Rubberduck.VBEditor; @@ -47,6 +48,28 @@ public FunctionDeclaration( _parameters = new List(); } + public FunctionDeclaration(ComMember member, Declaration parent, QualifiedModuleName module, + Attributes attributes) : this( + module.QualifyMemberName(member.Name), + parent, + parent, + member.ReturnType.TypeName, + null, + null, + Accessibility.Global, + null, + Selection.Home, + member.ReturnType.IsArray, + true, + null, + attributes) + { + _parameters = + member.Parameters.Select(decl => new ParameterDeclaration(decl, this, module)) + .Cast() + .ToList(); + } + public IEnumerable Parameters { get diff --git a/Rubberduck.Parsing/Symbols/ParameterDeclaration.cs b/Rubberduck.Parsing/Symbols/ParameterDeclaration.cs index bf030dc300..264e6a2f5c 100644 --- a/Rubberduck.Parsing/Symbols/ParameterDeclaration.cs +++ b/Rubberduck.Parsing/Symbols/ParameterDeclaration.cs @@ -1,4 +1,5 @@ using Antlr4.Runtime; +using Rubberduck.Parsing.ComReflection; using Rubberduck.Parsing.Grammar; using Rubberduck.VBEditor; @@ -76,6 +77,21 @@ public ParameterDeclaration(QualifiedMemberName qualifiedName, IsParamArray = isParamArray; } + public ParameterDeclaration(ComParameter parameter, Declaration parent, QualifiedModuleName module) + : this( + module.QualifyMemberName(parameter.Name), + parent, + parameter.TypeName, + null, + null, + parameter.IsOptional, + parameter.IsByRef, + parameter.IsArray) + { + IsParamArray = parameter.IsParamArray; + } + + public bool IsOptional { get { return _isOptional; } } public bool IsByRef { get { return _isByRef; } } public bool IsParamArray { get; set; } diff --git a/Rubberduck.Parsing/Symbols/ProceduralModuleDeclaration.cs b/Rubberduck.Parsing/Symbols/ProceduralModuleDeclaration.cs index 4bf4b41873..302cc6e046 100644 --- a/Rubberduck.Parsing/Symbols/ProceduralModuleDeclaration.cs +++ b/Rubberduck.Parsing/Symbols/ProceduralModuleDeclaration.cs @@ -1,4 +1,5 @@ using Rubberduck.Parsing.Annotations; +using Rubberduck.Parsing.ComReflection; using Rubberduck.Parsing.VBA; using Rubberduck.VBEditor; using System.Collections.Generic; @@ -30,10 +31,40 @@ public ProceduralModuleDeclaration( null, isBuiltIn, annotations, - attributes) + attributes) { } + + public ProceduralModuleDeclaration(ComModule statics, Declaration parent, QualifiedModuleName module, + Attributes attributes) + : this( + module.QualifyMemberName(statics.Name), + parent, + statics.Name, + true, + new List(), + attributes) { + IsPrivateModule = statics.IsRestricted; } + //These are the pseudo-module ctor for COM enumerations and types. + public ProceduralModuleDeclaration(ComEnumeration pseudo, Declaration parent, QualifiedModuleName module) + : this( + module.QualifyMemberName(pseudo.Name), + parent, + pseudo.Name, + true, + new List(), + new Attributes()) { } + + public ProceduralModuleDeclaration(ComStruct pseudo, Declaration parent, QualifiedModuleName module) + : this( + module.QualifyMemberName(pseudo.Name), + parent, + pseudo.Name, + true, + new List(), + new Attributes()) { } + public bool IsPrivateModule { get; internal set; } } } diff --git a/Rubberduck.Parsing/Symbols/ProjectDeclaration.cs b/Rubberduck.Parsing/Symbols/ProjectDeclaration.cs index 3af1c49f2e..39325b1327 100644 --- a/Rubberduck.Parsing/Symbols/ProjectDeclaration.cs +++ b/Rubberduck.Parsing/Symbols/ProjectDeclaration.cs @@ -1,4 +1,5 @@ -using Rubberduck.VBEditor; +using Rubberduck.Parsing.ComReflection; +using Rubberduck.VBEditor; using System.Collections.Generic; using System.Linq; @@ -31,6 +32,13 @@ public ProjectDeclaration( _projectReferences = new List(); } + public ProjectDeclaration(ComProject project, QualifiedModuleName module) + : this(module.QualifyMemberName(project.Name), project.Name, true) + { + MajorVersion = project.MajorVersion; + MinorVersion = project.MinorVersion; + } + public long MajorVersion { get; set; } public long MinorVersion { get; set; } diff --git a/Rubberduck.Parsing/Symbols/PropertyGetDeclaration.cs b/Rubberduck.Parsing/Symbols/PropertyGetDeclaration.cs index 70b4ed0361..24cd658d0e 100644 --- a/Rubberduck.Parsing/Symbols/PropertyGetDeclaration.cs +++ b/Rubberduck.Parsing/Symbols/PropertyGetDeclaration.cs @@ -1,5 +1,6 @@ using Antlr4.Runtime; using Rubberduck.Parsing.Annotations; +using Rubberduck.Parsing.ComReflection; using Rubberduck.Parsing.Grammar; using Rubberduck.Parsing.VBA; using Rubberduck.VBEditor; @@ -47,6 +48,29 @@ public PropertyGetDeclaration( _parameters = new List(); } + public PropertyGetDeclaration(ComMember member, Declaration parent, QualifiedModuleName module, + Attributes attributes) + : this( + module.QualifyMemberName(member.Name), + parent, + parent, + member.ReturnType.TypeName, + null, + null, + Accessibility.Global, + null, + Selection.Home, + member.ReturnType.IsArray, + true, + null, + attributes) + { + _parameters = + member.Parameters.Select(decl => new ParameterDeclaration(decl, this, module)) + .Cast() + .ToList(); + } + public IEnumerable Parameters { get diff --git a/Rubberduck.Parsing/Symbols/PropertyLetDeclaration.cs b/Rubberduck.Parsing/Symbols/PropertyLetDeclaration.cs index 28ab413fa0..24c4bf4e26 100644 --- a/Rubberduck.Parsing/Symbols/PropertyLetDeclaration.cs +++ b/Rubberduck.Parsing/Symbols/PropertyLetDeclaration.cs @@ -1,5 +1,6 @@ using Antlr4.Runtime; using Rubberduck.Parsing.Annotations; +using Rubberduck.Parsing.ComReflection; using Rubberduck.Parsing.VBA; using Rubberduck.VBEditor; using System.Collections.Generic; @@ -43,6 +44,26 @@ public PropertyLetDeclaration( _parameters = new List(); } + public PropertyLetDeclaration(ComMember member, Declaration parent, QualifiedModuleName module, + Attributes attributes) + : this( + module.QualifyMemberName(member.Name), + parent, + parent, + string.Empty, //TODO: Need to get the types for these. + Accessibility.Global, + null, + Selection.Home, + true, + null, + attributes) + { + _parameters = + member.Parameters.Select(decl => new ParameterDeclaration(decl, this, module)) + .Cast() + .ToList(); + } + public IEnumerable Parameters { get diff --git a/Rubberduck.Parsing/Symbols/PropertySetDeclaration.cs b/Rubberduck.Parsing/Symbols/PropertySetDeclaration.cs index e1fa51a168..fe8fdac781 100644 --- a/Rubberduck.Parsing/Symbols/PropertySetDeclaration.cs +++ b/Rubberduck.Parsing/Symbols/PropertySetDeclaration.cs @@ -1,5 +1,6 @@ using Antlr4.Runtime; using Rubberduck.Parsing.Annotations; +using Rubberduck.Parsing.ComReflection; using Rubberduck.Parsing.VBA; using Rubberduck.VBEditor; using System.Collections.Generic; @@ -43,6 +44,25 @@ public PropertySetDeclaration( _parameters = new List(); } + public PropertySetDeclaration(ComMember member, Declaration parent, QualifiedModuleName module, + Attributes attributes) : this( + module.QualifyMemberName(member.Name), + parent, + parent, + string.Empty, //TODO: Need to get the types for these. + Accessibility.Global, + null, + Selection.Home, + true, + null, + attributes) + { + _parameters = + member.Parameters.Select(decl => new ParameterDeclaration(decl, this, module)) + .Cast() + .ToList(); + } + public IEnumerable Parameters { get diff --git a/Rubberduck.Parsing/Symbols/ReferencedDeclarationsCollector.cs b/Rubberduck.Parsing/Symbols/ReferencedDeclarationsCollector.cs index 97c27ed71d..97d831d522 100644 --- a/Rubberduck.Parsing/Symbols/ReferencedDeclarationsCollector.cs +++ b/Rubberduck.Parsing/Symbols/ReferencedDeclarationsCollector.cs @@ -1,25 +1,12 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.IO; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; -using NLog; +using Rubberduck.Parsing.ComReflection; using Rubberduck.Parsing.VBA; using Rubberduck.VBEditor; -using CALLCONV = System.Runtime.InteropServices.ComTypes.CALLCONV; -using FUNCFLAGS = System.Runtime.InteropServices.ComTypes.FUNCFLAGS; -using TYPEDESC = System.Runtime.InteropServices.ComTypes.TYPEDESC; -using TYPEKIND = System.Runtime.InteropServices.ComTypes.TYPEKIND; -using INVOKEKIND = System.Runtime.InteropServices.ComTypes.INVOKEKIND; -using PARAMFLAG = System.Runtime.InteropServices.ComTypes.PARAMFLAG; -using TYPEATTR = System.Runtime.InteropServices.ComTypes.TYPEATTR; -using FUNCDESC = System.Runtime.InteropServices.ComTypes.FUNCDESC; -using ELEMDESC = System.Runtime.InteropServices.ComTypes.ELEMDESC; -using TYPEFLAGS = System.Runtime.InteropServices.ComTypes.TYPEFLAGS; -using VARDESC = System.Runtime.InteropServices.ComTypes.VARDESC; -using Rubberduck.Parsing.Annotations; -using IMPLTYPEFLAGS = System.Runtime.InteropServices.ComTypes.IMPLTYPEFLAGS; -using TYPELIBATTR = System.Runtime.InteropServices.ComTypes.TYPELIBATTR; using System.Linq; using Rubberduck.VBEditor.SafeComWrappers.Abstract; @@ -27,9 +14,9 @@ namespace Rubberduck.Parsing.Symbols { public class ReferencedDeclarationsCollector { - private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - private readonly RubberduckParserState _state; - + #region Native Stuff + // ReSharper disable InconsistentNaming + // ReSharper disable UnusedMember.Local /// /// Controls how a type library is registered. /// @@ -38,6 +25,8 @@ private enum REGKIND /// /// Use default register behavior. /// + + REGKIND_DEFAULT = 0, /// /// Register this type library. @@ -48,693 +37,249 @@ private enum REGKIND /// REGKIND_NONE = 2 } + // ReSharper restore UnusedMember.Local [DllImport("oleaut32.dll", CharSet = CharSet.Unicode)] private static extern int LoadTypeLibEx(string strTypeLibName, REGKIND regKind, out ITypeLib TypeLib); + // ReSharper restore InconsistentNaming + #endregion - public ReferencedDeclarationsCollector(RubberduckParserState state) - { - _state = state; - } - - private static readonly HashSet IgnoredInterfaceMembers = new HashSet { "QueryInterface", "AddRef", "Release", "GetTypeInfoCount", "GetTypeInfo", "GetIDsOfNames", "Invoke" }; + private readonly RubberduckParserState _state; + private SerializableProject _serialized; + private readonly List _declarations = new List(); - private static readonly IDictionary TypeNames = new Dictionary + private static readonly HashSet IgnoredInterfaceMembers = new HashSet { - {VarEnum.VT_DISPATCH, "Object"}, - {VarEnum.VT_VOID, string.Empty}, - {VarEnum.VT_VARIANT, "Variant"}, - {VarEnum.VT_BLOB_OBJECT, "Object"}, - {VarEnum.VT_STORED_OBJECT, "Object"}, - {VarEnum.VT_STREAMED_OBJECT, "Object"}, - {VarEnum.VT_BOOL, "Boolean"}, - {VarEnum.VT_BSTR, "String"}, - {VarEnum.VT_LPSTR, "String"}, - {VarEnum.VT_LPWSTR, "String"}, - {VarEnum.VT_I1, "Variant"}, // no signed byte type in VBA - {VarEnum.VT_UI1, "Byte"}, - {VarEnum.VT_I2, "Integer"}, - {VarEnum.VT_UI2, "Variant"}, // no unsigned integer type in VBA - {VarEnum.VT_I4, "Long"}, - {VarEnum.VT_UI4, "Variant"}, // no unsigned long integer type in VBA - {VarEnum.VT_I8, "Variant"}, // LongLong on 64-bit VBA - {VarEnum.VT_UI8, "Variant"}, // no unsigned LongLong integer type in VBA - {VarEnum.VT_INT, "Long"}, // same as I4 - {VarEnum.VT_UINT, "Variant"}, // same as UI4 - {VarEnum.VT_DATE, "Date"}, - {VarEnum.VT_CY, "Currency"}, - {VarEnum.VT_DECIMAL, "Currency"}, // best match? - {VarEnum.VT_EMPTY, "Empty"}, - {VarEnum.VT_R4, "Single"}, - {VarEnum.VT_R8, "Double"}, + "QueryInterface", + "AddRef", + "Release", + "GetTypeInfoCount", + "GetTypeInfo", + "GetIDsOfNames", + "Invoke" }; - private readonly Dictionary _comInformation = new Dictionary(); - - private ComParameter GetParameterInfo(TYPEDESC desc, ITypeInfo info) - { - var vt = (VarEnum)desc.vt; - TYPEDESC tdesc; - - switch (vt) - { - case VarEnum.VT_PTR: - tdesc = (TYPEDESC)Marshal.PtrToStructure(desc.lpValue, typeof(TYPEDESC)); - var pointer = GetParameterInfo(tdesc, info); - pointer.IsByRef = true; - return pointer; - case VarEnum.VT_USERDEFINED: - int href; - unchecked - { - href = (int)(desc.lpValue.ToInt64() & 0xFFFFFFFF); - } - try - { - ITypeInfo refTypeInfo; - info.GetRefTypeInfo(href, out refTypeInfo); - return new ComParameter(GetTypeName(refTypeInfo), false); - } - catch (Exception) - { - return new ComParameter("Object", false); - } - case VarEnum.VT_SAFEARRAY: - case VarEnum.VT_CARRAY: - case VarEnum.VT_ARRAY: - tdesc = (TYPEDESC)Marshal.PtrToStructure(desc.lpValue, typeof(TYPEDESC)); - var array = GetParameterInfo(tdesc, info); - array.IsArray = true; - array.Name += "()"; - return array; - default: - string result; - if (TypeNames.TryGetValue(vt, out result)) - { - return new ComParameter(result, false); - } - break; - } - return new ComParameter("Object", false); - } - - private string GetTypeName(ITypeInfo info) - { - string typeName; - string docString; // todo: put the docString to good use? - int helpContext; - string helpFile; - info.GetDocumentation(-1, out typeName, out docString, out helpContext, out helpFile); - - return typeName.Equals("LONG_PTR") ? "LongPtr" : typeName; //Quickfix for http://chat.stackexchange.com/transcript/message/30119269#30119269 - } + private readonly string _referenceName; + private readonly string _path; + private readonly int _referenceMajor; + private readonly int _referenceMinor; - private void LoadVersionFromTypeLib(ITypeLib tlb, ProjectDeclaration project) + public ReferencedDeclarationsCollector(RubberduckParserState state, IReference reference) { - var attribPtr = IntPtr.Zero; - tlb.GetLibAttr(out attribPtr); - var typeAttr = (TYPELIBATTR)Marshal.PtrToStructure(attribPtr, typeof(TYPELIBATTR)); - project.MajorVersion = typeAttr.wMajorVerNum; - project.MinorVersion = typeAttr.wMinorVerNum; + _state = state; + _path = reference.FullPath; + _referenceName = reference.Name; + _referenceMajor = reference.Major; + _referenceMinor = reference.Minor; } - - private bool SerializedVersionExists(ProjectDeclaration project) + + public bool SerializedVersionExists { - var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Rubberduck", "Declarations"); - if (!Directory.Exists(path)) + get { - return false; - } - //TODO: This is naively based on file name for now - this should attempt to deserialize any SerializableProject.Nodes in the directory and test for equity. - var testFile = Path.Combine(path, string.Format("{0}.{1}.{2}", project.ProjectName, project.MajorVersion, project.MinorVersion) + ".xml"); - if (File.Exists(testFile)) - { - return true; + var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Rubberduck", "Declarations"); + if (!Directory.Exists(path)) + { + return false; + } + //TODO: This is naively based on file name for now - this should attempt to deserialize any SerializableProject.Nodes in the directory and test for equity. + var testFile = Path.Combine(path, string.Format("{0}.{1}.{2}", _referenceName, _referenceMajor, _referenceMinor) + ".xml"); + return File.Exists(testFile); } - return false; } - private List LoadSerializedBuiltInReferences(ProjectDeclaration project) + private static readonly HashSet ProceduralTypes = + new HashSet(new[] + { + DeclarationType.Procedure, DeclarationType.Function, DeclarationType.PropertyGet, + DeclarationType.PropertyLet, DeclarationType.PropertySet + }); + + public List LoadDeclarationsFromXml() { var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Rubberduck", "Declarations"); - var file = Path.Combine(path, string.Format("{0}.{1}.{2}", project.ProjectName, project.MajorVersion, project.MinorVersion) + ".xml"); + var file = Path.Combine(path, string.Format("{0}.{1}.{2}", _referenceName, _referenceMajor, _referenceMinor) + ".xml"); var reader = new XmlPersistableDeclarations(); var deserialized = reader.Load(file); - return deserialized.Unwrap(); + + var declarations = deserialized.Unwrap(); + + foreach (var members in declarations.Where(d => d.DeclarationType != DeclarationType.Project && + d.ParentDeclaration.DeclarationType == DeclarationType.ClassModule && + ProceduralTypes.Contains(d.DeclarationType)) + .GroupBy(d => d.ParentDeclaration)) + { + _state.CoClasses.TryAdd(members.Select(m => m.IdentifierName).ToList(), members.First().ParentDeclaration); + } + return declarations; } - public List GetDeclarationsForReference(IReference reference) + public List LoadDeclarationsFromLibrary() { - var output = new List(); - var projectName = reference.Name; - var path = reference.FullPath; ITypeLib typeLibrary; // Failure to load might mean that it's a "normal" VBProject that will get parsed by us anyway. - LoadTypeLibEx(path, REGKIND.REGKIND_NONE, out typeLibrary); + LoadTypeLibEx(_path, REGKIND.REGKIND_NONE, out typeLibrary); if (typeLibrary == null) { - return output; - } - var projectQualifiedModuleName = new QualifiedModuleName(projectName, path, projectName); - var projectQualifiedMemberName = new QualifiedMemberName(projectQualifiedModuleName, projectName); - var projectDeclaration = new ProjectDeclaration(projectQualifiedMemberName, projectName, isBuiltIn: true); - LoadVersionFromTypeLib(typeLibrary, projectDeclaration); - if (SerializedVersionExists(projectDeclaration)) - { - Logger.Trace(string.Format("Deserializing reference '{0}'.", reference.Name)); - return LoadSerializedBuiltInReferences(projectDeclaration); + return _declarations; } - Logger.Trace(string.Format("COM reflecting reference '{0}'.", reference.Name)); - output.Add(projectDeclaration); - var typeCount = typeLibrary.GetTypeInfoCount(); - for (var i = 0; i < typeCount; i++) - { - ITypeInfo info; - try - { - typeLibrary.GetTypeInfo(i, out info); - } - catch (NullReferenceException) - { - return output; - } + var type = new ComProject(typeLibrary) { Path = _path }; - if (info == null) - { - continue; - } - - var typeName = GetTypeName(info); - var typeDeclarationType = GetDeclarationType(typeLibrary, i); - - QualifiedModuleName typeQualifiedModuleName; - QualifiedMemberName typeQualifiedMemberName; - if (typeDeclarationType == DeclarationType.Enumeration || typeDeclarationType == DeclarationType.UserDefinedType) - { - typeQualifiedModuleName = projectQualifiedModuleName; - typeQualifiedMemberName = new QualifiedMemberName(projectQualifiedModuleName, typeName); - } - else - { - typeQualifiedModuleName = new QualifiedModuleName(projectName, path, typeName); - typeQualifiedMemberName = new QualifiedMemberName(typeQualifiedModuleName, typeName); - } + var projectName = new QualifiedModuleName(type.Name, _path, type.Name); + var project = new ProjectDeclaration(type, projectName); + _serialized = new SerializableProject(project); + _declarations.Add(project); - IntPtr typeAttributesPointer; - info.GetTypeAttr(out typeAttributesPointer); - var typeAttributes = (TYPEATTR)Marshal.PtrToStructure(typeAttributesPointer, typeof(TYPEATTR)); + foreach (var module in type.Members) + { + var moduleName = new QualifiedModuleName(_referenceName, _path, + module.Type == DeclarationType.Enumeration || module.Type == DeclarationType.UserDefinedType + ? string.Format("_{0}", module.Name) + : module.Name); var attributes = new Attributes(); - - if (typeAttributes.wTypeFlags.HasFlag(TYPEFLAGS.TYPEFLAG_FPREDECLID)) + if (module.IsPreDeclared) { attributes.AddPredeclaredIdTypeAttribute(); } - - if (typeAttributes.wTypeFlags.HasFlag(TYPEFLAGS.TYPEFLAG_FAPPOBJECT)) + if (module.IsAppObject) { attributes.AddGlobalClassAttribute(); } - Declaration moduleDeclaration; - switch (typeDeclarationType) - { - case DeclarationType.ProceduralModule: - moduleDeclaration = new ProceduralModuleDeclaration(typeQualifiedMemberName, projectDeclaration, typeName, true, new List(), attributes); - break; - case DeclarationType.ClassModule: - var module = new ClassModuleDeclaration(typeQualifiedMemberName, projectDeclaration, typeName, true, new List(), attributes); - var implements = GetImplementedInterfaceNames(typeAttributes, info, module); - foreach (var supertypeName in implements) - { - module.AddSupertype(supertypeName); - } - moduleDeclaration = module; - break; - default: - var pseudoModuleName = string.Format("_{0}", typeName); - var pseudoParentModule = new ProceduralModuleDeclaration( - new QualifiedMemberName(projectQualifiedModuleName, pseudoModuleName), - projectDeclaration, - pseudoModuleName, - true, - new List(), - new Attributes()); + var declaration = CreateModuleDeclaration(module, moduleName, project, attributes); + var moduleTree = new SerializableDeclarationTree(declaration); + _declarations.Add(declaration); + _serialized.AddDeclaration(moduleTree); - // UDTs and ENUMs don't seem to have a module parent that's why we add a "fake" module - // so that the rest of the application can treat it normally. - moduleDeclaration = new Declaration( - typeQualifiedMemberName, - pseudoParentModule, - pseudoParentModule, - typeName, - null, - false, - false, - Accessibility.Global, - typeDeclarationType, - null, - Selection.Home, - false, - null, - true, - null, - attributes); - - output.Add(pseudoParentModule); - break; - } - - ComInformation comInfo; - if (typeAttributes.guid == Guid.Empty) + var membered = module as IComTypeWithMembers; + if (membered != null) { - comInfo = new ComInformation(typeAttributes, 0, info, typeName, typeQualifiedModuleName, moduleDeclaration, typeDeclarationType); - LoadDeclarationsInModule(output, comInfo); - } - else - { - if (_comInformation.TryGetValue(typeAttributes.guid, out comInfo)) - { - comInfo.TypeQualifiedModuleName = typeQualifiedModuleName; - comInfo.ModuleDeclaration = moduleDeclaration; - comInfo.TypeDeclarationType = typeDeclarationType; - } - else + foreach (var item in membered.Members.Where(m => !m.IsRestricted && !IgnoredInterfaceMembers.Contains(m.Name))) { - comInfo = new ComInformation(typeAttributes, 0, info, typeName, typeQualifiedModuleName, moduleDeclaration, typeDeclarationType); - _comInformation.Add(typeAttributes.guid, comInfo); - } - } + var memberDeclaration = CreateMemberDeclaration(item, moduleName, declaration); + _declarations.Add(memberDeclaration); - info.ReleaseTypeAttr(typeAttributesPointer); + var memberTree = new SerializableDeclarationTree(memberDeclaration); + moduleTree.AddChildTree(memberTree); - output.Add(moduleDeclaration); - } - - foreach (var member in _comInformation.Values) - { - LoadDeclarationsInModule(output, member); - } - - return output; - } - - private void LoadDeclarationsInModule(List output, ComInformation member) - { - if (member.TypeAttributes.typekind == TYPEKIND.TKIND_COCLASS) - { - GetCoClassInformation(member); - } - - for (var memberIndex = 0; memberIndex < member.TypeAttributes.cFuncs; memberIndex++) - { - string[] memberNames; - - IntPtr memberDescriptorPointer; - member.TypeInfo.GetFuncDesc(memberIndex, out memberDescriptorPointer); - var memberDescriptor = (FUNCDESC)Marshal.PtrToStructure(memberDescriptorPointer, typeof(FUNCDESC)); - - var memberDeclaration = CreateMemberDeclaration(memberDescriptor, member.TypeAttributes.typekind, member.TypeInfo, member.ImplTypeFlags, - member.TypeQualifiedModuleName, member.ModuleDeclaration, out memberNames); - if (memberDeclaration == null) - { - member.TypeInfo.ReleaseFuncDesc(memberDescriptorPointer); - continue; + var hasParams = memberDeclaration as IDeclarationWithParameter; + if (hasParams != null) + { + _declarations.AddRange(hasParams.Parameters); + memberTree.AddChildren(hasParams.Parameters); + } + var coClass = memberDeclaration as ClassModuleDeclaration; + if (coClass != null && item.IsDefault) + { + coClass.DefaultMember = memberDeclaration; + } + } } - if (member.ModuleDeclaration.DeclarationType == DeclarationType.ClassModule && - memberDeclaration is ICanBeDefaultMember && - ((ICanBeDefaultMember)memberDeclaration).IsDefaultMember) + var enumeration = module as ComEnumeration; + if (enumeration != null) { - ((ClassModuleDeclaration)member.ModuleDeclaration).DefaultMember = memberDeclaration; + var enumDeclaration = new Declaration(enumeration, declaration, moduleName); + _declarations.Add(enumDeclaration); + var members = enumeration.Members.Select(e => new Declaration(e, enumDeclaration, moduleName)).ToList(); + _declarations.AddRange(members); + + var enumTree = new SerializableDeclarationTree(enumDeclaration); + moduleTree.AddChildTree(enumTree); + enumTree.AddChildren(members); } - output.Add(memberDeclaration); - var parameterCount = memberDescriptor.cParams - (memberDescriptor.invkind.HasFlag(INVOKEKIND.INVOKE_PROPERTYGET) ? 0 : 1); - var parameters = new List(); - for (var paramIndex = 0; paramIndex < parameterCount; paramIndex++) + var structure = module as ComStruct; + if (structure != null) { - var parameter = CreateParameterDeclaration(memberNames, paramIndex, memberDescriptor, - member.TypeQualifiedModuleName, memberDeclaration, member.TypeInfo); - var declaration = memberDeclaration as IDeclarationWithParameter; - if (declaration != null) - { - parameters.Add(parameter); - declaration.AddParameter(parameter); - } - output.Add(parameter); - } - member.TypeInfo.ReleaseFuncDesc(memberDescriptorPointer); - if (parameters.Any() && memberDescriptor.cParamsOpt == -1) - { - parameters.Last().IsParamArray = true; + var typeDeclaration = new Declaration(structure, declaration, moduleName); + _declarations.Add(typeDeclaration); + var members = structure.Fields.Select(f => new Declaration(f, typeDeclaration, moduleName)).ToList(); + _declarations.AddRange(members); + + var typeTree = new SerializableDeclarationTree(typeDeclaration); + moduleTree.AddChildTree(typeTree); + typeTree.AddChildren(members); } - } - - for (var fieldIndex = 0; fieldIndex < member.TypeAttributes.cVars; fieldIndex++) - { - var declaration = CreateFieldDeclaration(member.TypeInfo, fieldIndex, member.TypeDeclarationType, member.TypeQualifiedModuleName, member.ModuleDeclaration); - output.Add(declaration); - } - } - - private void GetCoClassInformation(ComInformation member) - { - var componentMemberNames = new List(); - for (var implIndex = 0; implIndex < member.TypeAttributes.cImplTypes; implIndex++) - { - int href; - member.TypeInfo.GetRefTypeOfImplType(0, out href); - - ITypeInfo implTypeInfo; - member.TypeInfo.GetRefTypeInfo(href, out implTypeInfo); - IntPtr typeAttributesPointer; - implTypeInfo.GetTypeAttr(out typeAttributesPointer); - - var typeAttributes = (TYPEATTR)Marshal.PtrToStructure(typeAttributesPointer, typeof(TYPEATTR)); - - for (var i = 0; i < typeAttributes.cFuncs; i++) + var fields = module as IComTypeWithFields; + if (fields == null || !fields.Fields.Any()) { - var memberNames = new string[255]; - - IntPtr memberDescriptorPointer; - implTypeInfo.GetFuncDesc(i, out memberDescriptorPointer); - var memberDescriptor = (FUNCDESC)Marshal.PtrToStructure(memberDescriptorPointer, typeof(FUNCDESC)); - - if (!(memberDescriptor.invkind.HasFlag(INVOKEKIND.INVOKE_PROPERTYGET) || - memberDescriptor.invkind.HasFlag(INVOKEKIND.INVOKE_PROPERTYPUT) || - memberDescriptor.invkind.HasFlag(INVOKEKIND.INVOKE_PROPERTYPUTREF))) - { - continue; - } - - int namesArrayLength; - implTypeInfo.GetNames(memberDescriptor.memid, memberNames, 255, out namesArrayLength); - - if (!IgnoredInterfaceMembers.Contains(memberNames[0]) && - !componentMemberNames.Contains(memberNames[0])) - { - componentMemberNames.Add(memberNames[0]); - } + continue; } - - member.TypeInfo.ReleaseTypeAttr(typeAttributesPointer); + var declarations = fields.Fields.Select(f => new Declaration(f, declaration, projectName)).ToList(); + _declarations.AddRange(declarations); + moduleTree.AddChildren(declarations); } - - _state.CoClasses.TryAdd(componentMemberNames, member.ModuleDeclaration); + _state.BuiltInDeclarationTrees.TryAdd(_serialized); + return _declarations; } - private Declaration CreateMemberDeclaration(FUNCDESC memberDescriptor, TYPEKIND typeKind, ITypeInfo info, IMPLTYPEFLAGS parentImplFlags, - QualifiedModuleName typeQualifiedModuleName, Declaration moduleDeclaration, out string[] memberNames) + private Declaration CreateModuleDeclaration(IComType module, QualifiedModuleName project, Declaration parent, Attributes attributes) { - if (memberDescriptor.callconv != CALLCONV.CC_STDCALL) + var enumeration = module as ComEnumeration; + if (enumeration != null) { - memberNames = new string[] { }; - return null; + return new ProceduralModuleDeclaration(enumeration, parent, project); } - - memberNames = new string[255]; - int namesArrayLength; - info.GetNames(memberDescriptor.memid, memberNames, 255, out namesArrayLength); - - var memberName = memberNames[0]; - var funcValueType = (VarEnum)memberDescriptor.elemdescFunc.tdesc.vt; - var memberDeclarationType = GetDeclarationType(memberName, memberDescriptor, funcValueType, typeKind, parentImplFlags); - - if (((FUNCFLAGS)memberDescriptor.wFuncFlags).HasFlag(FUNCFLAGS.FUNCFLAG_FRESTRICTED) && - IgnoredInterfaceMembers.Contains(memberName)) // Ignore IDispatch and IUnknown members - quick-and-dirty for beta + var types = module as ComStruct; + if (types != null) { - return null; + return new ProceduralModuleDeclaration(types, parent, project); } - - var asTypeName = new ComParameter(string.Empty, false); - if (memberDeclarationType != DeclarationType.Procedure) + var coClass = module as ComCoClass; + var intrface = module as ComInterface; + if (coClass != null || intrface != null) { - asTypeName = GetParameterInfo(memberDescriptor.elemdescFunc.tdesc, info); + var output = coClass != null ? + new ClassModuleDeclaration(coClass, parent, project, attributes) : + new ClassModuleDeclaration(intrface, parent, project, attributes); + if (coClass != null) + { + var members = + coClass.Members.Where(m => !m.IsRestricted && !IgnoredInterfaceMembers.Contains(m.Name)) + .Select(m => m.Name); + _state.CoClasses.TryAdd(members.ToList(), output); + } + return output; } + return new ProceduralModuleDeclaration(module as ComModule, parent, project, attributes); + } + + private Declaration CreateMemberDeclaration(ComMember member, QualifiedModuleName module, Declaration parent) + { var attributes = new Attributes(); - if (memberName == "_NewEnum" && ((FUNCFLAGS)memberDescriptor.wFuncFlags).HasFlag(FUNCFLAGS.FUNCFLAG_FNONBROWSABLE)) + if (member.IsEnumerator) { - attributes.AddEnumeratorMemberAttribute(memberName); + attributes.AddEnumeratorMemberAttribute(member.Name); } - else if (memberDescriptor.memid == 0) + else if (member.IsDefault) { - attributes.AddDefaultMemberAttribute(memberName); + attributes.AddDefaultMemberAttribute(member.Name); } - else if (((FUNCFLAGS)memberDescriptor.wFuncFlags).HasFlag(FUNCFLAGS.FUNCFLAG_FHIDDEN)) + else if (member.IsHidden) { - attributes.AddHiddenMemberAttribute(memberName); + attributes.AddHiddenMemberAttribute(member.Name); } - switch (memberDeclarationType) + switch (member.Type) { + case DeclarationType.Event: case DeclarationType.Procedure: - return new SubroutineDeclaration( - new QualifiedMemberName(typeQualifiedModuleName, memberName), - moduleDeclaration, - moduleDeclaration, - asTypeName.Name, - Accessibility.Global, - null, - Selection.Home, - true, - null, - attributes); + return new SubroutineDeclaration(member, parent, module, attributes); case DeclarationType.Function: - return new FunctionDeclaration( - new QualifiedMemberName(typeQualifiedModuleName, memberName), - moduleDeclaration, - moduleDeclaration, - asTypeName.Name, - null, - null, - Accessibility.Global, - null, - Selection.Home, - asTypeName.IsArray, - true, - null, - attributes); + return new FunctionDeclaration(member, parent, module, attributes); case DeclarationType.PropertyGet: - return new PropertyGetDeclaration( - new QualifiedMemberName(typeQualifiedModuleName, memberName), - moduleDeclaration, - moduleDeclaration, - asTypeName.Name, - null, - null, - Accessibility.Global, - null, - Selection.Home, - asTypeName.IsArray, - true, - null, - attributes); + return new PropertyGetDeclaration(member, parent, module, attributes); case DeclarationType.PropertySet: - return new PropertySetDeclaration( - new QualifiedMemberName(typeQualifiedModuleName, memberName), - moduleDeclaration, - moduleDeclaration, - asTypeName.Name, - Accessibility.Global, - null, - Selection.Home, - true, - null, - attributes); + return new PropertySetDeclaration(member, parent, module, attributes); case DeclarationType.PropertyLet: - return new PropertyLetDeclaration( - new QualifiedMemberName(typeQualifiedModuleName, memberName), - moduleDeclaration, - moduleDeclaration, - asTypeName.Name, - Accessibility.Global, - null, - Selection.Home, - true, - null, - attributes); + return new PropertyLetDeclaration(member, parent, module, attributes); default: - return new Declaration( - new QualifiedMemberName(typeQualifiedModuleName, memberName), - moduleDeclaration, - moduleDeclaration, - asTypeName.Name, - null, - false, - false, - Accessibility.Global, - memberDeclarationType, - null, - Selection.Home, - false, - null, - true, - null, - attributes); - } - } - - private Declaration CreateFieldDeclaration(ITypeInfo info, int fieldIndex, DeclarationType typeDeclarationType, - QualifiedModuleName typeQualifiedModuleName, Declaration moduleDeclaration) - { - IntPtr ppVarDesc; - info.GetVarDesc(fieldIndex, out ppVarDesc); - - var varDesc = (VARDESC)Marshal.PtrToStructure(ppVarDesc, typeof(VARDESC)); - - var names = new string[255]; - int namesArrayLength; - info.GetNames(varDesc.memid, names, 255, out namesArrayLength); - - var fieldName = names[0]; - var memberType = GetDeclarationType(varDesc, typeDeclarationType); - - var asTypeName = GetParameterInfo(varDesc.elemdescVar.tdesc, info); - info.ReleaseVarDesc(ppVarDesc); - - return new Declaration(new QualifiedMemberName(typeQualifiedModuleName, fieldName), - moduleDeclaration, moduleDeclaration, asTypeName.Name, null, false, false, Accessibility.Global, memberType, null, - Selection.Home, false, null); - } - - private ParameterDeclaration CreateParameterDeclaration(IReadOnlyList memberNames, int paramIndex, - FUNCDESC memberDescriptor, QualifiedModuleName typeQualifiedModuleName, Declaration memberDeclaration, ITypeInfo info) - { - var paramName = memberNames[paramIndex + 1]; - - var paramPointer = new IntPtr(memberDescriptor.lprgelemdescParam.ToInt64() + Marshal.SizeOf(typeof(ELEMDESC)) * paramIndex); - var elementDesc = (ELEMDESC)Marshal.PtrToStructure(paramPointer, typeof(ELEMDESC)); - var isOptional = elementDesc.desc.paramdesc.wParamFlags.HasFlag(PARAMFLAG.PARAMFLAG_FOPT); - var paramDesc = elementDesc.tdesc; - var paramInfo = GetParameterInfo(paramDesc, info); - - return new ParameterDeclaration(new QualifiedMemberName(typeQualifiedModuleName, paramName), memberDeclaration, paramInfo.Name, null, null, isOptional, paramInfo.IsByRef, paramInfo.IsArray); - } - - private IEnumerable GetImplementedInterfaceNames(TYPEATTR typeAttr, ITypeInfo info, Declaration module) - { - var output = new List(); - for (var implIndex = 0; implIndex < typeAttr.cImplTypes; implIndex++) - { - int href; - info.GetRefTypeOfImplType(implIndex, out href); - - ITypeInfo implTypeInfo; - info.GetRefTypeInfo(href, out implTypeInfo); - - IntPtr typeAttributesPointer; - implTypeInfo.GetTypeAttr(out typeAttributesPointer); - - var typeAttributes = (TYPEATTR)Marshal.PtrToStructure(typeAttributesPointer, typeof(TYPEATTR)); - - IMPLTYPEFLAGS flags = 0; - try - { - info.GetImplTypeFlags(implIndex, out flags); - } - catch (COMException) { } - - var implTypeName = GetTypeName(implTypeInfo); - if (implTypeName != "IDispatch" && implTypeName != "IUnknown") - { - // skip IDispatch.. just about everything implements it and RD doesn't need to care about it; don't care about IUnknown either - output.Add(implTypeName); - } - - if (flags != 0) - { - ComInformation comInfo; - if (_comInformation.TryGetValue(typeAttributes.guid, out comInfo)) - { - _comInformation[typeAttributes.guid].ImplTypeFlags = - _comInformation[typeAttributes.guid].ImplTypeFlags | flags; - } - else - { - _comInformation.Add(typeAttributes.guid, - new ComInformation(typeAttributes, flags, implTypeInfo, implTypeName, module.QualifiedName.QualifiedModuleName, module, 0)); - } - } - - info.ReleaseTypeAttr(typeAttributesPointer); - } - return output; - } - - private DeclarationType GetDeclarationType(ITypeLib typeLibrary, int i) - { - TYPEKIND typeKind; - typeLibrary.GetTypeInfoType(i, out typeKind); - - DeclarationType typeDeclarationType = DeclarationType.Control; // todo: a better default - if (typeKind == TYPEKIND.TKIND_ENUM) - { - typeDeclarationType = DeclarationType.Enumeration; - } - else if (typeKind == TYPEKIND.TKIND_COCLASS || typeKind == TYPEKIND.TKIND_INTERFACE || - typeKind == TYPEKIND.TKIND_ALIAS || typeKind == TYPEKIND.TKIND_DISPATCH) - { - typeDeclarationType = DeclarationType.ClassModule; - } - else if (typeKind == TYPEKIND.TKIND_RECORD) - { - typeDeclarationType = DeclarationType.UserDefinedType; - } - else if (typeKind == TYPEKIND.TKIND_MODULE) - { - typeDeclarationType = DeclarationType.ProceduralModule; - } - return typeDeclarationType; - } - - private DeclarationType GetDeclarationType(string memberName, FUNCDESC funcDesc, VarEnum funcValueType, TYPEKIND typekind, IMPLTYPEFLAGS parentImplTypeFlags) - { - DeclarationType memberType; - if (funcDesc.invkind.HasFlag(INVOKEKIND.INVOKE_PROPERTYGET)) - { - memberType = DeclarationType.PropertyGet; - } - else if (funcDesc.invkind.HasFlag(INVOKEKIND.INVOKE_PROPERTYPUT)) - { - memberType = DeclarationType.PropertyLet; - } - else if (funcDesc.invkind.HasFlag(INVOKEKIND.INVOKE_PROPERTYPUTREF)) - { - memberType = DeclarationType.PropertySet; - } - else if ((parentImplTypeFlags.HasFlag(IMPLTYPEFLAGS.IMPLTYPEFLAG_FSOURCE) || - ((FUNCFLAGS)funcDesc.wFuncFlags).HasFlag(FUNCFLAGS.FUNCFLAG_FSOURCE))) - { - memberType = DeclarationType.Event; - } - else if (funcValueType == VarEnum.VT_VOID) - { - memberType = DeclarationType.Procedure; - } - else - { - memberType = DeclarationType.Function; - } - return memberType; - } - - private DeclarationType GetDeclarationType(VARDESC varDesc, DeclarationType typeDeclarationType) - { - var memberType = DeclarationType.Variable; - if (varDesc.varkind == VARKIND.VAR_CONST) - { - memberType = typeDeclarationType == DeclarationType.Enumeration - ? DeclarationType.EnumerationMember - : DeclarationType.Constant; - } - else if (typeDeclarationType == DeclarationType.UserDefinedType) - { - memberType = DeclarationType.UserDefinedTypeMember; + throw new InvalidEnumArgumentException(string.Format("Unexpected DeclarationType {0} encountered.", member.Type)); } - return memberType; } } } diff --git a/Rubberduck.Parsing/Symbols/SerializableDeclaration.cs b/Rubberduck.Parsing/Symbols/SerializableDeclaration.cs index e5cbecb777..a6143d8a63 100644 --- a/Rubberduck.Parsing/Symbols/SerializableDeclaration.cs +++ b/Rubberduck.Parsing/Symbols/SerializableDeclaration.cs @@ -11,7 +11,13 @@ public class SerializableDeclarationTree { public SerializableDeclaration Node; - public IEnumerable Children; + private List _children = new List(); + + public IEnumerable Children + { + get { return _children; } + set { _children = new List(value); } + } public SerializableDeclarationTree() { } @@ -29,6 +35,19 @@ public SerializableDeclarationTree(SerializableDeclaration node, IEnumerable declarations) + { + foreach (var child in declarations) + { + _children.Add(new SerializableDeclarationTree(child)); + } + } + + public void AddChildTree(SerializableDeclarationTree tree) + { + _children.Add(tree); + } } [DataContract] @@ -63,12 +82,36 @@ public SerializableProject(Declaration declaration) [DataMember(IsRequired = true)] public SerializableDeclaration Node { get; set; } [DataMember(IsRequired = true)] - public IEnumerable Declarations { get; set; } + + private List _declarations = new List(); + + public IEnumerable Declarations + { + get { return _declarations; } + set { _declarations = new List(value); } + } + [DataMember(IsRequired = true)] public long MajorVersion { get; set; } [DataMember(IsRequired = true)] public long MinorVersion { get; set; } + public void AddDeclaration(SerializableDeclarationTree tree) + { + _declarations.Add(tree); + } + + private readonly Dictionary _pseudoLookup = new Dictionary(); + public SerializableDeclarationTree GetPseudoDeclaration(Declaration declaration) + { + if (!_pseudoLookup.ContainsKey(declaration.IdentifierName)) + { + _declarations.Add(new SerializableDeclarationTree(declaration)); + } + + return _pseudoLookup[declaration.IdentifierName]; + } + public List Unwrap() { var project = (ProjectDeclaration)Node.Unwrap(null); diff --git a/Rubberduck.Parsing/Symbols/SubroutineDeclaration.cs b/Rubberduck.Parsing/Symbols/SubroutineDeclaration.cs index 216c233c29..648b4afc28 100644 --- a/Rubberduck.Parsing/Symbols/SubroutineDeclaration.cs +++ b/Rubberduck.Parsing/Symbols/SubroutineDeclaration.cs @@ -1,5 +1,6 @@ using Antlr4.Runtime; using Rubberduck.Parsing.Annotations; +using Rubberduck.Parsing.ComReflection; using Rubberduck.Parsing.VBA; using Rubberduck.VBEditor; using System.Collections.Generic; @@ -43,6 +44,26 @@ public SubroutineDeclaration( _parameters = new List(); } + public SubroutineDeclaration(ComMember member, Declaration parent, QualifiedModuleName module, + Attributes attributes) + : this( + module.QualifyMemberName(member.Name), + parent, + parent, + string.Empty, + Accessibility.Global, + null, + Selection.Home, + true, + null, + attributes) + { + _parameters = + member.Parameters.Select(decl => new ParameterDeclaration(decl, this, module)) + .Cast() + .ToList(); + } + public IEnumerable Parameters { get diff --git a/Rubberduck.Parsing/VBA/RubberduckParser.cs b/Rubberduck.Parsing/VBA/RubberduckParser.cs index dfab250603..ded3a6ff3e 100644 --- a/Rubberduck.Parsing/VBA/RubberduckParser.cs +++ b/Rubberduck.Parsing/VBA/RubberduckParser.cs @@ -432,23 +432,24 @@ private void SyncComReferences(IReadOnlyList projects) { try { - Logger.Trace(string.Format("Loading referenced type '{0}'.", reference.Name)); - var comReflector = new ReferencedDeclarationsCollector(State); + Logger.Trace(string.Format("Loading referenced type '{0}'.", localReference.Name)); - var items = comReflector.GetDeclarationsForReference(localReference); - var root = items.OfType().SingleOrDefault(); - var serialize = new List(items); - foreach (var declaration in serialize) + var comReflector = new ReferencedDeclarationsCollector(State, localReference); + if (comReflector.SerializedVersionExists) { - State.AddDeclaration(declaration); + Logger.Trace(string.Format("Deserializing reference '{0}'.", localReference.Name)); + foreach (var declaration in comReflector.LoadDeclarationsFromXml()) + { + State.AddDeclaration(declaration); + } } - serialize.Remove(root); - - var project = GetSerializableProject(root, serialize); - if (project != null) + else { - var added = State.BuiltInDeclarationTrees.TryAdd(project); - //if (!added) { throw new Exception();} + Logger.Trace(string.Format("COM reflecting reference '{0}'.", localReference.Name)); + foreach (var declaration in comReflector.LoadDeclarationsFromLibrary()) + { + State.AddDeclaration(declaration); + } } } catch (Exception exception) @@ -492,31 +493,6 @@ private void SyncComReferences(IReadOnlyList projects) } } - private SerializableProject GetSerializableProject(ProjectDeclaration declaration, List declarations) - { - var project = new SerializableProject(declaration); - var children = new List(); - var nodes = declarations.Where(x => x.ParentDeclaration.Equals(declaration)).ToList(); - foreach (var item in nodes) - { - children.Add(GetSerializableTreeForDeclaration(item, declarations)); - } - project.Declarations = children; - return project; - } - - private SerializableDeclarationTree GetSerializableTreeForDeclaration(Declaration declaration, List declarations) - { - var children = new List(); - var nodes = declarations.Where(x => x.ParentDeclaration.Equals(declaration)).ToList(); - declarations.RemoveAll(nodes.Contains); - foreach (var item in nodes) - { - children.Add(GetSerializableTreeForDeclaration(item, declarations)); - } - return new SerializableDeclarationTree(declaration, children); - } - private void UnloadComReference(IReference reference, IReadOnlyList projects) { var referencedProjectId = GetReferenceProjectId(reference, projects); diff --git a/Rubberduck.VBEEditor/Extensions/IDEExtensions.cs b/Rubberduck.VBEEditor/Extensions/IDEExtensions.cs index efc6215bba..3e9bbf0c2c 100644 --- a/Rubberduck.VBEEditor/Extensions/IDEExtensions.cs +++ b/Rubberduck.VBEEditor/Extensions/IDEExtensions.cs @@ -5,14 +5,43 @@ using Rubberduck.VBEditor.Application; using Rubberduck.VBEditor.SafeComWrappers.Abstract; using Rubberduck.VBEditor.SafeComWrappers.MSForms; +using Exception = System.Exception; namespace Rubberduck.VBEditor.Extensions { public static class VBEExtensions { + private static readonly Dictionary HostAppMap = new Dictionary + { + {"EXCEL.EXE", typeof(ExcelApp)}, + {"WINWORD.EXE", typeof(WordApp)}, + {"MSACCESS.EXE", typeof(AccessApp)}, + {"POWERPNT.EXE", typeof(PowerPointApp)}, + {"OUTLOOK.EXE", typeof(OutlookApp)}, + {"WINPROJ.EXE", typeof(ProjectApp)}, + {"MSPUB.EXE", typeof(PublisherApp)}, + {"VISIO.EXE", typeof(VisioApp)}, + {"ACAD.EXE", typeof(AutoCADApp)}, + {"CORELDRW.EXE", typeof(CorelDRAWApp)}, + {"SLDWORKS.EXE", typeof(SolidWorksApp)}, + }; + /// Returns the type of Office Application that is hosting the VBE. public static IHostApplication HostApplication(this IVBE vbe) { + var host = Path.GetFileName(System.Windows.Forms.Application.ExecutablePath).ToUpperInvariant(); + //This needs the VBE as a ctor argument. + if (host.Equals("SLDWORKS.EXE")) + { + return new SolidWorksApp(vbe); + } + //The rest don't. + if (HostAppMap.ContainsKey(host)) + { + return (IHostApplication)Activator.CreateInstance(HostAppMap[host]); + } + + //Guessing the above will work like 99.9999% of the time for supported applications. var project = vbe.ActiveVBProject; { if (project.IsWrappingNullReference) @@ -114,6 +143,10 @@ public static IHostApplication HostApplication(this IVBE vbe) /// Returns whether the host supports unit tests. public static bool HostSupportsUnitTests(this IVBE vbe) { + var host = Path.GetFileName(System.Windows.Forms.Application.ExecutablePath).ToUpperInvariant(); + if (HostAppMap.ContainsKey(host)) return true; + //Guessing the above will work like 99.9999% of the time for supported applications. + var project = vbe.ActiveVBProject; { if (project.IsWrappingNullReference) diff --git a/Rubberduck.VBEEditor/Native/WinEvents.cs b/Rubberduck.VBEEditor/Native/WinEvents.cs new file mode 100644 index 0000000000..67baf8a3b6 --- /dev/null +++ b/Rubberduck.VBEEditor/Native/WinEvents.cs @@ -0,0 +1,226 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Runtime.InteropServices; +using System.Text; + +namespace Rubberduck.VBEditor.Native +{ + public static class WinEvents + { + #region Debugging symbol lookups + public static readonly Dictionary EventNameLookup = new Dictionary + { + {0x1, "EVENT_SYSTEM_SOUND"}, + {0x2, "EVENT_SYSTEM_ALERT"}, + {0x3, "EVENT_SYSTEM_FOREGROUND"}, + {0x4, "EVENT_SYSTEM_MENUSTART"}, + {0x5, "EVENT_SYSTEM_MENUEND"}, + {0x6, "EVENT_SYSTEM_MENUPOPUPSTART"}, + {0x7, "EVENT_SYSTEM_MENUPOPUPEND"}, + {0x8, "EVENT_SYSTEM_CAPTURESTART"}, + {0x9, "EVENT_SYSTEM_CAPTUREEND"}, + {0xa, "EVENT_SYSTEM_MOVESIZESTART"}, + {0xb, "EVENT_SYSTEM_MOVESIZEEND"}, + {0xc, "EVENT_SYSTEM_CONTEXTHELPSTART"}, + {0xd, "EVENT_SYSTEM_CONTEXTHELPEND"}, + {0xe, "EVENT_SYSTEM_DRAGDROPSTART"}, + {0xf, "EVENT_SYSTEM_DRAGDROPEND"}, + {0x10, "EVENT_SYSTEM_DIALOGSTART"}, + {0x11, "EVENT_SYSTEM_DIALOGEND"}, + {0x12, "EVENT_SYSTEM_SCROLLINGSTART"}, + {0x13, "EVENT_SYSTEM_SCROLLINGEND"}, + {0x14, "EVENT_SYSTEM_SWITCHSTART"}, + {0x15, "EVENT_SYSTEM_SWITCHEND"}, + {0x16, "EVENT_SYSTEM_MINIMIZESTART"}, + {0x17, "EVENT_SYSTEM_MINIMIZEEND"}, + {0x8000, "EVENT_OBJECT_CREATE"}, + {0x8001, "EVENT_OBJECT_DESTROY"}, + {0x8002, "EVENT_OBJECT_SHOW"}, + {0x8003, "EVENT_OBJECT_HIDE"}, + {0x8004, "EVENT_OBJECT_REORDER"}, + {0x8005, "EVENT_OBJECT_FOCUS"}, + {0x8006, "EVENT_OBJECT_SELECTION"}, + {0x8007, "EVENT_OBJECT_SELECTIONADD"}, + {0x8008, "EVENT_OBJECT_SELECTIONREMOVE"}, + {0x8009, "EVENT_OBJECT_SELECTIONWITHIN"}, + {0x800A, "EVENT_OBJECT_STATECHANGE"}, + {0x800B, "EVENT_OBJECT_LOCATIONCHANGE"}, + {0x800C, "EVENT_OBJECT_NAMECHANGE"}, + {0x800D, "EVENT_OBJECT_DESCRIPTIONCHANGE"}, + {0x800E, "EVENT_OBJECT_VALUECHANGE"}, + {0x800F, "EVENT_OBJECT_PARENTCHANGE"}, + {0x8010, "EVENT_OBJECT_HELPCHANGE"}, + {0x8011, "EVENT_OBJECT_DEFACTIONCHANGE"}, + {0x8012, "EVENT_OBJECT_ACCELERATORCHANGE"}, + }; + + public static readonly Dictionary ObjectIdNameLookup = new Dictionary + { + { 0x00000000, "OBJID_WINDOW" }, + { 0xFFFFFFFF, "OBJID_SYSMENU" }, + { 0xFFFFFFFE, "OBJID_TITLEBAR" }, + { 0xFFFFFFFD, "OBJID_MENU" }, + { 0xFFFFFFFC, "OBJID_CLIENT" }, + { 0xFFFFFFFB, "OBJID_VSCROLL" }, + { 0xFFFFFFFA, "OBJID_HSCROLL" }, + { 0xFFFFFFF9, "OBJID_SIZEGRIP" }, + { 0xFFFFFFF8, "OBJID_CARET" }, + { 0xFFFFFFF7, "OBJID_CURSOR" }, + { 0xFFFFFFF6, "OBJID_ALERT" }, + { 0xFFFFFFF5, "OBJID_SOUND" }, + { 0xFFFFFFF4, "OBJID_QUERYCLASSNAMEIDX" }, + { 0xFFFFFFF0, "OBJID_NATIVEOM" }, + }; + #endregion + + #region System enumerations + // ReSharper disable InconsistentNaming + //See https://msdn.microsoft.com/en-us/library/windows/desktop/dd318066(v=vs.85).aspx + public enum EventConstant : uint + { + EVENT_MIN = 0x1, + EVENT_SYSTEM_SOUND = 0x1, + EVENT_SYSTEM_ALERT = 0x2, + EVENT_SYSTEM_FOREGROUND = 0x3, + EVENT_SYSTEM_MENUSTART = 0x4, + EVENT_SYSTEM_MENUEND = 0x5, + EVENT_SYSTEM_MENUPOPUPSTART = 0x6, + EVENT_SYSTEM_MENUPOPUPEND = 0x7, + EVENT_SYSTEM_CAPTURESTART = 0x8, + EVENT_SYSTEM_CAPTUREEND = 0x9, + EVENT_SYSTEM_MOVESIZESTART = 0xa, + EVENT_SYSTEM_MOVESIZEEND = 0xb, + EVENT_SYSTEM_CONTEXTHELPSTART = 0xc, + EVENT_SYSTEM_CONTEXTHELPEND = 0xd, + EVENT_SYSTEM_DRAGDROPSTART = 0xe, + EVENT_SYSTEM_DRAGDROPEND = 0xf, + EVENT_SYSTEM_DIALOGSTART = 0x10, + EVENT_SYSTEM_DIALOGEND = 0x11, + EVENT_SYSTEM_SCROLLINGSTART = 0x12, + EVENT_SYSTEM_SCROLLINGEND = 0x13, + EVENT_SYSTEM_SWITCHSTART = 0x14, + EVENT_SYSTEM_SWITCHEND = 0x15, + EVENT_SYSTEM_MINIMIZESTART = 0x16, + EVENT_SYSTEM_MINIMIZEEND = 0x17, + EVENT_OEM_DEFINED_START = 0x0101, + EVENT_OEM_DEFINED_END = 0x01FF, + EVENT_AIA_START = 0xA000, + EVENT_AIA_END = 0xAFFF, + EVENT_UIA_EVENTID_START = 0x4E00, + EVENT_UIA_EVENTID_END = 0x4EFF, + EVENT_UIA_PROPID_START = 0x7500, + EVENT_UIA_PROPID_END = 0x75FF, + EVENT_OBJECT_START = 0x8000, + EVENT_OBJECT_CREATE = 0x8000, + EVENT_OBJECT_DESTROY = 0x8001, + EVENT_OBJECT_SHOW = 0x8002, + EVENT_OBJECT_HIDE = 0x8003, + EVENT_OBJECT_REORDER = 0x8004, + EVENT_OBJECT_FOCUS = 0x8005, + EVENT_OBJECT_SELECTION = 0x8006, + EVENT_OBJECT_SELECTIONADD = 0x8007, + EVENT_OBJECT_SELECTIONREMOVE = 0x8008, + EVENT_OBJECT_SELECTIONWITHIN = 0x8009, + EVENT_OBJECT_STATECHANGE = 0x800A, + EVENT_OBJECT_LOCATIONCHANGE = 0x800B, + EVENT_OBJECT_NAMECHANGE = 0x800C, + EVENT_OBJECT_DESCRIPTIONCHANGE = 0x800D, + EVENT_OBJECT_VALUECHANGE = 0x800E, + EVENT_OBJECT_PARENTCHANGE = 0x800F, + EVENT_OBJECT_HELPCHANGE = 0x8010, + EVENT_OBJECT_DEFACTIONCHANGE = 0x8011, + EVENT_OBJECT_ACCELERATORCHANGE = 0x8012, + EVENT_OBJECT_INVOKED = 0x8013, + EVENT_OBJECT_CONTENTSCROLLED = 0x8015, + EVENT_SYSTEM_ARRANGMENTPREVIEW = 0x8016, + EVENT_OBJECT_LIVEREGIONCHANGED = 0x8019, + EVENT_OBJECT_HOSTEDOBJECTSINVALIDATED = 0x8020, + EVENT_OBJECT_DRAGSTART = 0x8021, + EVENT_OBJECT_DRAGCANCEL = 0x8022, + EVENT_OBJECT_DRAGCOMPLETE = 0x8023, + EVENT_OBJECT_DRAGENTER = 0x8024, + EVENT_OBJECT_DRAGLEAVE = 0x8025, + EVENT_OBJECT_DRAGDROPPED = 0x8026, + EVENT_OBJECT_IME_SHOW = 0x8027, + EVENT_OBJECT_IME_HIDE = 0x8028, + EVENT_OBJECT_IME_CHANGE = 0x8029, + EVENT_OBJECT_TEXTEDIT_CONVERSIONTARGETCHANGED = 0x8030, + EVENT_OBJECT_TEXTSELECTIONCHANGED = 0x8014, + EVENT_OBJECT_END = 0x80FF, + EVENT_MAX = 0x7FFFFFFF + } + // possible marshaling unmanaged type conflict/problem between 32/64 bit + + public enum ObjId : uint + { + OBJID_WINDOW = 0x00000000, + OBJID_SYSMENU = 0xFFFFFFFF, + OBJID_TITLEBAR = 0xFFFFFFFE, + OBJID_MENU = 0xFFFFFFFD, + OBJID_CLIENT = 0xFFFFFFFC, + OBJID_VSCROLL = 0xFFFFFFFB, + OBJID_HSCROLL = 0xFFFFFFFA, + OBJID_SIZEGRIP = 0xFFFFFFF9, + OBJID_CARET = 0xFFFFFFF8, + OBJID_CURSOR = 0xFFFFFFF7, + OBJID_ALERT = 0xFFFFFFF6, + OBJID_SOUND = 0xFFFFFFF5, + OBJID_QUERYCLASSNAMEIDX = 0xFFFFFFF4, + OBJID_NATIVEOM = 0xFFFFFFF0 + } + + public enum WinEventFlags : uint + { + WINEVENT_OUTOFCONTEXT = 0x0000, + WINEVENT_SKIPOWNTHREAD = 0x0001, + WINEVENT_SKIPOWNPROCESS = 0x0002, + WINEVENT_INCONTEXT = 0x0004 + } + // ReSharper restore InconsistentNaming + #endregion + + #region API declarations + + public delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, uint idObject, uint idChild, uint dwEventThread, uint dwmsEventTime); + + [DllImport("user32.dll")] + public static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, IntPtr lpfnWinEventProc, uint idProcess, + uint idThread, uint dwFlags); + + [DllImport("user32.dll")] + public static extern int GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); + + [DllImport("user32.dll")] + public static extern bool UnhookWinEvent(IntPtr hWinEventHook); + + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] + public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount); + + #endregion + + #region Extension methods + + public static string ToObjectIdString(this uint objectId) + { + return ObjectIdNameLookup.ContainsKey(objectId) ? ObjectIdNameLookup[objectId] : objectId.ToString(CultureInfo.InvariantCulture); + } + + public static string ToEventIdString(this uint eventId) + { + return EventNameLookup.ContainsKey(eventId) ? EventNameLookup[eventId] : eventId.ToString(CultureInfo.InvariantCulture); + } + + public static string ToClassName(this IntPtr hwnd) + { + var buffer = new StringBuilder(256); + if (hwnd != IntPtr.Zero) + { + return GetClassName(hwnd, buffer, buffer.Capacity) != 0 ? buffer.ToString() : string.Empty; + } + return string.Empty; + } + + #endregion + } +} diff --git a/Rubberduck.VBEEditor/Rubberduck.VBEditor.csproj b/Rubberduck.VBEEditor/Rubberduck.VBEditor.csproj index fb24c607c5..c868389a43 100644 --- a/Rubberduck.VBEEditor/Rubberduck.VBEditor.csproj +++ b/Rubberduck.VBEEditor/Rubberduck.VBEditor.csproj @@ -127,6 +127,7 @@ + diff --git a/Rubberduck.VBEEditor/SafeComWrappers/VBA/VBE.cs b/Rubberduck.VBEEditor/SafeComWrappers/VBA/VBE.cs index 0d598b96c8..32cc72e61e 100644 --- a/Rubberduck.VBEEditor/SafeComWrappers/VBA/VBE.cs +++ b/Rubberduck.VBEEditor/SafeComWrappers/VBA/VBE.cs @@ -1,18 +1,33 @@ using System; +using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; +using Rubberduck.VBEditor.Native; using Rubberduck.VBEditor.SafeComWrappers.Abstract; using Rubberduck.VBEditor.SafeComWrappers.Office.Core; using Rubberduck.VBEditor.SafeComWrappers.Office.Core.Abstract; +using IAddIns = Rubberduck.VBEditor.SafeComWrappers.Abstract.IAddIns; +using IWindow = Rubberduck.VBEditor.SafeComWrappers.Abstract.IWindow; +using IWindows = Rubberduck.VBEditor.SafeComWrappers.Abstract.IWindows; using VB = Microsoft.Vbe.Interop; namespace Rubberduck.VBEditor.SafeComWrappers.VBA { public class VBE : SafeComWrapper, IVBE { + // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable + //private readonly WinEvents.WinEventDelegate _events; + //private static IntPtr _hook; + public VBE(VB.VBE target) :base(target) { + //_events = WinEventProc; + //uint proc; + //WinEvents.GetWindowThreadProcessId(new IntPtr(target.MainWindow.HWnd), out proc); + //_hook = WinEvents.SetWinEventHook((uint) WinEvents.EventConstant.EVENT_MIN, + // (uint) WinEvents.EventConstant.EVENT_MAX, IntPtr.Zero, Marshal.GetFunctionPointerForDelegate(_events), proc, 0, + // (uint) WinEvents.WinEventFlags.WINEVENT_OUTOFCONTEXT); } public string Version @@ -86,6 +101,7 @@ public IWindows Windows public override void Release(bool final = false) { + //WinEvents.UnhookWinEvent(_hook); if (!IsWrappingNullReference) { VBProjects.Release(); @@ -130,5 +146,18 @@ public static void SetSelection(IVBProject vbProject, Selection selection, strin var pane = module.CodePane; pane.Selection = selection; } + + //private void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, uint idObject, uint idChild, uint dwEventThread, uint dwmsEventTime) + //{ + ////I don't care about the mouse pointer right now. + //if (idObject == (uint)WinEvents.ObjId.OBJID_CURSOR) return; + + //Debug.WriteLine("Intercepted event {0} for hwnd {1:X8} ({4}), object {2}, child {3}.", + // eventType.ToEventIdString(), + // hwnd.ToInt32(), + // idObject.ToObjectIdString(), + // idChild, + // hwnd.ToClassName()); + //} } }