Implementing WinRT Interfaces Defined Using WRL/C++ with WRL/C++, C++/CX and C#
This article demonstrates how to implement interfaces defined for a WinRT component using WRL and C++.
Introduction
This 3rd article explores the most native way of building WinRT components with Visual Studio 2012. The lowest level of implementation of WinRT components is a COM based technology that has been updated for Windows 8 modern style. In COM you were using ATL (ActiveX Template Library) to develop components, in WinRT the lowest framework provided by Microsoft is WRL (Windows Runtime Library). WRL is what ATL was to COM, a C++ template library that provides classes to develop WinRT components directly in C++:
In the previous article I defined interfaces in C++/CX and then implemented those interfaces with C++/CX and C#. Let's now revisit the creation of those interfaces and use WRL/C++ to define them and implement them. Then we'll implement them with C++/CX and C#. This will give three different ways of implementing the same interfaces, two native technologies that are WRL/C++ and C++/CX, and one managed technology which is C#. As C# is based on the CLR, any other .NET language can be use and the code will be very similar.
Once again while writing this code I discovered issues that might be bugs, but so far I don't have any confirmation from Microsoft, so what I'm writing in this article is valid only at the time of the publication. Stay tuned for any update!
Background
I suggest that before going through this article you first read the first and the second article I previously wrote. As I just mentioned, as WinRT is fully based on the COM technology introduced in Windows 3.11 for OLE (Object Linking and Embedding) you can also read some COM reference book or tutorial to know about this technology if you're not yet familiar with it.
The Interfaces
In the previous articles I defined three simple interfaces IPerson
,
ICitizen
and IAddress
to illustrate some
fundamentals of
interface/component development. In C++/CX and C# the compilers are
generating interfaces with the public part of the implementation of the
components. In COM an object only exists through its interfaces, so it
is necessary to design those interfaces and build your architecture
with them.
With WRL interfaces must be defined using MIDL (Microsoft Interface Description Language). This MIDL although similar to the one in COM seems to have some major differences.
The interfaces are defines as follows.
namespace WRLCompV1
{
interface IPerson;
interface IAddress;
interface ICitizen;
interface ISaveable;
runtimeclass PersonClass;
runtimeclass AddressClass;
runtimeclass CitizenClass;
[uuid(0d585932-fbc4-4b0a-90b5-ccf34aefd4c6)]
[version(COMPONENT_VERSION)]
interface IPerson : IInspectable
{
[propget] HRESULT Name([out, retval] HSTRING* value);
[propput] HRESULT Name([in] HSTRING value);
[propget] HRESULT Surname([out, retval] HSTRING* value);
[propput] HRESULT Surname([in] HSTRING value);
}
[uuid(497783FC-D66D-4DF6-AAFC-C4D879AB22F1)]
[version(COMPONENT_VERSION)]
interface IAddress : IInspectable
{
[propget] HRESULT Street([out, retval] HSTRING* value);
[propput] HRESULT Street([in] HSTRING value);
[propget] HRESULT City([out, retval] HSTRING* value);
[propput] HRESULT City([in] HSTRING value);
}
[uuid(C3F9CEA3-B897-4A79-BF6C-02B5DF4DB77D)]
[version(COMPONENT_VERSION)]
interface ISaveable : IInspectable
{
HRESULT CanSave([out, retval] boolean *value);
}
[uuid(863571FC-4CBB-47E8-8BD3-2709D5CB7D0D)]
[version(COMPONENT_VERSION)]
interface ICitizen : IInspectable
{
[propget] HRESULT Name([out, retval] HSTRING* value);
[propput] HRESULT Name([in] HSTRING value);
[propget] HRESULT Surname([out, retval] HSTRING* value);
[propput] HRESULT Surname([in] HSTRING value);
[propget] HRESULT Address([out, retval] IAddress** value);
[propput] HRESULT Address([in] IAddress* value);
}
[version(COMPONENT_VERSION)]
[activatable(COMPONENT_VERSION)]
runtimeclass PersonClass
{
[default] interface IPerson;
interface ISaveable;
}
[version(COMPONENT_VERSION)]
[activatable(COMPONENT_VERSION)]
runtimeclass AddressClass
{
[default] interface IAddress;
interface ISaveable;
}
[version(COMPONENT_VERSION)]
[activatable(COMPONENT_VERSION)]
runtimeclass CitizenClass
{
[default] interface ICitizen;
interface IPerson;
interface ISaveable;
}
}
This MIDL description of the interfaces when compiled with C++ will
generate a .h header file that contains all the C and C++ definition of
the interfaces. All interfaces have to inherit from IInspectable
.
So
far I discovered that unlike the MIDL of COM it is not possible to use
inheritance of anything else than IInspectable
, the
compiler prevents
it even if the base interface you are using already inherits from
IInspectable
. You can't neither inherit from IInspectable
and an other
interface. I don't have any confirmation from Microsoft if it is a
limitation or a bug. In my opinion this is a serious limitation and I
don't really understand why they did that. However, it is still possible
for a component to implement several distinct interfaces.
When you create your MIDL code you must first declare the interfaces and the runtime classes in that order, then the interfaces must be defined and then the runtime classes. If you define a runtime class that uses an interface that hasn't been defined yet it won't compile, even if you defined it afterward in the same file.
When you compile the MIDL, the compiler generates the class headers for the interfaces and it defines as an external the runtime class name strings.
The C++ code is created under the namespace ABI and you then find your own namespace. This is a Microsoft design decision to put all the WRL code under the namespace ABI.
There are few differences with the legacy COM components, the component that you create are not registered globally in the system registry. When the application is distributed through the Windows Store all component must be included in the package and cannot be shared with other Apps. Only if you distribute your application within a corporation you will be able to share components with different application. This restriction implies that you basically cannot build an architecture with a plug-in mechanism where you can freely extend an application distributed with the Windows Store.
Visual Studio 2012 doesn't come with a project template for WRL development in C++, you have to get a rudimentary one from the Online templates and import it. The template name isWRLClassLibrary
. This
template won't generate a lot of code for you and there is no wizard to
add method or property to a class, you'll have to do everything
manually. This project template is in fact less powerful than the one
provided in Visual C++ to develop COM components with ATL but the
framework is now simpler than the original ATL.
Let's have a look at the implementation of those interfaces in C++.
Implementing the interfaces with C++ and WRL
When you create the project the template comes with a sample implementation class, any other class has to be done manually. Fortunately the necessary code to implement a WinRT component is quite light. The class needs to inherit from the RuntimeClass template class. There are several templates provided depending the number of interfaces your component has to implement.
namespace ABI { namespace WRLCompV1
{
/**
WRL component class for the IPerson interface. Also implements ISaveable as
a seperate interface
*/
class PersonClass: public RuntimeClass<IPerson, ISaveable>, protected SaveableHelper
{
InspectableClass(L"WRLCompV1.PersonClass", BaseTrust)
private:
std::wstring m_name;
std::wstring m_surname;
public:
PersonClass()
{
}
public:
// IPerson
IFACEMETHODIMP get_Name(_Out_ HSTRING* value);
IFACEMETHODIMP put_Name(_In_ HSTRING value);
IFACEMETHODIMP get_Surname(_Out_ HSTRING* value);
IFACEMETHODIMP put_Surname(_In_ HSTRING value);
// ISaveable
IFACEMETHODIMP CanSave(_Out_ boolean* value);
protected:
virtual boolean CanSaveImpl();
};
ActivatableClass(PersonClass);
// Implementation of IPerson
StringProperty(PersonClass, Name, m_name);
StringProperty(PersonClass, Surname, m_surname);
// Implementation of ISaveable
boolean PersonClass::CanSaveImpl()
{
return m_name.length() > 0 && m_surname.length() > 0;
}
CanSaveMethod(PersonClass);
}}
A WinRT runtime class will always have to inherit from the template
class RuntimeClass
. A macro is provided to implement the IInspectable
interface (and the IUnknown
at the same time) and another
macro has to
be used in the implementation part of the class to make the class
activatable.
I used some predifined macros such as IFACEMETHODIMP
, STDMETHODIMP
to
declare and implement the property methods of the class and I created a
simple macro
StringProperty
to implement a the methods of a get/set
property. If
you're not familiar with C++, macros are very popular with C++
developer and are a powerful mean to simplify the code.
To implement the ISaveable
interface which is to be
implemented by the
three components I have tried to use a base class but I couldn't
achieve exactly what I wanted to do because of some pattern used by the
RuntimeClass
. However with a simple macro I made it almost
like it
should be.
While implementing the ICitizen
class I ran into a
strange issue and I
couldn't use the ComPtr
class. The ICitizen
object has a composition
with an IAddress
object. Normally the IAddress
pointer should be
managed with a ComPtr
class, but it appears that the
release sequence
of the IAddress
pointer has some issue if the get Address
property of
ICitzen
is called.
To workaround this problem I used the raw reference counting of COM
calling manually AddRef()
and Release()
of IUnknown
.
My guess is that the problem occurs when the ComPtr
is
used as an instance member of the runtime
class as in other cases it works perfectly.
The code of the CitizenClass
is the given there (I
removed some parts
that don't relate to the ComPtr
issue)
class CitizenClass: public RuntimeClass<ICitizen, IPerson, ISaveable>, protected SaveableHelper
{
InspectableClass(L"WRLCompV1.CitizenClass", BaseTrust)
private:
std::wstring m_name;
std::wstring m_surname;
ABI::WRLCompV1::IAddress* m_pAddress;
public:
CitizenClass()
{
m_pAddress = nullptr;
ComPtr<ABI::WRLCompV1::IAddress> pAddress;
HStringReference activableClassId(L"WRLCompV1.AddressClass");
HRESULT hr = ActivateInstance<ComPtr<ABI::WRLCompV1::IAddress>>(activableClassId.Get(), &pAddress);
// Detach the pointer from the ComPtr class and manually call AddRef()
m_pAddress = pAddress.Detach();
m_pAddress->AddRef();
}
~CitizenClass()
{
// When the class is destroyed, release the reference of IAddress
if (m_pAddress != nullptr)
{
m_pAddress->Release();
}
}
public:
// IPerson, ICitizen
...
};
// Implementation of ICitizen
STDMETHODIMP CitizenClass::get_Address(ABI::WRLCompV1::IAddress** value)
{
HRESULT hr = E_POINTER;
if (value != nullptr)
{
*value = m_pAddress;
hr = S_OK;
}
return hr;
}
STDMETHODIMP CitizenClass::put_Address(ABI::WRLCompV1::IAddress* value)
{
HRESULT hr = E_POINTER;
if (value != nullptr)
{
// Release the previously used IAddress
if (m_pAddress != nullptr)
{
m_pAddress->Release();
}
// Set the given one and increase the reference count
m_pAddress = value;
m_pAddress->AddRef();
hr = S_OK;
}
return hr;
}
// Implementation of ISaveable
boolean CitizenClass::CanSaveImpl()
{
boolean canSave = true;
ComPtr<ABI::WRLCompV1::ISaveable> pSaveable;
ComPtr<ABI::WRLCompV1::IAddress> pAddress = m_pAddress;
HRESULT hr = pAddress.As(&pSaveable);
if (SUCCEEDED(hr))
{
hr = pSaveable->CanSave(&canSave);
}
return m_name.length() > 0 && m_surname.length() > 0 && canSave;
}
Implementing the interfaces with C++/CX and C#
Implementing the given three interfaces with C++/CX and C# is very simple, you simply need to add a reference to the WRL DLL to the project and use the namespace where the interfaces are declared. Note that when you use the namespace in C++/CX or C# the root namespace ABI doesn't exist, it is only necessary in C++ with WRL. In fact when you import the DLL in your project it is through the windows metadata file and this file doesn't contain any reference to ABI.
In C++/CX you don't need to include any .h for your component as anything the compiler needs is provided when referencing the DLL to the project. You can eventually use the using namespace statement. The following is the code of the C# implementation. The C++/CX is very similar and basically the same as an implementation given in the first article.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WRLCompV1;
namespace WinRTCompCsharpWRLItf
{
/// <summary>
/// The class Citizen implements in C# the following interfaces of WRLCompV1 namespace:
/// - ICitizen
/// - IPerson
/// - ISaveable
/// </summary>
public sealed class Citizen : ICitizen, IPerson, ISaveable
{
private IPerson person;
private IAddress address;
public Citizen()
{
person = new Person();
address = new Address();
}
#region Explicit implementation of IPerson
string IPerson.Name
{
get
{
return person.Name;
}
set
{
person.Name = value;
}
}
string IPerson.Surname
{
get
{
return person.Surname;
}
set
{
person.Surname = value;
}
}
#endregion
#region Explicit implementation of ICitizen
IAddress ICitizen.Address
{
get
{
return address;
}
set
{
address = value;
}
}
string ICitizen.Name
{
get
{
return ((IPerson) this).Name;
}
set
{
((IPerson)this).Name = value;
}
}
string ICitizen.Surname
{
get
{
return ((IPerson)this).Surname;
}
set
{
((IPerson)this).Surname = value; ;
}
}
#endregion
#region Explicit implementation of ISaveable
public bool CanSave()
{
return
((ISaveable)person).CanSave() &&
((ISaveable)address).CanSave();
}
#endregion
}
}
Like in the previous article about C# implementation of a C++/CX interface, you need to explicitly implement the given interfaces to avoid a bug of the C# compiler.
The Unit Test using WRL and C++
I wrote some simple unit test to demonstrate the creation of the different components using WRL/C++. This is the only way that you can use to create a WinRT component the old COM fashion way. You don't need to use GUID, WRL provides some more friendly way to create the components.
Although only one set of component is implemented in C++ with WRL,
they can all be loaded by C++ code. The creation of an instance of a
WinRT component with WRL is done using the method
Windows::Foundation::ActivateInstance()
. The flollowing
code shows how
to use this static method.
template<typename T>
void CreateComponent(const wchar_t* activableClassId,
::Microsoft::WRL::Details::ComPtrRef<ComPtr<T>> pInstance)
{
HStringReference runtimeClass(activableClassId);
HRESULT hr = Windows::Foundation::ActivateInstance< ComPtr<T>>( runtimeClass.Get(), pInstance );
Assert::IsTrue(SUCCEEDED(hr));
}
With this method it is possible to create an instance of an object by
its name, like you would have done using COM anc CoCreateInstance()
.
This way you could implement a pseudo plug-in mechanism where you could
list the name of the component in an XML file for example, giving you
the ability to add new components without recompiling the code as long
as they implement expected interfaces. However, you will need to
distribute all the components with the package of the application, they
can't be delivered separately except if your application is an
enterprise application not deployed through the Windows store.
The unit test also demonstrate how implementations with different technologies can be mixed without any problem. The code of the test method is there.
void TestMixCitizenAddress(ComPtr<ABI::WRLCompV1::ICitizen> pICitizen,
const wchar_t* wsName,
const wchar_t* wsSurname,
ComPtr<ABI::WRLCompV1::IAddress> pIAddress,
const wchar_t* wsStreet,
const wchar_t* wsCity,
const wchar_t* wsStreet2,
const wchar_t* wsCity2)
{
// Get the IPerson interface of ICitizen
ComPtr<ABI::WRLCompV1::IPerson> pIPerson;
HRESULT hr = pICitizen.As(&pIPerson);
Assert::IsTrue(SUCCEEDED(hr));
// Test the IPerson (of ICitizen...)
TestIPerson(pIPerson, wsName, wsSurname);
ComPtr<ABI::WRLCompV1::IAddress> pCitizenAddress;
hr = pICitizen->get_Address(&pCitizenAddress);
Assert::IsTrue(SUCCEEDED(hr));
// Test the integrated address
TestIAddress(pCitizenAddress, wsStreet, wsCity);
// Test the separate address
TestIAddress(pIAddress, wsStreet2, wsCity2);
// Set Address of Citizen with the separate address
hr = pICitizen->put_Address(pIAddress.Get());
Assert::IsTrue(SUCCEEDED(hr));
// Get the Address of Citizen
hr = pICitizen->get_Address(&pCitizenAddress);
Assert::IsTrue(SUCCEEDED(hr));
HSTRING hStreet;
HSTRING hCity;
// Get the Street and the City
TestGetProperty(pCitizenAddress, Street, hStreet);
TestGetProperty(pCitizenAddress, City, hCity);
HString street;
HString city;
street.Set(hStreet);
city.Set(hCity);
// Validate the test
Assert::AreEqual(wsStreet2, street.GetRawBuffer(nullptr));
Assert::AreEqual(wsCity2, city.GetRawBuffer(nullptr));
}
Those of you who have experience with COM will recognize the structure of the code as WRL is very similar to ATL. The complete code of the components and of the unit test is given in the attached solution.
Points of Interest
Like the previous articles the class hierarchy that I used is very simple but it illustrate many possibilities of WinRT components and WRL. I ran into many issues, some that I could solve easily and some that are bugs of the compilers or of the framework, but fortunately they currently all have a workaround.
WinRT components also have many limitations compared to legacy COM components and I find some of those limitations quite frustrating specially if you are a COM power user.
Currently WRL and the new technologies like type projection are only available for Windows store application. It would be very useful if Microsoft could provide the modern features like type projection to the legacy COM technology for the regular windows platform.