Skip to content

Commit 5390fa3

Browse files
committed
Fix server registration and versioning
1 parent 56d5d10 commit 5390fa3

File tree

9 files changed

+221
-47
lines changed

9 files changed

+221
-47
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<DnaLibrary Name="ExcelDna.IntelliSense.Tools Add-In" RuntimeVersion="v4.0">
2+
<ExternalLibrary Path="ExcelDna.IntelliSense.Tools.dll" LoadFromBytes="true" Pack="true" />
3+
4+
<!--
5+
The RuntimeVersion attribute above allows two settings:
6+
* RuntimeVersion="v2.0" - for .NET 2.0, 3.0 and 3.5
7+
* RuntimeVersion="v4.0" - for .NET 4 and 4.5
8+
9+
Additional referenced assemblies can be specified by adding 'Reference' tags.
10+
These libraries will not be examined and registered with Excel as add-in libraries,
11+
but will be packed into the -packed.xll file and loaded at runtime as needed.
12+
For example:
13+
14+
<Reference Path="Another.Library.dll" Pack="true" />
15+
16+
Excel-DNA also allows the xml for ribbon UI extensions to be specified in the .dna file.
17+
See the main Excel-DNA site at http://excel-dna.net for downloads of the full distribution.
18+
-->
19+
20+
</DnaLibrary>
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
4+
<PropertyGroup>
5+
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
6+
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
7+
<ProjectGuid>{136CAA79-037B-476F-B1A5-0E2A3BEC4BEE}</ProjectGuid>
8+
<OutputType>Library</OutputType>
9+
<AppDesignerFolder>Properties</AppDesignerFolder>
10+
<RootNamespace>ExcelDna.IntelliSense.Tools</RootNamespace>
11+
<AssemblyName>ExcelDna.IntelliSense.Tools</AssemblyName>
12+
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
13+
<FileAlignment>512</FileAlignment>
14+
</PropertyGroup>
15+
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
16+
<DebugSymbols>true</DebugSymbols>
17+
<DebugType>full</DebugType>
18+
<Optimize>false</Optimize>
19+
<OutputPath>bin\Debug\</OutputPath>
20+
<DefineConstants>DEBUG;TRACE</DefineConstants>
21+
<ErrorReport>prompt</ErrorReport>
22+
<WarningLevel>4</WarningLevel>
23+
</PropertyGroup>
24+
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
25+
<DebugType>pdbonly</DebugType>
26+
<Optimize>true</Optimize>
27+
<OutputPath>bin\Release\</OutputPath>
28+
<DefineConstants>TRACE</DefineConstants>
29+
<ErrorReport>prompt</ErrorReport>
30+
<WarningLevel>4</WarningLevel>
31+
</PropertyGroup>
32+
<ItemGroup>
33+
<Reference Include="ExcelDna.Integration, Version=0.33.5730.40390, Culture=neutral, processorArchitecture=MSIL">
34+
<HintPath>..\packages\ExcelDna.Integration.0.33.9\lib\ExcelDna.Integration.dll</HintPath>
35+
<Private>False</Private>
36+
</Reference>
37+
<Reference Include="System" />
38+
<Reference Include="System.Core" />
39+
<Reference Include="System.Xml.Linq" />
40+
<Reference Include="System.Data.DataSetExtensions" />
41+
<Reference Include="Microsoft.CSharp" />
42+
<Reference Include="System.Data" />
43+
<Reference Include="System.Net.Http" />
44+
<Reference Include="System.Xml" />
45+
</ItemGroup>
46+
<ItemGroup>
47+
<Compile Include="RegistrationFunctions.cs" />
48+
<Compile Include="Properties\AssemblyInfo.cs" />
49+
</ItemGroup>
50+
<ItemGroup>
51+
<Content Include="ExcelDna.IntelliSense.Tools-AddIn.dna">
52+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
53+
</Content>
54+
<None Include="packages.config" />
55+
</ItemGroup>
56+
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
57+
<PropertyGroup>
58+
<PostBuildEvent>xcopy "$(SolutionDir)\packages\ExcelDna.AddIn.0.33.9\tools\ExcelDna.xll" "$(TargetDir)ExcelDna.IntelliSense.Tools-AddIn.xll*" /C /Y
59+
xcopy "$(TargetDir)ExcelDna.IntelliSense.Tools-AddIn.dna*" "$(TargetDir)ExcelDna.IntelliSense.Tools-AddIn64.dna*" /C /Y
60+
xcopy "$(SolutionDir)\packages\ExcelDna.AddIn.0.33.9\tools\ExcelDna64.xll" "$(TargetDir)ExcelDna.IntelliSense.Tools-AddIn64.xll*" /C /Y
61+
"$(SolutionDir)\packages\ExcelDna.AddIn.0.33.9\tools\ExcelDnaPack.exe" "$(TargetDir)ExcelDna.IntelliSense.Tools-AddIn.dna" /Y
62+
"$(SolutionDir)\packages\ExcelDna.AddIn.0.33.9\tools\ExcelDnaPack.exe" "$(TargetDir)ExcelDna.IntelliSense.Tools-AddIn64.dna" /Y</PostBuildEvent>
63+
</PropertyGroup>
64+
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
65+
Other similar extension points exist, see Microsoft.Common.targets.
66+
<Target Name="BeforeBuild">
67+
</Target>
68+
<Target Name="AfterBuild">
69+
</Target>
70+
-->
71+
</Project>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using System.Reflection;
2+
using System.Runtime.CompilerServices;
3+
using System.Runtime.InteropServices;
4+
5+
// General Information about an assembly is controlled through the following
6+
// set of attributes. Change these attribute values to modify the information
7+
// associated with an assembly.
8+
[assembly: AssemblyTitle("ExcelDna.IntelliSense.Tools")]
9+
[assembly: AssemblyDescription("")]
10+
[assembly: AssemblyConfiguration("")]
11+
[assembly: AssemblyCompany("")]
12+
[assembly: AssemblyProduct("ExcelDna.IntelliSense.Tools")]
13+
[assembly: AssemblyCopyright("Copyright © 2016")]
14+
[assembly: AssemblyTrademark("")]
15+
[assembly: AssemblyCulture("")]
16+
17+
// Setting ComVisible to false makes the types in this assembly not visible
18+
// to COM components. If you need to access a type in this assembly from
19+
// COM, set the ComVisible attribute to true on that type.
20+
[assembly: ComVisible(false)]
21+
22+
// The following GUID is for the ID of the typelib if this project is exposed to COM
23+
[assembly: Guid("136caa79-037b-476f-b1a5-0e2a3bec4bee")]
24+
25+
// Version information for an assembly consists of the following four values:
26+
//
27+
// Major Version
28+
// Minor Version
29+
// Build Number
30+
// Revision
31+
//
32+
// You can specify all the values or you can default the Build and Revision Numbers
33+
// by using the '*' as shown below:
34+
// [assembly: AssemblyVersion("1.0.*")]
35+
[assembly: AssemblyVersion("1.0.0.0")]
36+
[assembly: AssemblyFileVersion("1.0.0.0")]
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using ExcelDna.Integration;
7+
using Microsoft.Win32;
8+
9+
namespace ExcelDna.IntelliSense.Tools
10+
{
11+
public static class RegistrationFunctions
12+
{
13+
const string DisabledVersionsMachineKeyName = @"HKEY_LOCAL_MACHINE\Software\ExcelDna\IntelliSense";
14+
const string DisabledVersionsUserKeyName = @"HKEY_CURRENT_USER\Software\ExcelDna\IntelliSense";
15+
const string DisabledVersionsValueName = "DisabledVersions";
16+
const string DisabledVersionsVariable = "EXCELDNA_INTELLISENSE_DISABLEDVERSIONS";
17+
18+
const string ServersVariable = "EXCELDNA_INTELLISENSE_SERVERS";
19+
const string ActiveServerVariable = "EXCELDNA_INTELLISENSE_ACTIVE_SERVER";
20+
21+
[ExcelFunction(Description ="returns a 7x2 array of information about the IntelliSense server status")]
22+
public static object IntelliSenseStatus()
23+
{
24+
var machineDisabled = Registry.GetValue(DisabledVersionsMachineKeyName, DisabledVersionsValueName, null) as string;
25+
var userDisabled = Registry.GetValue(DisabledVersionsUserKeyName, DisabledVersionsValueName, null) as string;
26+
var environmentDisabled = Environment.GetEnvironmentVariable(DisabledVersionsVariable) as string;
27+
var serversString = Environment.GetEnvironmentVariable(ServersVariable) as string; // We could split on ';', then on ','
28+
var activeServer = Environment.GetEnvironmentVariable(ActiveServerVariable) as string;
29+
var parts = activeServer?.Split(',');
30+
var activeRegistrationXllPath = parts?[0];
31+
var activeRegistrationId = parts?[1];
32+
var activeRegistrationVersion = parts?[2];
33+
34+
return new object[,]
35+
{
36+
{ "RegistryMachineDisabled", machineDisabled ?? "" },
37+
{ "RegistryUserDisabled", userDisabled ?? "" },
38+
{ "EnvironmentDisabled", environmentDisabled ?? "" },
39+
{ "Servers", serversString ?? "" },
40+
{ "ActiveServerXllPath", activeRegistrationXllPath ?? "" },
41+
{ "ActiveServerId", activeRegistrationId ?? "" },
42+
{ "ActiveServerVersion", activeRegistrationVersion ?? "" },
43+
};
44+
}
45+
}
46+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<packages>
3+
<package id="ExcelDna.AddIn" version="0.33.9" targetFramework="net45" />
4+
<package id="ExcelDna.Integration" version="0.33.9" targetFramework="net45" />
5+
</packages>

Source/ExcelDna.IntelliSense/IntelliSenseServer.cs

Lines changed: 30 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
namespace ExcelDna.IntelliSense
1111
{
12-
// This class implements the registration and activation of this add-in as an IntelliSense Server.
12+
// This class implements the registration and activation of the calling add-in as an IntelliSense Server.
1313
//
1414
// Among different add-ins that are loaded into an Excel process, at most one IntelliSenseServer can be Active.
1515
// This should always be the IntelliSenseServer with the greatest version number among those registered.
@@ -25,29 +25,10 @@ namespace ExcelDna.IntelliSense
2525
// This is against a unique GUID-based name for every registered server, so that the Activate call can be made on an inactive server.
2626
// (To be called in a macro context only, e.g. from AutoOpen.)
2727

28-
// The Active Server also registers a macro with Excel under a well-know name (not the server-specific GUID),
29-
// which passes through to a specific provider.
30-
// This is so that an add-in can tell (some provider in) the active server to update
31-
// E.g. Application.Run("ExcelDna.IntelliSense.Refresh", ' NOT WITH: "VBA") , ActiveWorkbook.Name)
32-
// Application.Run("ExcelDna.IntelliSense.Refresh", ' NOT WITH: "XLL") , ExcelDnaUtil.XllPath)
33-
// Application.Run("ExcelDna.IntelliSense.Refresh", ' NOT WITH: "XML") , @"C:\Temp\MyInfo.xml")
34-
// XlCall.Excel(XlCall.xlcRun, "ExcelDna.IntelliSense.Refresh")
35-
// NB: This can't be the only way a provider knows what to load, because we don't want to do a hand-over
36-
// when a new Server becomes Active (there might not have been a server loaded at the start).
37-
// So we want the provider to always be able to scan to get the info.
38-
// The Refresh call just allows a re-scan in a macro context (and of course would fail if no Server is Active)
39-
40-
// Now:
41-
// When a Server becomes active it lets all the providers scan.
42-
// When a new add-in is then loaded, it calls the ExcelDna.IntelliSense.Refresh macro to force a re-scan.
43-
// When an add-in dynamically registers some more UDFs, it can call Update to force a rescan.
44-
// So there is no way to say "listen to this .xml file".
45-
// So the add-in should provide a discoverable way that a provider can call it to say "what files must I listen to?".
46-
4728
// REMEMBER: COM events are not necessarily safe macro contexts.
4829
public static class IntelliSenseServer
4930
{
50-
const string ServerVersion = "0.0.10"; // TODO: Define and manage this somewhere else
31+
const string ServerVersion = "0.0.11"; // TODO: Define and manage this somewhere else
5132

5233
// NOTE: Do not change these constants in custom versions.
5334
// They are part of the co-operative safety mechanism allowing different add-ins providing IntelliSense to work together safely.
@@ -82,7 +63,7 @@ public static void Register()
8263
if (IsDisabled())
8364
return;
8465

85-
RegisterControlFunction();
66+
RegisterControlMacro();
8667
PublishRegistration();
8768

8869
bool shouldActivate = false;
@@ -102,11 +83,11 @@ public static void Register()
10283
Logger.Initialization.Info($"IntelliSenseServer not being activated now. Active Version: {activeInfo.Version}");
10384
}
10485
// Else we're not activating - there is an active server and it is the same version or newer
105-
// TODO: Tell it to load our UDFs somehow - maybe call a hidden macro?
10686

107-
if (shouldActivate)
87+
if (shouldActivate &&
88+
(activeInfo == null || DeactivateServer(activeInfo)))
10889
{
109-
var activated = Activate();
90+
Activate();
11091
}
11192

11293
AppDomain.CurrentDomain.DomainUnload += CurrentDomain_DomainUnload;
@@ -219,12 +200,12 @@ static bool ActivateServer(RegistrationInfo registrationInfo)
219200
// Suppress errors if things go wrong, including unexpected return types.
220201
try
221202
{
222-
var result = ExcelDna.Integration.XlCall.Excel(ExcelDna.Integration.XlCall.xlfCall, registrationInfo.GetControlMacroName(), ControlMessageDeactivate);
203+
var result = ExcelDna.Integration.XlCall.Excel(ExcelDna.Integration.XlCall.xlUDF, registrationInfo.GetControlMacroName(), ControlMessageDeactivate);
223204
return (bool)result;
224205
}
225-
catch (Exception /*ex*/)
206+
catch (Exception ex)
226207
{
227-
// TODO: Log
208+
Logger.Initialization.Error(ex, $"IntelliSenseServer {registrationInfo.ToRegistrationString()} could not be activated.");
228209
return false;
229210
}
230211
}
@@ -236,12 +217,17 @@ static bool DeactivateServer(RegistrationInfo registrationInfo)
236217
// Suppress errors if things go wrong, including unexpected return types.
237218
try
238219
{
239-
var result = ExcelDna.Integration.XlCall.Excel(ExcelDna.Integration.XlCall.xlfCall, registrationInfo.GetControlMacroName(), ControlMessageDeactivate);
220+
var result = ExcelDna.Integration.XlCall.Excel(ExcelDna.Integration.XlCall.xlUDF, registrationInfo.GetControlMacroName(), ControlMessageDeactivate);
221+
if (result is ExcelError)
222+
{
223+
Logger.Initialization.Error($"IntelliSenseServer {registrationInfo.ToRegistrationString()} could not be deactivated.");
224+
return false;
225+
}
240226
return (bool)result;
241227
}
242-
catch (Exception /*ex*/)
228+
catch (Exception ex)
243229
{
244-
// TODO: Log
230+
Logger.Initialization.Error(ex, $"IntelliSenseServer Deactivate call for {registrationInfo.ToRegistrationString()} failed.");
245231
return false;
246232
}
247233
}
@@ -285,9 +271,14 @@ public int CompareTo(RegistrationInfo other)
285271
return CompareVersions(Version, other.Version);
286272
}
287273

