65.9K
CodeProject 正在变化。 阅读更多。
Home

通用加载项

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.79/5 (13投票s)

2006年10月4日

GPL3

14分钟阅读

viewsIcon

72890

downloadIcon

993

为 DevStudio、Visual Studio 和 Office 编写插件

引言

本文介绍了一种编写插件的方法,使得一个二进制文件可以在多个版本的 DevStudio、Visual Studio 和 Office 中运行。它使用了 C++ 和 ATL,但其原理也适用于其他语言和其他框架。

背景

多年来,微软 IDE 版本之间插件编程模型的更改让我感到沮丧。我投入了大量精力为 DevStudio 6 编写了一些插件,却发现升级到 Visual Studio 2003 时不得不完全重写它们。然后,当我迁移到 Visual Studio 2005 时,对象模型再次发生了变化(尽管很小),又需要一次更新。我在不同的项目上仍然同时使用这三个 IDE,所以不能放弃旧版本;我必须维护我所写的任何给定插件的这三个版本。

这让我非常恼火,以至于我设计了一种编写插件的方案,使得同一个 DLL 可以加载到 DevStudio 6、Visual Studio 2003、2005 和 2008,甚至 Office 2003 中,并作为每个插件的主机。由于我怀疑不止我一个人经历过这种情况,所以我决定写下我所做的工作。

插件的通用布局

核心思想非常简单;大部分工作在于解决大量的实现细节。在本文的其余部分,我将引用我为说明我的方法而编写的示例插件 SampleCAI(请参阅下载)。但在深入细节之前,让我先介绍我的通用方案。

我将构建一个进程内 COM 服务器(即一个 *.dll),它导出一个实现我们目标主机所需的所有接口的单个组件。该组件将以目标主机能够识别的方式向每个主机展示自己,作为一个插件。例如,当 DevStudio 实例化我们的组件时,它会请求 IDSAddIn 接口。只要我们实现了 IDSAddIn,DevStudio 就会将其视为符合 DevStudio 标准的插件。我们可以实现一百个其他接口,呈现一个 UI,处理 HTTP 请求,等等——DevStudio 不会知道也不会在意。

同样,当 Visual Studio 2003、2005、2008 或 Office 实例化我们的组件时,它们会向我们的组件请求 IDTEExtensibility2IDTECommandTarget 接口。同样,只要我们实现了这两个接口,宿主应用程序就会将其视为插件,而不管我们还能做什么。

简而言之,如果您创建了一个实现这三个接口的 COM 组件,我们所有的宿主都将愉快地将该组件加载为符合其各自模型的插件。

Class diagram

顺便说一下,DevStudio 6 和 Visual Studio 接口之间确实存在名称冲突(例如 ITextDocument)。我通过 #include 'objmodel' 中的 DevStudio 6 头文件来处理这个问题,并将这些标识符保留在全局 namespace 中。然后,我 #import 了描述 Visual Studio 可扩展性模型的 typelib,利用了 import 默认创建新 namespace 的行为来包含导入的 typelib 中定义的所有实体。

该示例是一个标准的 Win32 DLL 项目,基于 ATL 使用 C++ 实现。我将一步步讲解实现过程,并尝试解释我做了什么以及为什么。

CAdd-In 类

SampleCAI.cpp 是样板 ATL 代码;它实现了 DLLMain 和所有进程内 COM 服务器所需的四个导出。真正开始的部分是 SampleCAI.dll 的唯一 COM 组件 CoAddIn。这是 CoAddIn 的 IDL 定义:

  [
    uuid(5f4e04a1-1a92-11db-89d7-00508d75f9f1),
    helpstring("Common Add-In Sample Add-In Object")
  ]
  coclass CoAddIn
  {
    [default] interface IUnknown /*IDSAddIn*/;
  }

