Using IManagedAddin in VC++ to load VSTO VBA add-ins - c++

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.

Related

C++ Innacessible methods of an exnternal struct within a scope resolution operator

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

Issue with initializing COM object

I compiled the following VB code and got the dll ("TestVB.dll") and tlb ("TestVB.tlb") files as output.
Imports System
Imports System.Runtime.InteropServices
Namespace TesterNS
<ComVisible(True),
Guid("4B673F5A-A953-4C20-9A90-8F94ED2F6DDF"),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)>
Public Interface _Tester
Function GetMonth() As Integer
End Interface
<ComVisible(True),
Guid("FEC833EE-37E9-4406-9344-8A8BD5C43B07"),
ClassInterface(ClassInterfaceType.None),
ProgId("Tester.Numbers1")> Public Class Tester
Implements _Tester
Public Tester()
Public Function GetMonth() As Integer Implements _Tester.GetMonth
GetMonth = DateTime.Now.Month
End Function
End Class
End Namespace
I selected the
Register for COM interop
option in the project settings for the VB project.
I am trying to access the above functionality from a C++ code.
I included the following import statement in the stdafx.h file in C++.
#import "TestVB.tlb" no_function_mapping, no_namespace, named_guids
and trying to access the COM object as follows:
HRESULT hr = CoInitialize(NULL);
_TesterPtr t1 = NULL;
CLSID cls;
std::string s = "";
bool flag = false;
hr = CLSIDFromProgID(L"Tester.Numbers1", &cls);
if (SUCCEEDED(hr))
{
hr = CoCreateInstance(cls, NULL, CLSCTX_INPROC_SERVER, __uuidof(_Tester), (void**)&t1);
if (SUCCEEDED(hr))
s = "success";
else
s = "fail";
}
I am seeing hr = REGDB_E_CLASSNOTREG Class not registered error after making the CoCreateInstance call.
I am not sure what I am doing wrong.
In my C++ application, there is a call to AfxOleInit() before CoInitialize(NULL). If I remove the call to CoInitialize(NULL), I was able to get it working.

COM - Implementing DllGetClassObject

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.

Windows 7 taskbar state with minimal code

What would be the shortest code to set the state of a Windows 7 taskbar button for a known window handle?
The goal is to write a console utility that changes the progress and state (colour) of the console window taskbar item from a batch script. While the script performs different tasks, the taskbar item of its console window should represent the current state.
I get the window handle with the GetConsoleWindow() function, but then it seems to require loads of COM and Shell API stuff that I don't understand. One example I've found uses a whole GUI application with MFC to demonstrate the API, but most of it is way too complicated for my little tool and I don't understand it well enough to remove the stuff I don't need.
The tool should compile on Windows 7 with VS2010 (C++) but also run on earlier Windows versions (doing nothing if a feature is not available).
I created a class to set the progress in the Win7 taskbar for a project at one time. It's a wrapper for the ITaskBarList3 interface available from the Windows Shell. It's specifically done with ITaskBarList3.SetProgressState and ITaskBarList3.SetProgressValue functions.
This is the code I dug up:
#include <shobjidl.h>
#include <windows.h>
#pragma comment(lib, "Shell32.lib")
#pragma comment(lib, "Ole32.lib")
class Win7TaskbarProgress
{
public:
Win7TaskbarProgress();
virtual ~Win7TaskbarProgress();
void SetProgressState(HWND hwnd, TBPFLAG flag);
void SetProgressValue(HWND hwnd, ULONGLONG ullCompleted, ULONGLONG ullTotal);
private:
bool Init();
ITaskbarList3* m_pITaskBarList3;
bool m_bFailed;
};
Win7TaskbarProgress::Win7TaskbarProgress()
{
m_pITaskBarList3 = NULL;
m_bFailed = false;
}
Win7TaskbarProgress::~Win7TaskbarProgress()
{
if (m_pITaskBarList3)
{
m_pITaskBarList3->Release();
CoUninitialize();
}
}
void Win7TaskbarProgress::SetProgressState( HWND hwnd, TBPFLAG flag )
{
if (Init())
m_pITaskBarList3->SetProgressState(hwnd, flag);
}
void Win7TaskbarProgress::SetProgressValue( HWND hwnd, ULONGLONG ullCompleted, ULONGLONG ullTotal )
{
if (Init())
m_pITaskBarList3->SetProgressValue(hwnd, ullCompleted, ullTotal);
}
bool Win7TaskbarProgress::Init()
{
if (m_pITaskBarList3)
return true;
if (m_bFailed)
return false;
// Initialize COM for this thread...
CoInitialize(NULL);
CoCreateInstance(CLSID_TaskbarList, NULL, CLSCTX_INPROC_SERVER, IID_ITaskbarList3, (void **)&m_pITaskBarList3);
if (m_pITaskBarList3)
return true;
m_bFailed = true;
CoUninitialize();
return false;
}
Note you still need to call RegisterWindowMessage("TaskbarButtonCreated") and ChangeWindowMessageFilterEx() to setup an message filter before SetProgressValue() can work.
According to the MSDN docs you are supposed to recreate your object each time you get the created message but I found I just had to do the ChangeWindowMessageFilterEx() and it works fine for normal circumstances.

Convert c++ (DLL) project to COM DLL project [closed]

