-
Notifications
You must be signed in to change notification settings - Fork 52
Architecture
The IntelliSense service loaded into an Excel process is a shared service that needs to be coordinated between independent add-ins. This page describes the how the IntelliSense library is designed to support correct operation even when multiple independent add-ins can provide IntelliSense services, and might be loaded and unloaded during a the Excel process lifetime.
An add-in that provides registration information for UDF functions to the IntelliSense mechanism is an "IntelliSense Provider". All Excel-DNA add-ins since v 0.32 are IntelliSense Providers through the GetFunctionRegistrationInfo() feature. In future we might make some mechanisms for other IntelliSense Providers, for example:
- a mechanism for regular VBA add-ins
- xml or other mark-up files that describe the extra function info, so that any add-in (non Excel-DNA) can use the IntelliSense feature.
We'll need to decide how IntelliSense Providers can provide enhanced information beyond the current function and argument descriptions, e.g. enum lists or hyperlinks, but details of that can come later.
The mechanism that IntelliSense providers use to 'publish' the information should allow the information to be queried at any time (the information should be pulled from the IntelliSense Provider, rather than pushed into the IntelliSense Server).
An add-in that is able to perform the IntelliSense display is an "IntelliSense Server". An add-in becomes an IntelliSense Server by referencing ExcelDna.IntelliSense.dll, and calling ExcelDna.IntelliSense.IntelliSenseServer.Register() from the add-in's IExcelAddIn.AutoOpen. The ExcelDna.IntelliSense.dll may be packed with as usual.
An Excel process can have only one IntelliSense Server as the "Active" IntelliSense Server. This is the add-in that is actively hooked into the UI Automation, popping up the windows etc. The mechanism for registration and activation described below, ensures that the Active IntelliSense Server is always the one with the newest version.
Likewise there is a deactivation process, so that if an add-in that is the Active IntelliSense Server is unloaded, there is a way (some arbitrary choice) to Activate one of the latest-version IntelliSense Servers among those loaded.
So suppose an IntelliSense Server add-in loads and becomes the Active one, then later an add-in with a newer version IntelliSense server is loaded. This add-in must now tell the Active IntelliSense Server to deactivate, and itself then become the Active Server.
-
We should be able to cleanly unload an IntelliSense Server add-in, whether it is the Active one or not. This means that a call to ExcelIntegration.UnregisterXLL(...) should succeed in unloading the .xll and assemblies completely (as long as COM features are not present - I think those might cause some problems unrelated to the IntelliSense).
-
Every IntelliSense Server that is loaded should register itself (if it is not a Disabled version - see below), and should unregister itself when unloaded. Registration details are described below.
-
If an IntelliSense Server is the first one loaded into the process, it should be become Active.
-
If an IntelliSense Server is not the first one loaded, and does not have a version higher than the Active Server, it should not become Active.
-
If an IntelliSense Server is not the first one loaded, and has a version higher than the Active Server it should deactivate the Active Server (how?) and become the Active Server itself.
-
If an IntelliSense Server that is not Active is unloaded (receives the AppDomain.DomainUnload event), it should unregister itself.
-
If an IntelliSense Server that is Active is unloaded (receives the AppDomain.DomainUnload event), it should deactivate itself, then activate one of the IntelliSense servers that are registered with the highest version.
-
One problematic scenario is when a sequence of IntelliSense Server add-ins are loaded, where they have increasing versions. Each server will become Active, and then be deactivated by the next one that loads. To deal with this scenario, we need to ensure that activation is light-weight and fast, and that the significant work of installing the UI Automation handlers and gathering the IntelliSense Provider information is delayed a bit. Activation might have to be a two-phase process, where a server is first activated (becomes the Active server) and then does internal processing after a dealy, and from a separate Thread (calling back via ExcelAsyncUtil.QueueAsMacro to get the registration info if need be). A quantifiable goal might be that enabling the IntelliSense loading for a sequence of 10 IntelliSense Servers should not add more than a second to the load time (in a 'normal' environment).
Registration is the discovery mechanism for the independent add-ins to coordinate the loading. This needs to be some process-wide structure, with a well-known or discoverable location.
- We use an environment variable for this purpose. (Some alternatives to consider: An Excel Name via xlfSetName would be nice, but there might be length restriction..., COM Monikers, COM RegisterClassXXX, File or Registry.)
- So we might have:
EXCELDNA_INTELLISENSE_SERVERS=C:\...\MyFirstAddIn.xll,3b4c7d9xxxxxxxx,1.2.3;AnotherAddIn.xll,xxxxxxxx,1.1.3
where the parts are ;-delimited, and each part is an .xll path, a unique per-.xll Guid and an IntelliSense Server version. - We assume the registration check will run on the main Excel thread, so there is no synchronization required when updating the variable.
- We keep track of the Active server in a variable:
EXCELDNA_INTELLISENSE_ACTIVE_SERVER=C:\...\MyFirstAddIn.xll,2b4c76dxxxxxxx,1.2.3
- Each IntelliSense server has a unique ServerId (per session, not necessarily the same as the stable .xll Guid used elsewhere).
- Each IntelliSense server register with Excel an ExcelCommmand called IntelliSenseControl_xxxxxxxx, with object->object signature.
- Calls between servers are via Excel the IntelliSenseControl macro.
- IntelliSense Provider details are not handed over during Activation / Deactivation - the server must discover and load all the provider information after activation (not during the activation call).
- We need the following:
-
IntelliSenseServer.Register()
- Called directly (not via reflection) from AutoOpen. -
IntelliSenseServer.Activate()
- Called internally from the Register() call, or via the IntelliSenseControl macro from another server. -
IntelliSenseServer.Deactivate()
- Called internally from the AppDomain_DomainUnload event handler, or via the IntelliSenseControl macro from another server when that server figures out that it must become the active server itself.
-
We need to have a master-switch (a registry entry or something) that completely disables IntelliSense. It must be easy (or at least possible) to use add-ins that are IntelliSense Servers without having the IntelliSense part operating. Otherwise, an add-in that is an IntelliSense Server is useless if the IntelliSense doesn't work well on a particular environment. I suggest the following:
- Versions numbers are dotted integer strings, e.g. "1.2.3". An invalid version number string is treated as "0".
- A valid "IntelliSense Disable String" is either the "" wildcars, or a (possibly empty) comma-separated list of entries that are either a version number or a version number with a "." wildcard appended. Example of valid disable strings are "", "", "1.2", "1.2,3.", ",,1.2.*". An invalid disable string matches no versions.
- When activated (loading?), an IntelliSense Server will read determine the current IntelliSense Disable String by reading machine and user keys, and an environment variable, and joining those together. We check:
- HKLM\Software\ExcelDna\IntelliSense\DisabledVersions,
- HKCU\Software\ExcelDna\IntelliSense\DisabledVersions, and
- An environment variable called EXCELDNA_INTELLISENSE_DISABLEDVERSIONS.
- When an IntelliSense Server is registered, it compares its own version to the versions in the Disable String, and if it matches any (respecting wildcards) then it will not load.
- CONSIDER: Do we need a way to disable the IntelliSense Server in a particular add-in? (This might be handy if a rogue add-in changes the IntelliSense in a breaking way, but does not update the version). I think not - having the wildcard disable should be good enough to at least keep the rogue add-in running and suppress the problematic IntelliSense. An evil add-in might skip all the checks, but is then not under our cooperating umbrella anymore.