由于 DevStudio 发现新插件的方式,DLL 只能导出一个组件。当您将 DevStudio IDE 指向一个 DLL 并要求将其加载为插件时,DevStudio 似乎会调用 LoadLibrary,然后在该 DLL 上调用 RegisterClassObjects。它似乎会监视注册表,以找出该调用注册了哪些 COM 组件。我说“似乎”和“看上去”是因为这个机制没有文档记录;各种插件作者是通过经验推断出来的(例如,请参阅 [1])。

无论如何,DevStudio 会确定您的 DLL 导出了哪些 COM 组件,并对每个组件调用 CoCreateInstance。在创建组件后,它会调用 QueryInterface,查找 IDSAddIn。如果 QI 失败,DevStudio 会通知用户并拒绝加载该 DLL 作为插件。因此,如果我们实现了两个 COM 对象,一个用于 DevStudio,一个用于 Visual Studio,DevStudio 会发现其对第二个组件的 QI 失败,认为该 DLL 不是有效的插件,并拒绝加载 **整个** DLL。

因此,SampleCAI.dll 导出且仅导出 CoAddIn 这一个 COM 组件,该组件实现了 IDSAddIn。请注意,我实际上并没有在 IDL 中声明 IDSAddIn:那只是因为 IDSAddIn 只在 C/C++ 头文件(ADDAUTO.H)中定义;我们没有它的 IDL。

现在,让我们看一下 C++ 类 CAddIn,它是 COM 组件 CoAddIn 的实现。

  class ATL_NO_VTABLE CAddIn :
    // Standard ATL parent classes...
  {
  public:
    /// ATL requires a default ctor
    CAddIn();
    /// ATL-defined initialization routine
    HRESULT FinalConstruct();
    /// ATL-defined cleanup routine
    void FinalRelease();

  # ifndef DOXYGEN_INVOKED        // Shield the macros from doxygen...
    // Stock ATL macros...
    // Tell ATL which interfaces we support
    BEGIN_COM_MAP(CAddIn)
      COM_INTERFACE_ENTRY(ISupportErrorInfo)
      COM_INTERFACE_ENTRY_AGGREGATE(IID_IDSAddIn, m_pDSAddIn)
      COM_INTERFACE_ENTRY_AGGREGATE(EnvDTE::IID_IDTCommandTarget,
                                    m_pDTEAddIn)
      COM_INTERFACE_ENTRY_AGGREGATE(AddInDO::IID__IDTExtensibility2,
                                    m_pDTEAddIn)
      COM_INTERFACE_ENTRY_AGGREGATE(IID_IDispatch, m_pDTEAddIn)
    END_COM_MAP()
  # endif // not DOXYGEN_INVOKED

  // ...

  public:
    /// Display our configuration dialog
    void Configure();
    /// Carry out our command
    void SayHello();

  private:
    /// Reference on our aggregated instance of CoDSAddIn
    CComPtr m_pDSAddIn;
    /// Reference on our aggregated instance of CDTEAddIn
    CComPtr m_pDTEAddIn;

  };

正如您所见,CAddIn 是一个普通的 ATL 类,实现了一个 COM 组件。第一个值得关注的点是接口映射。如上所述,CAddIn 类导出了 IDSAddIn。但是,我们通过一个聚合体 m_pDSAddIn 来支持它。这是一个从属 COM 对象。

  [
    uuid(5f4e04a2-1a92-11db-89d7-00508d75f9f1),
    helpstring("Common AddIn Sample DevStudio 6 AddIn Object"),
    noncreatable
  ]
  coclass CoDSAddIn
  {
    [default] interface IUnknown /*IDSAddIn*/;
  }

请注意 noncreatable 标签;我们也不会注册它。严格来说,这样做并非必需;我创建这个类纯粹是为了代码的可读性。将所有对象模型(即 DevStudio、Visual Studio 和 Office)的代码添加到 CAddIn 会使类过于庞大。这纯粹是我个人的偏好;从外部来看,调用者不会知道任何区别。

结果是,当 DevStudio 调用 QueryInterface 查找 IDSAddIn 时,我们将将其委托给 m_pDSAddIn

