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

Raymond Chen

Raymond

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.

 

5 comments

Comments are closed. Login to edit/delete your existing comments

  • Avatar
    James Touton

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

    • Raymond Chen
      Raymond ChenMicrosoft employee

      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.

      • Avatar
        紅樓鍮

        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…

      • Avatar
        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.