Search code examples
c++ms-wordcomms-officeoffice-automation

What is the correct, modern, c++ to call the Office automation COM APIs


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?


Solution

  • 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);
            }
        }