m_pDTEAddIn 将持有 CoDTEAddIn 的引用。这是另一个类似于 CoDSAddin 的聚合体,但支持 Visual Studio 和 Office 2003 所需的接口。这是该组件的 IDL 定义:

  [
    uuid(5f4e04a3-1a92-11db-89d7-00508d75f9f1),
    helpstring("Common AddIn Sample DTE-Compatible AddIn"),
    noncreatable
  ]
  coclass CoDTEAddIn
  {
    [default] interface IDispatch;
  }

下一个棘手的问题是 IDTEExtensibility2IDTECommandTarget 都是双重接口;如果收到 QI 请求 IDispatch,我们应该怎么做?这是我不喜欢双重接口的众多原因之一,但那是另一篇文章的内容!在这种情况下,事实证明 IDE 希望我们返回 IDTEExtensibility2。幸运的是,IDSAddIn 是自定义的,所以那里没有冲突。

下一个值得关注的点是 public 方法 ConfigureSayHello。该示例只能做两件事:配置自身和问好。此功能位于 CAddIn 中。想法是将插件的“核心功能”集中在 CAddIn 中,并将任务委托给聚合的辅助类来处理每个宿主应用程序。当它们检测到宿主应用程序已调用某个命令时,它们将调用 CAddIn 来执行工作。毕竟,这次尝试的全部意义在于消除多个插件中代码重复的需要。

DevStudio 6 宿主

在 DevStudio 中宿主方面,我做了一些非标准的处理。严格来说,这些对于将 AddIn 加载到多个宿主中不是必需的,它们只是让插件更好用一些。

当您将 DevStudio IDE 指向一个 DLL 并要求将其加载为 AddIn 时,DevStudio 会在新 AddIn 下创建注册表项:

  HKEY_CURRENT_USER\Software\Microsoft\DevStudio\6.0\AddIns

但是,组件可以在安装过程中作为其一部分在 tersebut 键下“自我注册”,从而省去用户的麻烦。然而,这意味着新 AddIn 首次加载时,OnConnectvfFirstTime 参数不会设置为 VARIANT_TRUE。这是一个问题,因为这通常是我们判断我们的插件是首次加载的方式,因此也是执行一次性设置任务(例如创建工具栏)的时候。

创建工具栏很复杂,因为:

  1. 我们需要自己跟踪它是否已被创建
  2. vfFirstTimefalse 时调用 AddCommandBarButton 会失败

我通过在注册表中写入一个 boolean 来解决了问题 #1。我通过向一个隐藏的消息窗口发送消息来解决了问题 #2(事实证明,在 OnConnection 上下文之外调用 AddCommandBarButton 会成功)。

我从 Nick Hodapp 的文章“Undocumented Visual C++” [1] 中学到了这一点。这是示例使用的注册表脚本:

  HKCU
  {
    NoRemove Software
    {
      NoRemove Microsoft
      {
        NoRemove DevStudio
        {
          NoRemove '6.0'
          {
            NoRemove AddIns
            {
              ForceRemove 'SampleCAI.CoAddin.1' = s '1'
              {
                 val Description = s 'Sample Common AddIn Developer Studio Add-in'
                 val DisplayName = s 'SampleCAI'
                 val Filename    = s '%MODULE%'
              }
            }
          }
        }
      }
    }
  }

同一篇文章中的另一个技巧是命名工具栏的方法。使用标准的 AddIn API,您的新工具栏将被命名为 Toolbar<n>其中 <n> 是未命名工具栏的数量(这很烦人且丑陋)。相反,示例 AddIn 会挂钩工具栏创建并将其窗口名称更改为更友好的名称。注意:新名称不能超过目标名称(通常为八个字符)。

