1
votes

I have a C++ application and I'm writing a new Outlook add-in, which I think will be with VSTO. I want to have communication between then and I'm trying to figure out the best way to do it. On the MS docs they mention how to expose your COM class to external solutions using RequestComAddInAutomationService. I am very new to COM but I read some online and got to the following solution. I read that you're supposed to build the Add-in (for x86 as my Outlook version and not AnyCPU), take the created .tlb file and convert it to .tlh using the #import directive, and then #include the .tlh file to have the appropriate types.

ThisAddin.cs

namespace FirstOutlookAddIn
{

public partial class ThisAddIn
{
    Outlook.Inspectors inspectors;
    private AddInUtilities gUtilities;

    private void ThisAddIn_Startup(object sender, System.EventArgs e)
    {
        inspectors = this.Application.Inspectors;
        inspectors.NewInspector +=
        new Microsoft.Office.Interop.Outlook.InspectorsEvents_NewInspectorEventHandler(Inspectors_NewInspector);
    }

    void Inspectors_NewInspector(Microsoft.Office.Interop.Outlook.Inspector Inspector)
    {
        Outlook.MailItem mailItem = Inspector.CurrentItem as Outlook.MailItem;
        if (mailItem != null)
        {
            if (mailItem.EntryID == null)
            {
                gUtilities.SetMailItem(mailItem);
                mailItem.Subject = "This text was added by using code";
                mailItem.Body = "This text was added by using code";
            }

        }
    }

    protected override object RequestComAddInAutomationService()
    {
        if (gUtilities == null)
            gUtilities = new AddInUtilities();

        return gUtilities;
    }

    private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
    {
        // Note: Outlook no longer raises this event. If you have code that 
        //    must run when Outlook shuts down, see https://go.microsoft.com/fwlink/?LinkId=506785
    }

    #region VSTO generated code

    /// <summary>
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    private void InternalStartup()
    {
        this.Startup += new System.EventHandler(ThisAddIn_Startup);
        this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
    }

    #endregion
}

}

IAddInUtilities.cs

using System.Runtime.InteropServices;
namespace FirstOutlookAddIn
{
    [ComVisible(true)]
    public interface IAddInUtilities
    {
        void MyExportedFunction();
    }
}

AddInUtilities.cs

using Outlook = Microsoft.Office.Interop.Outlook;
using System.Runtime.InteropServices;
namespace FirstOutlookAddIn
{
    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.None)]
    public class AddInUtilities : StandardOleMarshalObject, IAddInUtilities
    {
        Outlook.MailItem globalMailItem;
        public void SetMailItem(Outlook.MailItem item) => globalMailItem = item;
        public void MyExportedFunction()
        {
            globalMailItem.Body = "I was called from outside!";
        }
    }
}

main.cpp

//#import "FirstOutlookAddIn.tlb" named_guids raw_interfaces_only

#include <iostream>
struct IUnknown; // Workaround for "combaseapi.h(229): error C2187: syntax error: 'identifier' was unexpected here" when using /permissive-
#include <Objbase.h>
#include "Debug\FirstOutlookAddIn.tlh"

int main() {
    CoInitializeEx(nullptr, COINIT_MULTITHREADED);
    FirstOutlookAddIn::IAddInUtilities* pIFace;
    // create the object and obtain a pointer to the sought interface
    auto res = CoCreateInstance(
        FirstOutlookAddIn::CLSID_AddInUtilities,
        nullptr,
        CLSCTX_LOCAL_SERVER,
        FirstOutlookAddIn::IID_IAddInUtilities,
        (LPVOID*)&pIFace);
    if (res != S_OK)
    {
        std::cout << "Failed with: " << res;
    }
    auto res1 = pIFace->MyExportedFunction(); // use the object
    std::cout << "Res: " << res1;
    pIFace->Release(); // free the object
    CoUninitialize();
}

The problem is CoCreateInstance returns REGDB_E_CLASSNOTREG Class not registered. The relevant Registry tree looks like that:

HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Classes\CLSID{5008A102-08E5-3F59-AADD-03875524CAD0} = FirstOutlookAddIn.AddInUtilities Computer\HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Classes\CLSID{5008A102-08E5-3F59-AADD-03875524CAD0}\InprocServer32: Computer\HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Classes\CLSID{5008A102-08E5-3F59-AADD-03875524CAD0}\InprocServer32 Computer\HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Classes\CLSID{5008A102-08E5-3F59-AADD-03875524CAD0}\InprocServer32\1.0.0.0: Computer\HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Classes\CLSID{5008A102-08E5-3F59-AADD-03875524CAD0}\InprocServer32\1.0.0.0 Computer\HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Classes\CLSID{5008A102-08E5-3F59-AADD-03875524CAD0}\ProgId = FirstOutlookAddIn.AddInUtilities

What am I doing wrong? Do I understand correctly the possibility here, having my DLL loading inside Outlook.exe and being able to invoke functions in it from an external application? Thanks in advance!!!

2

2 Answers

1
votes

You are not supposed to create an instance of that COM class from your C++ app - create an instance of the Outlook.Application object, use Application.COMAddins collection to get to your addin, then use COMAddin.Object property to retrieve the interface implemented by your addin.
See, for example, https://blogs.msdn.microsoft.com/andreww/2007/01/15/vsto-add-ins-comaddins-and-requestcomaddinautomationservice/

1
votes

After some digging around I figured it out. I'm using Microsoft's CppAutomateOutlook example

It has 2 implementation options, one is using COM's smart pointers (e.g. spMail->Subject = _bstr_t(L"Feedback of All-In-One Code Framework");), and one is using a raw IDispatch interface. I used the second option and modified CoCreateInstance to be GetActiveObject, so I could interact with an already running instance of Outlook. This is my current code:

DWORD WINAPI AutomateOutlookByCOMAPI(LPVOID lpParam)
{
    // Initializes the COM library on the current thread and identifies 
    // the concurrency model as single-thread apartment (STA). 
    // [-or-] CoInitialize(NULL);
    // [-or-] CoCreateInstance(NULL);
    CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);

    // Define vtMissing for optional parameters in some calls.
    VARIANT vtMissing;
    vtMissing.vt = VT_EMPTY;

    // Get CLSID of the server
    CLSID clsid;
    HRESULT hr;

    // Option 1. Get CLSID from ProgID using CLSIDFromProgID.
    LPCOLESTR progID = L"Outlook.Application";
    hr = CLSIDFromProgID(progID, &clsid);
    if (FAILED(hr))
    {
        wprintf(L"CLSIDFromProgID(\"%s\") failed w/err 0x%08lx\n", progID, hr);
        return 1;
    }
    // Option 2. Build the CLSID directly.
    /*const IID CLSID_Application = 
    {0x0006F03A,0x0000,0x0000,{0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}};
    clsid = CLSID_Application;*/

    // Get the IDispatch interface of the running instance

    IUnknown *pUnk = NULL;
    IDispatch *pOutlookApp = NULL;
    hr = GetActiveObject(
        clsid, NULL, (IUnknown**)&pUnk
    );

    if (FAILED(hr))
    {
        wprintf(L"GetActiveObject failed with w/err 0x%08lx\n", hr);
        return 1;
    }

    hr = pUnk->QueryInterface(IID_IDispatch, (void **)&pOutlookApp);
    if (FAILED(hr))
    {
        wprintf(L"QueryInterface failed with w/err 0x%08lx\n", hr);
        return 1;
    }

    _putws(L"Outlook.Application is found");

    IDispatch *comAddins = NULL;
    {
        VARIANT result;
        VariantInit(&result);
        AutoWrap(DISPATCH_PROPERTYGET, &result, pOutlookApp, L"COMAddins", 0);
        comAddins = result.pdispVal;
    }

    IDispatch *myAddin = NULL;
    {
        VARIANT x;
        x.vt = VT_BSTR;
        x.bstrVal = SysAllocString(L"FirstOutlookAddIn");

        VARIANT result;
        VariantInit(&result);
        AutoWrap(DISPATCH_METHOD, &result, comAddins, L"Item", 1, x);
        myAddin = result.pdispVal;

        VariantClear(&x);
    }

    IDispatch *myAddinObj = NULL;
    {
        VARIANT result;
        VariantInit(&result);
        AutoWrap(DISPATCH_PROPERTYGET, &result, myAddin, L"Object", 0);
        myAddinObj = result.pdispVal;
    }

    {
        VARIANT result;
        VariantInit(&result);
        AutoWrap(DISPATCH_METHOD, &result, myAddinObj, L"MyExportedFunction", 0);
    }

    // ... Cleanup code
}