33using System . Diagnostics ;
44using System . IO ;
55using System . Linq ;
6+ using System . Threading ;
67using ExcelDna . Integration ;
78
89namespace 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