这样,我们来看一下 CDSAddIn,它是实现 DevStudio AddIn 的 C++ 类:

  class ATL_NO_VTABLE CDSAddIn :
      public CComObjectRootEx,
      public CComCoClass,
      public ISupportErrorInfo,
      public IDSAddIn
  {
  public:
    ...
    /// Private initialization routine
    void SetParam(CAddIn *pParent);

    ...
    /// Tell the ATL Registrar *not* to register us
    DECLARE_NO_REGISTRY();
    /// This component may only be created as an aggregate
    DECLARE_ONLY_AGGREGATABLE(CDSAddIn)
    ...
    /// Tell ATL which interfaces we support
    BEGIN_COM_MAP(CDSAddIn)
      COM_INTERFACE_ENTRY(IDSAddIn)
      COM_INTERFACE_ENTRY(ISupportErrorInfo)
    END_COM_MAP()

    ...
  private:
    ...
    /// Non-owning reference to our parent CAddIn instance
    CAddIn *m_pParent;
    ...

  };

总的来说,这是一个实现 IDSAddIn 的标准的 ATL COM 类。事实上,对于 DevStudio AddIn 开发者来说,它的实现应该很熟悉。它对 IDSAddIn::OnConnection 的实现向 DevStudio 提供了一个暴露我们命令的 dispinterface,并接收它感兴趣的任何 DevStudio 事件。在 IDSAddIn::OnDisconnection 中,它会断开该连接。

注释

  1. COM coclass CoDSAddIn **不能**直接创建;它甚至没有注册,并且无论如何,没有聚合体创建都会失败。
  2. CDSAddIn 维护一个(非拥有)回指指针指向其父 CAddIn。这使得它可以委托给其父级的命令实现。我猜这并非最通用的设计:一个 COM 对象如此密切地了解另一个对象的实现。然而,考虑到此组件 **绝不会** 在其他地方使用,便利性似乎是值得的。

Visual Studio 和 Office 宿主

如上所述,我们的插件将通过另一个 COM 聚合体 CoDTEAddIn 提供 IDTEExtensibility2IDTECommandTarget 的实现。这个 coclass 由 CDTEAddIn 实现:

  class ATL_NO_VTABLE CDTEAddIn :
    // Stock ATL parent classes...
  {
  public:
    /// Application host flavors
    enum Host
    {
      /// Sentinel value
      Host_Unknown,
      /// Visual Studio 2003
      Host_VS2003,
      /// Visual Studio 2005
      Host_VS2005,
      /// Excel 2003
      Host_Excel2003,

      // Add new hosts here...

    };

  public:
    ...
    /// Private initialization routine
    void SetParam(CAddIn *pParent);
    ...
    /// Tell the ATL Registrar *not* to register us
    DECLARE_NO_REGISTRY();
    /// This component may only be created as an aggregate
    DECLARE_ONLY_AGGREGATABLE(CDTEAddIn)
    /// Tell ATL which interfaces we support
    BEGIN_COM_MAP(CDTEAddIn)
      COM_INTERFACE_ENTRY(ISupportErrorInfo)
      COM_INTERFACE_ENTRY(EnvDTE::IDTCommandTarget)
      COM_INTERFACE_ENTRY(AddInDO::_IDTExtensibility2)
      COM_INTERFACE_ENTRY2(IDispatch, AddInDO::IDTExtensibility2)
    END_COM_MAP()
    ...

  private:
    ...
    /// Reference to our host's Application object
    CComPtr&lt;:_dte&gt; m_pApp;
    /// Reference to our host's Application object
    CComPtr&lt;:_application&gt; m_pExcel;
    /// Which host are we loaded into
    Host m_nHost;
    /// Non-owning reference to our parent CAddIn instance
    CAddIn *m_pParent;
    ...
  }; // End CDTEAddIn.<:_dte&gt;<:_application&gt;

