Skip to content

Commit 9fd24bb

Browse files
committed
Registration update from loader notification
1 parent 323d816 commit 9fd24bb

File tree

4 files changed

+197
-46
lines changed

4 files changed

+197
-46
lines changed

Source/ExcelDna.IntelliSense/IntelliSenseDisplay.cs

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,13 @@
22
using System.Collections.Generic;
33
using System.Diagnostics;
44
using System.Linq;
5-
using System.Text.RegularExpressions;
65
using System.Threading;
76
using System.Windows;
8-
using System.Windows.Forms;
97

108
namespace ExcelDna.IntelliSense
119
{
12-
[Serializable]
1310
class IntelliSenseFunctionInfo
1411
{
15-
[Serializable]
1612
public class ArgumentInfo
1713
{
1814
public string ArgumentName;
@@ -59,6 +55,15 @@ public IntelliSenseDisplay(SynchronizationContext syncContextMain, UIMonitor uiM
5955
_uiMonitor.StateUpdate += StateUpdate;
6056
}
6157

58+
// TODO: Still not sure how to delete / unregister...
59+
internal void UpdateFunctionInfos(IEnumerable<IntelliSenseFunctionInfo> functionInfos)
60+
{
61+
foreach (var fi in functionInfos)
62+
{
63+
RegisterFunctionInfo(fi);
64+
}
65+
}
66+
6267
#region Update Preview Filtering
6368
// This runs on the UIMonitor's automation thread
6469
// Allows us to enable the update to be raised on main thread
@@ -411,11 +416,26 @@ public void Dispose()
411416
_syncContextMain = null;
412417
}
413418

419+
// TODO: Think about case again
420+
// TODO: Consider locking...
414421
public void RegisterFunctionInfo(IntelliSenseFunctionInfo functionInfo)
415422
{
416423
// TODO : Dictionary from KeyLookup
417-
_functionInfoMap.Add(functionInfo.FunctionName, functionInfo);
424+
IntelliSenseFunctionInfo oldFunctionInfo;
425+
if (!_functionInfoMap.TryGetValue(functionInfo.FunctionName, out oldFunctionInfo))
426+
{
427+
_functionInfoMap.Add(functionInfo.FunctionName, functionInfo);
428+
}
429+
else
430+
{
431+
// Update against the function name
432+
_functionInfoMap[functionInfo.FunctionName] = functionInfo;
433+
}
434+
}
435+
436+
public void UnregisterFunctionInfo(IntelliSenseFunctionInfo functionInfo)
437+
{
438+
_functionInfoMap.Remove(functionInfo.FunctionName);
418439
}
419-
// TODO: How to UnregisterFunctionInfo ...?
420440
}
421441
}

Source/ExcelDna.IntelliSense/IntelliSenseProvider.cs

Lines changed: 121 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Diagnostics;
44
using System.IO;
55
using System.Linq;
6+
using System.Threading;
67
using ExcelDna.Integration;
78

89
namespace ExcelDna.IntelliSense
@@ -29,11 +30,16 @@ namespace ExcelDna.IntelliSense
2930

3031
// We expect the server to hook some Excel events to provide the entry points... (not sure what this means anymore...?)
3132

32-
// Consider interaction with Application.MacroOptions. (or not?)
33+
// TODO: Consider interaction with Application.MacroOptions. (or not?)
3334

34-
interface IIntelliSenseProvider
35+
// TODO: We might relax the threading rules, to say that Refresh runs on the same thread as Invalidate
36+
// TODO: We might get rid of Refresh (since that runs in the Invalidate context)
37+
interface IIntelliSenseProvider : IDisposable
3538
{
36-
void Refresh(); // Executed in a macro context, on the main Excel thread
39+
void Initialize(); // Executed in a macro context, on the main Excel thread
40+
void Refresh(); // Executed in a macro context, on the main Excel thread
41+
event EventHandler Invalidate; // Must be raised in a macro context, on the main thread
42+
3743
IEnumerable<IntelliSenseFunctionInfo> GetFunctionInfos(); // Called from a worker thread - no Excel or COM access (probably an MTA thread involved in the UI Automation)
3844
}
3945

@@ -46,21 +52,13 @@ class XllRegistrationInfo
4652
bool _regInfoNotAvailable = false; // Set to true if we know for sure that reginfo is #N/A
4753
double _version = -1; // Version indicator to enumerate from scratch
4854
object[,] _regInfo = null; // Default value
49-
LoaderNotification _dllLoadNotification;
5055