274+
public static string GetControlMacroName(Guid serverId)
275+
{
276+
return "IntelliSenseServerControl_" + serverId.ToString("N");
277+
}
278+
288279
public string GetControlMacroName()
289280
{
290-
return "IntelliSenseControl_" + ServerId.ToString("N");
281+
return GetControlMacroName(ServerId);
291282
}
292283

293284
// 1.2.0 is equal to 1.2
@@ -389,13 +380,16 @@ static int[] ParseVersion(string versionString)
389380
return version;
390381
}
391382

392-
// Version patterns are ","-joined lists of (dotted integer strings, with a possible trailing .* wildcard).
383+
// Version patterns are either a single * (universal match) or a ","-joined lists of (dotted integer strings, with a possible trailing .* wildcard).
393384
// e.g. 1.2.*, which would be matched with regex 1\.2(\.\d+)*
394385
static bool IsVersionMatch(string version, string versionPattern)
395386
{
396387
if (string.IsNullOrEmpty(versionPattern))
397388
return false;
398389

390+
if (versionPattern == "*")
391+
return true; // Universal pattern - matches all versions
392+
399393
var regexParts = new List<string>();
400394
var parts = versionPattern.Split(',');
401395
foreach (var part in parts)
@@ -427,12 +421,12 @@ static RegistrationInfo GetHighestPublishedRegistration()
427421

428422
#region IntelliSense control function registered with Excel
429423

430-
static void RegisterControlFunction()
424+
static void RegisterControlMacro()
431425
{
432426
var method = typeof(IntelliSenseServer).GetMethod(nameof(IntelliSenseServerControl), BindingFlags.Static | BindingFlags.Public);
433-
var name = "IntelliSenseServerControl_" +_serverId.ToString("N");
427+
var name = RegistrationInfo.GetControlMacroName(_serverId);
434428
ExcelIntegration.RegisterMethods(new List<MethodInfo> { method },
435-
new List<object> { new ExcelCommandAttribute { Name = name } },
429+
new List<object> { new ExcelCommandAttribute { Name = name } }, // Macros in .xlls are always hidden
436430
new List<List<object>> { new List<object> { null } });
437431
// No Unregistration - that will happen automatically (and is only needed) when we are unloaded.
438432
}

Source/ExcelDna.IntelliSense/IntellisenseHelper.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66

77
namespace ExcelDna.IntelliSense
88
{
9-
// TODO: This is to be replaced by the Provider / Server info retrieval mechanism
10-
// First version might run on a timer for updates.
119
class IntelliSenseHelper : IDisposable
1210
{
1311
readonly SynchronizationContext _syncContextMain; // Main thread, not macro context

Source/ExcelDna.IntelliSense/Providers/IIntelliSenseProvider.cs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,10 @@ namespace ExcelDna.IntelliSense
1212
// and/or the scope of some provider (e.g. add support for enums).
1313
// No provision is made at the moment for user-created providers or an external provider API.
1414

15-
// The server, upon activation and at other times (when? when ExcelDna.IntelliSense.Refresh is called ?) will call the provider to get the IntelliSense info.
16-
// Maybe the provider can also raise an Invalidate event, to prod the server into reloading the IntelliSense info for that provider
15+
// Upon activation the server will call the provider to get the IntelliSense info.
16+
// The provider can also raise an Invalidate event, to prod the server into reloading the IntelliSense info for that provider
1717
// (a bit like the ribbon Invalidate works).
18-
// E.g. the XmlProvider might read from a file, and put a FileWatcher on the file so that whenever the file changes,
19-
// the server calls back to get the updated info.
20-
18+
2119
// A major concern is the context in which the provider is called from the server.
2220
// We separate the Refresh call from the calls to get the info:
2321
// The Refresh calls are always in a macro context, from the main Excel thread and should be as fast as possible.
@@ -26,11 +24,11 @@ namespace ExcelDna.IntelliSense
2624

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

29-
// TODO: Consider interaction with Application.MacroOptions. (or not?)
27+
// CONSIDER: Is there a way to get the register info from calls to Application.MacroOptions?
3028

3129
// TODO: We might relax the threading rules, to say that Refresh runs on the same thread as Invalidate
3230
// TODO: We might get rid of Refresh (since that runs in the Invalidate context)
33-
// TODO: The two providers have been refactored to work very similarly - maybe be can extract out a base class...
31+
// CONSIDER: The two providers have been refactored to work very similarly - maybe be can extract out a base class...
3432
interface IIntelliSenseProvider : IDisposable
3533
{
3634
void Initialize(); // Executed in a macro context, on the main Excel thread

0 commit comments

Comments
 (0)