I haven't used c++ much in years, and have no experience with using COM from c++. I've written some code in c# using Office interop to work with Word, Excel, and PowerPoint, but am struggling to port that to c++. I've found Office Automation Using Visual C++, which isn't super helpful. It describes 3 ways to do it:
with the #import directive (which it calls buggy and discourages), with MFC, or with pure c++.
The code sample for pure c++ is extremely old-school, and looks to have been written at least 15 years ago. And instead of coding against a strongly-typed interface, it does everything with strings and variants.
I'm trying to do the best of all worlds, the clean code of MFC, but in a modern c++/winrt environment, with smart pointers, etc. In c# for example, I can do:
Windows.Win32.PInvoke.CLSIDFromProgIDEx("Word.Application", out Guid clsid);
Word.Application app = Windows.Win32.PInvoke.GetActiveObject(clsid, null, out object obj) as Word.Application;
app.Activate();
In c++ I have:
CLSID clsid;
check_hresult(CLSIDFromProgID(L"Word.Application", &clsid));
com_ptr<::IUnknown> unk;
check_hresult(GetActiveObject(clsid, NULL, unk.put()));
But then how do I actually convert it to the interface?
Thanks for the comments! I was able to get it working, for Word, and also Excel and PowerPoint, using #import
. Despite that old documentation saying otherwise, I've not found it to be buggy, though certainly much harder to consume than from c#. Here's a code sample showing calling each of the different interfaces:
#import "C:\Program Files\Microsoft Office\Root\VFS\ProgramFilesCommonX64\Microsoft Shared\OFFICE16\MSO.dll"
using namespace Office;
#import "C:\Program Files\Microsoft Office\root\Office16\MSWORD.OLB" raw_native_types auto_rename
#import "C:\Program Files\Microsoft Office\root\Office16\MSPPT.OLB" raw_native_types
#import "C:\Program Files\Microsoft Office\root\Office16\EXCEL.EXE" raw_native_types auto_rename
using namespace winrt;
using namespace std;
using namespace wil;
using namespace Windows::Foundation;
template <typename T>
impl::com_ref<T> GetApplication(LPCOLESTR programId)
{
CLSID clsid{};
check_hresult(CLSIDFromProgID(programId, &clsid));
com_ptr<::IUnknown> unk;
impl::com_ref<T> app = SUCCEEDED(GetActiveObject(clsid, NULL, unk.put()))
? unk.as<T>()
: create_instance<T>(clsid, CLSCTX_LOCAL_SERVER);
return app;
}
int main()
{
init_apartment();
{
auto app = GetApplication<PowerPoint::_Application>(L"PowerPoint.Application");
unique_bstr path{};
if (app->ActivePresentation)
{
path.reset(app->ActivePresentation->FullName);
wcout << path.get() << endl;
}
}
{
auto app = GetApplication<Excel::_Application>(L"Excel.Application");
wcout << app->ActiveWorkbook->GetFullName(0) << endl;
}
{
auto app = GetApplication<Word::_Application>(L"Word.Application");
wcout << app->ActiveDocument->FullName << endl;
}
}
Unfortunately, you have to use variants for various operations with the API, here's an example of how I did that:
unique_bstr path{};
unique_variant page{};
if (app->ActiveDocument)
{
path.reset(app->ActiveDocument->FullName);
wcout << path.get() << endl;
page.reset(app->Selection->Information[Word::WdInformation::wdActiveEndPageNumber]);
}
if (path)
{
unique_variant pathVariant{};
InitVariantFromString(path.get(), &pathVariant);
app->Documents->Open(&pathVariant);
app->Activate();
unique_variant goToPageVariant{};
if (SUCCEEDED(InitVariantFromInt32(Word::WdGoToItem::wdGoToPage, &goToPageVariant)))
{
unique_variant nullVariant{};
app->ActiveWindow->Selection->GoTo(&goToPageVariant, &nullVariant, &page, &nullVariant);
}
}