下面是新的和改进的Rubber鸭2.x入口点类,基于MZ-Tools 8.0's Connect.VBA实现的IDTExtensibility2 COM接口,由MZ-Tools本人Carlos先生慷慨地共享给Rubber鸭团队。
在撰写本文时,所做的修改尚未合并到项目的主存储库中,但您可以在在我自己的叉子中提交8ac768d9上查看它。
为了让它成为一个好公民,我还需要希姆插件,并在它自己的专用AppDomain中运行(.net创建RCW的per-AppDomain,这就是为什么它确实很重要),所以还有许多问题超出了完全稳定Rubber鸭2.x的“切入点”,但现在我感兴趣的是关于IDTExtensibility2实现的反馈--这是一个有用的补充--我发现,“做正确的事情”比"hello world“教程所暗示的要棘手得多。
此外,由于我们使用宁特进行依赖项注入,我还感兴趣的是这里的组合根、_kernel实例的处理,以及可以加载/卸载外接程序的各种不同方式,这样做(看起来很好)是否正确。
当然,任何其他反馈也是受欢迎的。
using Extensibility;
using Microsoft.Vbe.Interop;
using Ninject;
using Ninject.Extensions.Factory;
using Rubberduck.Root;
using Rubberduck.UI;
using System;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Windows.Threading;
using Ninject.Extensions.Interception;
using NLog;
using Rubberduck.Settings;
using Rubberduck.SettingsProvider;
namespace Rubberduck
{
/// <remarks>
/// Special thanks to Carlos Quintero (MZ-Tools) for providing the general structure here.
/// </remarks>
[ComVisible(true)]
[Guid(ClassId)]
[ProgId(ProgId)]
[EditorBrowsable(EditorBrowsableState.Never)]
// ReSharper disable once InconsistentNaming // note: underscore prefix hides class from COM API
public class _Extension : IDTExtensibility2
{
private const string ClassId = "8D052AD8-BBD2-4C59-8DEC-F697CA1F8A66";
private const string ProgId = "Rubberduck.Extension";
private VBE _ide;
private AddIn _addin;
private bool _isInitialized;
private bool _isBeginShutdownExecuted;
private IKernel _kernel;
private App _app;
private readonly Logger _logger = LogManager.GetCurrentClassLogger();
public void OnAddInsUpdate(ref Array custom) { }
// ReSharper disable InconsistentNaming
public void OnConnection(object Application, ext_ConnectMode ConnectMode, object AddInInst, ref Array custom)
{
try
{
_ide = (VBE)Application;
_addin = (AddIn)AddInInst;
_addin.Object = this;
switch (ConnectMode)
{
case ext_ConnectMode.ext_cm_Startup:
// normal execution path - don't initialize just yet, wait for OnStartupComplete to be called by the host.
break;
case ext_ConnectMode.ext_cm_AfterStartup:
InitializeAddIn();
break;
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
Assembly LoadFromSameFolder(object sender, ResolveEventArgs args)
{
var folderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
var assemblyPath = Path.Combine(folderPath, new AssemblyName(args.Name).Name + ".dll");
if (!File.Exists(assemblyPath))
{
return null;
}
var assembly = Assembly.LoadFile(assemblyPath);
return assembly;
}
public void OnStartupComplete(ref Array custom)
{
InitializeAddIn();
}
public void OnBeginShutdown(ref Array custom)
{
_isBeginShutdownExecuted = true;
ShutdownAddIn();
}
// ReSharper disable InconsistentNaming
public void OnDisconnection(ext_DisconnectMode RemoveMode, ref Array custom)
{
switch (RemoveMode)
{
case ext_DisconnectMode.ext_dm_UserClosed:
ShutdownAddIn();
break;
case ext_DisconnectMode.ext_dm_HostShutdown:
if (_isBeginShutdownExecuted)
{
// this is the normal case: nothing to do here, we already ran ShutdownAddIn.
}
else
{
// some hosts do not call OnBeginShutdown: this mitigates it.
ShutdownAddIn();
}
break;
}
}
private void InitializeAddIn()
{
if (_isInitialized)
{
// The add-in is already initialized. See:
// The strange case of the add-in initialized twice
// http://msmvps.com/blogs/carlosq/archive/2013/02/14/the-strange-case-of-the-add-in-initialized-twice.aspx
return;
}
_kernel = new StandardKernel(new NinjectSettings { LoadExtensions = true }, new FuncModule(), new DynamicProxyModule());
try
{
var currentDomain = AppDomain.CurrentDomain;
currentDomain.AssemblyResolve += LoadFromSameFolder;
var config = new XmlPersistanceService<GeneralSettings>
{
FilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Rubberduck", "rubberduck.config")
};
var settings = config.Load(null);
if (settings != null)
{
try
{
var cultureInfo = CultureInfo.GetCultureInfo(settings.Language.Code);
Dispatcher.CurrentDispatcher.Thread.CurrentUICulture = cultureInfo;
}
catch (CultureNotFoundException) { }
}
_kernel.Load(new RubberduckModule(_ide, _addin));
_app = _kernel.Get<App>();
_app.Startup();
_isInitialized = true;
}
catch (Exception exception)
{
_logger.Fatal(exception);
System.Windows.Forms.MessageBox.Show(exception.ToString(), RubberduckUI.RubberduckLoadFailure, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void ShutdownAddIn()
{
if (_app != null)
{
_app.Shutdown();
_app = null;
}
if (_kernel != null)
{
_kernel.Dispose();
_kernel = null;
}
Marshal.ReleaseComObject(_addin);
Marshal.ReleaseComObject(_ide);
_isInitialized = false;
}
}
}发布于 2016-09-22 08:35:49
处理方法OnConnection、OnStartupComplete、OnBeginShutdown和OnDisconnection及其标志的代码是正确的。我不能评论根组成或NInject。
有些事情值得在代码中记录,说明为什么必须这样做,以防有一天有人认为代码可以“简化”:
只有一台VB6主机和一台VB5主机,但是有大量的VBA主机:不仅有Office版本(2000、2002、2003、2007、2010、2013和2016年),还有其他第三方主机提供VBA:
上面的代码足够健壮,可以处理所有在我经验中的代码。
https://codereview.stackexchange.com/questions/141956
复制相似问题