Skip to content

Commit 22a42d3

Browse files
committed
Introduce SafeIDispatchWrapper class to simplify the management of COM interfaces for where we do not want to define a full SafeComWrapper
Clean up the messy implementation in AccessApp and use SafeIDispatchWrapper Split HostDocument and its associated objects into its own file
1 parent e3fceb3 commit 22a42d3

File tree

7 files changed

+121
-139
lines changed

7 files changed

+121
-139
lines changed

Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerComponentViewModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ public CodeExplorerComponentViewModel(CodeExplorerItemViewModel parent, Declarat
7070
case ComponentType.Document:
7171
string parenthesizedName;
7272
using (var app = _vbe.HostApplication())
73-
using (var document = app.GetDocument(qualifiedModuleName))
7473
{
74+
var document = app.GetDocument(qualifiedModuleName);
7575
parenthesizedName = document.Name ?? string.Empty;
7676
}
7777

Rubberduck.VBEEditor/SafeComWrappers/Abstract/HostApplicationBase.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,9 @@ private static IProperty ApplicationPropertyFromDocumentModule(IVBE vbe)
142142

143143
public string ApplicationName { get; }
144144

145-
public virtual IEnumerable<IHostDocument> GetDocuments() => null;
145+
public virtual IEnumerable<HostDocument> GetDocuments() => null;
146146

147-
public virtual IHostDocument GetDocument(QualifiedModuleName moduleName) => null;
147+
public virtual HostDocument GetDocument(QualifiedModuleName moduleName) => null;
148148

149149
public override bool Equals(ISafeComWrapper<TApplication> other)
150150
{

Rubberduck.VBEEditor/SafeComWrappers/SafeIDispatchWrapper.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@
44

55
namespace Rubberduck.VBEditor.SafeComWrappers
66
{
7+
public class SafeIDispatchWrapper<TDispatch> : SafeIDispatchWrapper
8+
{
9+
public SafeIDispatchWrapper(TDispatch target, bool rewrapping = false) : base(target, rewrapping)
10+
{ }
11+
12+
public new TDispatch Target => (TDispatch) base.Target;
13+
}
14+
715
public class SafeIDispatchWrapper : SafeComWrapper<dynamic>
816
{
917
public SafeIDispatchWrapper(object target, bool rewrapping = false) : base(target, rewrapping)
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
using System;
2+
3+
namespace Rubberduck.VBEditor.SafeComWrappers.Abstract
4+
{
5+
/// <summary>
6+
/// Provides a generic manner of reporting document state for use within Rubberduck
7+
/// </summary>
8+
/// <remarks>
9+
/// Different hosts may have different states and they may behave differently. For example,
10+
/// Excel's Worksheet document has no true design-time state; only time it is locked is when
11+
/// the focus is inside the formula box in the Excel UI. On the other hand, Access has both
12+
/// a design time and a run time state for its Form document and Report document. The enum
13+
/// is meant to provide a generic representation and must correspond exactly to how the host
14+
/// will treat the document. All hosts need not implement the full set of the enum but must
15+
/// represent it exactly as per the description of the enum member.
16+
/// </remarks>
17+
public enum DocumentState
18+
{
19+
/// <summary>
20+
/// The document is not open and its accompanying <see cref="IVBComponent"/> is not available.
21+
/// </summary>
22+
Closed,
23+
/// <summary>
24+
/// The document is open in design mode.
25+
/// </summary>
26+
DesignView,
27+
/// <summary>
28+
/// The document is open in non-design mode. Not all design-time operations are available.
29+
/// </summary>
30+
ActiveView
31+
}
32+
33+
public interface IHostDocument
34+
{
35+
string Name { get; }
36+
string ClassName { get; }
37+
DocumentState State { get; }
38+
bool TryGetTarget(out SafeIDispatchWrapper iDispatch);
39+
}
40+
41+
public class HostDocument : IHostDocument
42+
{
43+
private readonly Func<SafeIDispatchWrapper> _getTargetFunc;
44+
45+
public HostDocument(string name, string className, DocumentState state, Func<SafeIDispatchWrapper> getTargetFunc)
46+
{
47+
Name = name;
48+
ClassName = className;
49+
State = state;
50+
51+
_getTargetFunc = getTargetFunc;
52+
}
53+
54+
public string Name { get; }
55+
public string ClassName { get; }
56+
public DocumentState State { get; }
57+
58+
public bool TryGetTarget(out SafeIDispatchWrapper iDispatch)
59+
{
60+
if (_getTargetFunc == null)
61+
{
62+
iDispatch = null;
63+
return false;
64+
}
65+
66+
try
67+
{
68+
iDispatch = _getTargetFunc.Invoke();
69+
return true;
70+
}
71+
catch
72+
{
73+
iDispatch = null;
74+
return false;
75+
}
76+
}
77+
}
78+
}
Lines changed: 3 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.ComponentModel;
4-
using System.Runtime.InteropServices;
53

64
namespace Rubberduck.VBEditor.SafeComWrappers.Abstract
75
{
@@ -25,70 +23,14 @@ public interface IHostApplication : IDisposable
2523
/// with using those properties when the document is not in a design mode. For
2624
/// that reason, it's better to get the data using host's object model instead.
2725
/// </remarks>
28-
IEnumerable<IHostDocument> GetDocuments();
26+
IEnumerable<HostDocument> GetDocuments();
2927

3028
/// <summary>
3129
/// Gets data for a host-specific document not otherwise exposed via VBIDE API
3230
/// </summary>
3331
/// <param name="moduleName"><see cref="QualifiedModuleName"/> representing a VBComponent object</param>
34-
/// <returns><see cref="IHostDocument"/> data</returns>
32+
/// <returns><see cref="HostDocument"/> data</returns>
3533
/// <inheritdoc cref="GetDocuments"/>
36-
IHostDocument GetDocument(QualifiedModuleName moduleName);
37-
}
38-
39-
public enum DocumentState
40-
{
41-
/// <summary>
42-
/// The document is not open and its accompanying <see cref="IVBComponent"/> may not be available.
43-
/// </summary>
44-
Closed,
45-
/// <summary>
46-
/// The document is open in design mode.
47-
/// </summary>
48-
DesignView,
49-
/// <summary>
50-
/// The document is open in non-design mode. It may not be safe to parse the document in this state.
51-
/// </summary>
52-
ActiveView
53-
}
54-
55-
public interface IHostDocument
56-
{
57-
string Name { get; }
58-
string ClassName { get; }
59-
DocumentState State { get; }
60-
bool TryGetTarget(out SafeIDispatchWrapper iDispatch)
61-
}
62-
63-
public class HostDocument : IHostDocument
64-
{
65-
private readonly Func<SafeIDispatchWrapper> _getTargetFunc;
66-
67-
public HostDocument(string name, string className, DocumentState state, Func<SafeIDispatchWrapper> getTargetFunc)
68-
{
69-
Name = name;
70-
ClassName = className;
71-
State = state;
72-
73-
_getTargetFunc = getTargetFunc;
74-
}
75-
76-
public string Name { get; }
77-
public string ClassName { get; }
78-
public DocumentState State { get; }
79-
80-
public bool TryGetTarget(out SafeIDispatchWrapper iDispatch)
81-
{
82-
try
83-
{
84-
iDispatch = _getTargetFunc.Invoke();
85-
return true;
86-
}
87-
catch
88-
{
89-
iDispatch = null;
90-
return false;
91-
}
92-
}
34+
HostDocument GetDocument(QualifiedModuleName moduleName);
9335
}
9436
}
Lines changed: 27 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
using System;
21
using System.Collections.Generic;
3-
using System.Runtime.InteropServices;
42
using Microsoft.Office.Interop.Access;
53
using Rubberduck.VBEditor.SafeComWrappers.Abstract;
64

@@ -11,117 +9,73 @@ public class AccessApp : HostApplicationBase<Microsoft.Office.Interop.Access.App
119
{
1210
public AccessApp() : base("Access") { }
1311

14-
public override IHostDocument GetDocument(QualifiedModuleName moduleName)
12+
public override HostDocument GetDocument(QualifiedModuleName moduleName)
1513
{
1614
if (moduleName.ComponentName.StartsWith("Form_"))
1715
{
1816
var name = moduleName.ComponentName.Substring("Form_".Length);
19-
_CurrentProject currentProject = null;
20-
AllObjects allForms = null;
21-
AccessObject accessObject = null;
22-
Forms forms = null;
23-
try
24-
{
25-
currentProject = Application.CurrentProject;
26-
forms = Application.Forms;
27-
allForms = currentProject.AllForms;
28-
accessObject = allForms[name];
29-
17+
using (var currentProject = new SafeIDispatchWrapper<_CurrentProject>(Application.CurrentProject))
18+
using (var allForms = new SafeIDispatchWrapper<AllObjects>(currentProject.Target.AllForms))
19+
using (var forms = new SafeIDispatchWrapper<Forms>(Application.Forms))
20+
using (var accessObject = new SafeIDispatchWrapper<AccessObject>(allForms.Target[name]))
21+
{
3022
return LoadHostDocument("Access.Form", accessObject, forms);
3123
}
32-
finally
33-
{
34-
if (forms != null) Marshal.ReleaseComObject(forms);
35-
if (accessObject != null) Marshal.ReleaseComObject(accessObject);
36-
if (allForms != null) Marshal.ReleaseComObject(allForms);
37-
if (currentProject != null) Marshal.ReleaseComObject(currentProject);
38-
}
3924
}
4025

4126
if (moduleName.ComponentName.StartsWith("Report_"))
4227
{
4328
var name = moduleName.ComponentName.Substring("Report_".Length);
44-
_CurrentProject currentProject = null;
45-
AllObjects allForms = null;
46-
AccessObject accessObject = null;
47-
Reports reports = null;
48-
try
29+
using (var currentProject = new SafeIDispatchWrapper<_CurrentProject>(Application.CurrentProject))
30+
using (var allReports = new SafeIDispatchWrapper<AllObjects>(currentProject.Target.AllReports))
31+
using (var reports = new SafeIDispatchWrapper<Reports>(Application.Reports))
32+
using (var accessObject = new SafeIDispatchWrapper<AccessObject>(allReports.Target[name]))
4933
{
50-
currentProject = Application.CurrentProject;
51-
reports = Application.Reports;
52-
allForms = currentProject.AllForms;
53-
accessObject = allForms[name];
54-
5534
return LoadHostDocument("Access.Report", accessObject, reports);
5635
}
57-
finally
58-
{
59-
if (reports != null) Marshal.ReleaseComObject(reports);
60-
if (accessObject != null) Marshal.ReleaseComObject(accessObject);
61-
if (allForms != null) Marshal.ReleaseComObject(allForms);
62-
if (currentProject != null) Marshal.ReleaseComObject(currentProject);
63-
}
6436
}
6537

6638
return null;
6739
}
6840

69-
public override IEnumerable<IHostDocument> GetDocuments()
41+
public override IEnumerable<HostDocument> GetDocuments()
7042
{
7143
var result = new List<HostDocument>();
72-
_CurrentProject currentProject = null;
73-
AllObjects allObjects = null;
74-
Forms forms = null;
75-
Reports reports = null;
76-
77-
try
44+
using (var currentProject = new SafeIDispatchWrapper<_CurrentProject>(Application.CurrentProject))
45+
using (var allForms = new SafeIDispatchWrapper<AllObjects>(currentProject.Target.AllForms))
46+
using (var allReports = new SafeIDispatchWrapper<AllObjects>(currentProject.Target.AllReports))
47+
using (var forms = new SafeIDispatchWrapper<Forms>(Application.Forms))
48+
using (var reports = new SafeIDispatchWrapper<Reports>(Application.Reports))
7849
{
79-
currentProject = Application.CurrentProject;
80-
allObjects = currentProject.AllForms;
81-
forms = Application.Forms;
82-
83-
PopulateList(ref result, "Access.Form", allObjects, forms);
84-
85-
Marshal.ReleaseComObject(allObjects);
86-
87-
allObjects = currentProject.AllReports;
88-
reports = Application.Reports;
89-
90-
PopulateList(ref result, "Access.Report", allObjects, reports);
50+
PopulateList(ref result, "Access.Form", allForms, forms);
51+
PopulateList(ref result, "Access.Report", allReports, reports);
9152
}
92-
finally
93-
{
94-
if (allObjects != null) Marshal.ReleaseComObject(allObjects);
95-
if (forms != null) Marshal.ReleaseComObject(forms);
96-
if (reports != null) Marshal.ReleaseComObject(reports);
97-
if (currentProject != null) Marshal.ReleaseComObject(currentProject);
98-
}
99-
53+
10054
return result;
10155
}
10256

103-
private void PopulateList(ref List<HostDocument> result, string className, AllObjects allObjects, dynamic objects)
57+
private void PopulateList(ref List<HostDocument> result, string className, SafeIDispatchWrapper<AllObjects> allObjects, dynamic objects)
10458
{
105-
foreach (AccessObject accessObject in allObjects)
59+
foreach (AccessObject rawAccessObject in allObjects.Target)
60+
using (var accessObject = new SafeIDispatchWrapper<AccessObject>(rawAccessObject))
10661
{
10762
var item = LoadHostDocument(className, accessObject, objects);
10863
result.Add(item);
10964
}
11065
}
11166

112-
private HostDocument LoadHostDocument(string className, AccessObject accessObject, dynamic objects)
67+
private HostDocument LoadHostDocument(string className, SafeIDispatchWrapper<AccessObject> accessObject, dynamic objects)
11368
{
11469
var state = DocumentState.Closed;
115-
if (!accessObject.IsLoaded)
70+
if (!accessObject.Target.IsLoaded)
11671
{
117-
return new HostDocument(accessObject.Name, className, null, state);
72+
return new HostDocument(accessObject.Target.Name, className, state, null);
11873
}
11974

120-
object target = objects[accessObject.Name];
121-
state = accessObject.CurrentView == AcCurrentView.acCurViewDesign
75+
state = accessObject.Target.CurrentView == AcCurrentView.acCurViewDesign
12276
? DocumentState.DesignView
12377
: DocumentState.ActiveView;
124-
return new HostDocument(accessObject.Name, className, target, state);
78+
return new HostDocument(accessObject.Target.Name, className, state, null);
12579
}
12680
}
12781
}

Rubberduck.VBEditor.VBA/SafeComWrappers/Application/FallbackApp.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ public FallbackApp(IVBE vbe)
1010
{ }
1111

1212
public string ApplicationName => "(unknown)";
13-
public IEnumerable<IHostDocument> GetDocuments() => null;
14-
public IHostDocument GetDocument(QualifiedModuleName moduleName) => null;
13+
public IEnumerable<HostDocument> GetDocuments() => null;
14+
public HostDocument GetDocument(QualifiedModuleName moduleName) => null;
1515
public void Dispose() { }
1616
}
1717
}

0 commit comments

Comments
 (0)