首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Rubber鸭子入口点的IDTExtensibility2实现

Rubber鸭子入口点的IDTExtensibility2实现
EN

Code Review用户
提问于 2016-09-20 19:19:15
回答 1查看 293关注 0票数 4

下面是新的和改进的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实例的处理,以及可以加载/卸载外接程序的各种不同方式,这样做(看起来很好)是否正确。

当然,任何其他反馈也是受欢迎的。

代码语言:javascript
复制
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;
        }
    }
}
EN

回答 1

Code Review用户

回答已采纳

发布于 2016-09-22 08:35:49

处理方法OnConnection、OnStartupComplete、OnBeginShutdown和OnDisconnection及其标志的代码是正确的。我不能评论根组成或NInject。

有些事情值得在代码中记录,说明为什么必须这样做,以防有一天有人认为代码可以“简化”:

  • 如果在OnConnection方法中调用ConnectMode为ext_ConnectMode.ext_cm_Startup时,某些主机(VB5)可能尚未初始化MainWindow属性或MainWindow.hWnd属性。因此,必须使用OnStartupComplete方法。
  • 如果在ShutdownAddIn为ext_DisconnectMode.ext_dm_HostShutdown时在OnDisconnection方法中调用RemoveMode,则某些主机崩溃。因此,必须使用OnBeginShutdown方法。这有一个小的副作用:在调用OnBeginShutdown之后,Office提示保存脏文档。如果用户取消了“保存提示符”对话框,主机将不会关闭,但外接程序已被卸载。这是一个很小的代价。

只有一台VB6主机和一台VB5主机,但是有大量的VBA主机:不仅有Office版本(2000、2002、2003、2007、2010、2013和2016年),还有其他第三方主机提供VBA:

  • CorelDRAW
  • Autodesk
  • PI ProcessBook (http://www.osisoft.com/pi-system/pi-capabilities/pi-system-tools/pi-processbook/)
  • SCADA系统,如GE iFix (http://www.geautomation.com/products/proficy-hmiscada-ifix)
  • 等。

上面的代码足够健壮,可以处理所有在我经验中的代码。

票数 3
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/141956

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档