5156
public XllRegistrationInfo(string xllPath)
5257
{
5358
_xllPath = xllPath;
54-
_dllLoadNotification = new LoaderNotification();
55-
_dllLoadNotification.LoadNotification += _dllLoadNotification_LoadNotification;
56-
}
57-
58-
private void _dllLoadNotification_LoadNotification(object sender, LoaderNotification.NotificationEventArgs e)
59-
{
60-
Debug.Print($"@>@>@>@> LoadNotification: {e.Reason} - {e.FullDllName}");
6159
}
6260

63-
// Called in a macro context
61+
// Called on the main thread in a macro context
6462
public void Refresh()
6563
{
6664
if (_regInfoNotAvailable)
@@ -74,7 +72,7 @@ public void Refresh()
7472
return;
7573
}
7674

77-
if (regInfoResponse == null)
75+
if (regInfoResponse == null || regInfoResponse.Equals(ExcelError.ExcelErrorNum))
7876
{
7977
// no update - versions match
8078
return;
@@ -133,33 +131,108 @@ public IEnumerable<IntelliSenseFunctionInfo> GetFunctionInfos()
133131
}
134132
}
135133

134+
ExcelSynchronizationContext _syncContextExcel;
136135
Dictionary<string, XllRegistrationInfo> _xllRegistrationInfos = new Dictionary<string, XllRegistrationInfo>();
136+
LoaderNotification _loaderNotification;
137+
public event EventHandler Invalidate;
137138

138-
public void Refresh()
139+
public ExcelDnaIntelliSenseProvider()
139140
{
140-
Logger.Provider.Info("ExcelDnaIntelliSenseProvider.Refresh");
141-
foreach (var xllPath in GetLoadedXllPaths())
141+
var threadRefresh = new Thread(RunRefreshWatch);
142+
threadRefresh.Start();
143+
}
144+
145+
// DANGER: Still subject to LoaderLock problem...
146+
void loaderNotification_LoadNotification(object sender, LoaderNotification.NotificationEventArgs e)
147+
{
148+
Debug.Print($"@>@>@>@> LoadNotification: {e.Reason} - {e.FullDllName}");
149+
if (e.FullDllName.EndsWith(".xll", StringComparison.OrdinalIgnoreCase))
150+
_syncContextExcel.Post(ProcessLoadNotification, e);
151+
}
152+
153+
// Runs on the main thread, in a macro context
154+
void ProcessLoadNotification(object state)
155+
{
156+
Debug.Assert(Thread.CurrentThread.ManagedThreadId == 1);
157+
// we might want to introduce a delay here, so that the .xll can complete loading...
158+
var notification = (LoaderNotification.NotificationEventArgs)state;;
159+
var xllPath = notification.FullDllName;
160+
lock (_xllRegistrationInfos)
142161
{
143162
XllRegistrationInfo regInfo;
144163
if (!_xllRegistrationInfos.TryGetValue(xllPath, out regInfo))
145164
{
146-
regInfo = new XllRegistrationInfo(xllPath);
147-
_xllRegistrationInfos[xllPath] = regInfo;
165+
if (notification.Reason == LoaderNotification.Reason.Loaded)
166+
{
167+
regInfo = new XllRegistrationInfo(xllPath);
168+
_xllRegistrationInfos[xllPath] = regInfo;
169+
regInfo.Refresh();
170+
OnInvalidate();
171+
}
172+
}
173+
else if (notification.Reason == LoaderNotification.Reason.Unloaded)
174+
{
175+
_xllRegistrationInfos.Remove(xllPath);
176+
}
177+
}
178+
}
179+
180+
void RunRefreshWatch()
181+
{
182+
_loaderNotification = new LoaderNotification();
183+
_loaderNotification.LoadNotification += loaderNotification_LoadNotification;
184+
_syncContextExcel = new ExcelSynchronizationContext();
185+
}
186+
187+
// Must be called on the main Excel thread
188+
public void Initialize()
189+
{
190+
Debug.Assert(Thread.CurrentThread.ManagedThreadId == 1);
191+
Logger.Provider.Info("ExcelDnaIntelliSenseProvider.Initialize");
192+
lock (_xllRegistrationInfos)
193+
{
194+
foreach (var xllPath in GetLoadedXllPaths())
195+
{
196+
XllRegistrationInfo regInfo;
197+
if (!_xllRegistrationInfos.TryGetValue(xllPath, out regInfo))
198+
{
199+
regInfo = new XllRegistrationInfo(xllPath);
200+
_xllRegistrationInfos[xllPath] = regInfo;
201+
regInfo.Refresh();
202+
}
148203
}
149-
regInfo.Refresh();
150204
}
151205
}
152206

