Skip to content

Commit 4f3efa4

Browse files
authored
Merge pull request #3798 from bclothier/EventInterface
Provide a method of pumping COM objects in a synchronous block (no user-facing changes)
2 parents 5b0c6c7 + 50db505 commit 4f3efa4

File tree

9 files changed

+192
-2
lines changed

9 files changed

+192
-2
lines changed

RetailCoder.VBE/API/VBA/ParserState.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ public sealed class ParserState : IParserState, IDisposable
5656
public ParserState()
5757
{
5858
UiDispatcher.Initialize();
59+
ComMessagePumper.Initialize();
5960
}
6061

6162
public void Initialize(Microsoft.Vbe.Interop.VBE vbe)

RetailCoder.VBE/App.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using Rubberduck.Parsing.Inspections.Resources;
1414
using Rubberduck.Parsing.UIContext;
1515
using Rubberduck.UI.Command;
16+
using Rubberduck.VBEditor.ComManagement;
1617
using Rubberduck.VBEditor.SafeComWrappers.Abstract;
1718
using Rubberduck.VersionCheck;
1819
using Application = System.Windows.Forms.Application;
@@ -50,6 +51,7 @@ public App(IVBE vbe,
5051
_configService.SettingsChanged += _configService_SettingsChanged;
5152

5253
UiDispatcher.Initialize();
54+
ComMessagePumper.Initialize();
5355
}
5456

5557
private void _configService_SettingsChanged(object sender, ConfigurationChangedEventArgs e)

