Skip to content

Commit 4e4445d

Browse files
committed
Refactor VBE function calls into dedicated class.
Move VBERuntime classes to VBEEditor project. Create dedicated ComMessagePumper class for pumping windows messages to handle COM objects accordingly.
1 parent 8667769 commit 4e4445d

File tree

6 files changed

+235
-74
lines changed

6 files changed

+235
-74
lines changed
Lines changed: 50 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,111 +1,87 @@
11
using System;
2-
using System.Runtime.InteropServices;
32
using System.Threading;
3+
using Rubberduck.VBEditor.ComManagement;
44

55
namespace Rubberduck.Parsing.UIContext
66
{
77
public static class UiSynchronizer
88
{
9-
private enum DllVersion
10-
{
11-
Unknown,
12-
Vbe6,
13-
Vbe7
14-
}
15-
16-
private static DllVersion _version;
17-
18-
private static SynchronizationContext UiContext { get; set; }
9+
private static readonly ReaderWriterLockSlim Sync;
10+
private const int NoTimeout = -1;
11+
private const int DefaultTimeout = NoTimeout;
1912

2013
static UiSynchronizer()
2114
{
22-
_version = DllVersion.Unknown;
15+
Sync = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
2316
}
24-
25-
public static void Initialize()
17+
18+
public static bool RequestComAccess(Func<bool> func, int timeout = DefaultTimeout)
2619
{
27-
if (UiContext == null)
20+
if (!Sync.TryEnterReadLock(timeout))
2821
{
29-
UiContext = SynchronizationContext.Current;
22+
throw new TimeoutException("Timeout exceeded while waiting to acquire a read lock");
3023
}
31-
}
3224

33-
/// <summary>
34-
/// Used to pump any pending COM messages. This should be used only as a part of
35-
/// synchronizing or to effect a block until all other threads has finished with
36-
/// their pending COM calls. This should be used by the UI thread **ONLY**;
37-
/// otherwise execptions will be thrown.
38-
/// </summary>
39-
/// <remarks>
40-
/// Typical use would be within a event handler for an event belonging to a COM
41-
/// object which require some synchronization with COM accesses from other threads.
42-
/// Events raised by COM are on UI thread by definition so the call stack originating
43-
/// from COM objects' events can use this method.
44-
/// </remarks>
45-
/// <returns>Count of open forms which is always zero for VBA hosts but may be nonzero for VB6 projects.</returns>
46-
public static int DoEvents()
47-
{
48-
CheckContext();
49-
50-
return ExecuteDoEvents();
25+
var result = func.Invoke();
26+
Sync.ExitReadLock();
27+
return result;
5128
}
5229

53-
private static int ExecuteDoEvents()
30+
public static void RequireExclusiveComAccess(Action func, int timeout = DefaultTimeout)
5431
{
55-
switch (_version)
32+
for (var i = 0; i < timeout || timeout == NoTimeout; i++)
5633
{
57-
case DllVersion.Vbe7:
58-
return rtcDoEvents7();
59-
case DllVersion.Vbe6:
60-
return rtcDoEvents6();
61-
default:
62-
return DetermineVersionAndExecute();
63-
}
64-
}
34+
ComMessagePumper.PumpMessages();
6535

66-
private static int DetermineVersionAndExecute()
67-
{
68-
int result;
69-
try
36+
if (Sync.TryEnterWriteLock(1))
37+
{
38+
break;
39+
}
40+
}
7041

42+
if (!Sync.IsWriteLockHeld)
7143
{
72-
result = rtcDoEvents7();
73-
_version = DllVersion.Vbe7;
44+
throw new TimeoutException("Timeout exceeded while waiting to acquire a write lock");
7445
}
75-
catch
46+
47+
func.Invoke();
48+
Sync.ExitWriteLock();
49+
}
50+
51+
public static bool RequireExclusiveComAccess(Func<bool> func, int timeout = DefaultTimeout, bool DeferToParse = true)
52+
{
53+
for (var i = 0; i < timeout || timeout == NoTimeout; i++)
7654
{
77-
try
78-
{
79-
result = rtcDoEvents6();
80-
_version = DllVersion.Vbe6;
81-
}
82-
catch
55+
ComMessagePumper.PumpMessages();
56+
57+
if (Sync.TryEnterWriteLock(1))
8358
{
84-
// we shouldn't be here.... Rubberduck is a VBA add-in, so how the heck could it have loaded without a VBE dll?!?
85-
throw new InvalidOperationException("Cannot execute DoEvents; the VBE dll could not be located.");
59+
break;
8660
}
8761
}
8862

89-
return result;
90-
}
91-
92-
private static void CheckContext()
93-
{
94-
if (UiContext == null)
63+
if (!Sync.IsWriteLockHeld)
9564
{
96-
throw new InvalidOperationException("UiSynchronizer is not initialized. Invoke Initialize() from UI thread first.");
65+
throw new TimeoutException("Timeout exceeded while waiting to acquire a write lock");
9766
}
98-
99-
if (UiContext != SynchronizationContext.Current)
67+
68+
var result = func.Invoke();
69+
if (!DeferToParse)
10070
{
101-
throw new InvalidOperationException("UiSynchronizer cannot be used in other threads. Only the UI thread can call methods on the UiSynchronizer");
71+
Sync.ExitWriteLock();
10272
}
73+
return result;
10374
}
10475

105-
[DllImport("vbe6.dll", EntryPoint = "rtcDoEvents")]
106-
private static extern int rtcDoEvents6();
76+
public static bool ReleaseExclusiveComAccess()
77+
{
78+
if (Sync.IsWriteLockHeld)
79+
{
80+
Sync.ExitWriteLock();
81+
return true;
82+
}
10783

108-
[DllImport("vbe7.dll", EntryPoint = "rtcDoEvents")]
109-
private static extern int rtcDoEvents7();
84+
return false;
85+
}
11086
}
11187
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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 IVBERuntime Runtime { get; set; }
11+
12+
public static void Initialize()
13+
{
14+
if (UiContext == null)
15+
{
16+
UiContext = SynchronizationContext.Current;
17+
}
18+
19+
Runtime = new VBERuntimeAccessor();
20+
}
21+
22+
/// <summary>
23+
/// Used to pump any pending COM messages. This should be used only as a part of
24+
/// synchronizing or to effect a block until all other threads has finished with
25+
/// their pending COM calls. This should be used by the UI thread **ONLY**;
26+
/// otherwise execptions will be thrown. This is mandatory when the COM objects are
27+
/// STA which would otherwise deadlock in other threads.
28+
/// </summary>
29+
/// <remarks>
30+
/// Typical use would be within a event handler for an event belonging to a COM
31+
/// object which require some synchronization with COM accesses from other threads.
32+
/// Events raised by COM are on UI thread by definition so the call stack originating
33+
/// from COM objects' events can use this method.
34+
/// </remarks>
35+
/// <returns>Count of open forms which is always zero for VBA hosts but may be nonzero for VB6 projects.</returns>
36+
public static int PumpMessages()
37+
{
38+
CheckContext();
39+
40+
return Runtime.DoEvents();
41+
}
42+
43+
private static void CheckContext()
44+
{
45+
if (UiContext == null)
46+
{
47+
throw new InvalidOperationException("UiSynchronizer is not initialized. Invoke Initialize() from UI thread first.");
48+
}
49+
50+
if (UiContext != SynchronizationContext.Current)
51+
{
52+
throw new InvalidOperationException("UiSynchronizer cannot be used in other threads. Only the UI thread can call methods on the UiSynchronizer");
53+
}
54+
}
55+
}
56+
}
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 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+
}

0 commit comments

Comments
 (0)