207+
// Must be called on the main Excel thread
208+
public void Refresh()
209+
{
210+
Debug.Assert(Thread.CurrentThread.ManagedThreadId == 1);
211+
Logger.Provider.Info("ExcelDnaIntelliSenseProvider.Refresh");
212+
lock (_xllRegistrationInfos)
213+
{
214+
foreach (var regInfo in _xllRegistrationInfos.Values)
215+
{
216+
regInfo.Refresh();
217+
}
218+
}
219+
}
220+
221+
// May be called from any thread
153222
public IEnumerable<IntelliSenseFunctionInfo> GetFunctionInfos()
154223
{
155-
return _xllRegistrationInfos.Values.SelectMany(ri => ri.GetFunctionInfos());
224+
lock (_xllRegistrationInfos)
225+
{
226+
return _xllRegistrationInfos.Values.SelectMany(ri => ri.GetFunctionInfos()).ToList();
227+
}
156228
}
157229

158230
// Called in macro context
159231
// Might be implemented by COM AddIns check, or checking loaded Modules with Win32
160232
// Application.AddIns2 also lists add-ins interactively loaded (Excel 2010+) and has IsOpen property.
161233
// See: http://blogs.office.com/2010/02/16/migrating-excel-4-macros-to-vba/
162234
// Alternative on old Excel is DOCUMENTS(2) which lists all loaded .xlm (also .xll?)
235+
// Alternative, more in line with our update watch, is to enumerate all loaded modules...
163236
IEnumerable<string> GetLoadedXllPaths()
164237
{
165238
// TODO: Implement properly...
@@ -172,6 +245,16 @@ IEnumerable<string> GetLoadedXllPaths()
172245
}
173246
}
174247
}
248+
249+
void OnInvalidate()
250+
{
251+
Invalidate?.Invoke(this, EventArgs.Empty);
252+
}
253+
254+
public void Dispose()
255+
{
256+
_loaderNotification.Dispose();
257+
}
175258
}
176259

177260
// For VBA code, (either in a regular workbook that is open, or in an add-in)
@@ -180,6 +263,8 @@ IEnumerable<string> GetLoadedXllPaths()
180263
// Initially we won't scope the IntelliSense to the Workbook where the UDFs are defined,
181264
// but we should consider that.
182265

266+
// TODO: Can't we read the Application.MacroOptions...?
267+
183268
class WorkbookIntelliSenseProvider : IIntelliSenseProvider
184269
{
185270
const string intelliSenseWorksheetName = "_IntelliSense_FunctionInfo_";
@@ -263,9 +348,14 @@ public IEnumerable<IntelliSenseFunctionInfo> GetFunctionInfos()
263348

264349
Dictionary<string, WorkbookRegistrationInfo> _workbookRegistrationInfos = new Dictionary<string, WorkbookRegistrationInfo>();
265350

266-
public void Refresh()
351+
public event EventHandler Invalidate;
352+
353+
public void Initialize()
267354
{
268-
Logger.Provider.Info("WorkbookIntelliSenseProvider.Refresh");
355+
Logger.Provider.Info("WorkbookIntelliSenseProvider.Initialize");
356+
357+
//var app = ExcelDnaUtil.Application;
358+
// app.WorkbookLoaded...
269359
foreach (var name in GetLoadedWorkbookNames())
270360
{
271361
WorkbookRegistrationInfo regInfo;
@@ -278,6 +368,11 @@ public void Refresh()
278368
}
279369
}
280370

371+
public void Refresh()
372+
{
373+
Logger.Provider.Info("WorkbookIntelliSenseProvider.Refresh");
374+
}
375+
281376
public IEnumerable<IntelliSenseFunctionInfo> GetFunctionInfos()
282377
{
283378
return _workbookRegistrationInfos.Values.SelectMany(ri => ri.GetFunctionInfos());
@@ -295,6 +390,10 @@ IEnumerable<string> GetLoadedWorkbookNames()
295390
yield return wb.Name;
296391
}
297392
}
393+
394+
public void Dispose()
395+
{
396+
}
298397
}
299398

300399
// The idea is that other add-in tools like XLW or XLL+ could provide IntelliSense info with an xml file.

0 commit comments

Comments
 (0)