April 24th, 2020

How do I use C++/WinRT to implement a classic COM interface that derives from another classic COM interface?

The C++/WinRT library can be used to implement both Windows Runtime interfaces as well as classic COM interfaces. One feature of classic COM that is absent (intentionally) from the Windows Runtime is interface derivation. If you’re writing a class that needs to implement a derived COM interface, how do you express it? (The WRL library calls this a “chained interface”.)

For concreteness, let’s suppose that you are implementing IFileSystemBindData and IFileSystemBindData2.

The naïve way is to say that you implement both interfaces:

struct MyFileSystemBindData :
    implements<MyFileSystemBindData,
        IFileSystemBindData,
        IFileSystemBindData2>
{
    // IFileSystemBindData
    HRESULT SetFindData(const WIN32_FIND_DATAW* pfd) override;
    HRESULT GetFindData(WIN32_FIND_DATAW* pfd) override;

    // IFileSystemBindData2
    HRESULT SetFileID(LARGE_INTEGER liFileID) override;
    HRESULT GetFileID(LARGE_INTEGER *pliFileID) override;
    HRESULT SetJunctionCLSID(REFCLSID clsid) override;
    HRESULT GetJunctionCLSID(CLSID *pclsid) override;
};

If you do this, you get ambiguous cast errors because the Query­Interface provided by the implements template ends up doing something like this:

if (is_guid_of<IFileSystemBindData>(iid)) {
  *result = static_cast<IFileSystemBindData*>(this);
} else if (is_guid_of<IFileSystemBindData2>(iid)) {
  *result = static_cast<IFileSystemBindData2*>(this);
}

The cast to IFileSystemBindData* is ambiguous because the compiler can’t tell whether you want the IFileSystemBindData that is the immediate base class, or whether you want the IFileSystemBindData that is the base class of the IFileSystemBindData2 interface.

But you didn’t need to do that anyway. The COM interfaces derive from each other, so you probably want them to share a vtable. Declaring that you implement both interfaces means that you get two vtables (one for each interface) rather than a shared vtable.

The way to define your object is to say that you implement only the derived interface:

struct MyFileSystemBindData :
    implements<MyFileSystemBindData,
        IFileSystemBindData2>
{
    ...
};

This gets rid of the ambiguous cast, because there is now only one way to get a IFileSystemBindData.

However, you also need to get the Query­Interface to respond to IID_IFileSystemBindData.

To do that, you can overload the winrt::is_guid_of function so that a check for IFileSystemBindData2 includes a test for IFileSystemBindData.

namespace winrt
{
  template<>
  bool is_guid_of<IFileSystemBindData2>(guid const& id) noexcept
  {
    return is_guid_of<IFileSystemBindData2, IFileSystemBindData>(id);
  }
}

This takes advantage of the variadic template overload of is_guid_of introduced in PR 107.

 

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

5 comments

Discussion is closed. Login to edit/delete existing comments.

  • James Touton

    Isn’t this a global solution to a local problem? This looks like an ODR violation waiting to happen.

    • Raymond ChenMicrosoft employee Author

      Fortunately, this is a global solution to a global problem. The fact that IFileSystemBindData2 derives from IFileSystemBindData2 is not a per-file opinion. And is_guid_of is specializable for exactly this purpose.

      • Tim Weis

        This may be a global solution to a global problem. I’m not convinced, though, that there is strictly a single possible implementation to any given solution. As such, this is indeed an ODR violation waiting to happen.

      • 紅樓鍮

        IMO however it would be more pleasant if implements automatically detected interface inheritance so that implements<IFileSystemBindData2> would also implement IFileSystemBindData and handle QI for both interfaces correctly…