首先应该引起您注意的是,该类知道它已被加载到哪个宿主中。虽然 Visual Studio **和** Office 2003 都使用此插件编程模型,但宿主应用程序本身向 **我们** 提供不同的接口。我们在 IDTExtensibility2 接口的 OnConnection 方法中需要考虑这一点。我们在 OnConnection 方法中猜测宿主类型:

  HRESULT hr = S_OK;            // Eventual return value

  try
  {
    // Validate our parameters...
    if (NULL == pApplication) throw _com_error(E_INVALIDARG);
    if (NULL == pAddInInst)   throw _com_error(E_INVALIDARG);

    // take a reference on the AddIn object representing us,
    m_pAddIn = com_cast&lt;:addin&gt;(pAddInInst);

    // & try to figure out what DTE-compatible host we're currently
    // loaded into:
    m_nHost = GuessHostType(pApplication);

    ATLTRACE2(atlTraceHosting, 2, "CoDTEAddIn has been loaded with a c"
              "onnect mode of %d (our host is %d).\n", nMode, m_nHost);

    ...<:addin&gt;

在验证了我们的参数之后,我们做的第一项实际工作包装在对 GuessHostType 的调用中;在这里,我们弄清楚我们被加载到什么样的环境中。现在,我见过一些插件通过调用 GetModuleFileName(NULL,...) 来获取它们被加载到的可执行文件的名称,但我采取了不同的方法。我的想法是,只要宿主实现了我期望的接口,我就可以与它通信。例如,像 Open Office 这样的应用程序套件可以通过实现适当的 COM 接口来宿主 Microsoft Office 插件。

GuessHostType 通过对 OnConnection 中提供的应用程序指针进行 QI 来获取各种接口:

  CDTEAddIn::Host CDTEAddIn::GuessHostType(IDispatch *pApp)
  {
    HRESULT hr = S_OK;

    // Are we being hosted by Visual Studio 2005?  I suspect this will be
    // the most common case.  Check by asking for an ENVDTE80::DTE2
    // interface...
    EnvDTE80::DTE2 *pDTE2Raw;
    hr = pApp->QueryInterface(EnvDTE80::IID_DTE2, (void**)&pDTE2Raw);
    if (SUCCEEDED(hr))
    {
      m_pApp = com_cast&lt;:_dte&gt;(pApp);
      pDTE2Raw->Release();

      return Host_VS2005;
    }

    // Ok-- maybe it's Visual Studio 2003...
    ...<:_dte&gt;

请注意,我们不区分 Visual Studio 2005 和 2008。事实证明,Visual Studio 2008 实现接口 EnvDTE80::IID_DTE2,并且其实现方式与 Visual Studio 2005 非常接近,因此,对我们而言,无需区分它们。

目标是将 m_nHost 填充为 Host 枚举的一个成员,以便其余逻辑“知道”如何行为。例如,我们在 OnConnection 中做的下一件事是调用 AddCommands

  void CDTEAddIn::AddCommands(AddInDO::ext_ConnectMode nMode)
  {
      switch (m_nHost)
      {
      case Host_VS2003:
        AddCommandsVS2003(nMode);
        break;
      case Host_VS2005:
        AddCommandsVS2005(nMode);
        break;
      ...

注释

  1. COM coclass CoDTEAddIn **不能**直接创建;它甚至没有注册,并且无论如何,没有聚合体创建都会失败。
  2. CDSAddIn 一样,CDTEAddIn 维护一个(非拥有)回指指针指向其父 CAddIn,原因相同。

结论

这些是大致的要点;正如我在开头提到的,大部分工作都在细节中。我附带了一个功能齐全的示例插件,它可以加载到 DevStudio、Visual Studio 2003、Visual Studio 2005、Visual Studio 2008 和 Excel 2003 中。它是一个 Visual Studio 2005 解决方案,包含插件本身及其相关的卫星 DLL。要安装它,只需构建 Debug 或 Release 配置;有一个生成后步骤会自动正确注册 DLL。

当然还有更多工作可以做;请参阅附录 A

享受--欢迎提问、反馈和建议。

附录 A - 未来工作

缓存 COM 组件创建

主要 COM 组件 CoAddIn 以聚合组件的形式实现了不同的宿主模型。今天,这两个组件都在 FinalConstruct 中实例化;最好迁移到某种缓存方案,以避免在加载到 DevStudio 时实例化(例如)CoDTEAddIn 的实例……

属性页

Visual Studio 2003 和 2005 允许其插件向它们在响应 Tools | Options 时显示的对话框添加页面。您可以通过添加一些额外的注册表项来告知 Visual Studio 您的页面或页面(请参阅示例中的 vs2003.rgsvs2005.rgs,或在此处 [查看])。

我曾认为为 DevStudio 6 和 Excel 2003 添加一个新的属性页会很好,但我没能弄清楚如何做到。我的方案是安装一个 CBT 钩子,并捕获 Tools | Options 属性表的创建。在那里,我会将消息发送回一个私有的、仅用于消息的窗口,该窗口会创建**我的**属性页,并将一个 PSM_ADDPAGE 消息发送到属性表,并附带我的新页面。

无论出于何种原因,我在一个小型测试应用程序中成功实现了这一点,但在 Dev Studio 6 或 Excel 2003 中均未成功。在这两种情况下,Tools | Options 属性表都没有“Dialog”这个“Windows”类(就像这个一样),所以这些应用程序可能有一些非标准的实现。

如果任何人对此有任何想法,或者比我更成功,我很想听听。

同样,最好让 Visual Studio 显示的页面在调用 Configure 时选择我们的页面。目前,它们会打开到上次查看的页面。我有一些想法,例如再次安装一个 CBT 钩子来捕获属性表的创建,并向其子树形控件发送消息,但我还没有做任何事情。 Jeff Paquette 告诉我,他在他的 VisEmacs 插件中成功使用了这一点。

其他应用程序

我没有 Visual Studio 2002 的副本,所以无法测试。我实现了对 Excel 2003 的支持,但仅此而已。为整个套件构建支持将会很棒。

项目模板

一个用于生成通用插件代码的项目模板将会很好。

附录 B - Visual Studio 命令和命令栏

添加命令和设置命令栏是我编写这个示例中最烦人的部分。在解决这个麻烦的过程中,我大量依赖 Carlos J. Quintero 的文章“HOWTO: Adding buttons, commandbars and toolbars to Visual Studio .NET from an add-in”[2]。

Carlos 描述了两种不同类型的 Visual Studio 命令栏:永久和临时。

永久命令栏

  1. 即使插件通过插件管理器被卸载,仍然显示在 IDE 中
  2. 仅创建一次(当 OnConnection 方法收到 ext_ConnectMode.ext_cm_UISetup 的值时……这在插件安装在机器上后仅发生一次)
  3. 通过 DTE.Commands.AddCommandBar() 添加
  4. 仅在插件卸载时(而不是在加载时)通过卸载程序中的自定义操作使用 DTE.Commands.RemoveCommandBar() 函数移除

临时命令栏

  1. 每次加载插件时使用 DTE.CommandBars.Add()CommandBar.Controls.Add() 函数(取决于命令栏类型:ToolbarCommandBarPopup)创建
  2. 每次卸载插件时,使用 CommandBar.Delete() 方法由插件移除

根据 Carlos 的说法,永久命令栏即使在用户卸载插件后仍然存在,“会让许多用户感到困惑”,因此,“大多数插件不使用这种方法”。他提出了以下方法:

  If ext_cm_AfterStartup or ext_cm_Startup

     Check for the command's existence through Commands::Item
     If not there, create it via Commands::AddNamedCommand for
     both 2003 & 2005

     Create a new (temporary) command bar by calling
     pTempCmdBar = ICommandBars::Add() (both 2003 & 2005!)

     Add a button:
     pTempCmdBar->AddControl

     pTempCmdBar->Visible = true;

  Then call pTempCmdBar->Delete() in OnDisconnect

请注意,他完全忽略了 ext_cm_UIStartup

对我来说,临时工具栏看起来还可以,除了两个问题:

  1. 每次启动 Visual Studio 时,工具栏都会重新出现,即使用户将其关闭了。
  2. 即使您注销了 AddIn,命令仍然存在;这倒不是什么大问题…… IDE 会检测到这一点并在下次调用命令时移除它们。

永久工具栏会尊重用户关闭它们的决定,但如果您注销了 AddIn 并删除了命令,该该死的东西**仍然**在那里,您无法删除它!

我还没有确定“正确”的解决方案。示例 AddIn 可以根据 #define SAMPLECAI_COMMAND_BAR_STYLE 的设置使用几种不同的方法。它可以取以下三个值之一:

  1. SAMPLECAI_COMMAND_BAR_TEMPORARY 使用临时命令栏
  2. SAMPLECAI_COMMAND_BAR_PERMANENT 使用永久命令栏
  3. SAMPLECAI_COMMAND_BAR_SEMIPERMANENT 使用永久命令栏,但在卸载时以编程方式删除命令栏

附录 C - 重置 Visual Studio

在开发 AddIn 的过程中,您可能会遇到需要重置 AddIn 对 Visual Studio 所做的所有 UI 更改的情况。

对于 Visual Studio 2005,您可以运行 devenv /resetaddin <AddInNamespace.Connect&gt;。不幸的是,Visual Studio 2003 只提供 devenv.exe /setup,这会重置**所有** UI 自定义(包括您的按键绑定!)。由于这有点过激,我编写了这个小 VBS 脚本:

  Dim objDTE
  Dim objCommand
  Dim objTb

  On Error Resume Next

  Set objDTE = CreateObject("VisualStudio.DTE.7.1")
  If objDTE Is Nothing Then
      MsgBox "Couldn't find VS 2003"
  Else

      Set objCommand = objDTE.Commands.Item("SampleCAI.CoAddIn.Configure")
      If objCommand Is Nothing Then
          MsgBox "The Configure command has already been deleted."
      Else
        objCommand.AddControl(objDTE.CommandBars.Item("Tools"))
        objCommand.Delete
      End If

      Set objCommand = objDTE.Commands.Item("SampleCAI.CoAddIn.SayHello")
      If objCommand Is Nothing Then
          MsgBox "The SayHello command has already been deleted."
      Else
        objCommand.AddControl(objDTE.CommandBars.Item("Tools"))
        objCommand.Delete
      End If

      Set objTb = objDTE.CommandBars.Item("SampleCAI")
      If objTb Is Nothing Then
          MsgBox "No (permanent) command bar named SampleCAI."
      Else
          objDTE.Commands.RemoveCommandBar(objTb)
          objTb.Delete
          set objTb = Nothing
      End If

      objDTE.Quit
      set objDTE = Nothing

  End If

  Set objDTE = CreateObject("VisualStudio.DTE.8.0")
  If objDTE Is Nothing Then
      MsgBox "Couldn't find VS 2005"
  Else

      Set objCommand = objDTE.Commands.Item("SampleCAI.CoAddIn.Configure")
      If objCommand Is Nothing Then
          MsgBox "The Configure command has already been deleted."
      Else
        'objCommand.AddControl(objDTE.CommandBars.Item("Tools"))
        objCommand.Delete
      End If

      Set objCommand = objDTE.Commands.Item("SampleCAI.CoAddIn.SayHello")
      If objCommand Is Nothing Then
          MsgBox "The SayHello command has already been deleted."
      Else
        'objCommand.AddControl(objDTE.CommandBars.Item("Tools"))
        objCommand.Delete
      End If

      Set objTb = objDTE.CommandBars.Item("SampleCAI")
      If objTb Is Nothing Then
          MsgBox "No (permanent) command bar named SampleCAI."
      Else
          objDTE.Commands.RemoveCommandBar(objTb)
          objTb.Delete
          set objTb = Nothing
      End If

      objDTE.Quit
      set objDTE = Nothing

  End If

参考文献

  1. Undocumented Visual C++ 作者:Nick Hodapp
  2. 为 Visual Studio .NET 的插件添加按钮、命令栏和工具栏
  3. 使用 VC++/ATL 构建 Office2K COM 插件

历史

  • 2006年10月1日:首次发布
  • 2008年4月26日:文章更新
© . All rights reserved.