在VC++中使用IManagedAddin加载VSTO VBA加载项

2022-08-12 00:00:00 outlook outlook-addin c++

我得到了一个用VBA编写的VSTO Outlook外接程序,并被要求缩短启动时间。我对外接程序和COM对象完全陌生,所以我需要一些帮助。

插件的启动时间从0.2秒到2.0秒不等,如果平均启动时间为>;1000ms,Outlook将禁用该插件。不幸的是,使用注册表黑客来强制启用外接程序不是一种选择。我还用一个空插件对它进行了测试,启动时间也可能长达1.8秒。我已经搜索了so和其他类似的站点以寻找解决方案,并且找到了一个涉及用非托管语言(如Delphi或C++)编写存根的站点,这些站点除了加载实际的外接程序外,什么都不做。应该执行此操作的接口是IManagedAddin。

我的问题是实现此接口。我已经在VC++中创建了一个简单的插件,实现_IDTExtensibility2的类在Connect.h中,如下所示。然而,我不知道如何实现IManagedAddin::Load来将我的外接程序加载到Outlook中,而且似乎也没有太多关于这方面的文档。如有任何帮助,我们将不胜感激!

编辑:更新了下面的代码

// Connect.h : Declaration of the CConnect

#pragma once
#include "resource.h"       // main symbols
#include "NativeAddin_i.h"
#include "IManagedAddin.h"
#include <Windows.h>
#include <iostream>



#if defined(_WIN32_WCE) && !defined(_CE_DCOM) && !defined(_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA)
#error "Single-threaded COM objects are not properly supported on Windows CE platform, such as the Windows Mobile platforms that do not include full DCOM support. Define _CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA to force ATL to support creating single-thread COM object's and allow use of it's single-threaded COM object implementations. The threading model in your rgs file was set to 'Free' as that is the only threading model supported in non DCOM Windows CE platforms."
#endif

using namespace ATL;

// CConnect

class ATL_NO_VTABLE CConnect :
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CConnect, &CLSID_Connect>,
    public IDispatchImpl<IConnect, &IID_IConnect, &LIBID_PixelLLib, /*wMajor =*/ 1, /*wMinor =*/ 0>,
    public IDispatchImpl<_IDTExtensibility2, &__uuidof(_IDTExtensibility2), &LIBID_AddInDesignerObjects, /* wMajor = */ 1, /* wMinor = */ 0>
{
public:
    static Outlook::_Application* outlookApp;
    static ext_ConnectMode connectMode;
    static LPDISPATCH addInInst;
    static SAFEARRAY** customArr;
    static UINT_PTR timerId;

    CConnect()
    {
    }

DECLARE_REGISTRY_RESOURCEID(106)


BEGIN_COM_MAP(CConnect)
    COM_INTERFACE_ENTRY(IConnect)
    COM_INTERFACE_ENTRY2(IDispatch, _IDTExtensibility2)
    COM_INTERFACE_ENTRY(_IDTExtensibility2)
END_COM_MAP()



    DECLARE_PROTECT_FINAL_CONSTRUCT()

    HRESULT FinalConstruct()
    {
        return S_OK;
    }

    void FinalRelease()
    {
    }

    static VOID CALLBACK TimerCallback(HWND hWnd, UINT nMsg, UINT timerId, DWORD dwTime)
    {
        KillTimer(NULL, timerId);

        HRESULT hr;
        BSTR manifestUrl = SysAllocString(L"file://path/to/manifest.vsto");

        // load add-in
        IID clsid;
        hr = IIDFromString(OLESTR("{99D651D7-5F7C-470E-8A3B-774D5D9536AC}"), &clsid); // VSTOAddinLoader CLSID

        IID iid;
        hr = IIDFromString(OLESTR("{B9CEAB65-331C-4713-8410-DDDAF8EC191A}"), &iid);
        IManagedAddin* loader;
        hr = CoCreateInstance(clsid, NULL, CLSCTX_ALL, iid, (void**)&loader);

        hr = loader->Load(manifestUrl, outlookApp);

        _IDTExtensibility2* ext;
        hr = loader->QueryInterface(IID__IDTExtensibility2, (void**)&ext);

        hr = ext->OnConnection(outlookApp, connectMode, addInInst, customArr);

        MSO::IRibbonExtensibility* ribbon;
        hr = (???)->QueryInterface(MSO::IID_IRibbonExtensibility, (void**)&ribbon);
    }


    STDMETHOD(OnConnection)(LPDISPATCH App, ext_ConnectMode ConnectMode, LPDISPATCH AddInInst, SAFEARRAY * * custom)
    {
        HRESULT hr;
        UINT time = 200;

        Outlook::_Application* app;
        hr = App->QueryInterface(__uuidof(Outlook::_Application), (void**)&app);

        // init static members
        outlookApp = app;
        connectMode = ConnectMode;
        addInInst = AddInInst;
        customArr = custom;

        timerId = SetTimer(NULL, 0, time, (TIMERPROC)&TimerCallback);
        return S_OK;
    }

    STDMETHOD(OnDisconnection)(ext_DisconnectMode RemoveMode, SAFEARRAY * * custom)
    {
        return S_OK;
    }

    STDMETHOD(OnAddInsUpdate)(SAFEARRAY * * custom)
    {
        return S_OK;
    }

    STDMETHOD(OnStartupComplete)(SAFEARRAY * * custom)
    {
        return S_OK;
    }

    STDMETHOD(OnBeginShutdown)(SAFEARRAY * * custom)
    {
        return S_OK;
    }
};

