Reputation: 300
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\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!!!
Upvotes: 1
Views: 1101
Reputation: 300
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
}
Upvotes: 1
Reputation: 66255
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/
Upvotes: 1