It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 10 years ago.
Hi I have a pure C++ project (DLL) which I would like to convert to COM project, e.g. all public interfaces defined in headers, will now be exposed as COM interfaces (IDL etc...). And the final product should be COM Dll.
How do I start? How do I define Any high level guidelines? good articles?
There are at least two parts to this problem:
First, COM objects are created using CoCreateInstance. CoCreateInstance looks for COM registrations in memory (via CoRegisterClassObject), in the applications manifest as a zero reg COM object, and finally, in the registry.
For zero reg, create an assembly manifest describing your dll so that consumers of your object can add a dependantAssembly reference to their application manifest.
Then, a COM dll needs at least two entry points: DllGetClassObject and DllCanUnloadNow
You implement DllGetClassObject by creating an instance of a factory object - that supports IClassFactory - that can be used to make instances of your actual object.
So, to summarize - a kind of TDD driven approach to implementing a COM dll:
Create a DLL with 'DllGetClassObject' and 'DllCanUnloadNow' entry points.
Create a new GUID to represent your object, and create a assembly manifest describing the COM object your dll (will) contain.
Create a test application, that calls CoCreateInstance with that GUID.
Calling CoCreateInstance should now land up in your DllGetClassObject call. Implement the class factory object.
Implement the CreateInstance method to create a new instance of your c++ class. Ensure that all your interfaces derive from (at least) IUnknown. Call QueryInterface on your own newly created object to get and return the desired interface.
Assuming Visual Studio (Express is ok) is your build environment:
Create a test exe:
// main.cpp
#include <windows.h>
#include <objbase.h>
#include <initguid.h>
DEFINE_GUID(CLSID_RegFreeOcx,0x00000000,0x0000,0x0000,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00);
#if defined _MSC_VER
#if !defined _WINDLL && !defined (_CONSOLE)
#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
#endif
#endif
#pragma comment(linker, "/manifestDependency:\"name='acme.RegFreeOcx' processorArchitecture='*' version='1.0.0.0' type='win32' \"")
int main(){
CoInitialize(NULL);
IUnknown* pUnk;
CoCreateInstance(CLSID_RegFreeOcx,NULL,CLSCTX_ALL,IID_IUnknown,(void**)&pUnk);
if(pUnk)
pUnk->Release();
}
Create the manifest for reg free activation of the COM dll:
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity name="Acme.RegFreeOcx" processorArchitecture="x86" version="1.0.0.0" type="win32" />
<file name = "RegFreeOcx.dll">
<comClass clsid="{00000000-0000-0000-0000-000000000000}" threadingModel="Apartment" />
</file>
</assembly>
Create a dll project
// dllmain.cpp
#include <windows.h>
#pragma comment(linker,"/export:DllGetClassObject=_DllGetClassObject#12,PRIVATE")
#pragma comment(linker,"/export:DllCanUnloadNow=_DllCanUnloadNow#0,PRIVATE")
#include <objbase.h>
#include <initguid.h>
DEFINE_GUID(CLSID_RegFreeOcx,0x00000000,0x0000,0x0000,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00);
#include "MyClassFactory.hpp"
STDAPI DllGetClassObject(REFCLSID clsid, REFIID riid,void** ppvObj)
{
if(CLSID_RegFreeOcx == clsid){
MyClassFactory* pFactory = new MyClassFactory();
HRESULT result = pFactory->QueryInterface(riid,ppvObj);
pFactory->Release();
return result;
}
return E_FAIL;
}
STDAPI DllCanUnloadNow()
{
return E_FAIL;
}
//MyClassFactory.hpp
#include <MyClass.hpp>
class MyClassFactory : public IClassFactory {
volatile ULONG _cRef;
public:
MyClassFactory():_cRef(1){}
virtual ~MyClassFactory(){}
public: // IUnknown
STDMETHODIMP_(ULONG)AddRef(){
return InterlockedIncrement(&_cRef);
}
STDMETHODIMP_(ULONG)Release(){
ULONG result = InterlockedDecrement(&_cRef);
if(!result) delete this;
return result;
}
STDMETHODIMP QueryInterface(REFIID riid, void** ppvObj){
if(riid == IID_IUnknown || riid == IID_IClassFactory)
*ppvObj = (IClassFactory*)this;
else {
*ppvObj=0;
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}
public: // IClassFactory
STDMETHODIMP CreateInstance(IUnknown* pUnkOuter,REFIID riid,void** ppvObj){
if(pUnkOuter)
return E_INVALIDARG;
MyClass* pClass = new MyClass();
HRESULT result = pClass->QueryInterface(riid,ppvObj);
pClass->Release();
return result;
}
STDMETHODIMP LockServer(BOOL fLock){
return E_NOTIMPL;
}
};
And finally, define your own class. You could use IDL to do this but, for other cpp consumers, you can just define it in a header file.
// IMyClass.h
#include <objbase.h>
DEFINE_GUID(IID_MyInterface,0x00000000,0x0000,0x0000,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00);
interface IMyInterface : IUnknown {
STDMETHOD(MyMethod)(void)PURE;
};
Implement the class that implements IMyInterface in a cpp file in much the same way that the ClassFactory was implemented (in terms of the IUnknown methods) - swapping out references to IClassFactory with your own interface.
THe canonical gentle introduction is Inside COM by Dale Rogenson. For real depth, try Essential COM by Don Box
The three big areas to worry about are:
Threading
Memory management and memory ownership conventions]
Types passed across COM interface boundaries - particularly not using STL
THe Box book also covers some of the really nasty things people do with COM - like the free threaded marshaller - which you might be applicable if your library is already thread safe and you want to avoid marshalling penalties.