OBJECT_ENTRY_AUTO(__uuidof(Connect), CConnect)
Outlook::_Application* CConnect::outlookApp = NULL;
ext_ConnectMode CConnect::connectMode = ext_cm_AfterStartup;
LPDISPATCH CConnect::addInInst = NULL;
SAFEARRAY** CConnect::customArr = NULL;

UINT_PTR CConnect::timerId = 0;
// IManagedAddin.h
#pragma once
#include "resource.h"
#include "NativeAddin_i.h"

struct __declspec(uuid("B9CEAB65-331C-4713-8410-DDDAF8EC191A"))
IManagedAddin : IUnknown
{
public:
    virtual STDMETHOD(Load)(BSTR bstrManifestUrl, LPDISPATCH pdisApplication) = 0;
    virtual STDMETHOD(Unload)() = 0;
};
// pch.h
#ifndef PCH_H
#define PCH_H

// add headers that you want to pre-compile here
#include "framework.h"

#import "libid:AC0714F2-3D04-11D1-AE7D-00A0C90F26F4" raw_interfaces_only, raw_native_types, named_guids, auto_search, no_namespace
#import "libid:2DF8D04C-5BFA-101B-BDE5-00AA0044DE52" raw_interfaces_only, raw_native_types, named_guids, auto_search, rename_namespace("MSO")
#import "libid:00062FFF-0000-0000-C000-000000000046" raw_interfaces_only, raw_native_types, named_guids, auto_search, rename_namespace("Outlook")

#endif //PCH_H

解决方案

我所做的是等待OnConnection回调被激发并启动计时器--您也许能够使用单独的线程,但在我的例子中,由于某些具有线程关联性的功能,处理必须在主线程上完成。OnConnection将为您提供Outlook.Application对象。

在计时器回调中(Outlook没有查看),使用CoCreateInstance(CLSID_IManagedAddin, ...)创建IManagedAddinCOM对象的实例。调用IManagedAddin::Load。路径的格式必须为file://c:/the/folder/myaddin.vsto

IDTExtensibility2接口QIIManagedAddin对象,并使用从本机OnConnection回调保存的参数调用IDTExtensibility2::OnConnection()

如果Outlook已经调用了OnStartupComplete的C++实现,请在VSTO加载项上调用OnStartupComplete。如果没有,您可以稍后再进行。

QI forIRibbonExtensibility,调用IRibbonExtensibility::GetCustomUI。或者,您可以在C++插件中对Ribbon XML进行硬编码。请注意,如果Outlook在C++外接程序上调用IRibbonExtensibility::GetCustomUI,并且您必须在有机会运行计时器代码之前将调用委托给VSTO外接程序,则您别无选择,只能立即调用上述代码,而不是在计时器回调中调用,因为GetCustomUI无法推迟。

如果您使用任务窗格(即ICustomTaskPaneConsumer接口由您的VSTO插件实现),您的C++插件也必须实现它(除了IDTExtensibility2ICustomTaskPaneConsumer接口)。齐IManagedAddinIServiceProvider接口,使用它调用IServiceProvider::QueryService(GUID_NULL, IID_ICustomTaskPaneConsumer, ...)。然后调用ICustomTaskPaneConsumer.CTPFactoryAvailable-请注意,ICustomTaskPaneConsumer不是来自您的VSTO插件的IDTExtensibility2接口,而是通过IManagedAddin接口上的IServiceProvider对象。

相关文章