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 is
WRLClassLibrary
. 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
{
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:
IFACEMETHODIMP get_Name(_Out_ HSTRING* value);
IFACEMETHODIMP put_Name(_In_ HSTRING value);
IFACEMETHODIMP get_Surname(_Out_ HSTRING* value);
IFACEMETHODIMP put_Surname(_In_ HSTRING value);
IFACEMETHODIMP CanSave(_Out_ boolean* value);
protected:
virtual boolean CanSaveImpl();
};
ActivatableClass(PersonClass);
StringProperty(PersonClass, Name, m_name);
StringProperty(PersonClass, Surname, m_surname);
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);
m_pAddress = pAddress.Detach();
m_pAddress->AddRef();
}
~CitizenClass()
{
if (m_pAddress != nullptr)
{
m_pAddress->Release();
}
}
public:
...
};
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)
{
if (m_pAddress != nullptr)
{
m_pAddress->Release();
}
m_pAddress = value;
m_pAddress->AddRef();
hr = S_OK;
}
return hr;
}
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
{
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)
{
ComPtr<ABI::WRLCompV1::IPerson> pIPerson;
HRESULT hr = pICitizen.As(&pIPerson);
Assert::IsTrue(SUCCEEDED(hr));
TestIPerson(pIPerson, wsName, wsSurname);
ComPtr<ABI::WRLCompV1::IAddress> pCitizenAddress;
hr = pICitizen->get_Address(&pCitizenAddress);
Assert::IsTrue(SUCCEEDED(hr));
TestIAddress(pCitizenAddress, wsStreet, wsCity);
TestIAddress(pIAddress, wsStreet2, wsCity2);
hr = pICitizen->put_Address(pIAddress.Get());
Assert::IsTrue(SUCCEEDED(hr));
hr = pICitizen->get_Address(&pCitizenAddress);
Assert::IsTrue(SUCCEEDED(hr));
HSTRING hStreet;
HSTRING hCity;
TestGetProperty(pCitizenAddress, Street, hStreet);
TestGetProperty(pCitizenAddress, City, hCity);
HString street;
HString city;
street.Set(hStreet);
city.Set(hCity);
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.