I was given a VSTO Outlook add-in written in VBA, and have been tasked to reduce the start-up time. I'm rather new to the whole add-in and COM objects thing so I need some help.
The add-in takes anywhere from 0.2s to 2.0s to startup, and Outlook disables the plugin if the average startup time is >1000ms. Using the registry hack to force enable the add-in is unfortunately not an option. I've also tested it with an empty add-in, which also can take up to 1.8s to startup. I've searched SO and other such sites for a solution, and across one involving writing a "stub" in an un-managed language such as Delphi or C++, which does nothing but load the actual add-in. The interface that's supposed to do this is IManagedAddin.
My issue is with implementing this interface. I've created a simple add-in in VC++ The class that implements _IDTExtensibility2 is in Connect.h, shown below. However, I don't have a clue how to implement IManagedAddin::Load to load my add-in into Outlook, and there doesn't seem to be a lot of documentation on this. Any help would be much appreciated!
EDIT: Updated code below
// 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
What I did was to wait until OnConnection callback fired and started a timer - you might be able to use a separate thread, but in my case the processing had to be done on the main thread due to some functionality that had thread affinity. OnConnection will give you Outlook.Application object.
In the timer callback (Outlook is not looking), create an instance of the IManagedAddin COM object using CoCreateInstance(CLSID_IManagedAddin, ...). Call IManagedAddin::Load. The path must be in the form file://c:/the/folder/myaddin.vsto.
QI the IManagedAddin object for IDTExtensibility2 interface and call IDTExtensibility2::OnConnection() using the parameters saved from the native OnConnection callback.
If Outlook has already called your C++ implementation of OnStartupComplete, call OnStartupComplete on the VSTO addin. If not, you can do it later.
QI for IRibbonExtensibility and call IRibbonExtensibility::GetCustomUI. Or you can hardcode the ribbon XML in the C++ addin. Note that if Outlook calls IRibbonExtensibility::GetCustomUI on your C++ addin, and you have to delegate the call to the VSTO addin before you had a chance to run your timer code, you have no choice but to call the above code immediately rather than in a timer callback since GetCustomUI cannot be postponed.
If you are using task panes (i.e. ICustomTaskPaneConsumer interface is implemented by your VSTO addin), your C++ addin must also implement it (besides the IDTExtensibility2 and ICustomTaskPaneConsumer interfaces). QI IManagedAddin for IServiceProvider interface and use it to call IServiceProvider::QueryService(GUID_NULL, IID_ICustomTaskPaneConsumer, ...). then call ICustomTaskPaneConsumer.CTPFactoryAvailable - note that ICustomTaskPaneConsumer does not come from your VSTO addin's IDTExtensibility2 interface but rather though the IServiceProvider object off the IManagedAddin interface.
So I'm developing an app that uses the desktop duplication API, however, when using the IDXGIDevice interface and trying to access its getParent method I get the following error
class "IDXGIDevice" has no member "GetParent"
When using header files and the scope resolution operator like this
DDAPI.h
#include <d3d11.h>
#include <dxgi1_2.h>
class DDAPI
{
public:
HRESULT InitDDA();
private:
ID3D11Device* m_Device;
}
DDAPI.cpp
HRESULT DDAPI::InitDDA()
{
IDXGIDevice* pDevice = nullptr;
IDXGIAdapter* pAdapter = nullptr;
m_Device->QueryInterface(__uuidof(IDXGIDevice), (void**)&pDevice);
pDevice->GetParent(__uuidof(IDXGIAdapter), (void**)&pAdapter); // Errors Here
}
(I have already initialised ID3D11Device m_Device)
However
If I was to create the class within the DDAPI.cpp file like this, it works and does not give me an error
class DDAPI
{
ID3D11Device* m_Device;
HRESULT InitDDA()
{
IDXGIDevice* pDevice = nullptr;
IDXGIAdapter* pAdapter = nullptr;
m_Device->QueryInterface(__uuidof(IDXGIDevice), (void**)&pDevice);
pDevice->GetParent(__uuidof(IDXGIAdapter), (void**)&pAdapter);
}
};
In the first example, IntelliSense does not show all the other methods that IDXGIDevice has (Just contains the base IUnknown interface methods) but it does in the 2nd example
This might just be an IntelliSense error, as there are no build errors, but if it's showing up as an error, I'm assuming it means I can do this another way
I have an atl-com dll that has an interface IMyInterface. I created a client with #import "Project.tlb" and it was generated the correct Project.tlh and Project.tli. So I included Project.tlb in my client. Now I'm trying to call the IMyInterfacePtr that #import generated like this:
IMyInterfacePtr *i;
CLSID clsid = __uuidof(Test);
REFIID iid = __uuidof(IMyInterfacePtr);
HRESULT hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, iid, (void**) &i);
So using the pointer to interface:
i->MyMethod();
But it didn't recognize "MyMethod", it's only showing the IDispatch interface that is from which my IMyInterface inherits.
What Am I doing wrong?
Thanks!
I am attempting to understand creating/using COM components without the help of MFC/ATL to know its inner workings.
I am using this codeguru article for reference.Following are the steps followed by me.
Created a Wind32 Dll,
Added a MIDL file and declared the interface IAdd and library name DemoMath; compiled the code using MIDL compiler.
Created CAddObj class deriving IAdd interface,provided implementation for IAdd and IUnknown interfaces.
Created class CAddFactory deriving from IClassFactory interface;provided implementation for IClassFactory methods.
Now creating DllGetClassObject to give client an option to invoke this function to get an instance of the class factory.
Following is the code:
#include "stdafx.h"
#include <objbase.h>
#include "AddObjFactory.h"
#include "IAdd_i.c"
STDAPI DllGetClassObject(const CLSID& clsid,
const IID& iid,
void** ppv)
{
//
//Check if the requested COM object is implemented in this DLL
//There can be more than 1 COM object implemented in a DLL
//
if (clsid == CLSID_AddObject)
{
//
//iid specifies the requested interface for the factory object
//The client can request for IUnknown, IClassFactory,
//IClassFactory2
//
CAddFactory *pAddFact = new CAddFactory;
if (pAddFact == NULL)
return E_OUTOFMEMORY;
else
{
return pAddFact->QueryInterface(iid , ppv);
}
}
//
//if control reaches here then that implies that the object
//specified by the user is not implemented in this DLL
//
return CLASS_E_CLASSNOTAVAILABLE;
}
Now where is CLSID_AddObject constant suppose to be defined
or Is it generated while compiling MIDL file(I didn't find it though)?
coclass IDL item will typically get you CLSID:
library Foo
{
//...
[
//...
]
coclass AddObject
{
//...
};
then on your "IAdd_i.c" you are already including:
MIDL_DEFINE_GUID(CLSID, CLSID_AddObject, ...);
this is what defines CLSID_AddObject.
I followed this tuorial to create a COM dll in Visual Basic. http://www.codeproject.com/KB/COM/Basics_of_Idl_file.aspx
I now want to use this dll in a C++ project. I used OLE/COM Viewer to create an .idl file as is described in the second half this tutorial.
http://www.codeproject.com/KB/COM/vb_from_vc.aspx
I compiled the .idl with the midl compiler and included the .h file that was created in my c++ project.
Here is my Visual Basic Code
<ComClass(MyComClass.ClassId, MyComClass.InterfaceId, MyComClass.EventsId)> _
Public Class MyComClass
#Region "COM GUIDs"
' These GUIDs provide the COM identity for this class
' and its COM interfaces. If you change them, existing
' clients will no longer be able to access the class.
Public Const ClassId As String = "46604f8a-85a2-4027-9728-0390534c9209"
Public Const InterfaceId As String = "30274029-711d-459a-9270-f9d73ad8737f"
Public Const EventsId As String = "5e234d69-5263-4001-86ff-c475b113a77d"
#End Region
' A creatable COM class must have a Public Sub New()
' with no parameters, otherwise, the class will not be
' registered in the COM registry and cannot be created
' via CreateObject.
Public Sub New()
MyBase.New()
End Sub
Protected Overrides Sub Finalize()
MyBase.Finalize()
End Sub
Public Sub DisplayMessage()
MsgBox("Hello from MyComClass!")
End Sub
End Class
Here is my c++ Code
// Declare an HRESULT and a pointer to the clsVBTestClass interface
HRESULT hr;
_MyComClass *IVBTestClass = NULL;
// Now we will intilize COM
hr = CoInitialize(0);
// Use the SUCCEEDED macro and see if we can get a pointer
// to the interface
if(SUCCEEDED(hr))
{
hr = CoCreateInstance( CLSID_MyComClass,
NULL,
CLSCTX_INPROC_SERVER,
IID__MyComClass,
(void**) &IVBTestClass);
// If we succeeded then call the CountStringLength method,
// if it failed then display an appropriate message to the user.
if(SUCCEEDED(hr))
{
long ReturnValue;
_bstr_t bstrValue("Hello World");
// We can test this HR as well if we wanted to
hr = IVBTestClass->DisplayMessage();
hr = IVBTestClass->Release();
}
else
{
}
}
// Uninitialize COM
CoUninitialize();
I receive the following errors when I compile my c++ project
error LNK2001: unresolved external symbol _CLSID_MyComClass
error LNK2001: unresolved external symbol IID_MyComClass
Could someone help me figure out what I am doing wrong?
If you did not get to the last part of creating a Type Library, that is important.
You then need an #import statement in your C++ code to use the .tlb file (or .dll if the type library is embedded in the dll, which is common).
#import is the equivalent to including a header file with COM, but generates a .tlh file (header) and a .tli (implementation) automatically.