Rubberduck.Parsing/UIContext/UiDispatcher.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,7 @@ public static Task<T> StartTask<T>(Func<T> func, TaskCreationOptions options = T
9898
{
9999
return StartTask(func, CancellationToken.None, options);
100100
}
101-
102-
101+
103102
private static void CheckInitialization()
104103
{
105104
if (UiContext == null) throw new InvalidOperationException("UiDispatcher is not initialized. Invoke Initialize() from UI thread first.");
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using System;
2+
using System.Threading;
3+
using Rubberduck.VBEditor.ComManagement.VBERuntime;
4+
5+
namespace Rubberduck.VBEditor.ComManagement
6+
{
7+
public static class ComMessagePumper
8+
{
9+
private static SynchronizationContext UiContext { get; set; }
10+
private static readonly Lazy<IVBERuntime> Runtime = new Lazy<IVBERuntime>(() => new VBERuntimeAccessor());
11+
12+
public static void Initialize()
13+
{
14+
if (UiContext == null)
15+
{
16+
UiContext = SynchronizationContext.Current;
17+
}
18+
}
19+
20+
/// <summary>
21+
/// Used to pump any pending COM messages. This should be used only as a part of
22+
/// synchronizing or to effect a block until all other threads has finished with
23+
/// their pending COM calls. This should be used by the UI thread **ONLY**;
24+
/// otherwise execptions will be thrown. This is mandatory when the COM objects are
25+
/// STA which would otherwise deadlock in other threads.
26+
/// </summary>
27+
/// <remarks>
28+
/// Typical use would be within a event handler for an event belonging to a COM
29+
/// object which require some synchronization with COM accesses from other threads.
30+
/// Events raised by COM are on UI thread by definition so the call stack originating
31+
/// from COM objects' events can use this method.
32+
/// </remarks>
33+
/// <returns>Count of open forms which is always zero for VBA hosts but may be nonzero for VB6 projects.</returns>
34+
public static int PumpMessages()
35+
{
36+
CheckContext();
37+
38+
return Runtime.Value.DoEvents();
39+
}
40+
41+
private static void CheckContext()
42+
{
43+
if (UiContext == null)
44+
{
45+
throw new InvalidOperationException("UiSynchronizer is not initialized. Invoke Initialize() from UI thread first.");
46+
}
47+
48+
if (UiContext != SynchronizationContext.Current)
49+
{
50+
throw new InvalidOperationException("UiSynchronizer cannot be used in other threads. Only the UI thread can call methods on the UiSynchronizer");
51+
}
52+
}
53+
}
54+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace Rubberduck.VBEditor.ComManagement.VBERuntime
2+
{
3+
internal interface IVBERuntime
4+
{
5+
float Timer();
6+
int DoEvents();
7+
}
8+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System.Runtime.InteropServices;
2+
3+
namespace Rubberduck.VBEditor.ComManagement.VBERuntime
4+
{
5+
internal class VBERuntime6 : IVBERuntime
6+
{
7+
private const string DllName = "vbe6.dll";
8+
9+
[DllImport(DllName)]
10+
private static extern int rtcDoEvents();
11+
public int DoEvents()
12+
{
13+
return rtcDoEvents();
14+
}
15+
16+
[DllImport(DllName)]
17+
private static extern float rtcGetTimer();
18+
public float Timer()
19+
{
20+
return rtcGetTimer();
21+
}
22+
}
23+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System.Runtime.InteropServices;
2+
3+
namespace Rubberduck.VBEditor.ComManagement.VBERuntime
4+
{
5+
internal class VBERuntime7 : IVBERuntime
6+
{
7+
private const string DllName = "vbe7.dll";
8+
9+
[DllImport(DllName)]
10+
private static extern int rtcDoEvents();
11+
public int DoEvents()
12+
{
13+
return rtcDoEvents();
14+
}
15+
16+
[DllImport(DllName)]
17+
private static extern float rtcGetTimer();
18+
public float Timer()
19+
{
20+
return rtcGetTimer();
21+
}
22+
}
23+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
using System;
2+
3+
namespace Rubberduck.VBEditor.ComManagement.VBERuntime
4+
{
5+
public class VBERuntimeAccessor : IVBERuntime
6+
{
7+
private enum DllVersion
8+
{
9+
Unknown,
10+
Vbe6,
11+
Vbe7
12+
}
13+
14+
private static DllVersion _version;
15+
private readonly IVBERuntime _runtime;
16+
17+
static VBERuntimeAccessor()
18+
{
19+
_version = DllVersion.Unknown;
20+
}
21+
22+
public VBERuntimeAccessor()
23+
{
24+
switch (_version)
25+
{
26+
case DllVersion.Vbe7:
27+
_runtime = new VBERuntime7();
28+
break;
29+
case DllVersion.Vbe6:
30+
_runtime = new VBERuntime6();
31+
break;
32+
default:
33+
_runtime = DetermineVersion();
34+
break;
35+
}
36+
}
37+
38+
private static IVBERuntime DetermineVersion()
39+
{
40+
IVBERuntime runtime;
41+
try
42+
{
43+
runtime = new VBERuntime7();
44+
runtime.Timer();
45+
_version = DllVersion.Vbe7;
46+
}
47+
catch
48+
{
49+
try
50+
{
51+
runtime = new VBERuntime6();
52+
runtime.Timer();
53+
_version = DllVersion.Vbe6;
54+
}
55+
catch
56+
{
57+
// we shouldn't be here.... Rubberduck is a VBA add-in, so how the heck could it have loaded without a VBE dll?!?
58+
throw new InvalidOperationException("Cannot execute DoEvents; the VBE dll could not be located.");
59+
}
60+
}
61+
62+
return _version != DllVersion.Unknown ? runtime : null;
63+
}
64+
65+
public float Timer()
66+
{
67+
return _runtime.Timer();
68+
}
69+
70+
public int DoEvents()
71+
{
72+
return _runtime.DoEvents();
73+
}
74+
}
75+
}

Rubberduck.VBEEditor/Rubberduck.VBEditor.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,12 @@
129129
<Reference Include="System.Windows.Forms" />
130130
</ItemGroup>
131131
<ItemGroup>
132+
<Compile Include="ComManagement\ComMessagePumper.cs" />
132133
<Compile Include="ComManagement\TypeLibs\IVBETypeLibsAPI.cs" />
134+
<Compile Include="ComManagement\VBERuntime\IVBERuntime.cs" />
135+
<Compile Include="ComManagement\VBERuntime\VBERuntime6.cs" />
136+
<Compile Include="ComManagement\VBERuntime\VBERuntime7.cs" />
137+
<Compile Include="ComManagement\VBERuntime\VBERuntimeAccessor.cs" />
133138
<Compile Include="ComManagement\WeakComSafe.cs" />
134139
<Compile Include="ComManagement\ComSafeManager.cs" />
135140
<Compile Include="ComManagement\IProjectsProvider.cs" />

0 commit comments

Comments
 (0)