Developer Preview Warning
This article and its contents are based on the first public developer
preview of Windows 8 and Visual Studio 11. The code snippets and other
information mentioned in the article may thus be subject to significant changes
when the OS/VS hits beta/RTM.
Targeting WinRT/Metro
WinRT is the new API for writing Metro applications on Windows 8. Unlike
traditional Windows APIs, which were C based, WinRT is a COM based C++ API. COM
itself has been significantly improved and enhanced to support modern object
oriented design paradigms. So you now have concepts like inheritance and static
functions (which might appear amusing to traditional COM developers). The big
advantage here has been that this allowed the developer division to bring out
true language parity when it came to releasing compilers for the various
Microsoft languages (Visual C++, C#, VB and now JS/HTML5). They achieved this
through the use of language specific projections where the underlying WinRT
objects are projected into language structures that are semantically
familiar to developers of specific programming languages. I don't really want to
talk about the WinRT programming model here, instead in this article I intend to
quickly go through the fundamentals of using Visual C++ to consume and create
WinRT objects.
Why use C++?
After years of enduring developer frameworks such as Windows Forms, WPF
(Avalon), and recently Silverlight that were clearly meant to be consumed from
C# and VB, and which required a C++ consumer to jump through some serious hoops
to have a shot at using them, this is a very valid question. Well here's the
gist of it. WinRT is native. I'll repeat that once more, this time with an
exclamation. WinRT is native! While the COM based design has made it palatable
for consumption by managed clients, you do incur the price of native-managed
interop when you do that. But then performance is often an overrated concept.
Why save 20 nanoseconds when the database/web-service call will take 2 seconds
anyway? Well, not all apps work that way. There are a substantial number of apps
where even a minor improvement in performance can mean a lot to the end-user.
And the way C++ has evolved, we've reached a point where we don't really have to
be masochists to get better performance. It's naive to pretend that using C++
will be as easy as using C# or VB. But it's equally naive to remain ignorant of
modem changes that the language and its libraries have gone through that makes
it a lot less complicated to use than say 5 years ago. If you are a C++
developer, would you be willing to put in say 15-20% more effort to get 15-30%
more performance? The answer to that question is the rationale for this article.
C++ Component Extensions
Now while WinRT itself was developed partially in C++, it was not designed for direct consumption from C++
callers. It had to support other languages too, and thus the COM based API. Now
when I say COM, I am not being accurate because WinRT is a lot more than COM,
but I'll continue to use the word COM here for lack of a better alternative, and
saying RT in lieu of COM sounds garish. Visual C++ 11 introduces a new
programming model / syntax for creating and consuming WinRT components. They
call it the C++ Component Extensions or C++/CX for short. Syntactically, it's
nearly identical (not 100% though) to the C++/CLI syntax that is used to target
the CLR. So if you've used C++/CLI before (or happened to write a book on it
like this guy I know of), you'll find the syntax really familiar. It's a little
like an alien spaceship landing on earth and then we find that the aliens speak
English but with a Welsh accent. The accent is weird for sure, but it's still
English of sorts.
Well the similarity is purely syntactical though, the semantic meanings and
implications are entirely different. C++/CX is native code, C++/CLI is managed
code. C++/CX allows the developer to focus on the important things, like
designing his code and data structures instead of wasting time getting the COM
calls right. One of the reasons COM struggled to remain popular once .NET was
out was that when doing serious COM, you end up spending way too much time doing
plumbing work instead of focusing on your actual application.
ref new
and ^
(hat)
C++/CLI had the gcnew
keyword added to it which was the CLR
equivalent of the native new
. Similarly C++/CX has the contextual
ref new
keyword to create WinRT objects. ref new
returns a ^
(hat) which is similar to a *
(pointer)
except that it is used with ref-counted COM objects. Here's a code example.
WinRTComponentDll2::WinRTComponent^ comp = ref new WinRTComponentDll2::WinRTComponent();
Notice the call to ref new
(as opposed to new
) and
how the returned variable is of type ^
(and not *
). I
used that syntax to show how the returned type is a hat, normally I'd just have
used auto
.
auto comp = ref new WinRTComponentDll2::WinRTComponent();
Or I could have totally avoided ref new
and used
stack semantics.
WinRTComponentDll2::WinRTComponent comp;
So if you have an aversion to seeing the hat all over your code, you can
avoid seeing it in some places. You do need to keep in mind that you are not
merely creating a C++ object in the heap. The comp
object we
created is a COM object. Under WinRT, COM objects are created via an activation
factory. An activation factory implements the IActivationFactory
interface.
MIDL_INTERFACE("00000035-0000-0000-C000-000000000046")
IActivationFactory : public IInspectable
{
public:
virtual HRESULT STDMETHODCALLTYPE ActivateInstance(
__RPC__deref_out_opt IInspectable **instance) = 0;
}
WinRT includes a RoGetActivationFactory
function that gives you
the activation factory for a specific WinRT class. If you implement your own
WinRT class, then you need to implement a factory as well (that implements
IActivationFactory
). That said, you don't really need to do that because
the compiler will generate that for you when you use C++/CX. So once the
IActivationFactory
object is obtained, the
ActivateInstance
method is called on it. If the call is successful, a pointer to a
IInspectable
object is returned (via the out parameter).
IInspectable
is to WinRT what
IUnknown
was to traditional
COM.
IInspectable
inherits from
IUnknown
, so this is
what makes every WinRT component a COM object as well in a manner of speaking.
MIDL_INTERFACE("AF86E2E0-B12D-4c6a-9C5A-D7AA65101E90")
IInspectable : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE GetIids(
__RPC__out ULONG *iidCount,
__RPC__deref_out_ecount_full_opt(*iidCount) IID **iids) = 0;
virtual HRESULT STDMETHODCALLTYPE GetRuntimeClassName(
__RPC__deref_out_opt HSTRING *className) = 0;
virtual HRESULT STDMETHODCALLTYPE GetTrustLevel(
__RPC__out TrustLevel *trustLevel) = 0;
};
Every WinRT object will implement IUnknown
and
IInspectable
. Once we have the
IInspectable
object, getting
the interface we want is a mere matter of calling
QueryInterface
.
The compiler also generates code to call
AddRef
and
Release
as appropriate. So when you do something that's seemingly quite trivial (calling
ref new
), there's a good bit of stuff that goes on underneath which
is something to keep in mind from a performance perspective.
What's a hat?
Deon Brewis had a C++ session at //Build/ where he defined a hat as syntactic
sugar for a pointer to a vtable, or in other words a pointer to a pointer to
function pointers. Just to put that in better light, take a look at the
following code.
WinRTComponentDll2::WinRTComponent^ comp = ref new WinRTComponentDll2::WinRTComponent();
typedef HRESULT (__stdcall* GetRuntimeClassNameFunc)(Object^, HSTRING*);
auto pGetRuntimeClassNameFunc = (*reinterpret_cast<GetRuntimeClassNameFunc**>(comp))[4];
HSTRING className;
HRESULT hr = pGetRuntimeClassNameFunc(comp, &className);
if(SUCCEEDED(hr))
{
WindowsDeleteString(className);
className = nullptr;
}
So, how do I know that the function pointer I need is the 5th in the vtable.
Well IUnknown
has QI
, AddRef
, and
Release
- so that takes up the first 3 spots, then we have the
IInspectable
methods with
GetIids
at 4th spot, followed by
GetRuntimeClassName
, and then
GetTrustLevel
. This is
fixed and will never change for any WinRT object.
Every time you invoke a method on a hat, it's a COM call on the appropriate
interface. Consider a simple method call such as the following.
comp->Method(5);
This will result in a QueryInterface
call for the appropriate
interface. C++/CX will wrap all non-virtual methods defined in a custom WinRT
class into a compiler generated interface which in the dev preview shows up as
__IWinRTComponentPublicNonVirtuals
in the winmd metadata file. So
every method call on a hat will be a virtual function call, something to keep in
mind.
Using Platform::String
In the earlier code example, you may have seen the use of an HSTRING
.
An HSTRING
is a handle to a WinRT string and seems to be the RT
equivalent of COM's BSTR
. Strings are immutable in WinRT. There are
methods such as WindowsCreateString
, WindowsDeleteString
etc. that can be used to manipulate HSTRING
s. You can also get the
raw buffer using WindowsGetStringRawBuffer
. As you might imagine,
it's not convenient at all to be doing all this and quite unsurprisingly, the
C++ library includes a wrapper/helper class called Platform::String
.
Here's a small code snippet that manipulates strings and shows the corresponding
WinRT calls that the wrapper makes for you.
void Foo()
{
String^ str = "Hello C++ world";
auto len = str->Length();
String^ copy = str;
str = nullptr;
auto raw = copy->Data();
raw = nullptr;
copy = nullptr;
}
Pretty much every operation with a String^
results in a WinRT
call. The compiler might optimize some of those calls away but in such scenarios
it's best to not always depend on the compiler to do that for you or to make
such assumptions. This is very important to be aware of. When designing custom
WinRT components, you should only ever use String^
at ABI
boundaries. For all internal code you absolutely must use C++ strings (like
std::wstring
). And since you are C++ programmers, here's another
piece of advice. Never modify the raw backing buffer as WinRT will always assume
that the raw buffer is immutable and null-terminated.
Creating WinRT components
Consuming components is merely a matter of ref new
and then
invoking methods on the hat. Creating custom components is just about as easy
too.
public ref class WinRTComponent sealed
{
int _data;
public:
WinRTComponent();
~WinRTComponent();
property int Data
{
int get()
{
return _data;
}
void set(int value)
{
_data = value;
}
}
int Method(int i);
};
The syntax is eerily similar to C++/CLI (in my opinion, that is by design).
COM does not really have properties so getter and setter methods are generated.
In C++/CLI you could not mix types together, as in you could not embed a native
class in a managed class or a managed class in a native class without using
indirections like gcroot
or CAutoNativePtr
(which I
wrote). There are no such restrictions with C++/CX. You can mix C++ and RT
types.
class NonRTClass
{
};
public ref class WinRTComponent sealed
{
NonRTClass _nonRTClass;
};
class AnotherNonRTClass
{
WinRTComponent^ _winRTComponent;
};
When designing WinRT components, the one restriction you have is that you
must use only WinRT types in your public methods. You cannot use C++ types in
your public interface. Fortunately, the compiler won't let you do this
inadvertently.
public ref class Ref sealed
{
private:
void Foo(std::wstring) {
}
public:
void Bar(std::wstring) {
}
};
Notice how the compiler doesn't care what you do with your private methods.
It's just the public methods that it cares about.
The Object
class
All WinRT components derive directly or indirectly from Object
.
The .NET paradigm where System::Object
is the absolute base class
with overridable methods does not translate too well here. WinRT was not
designed with inheritance in mind and inheritance is officially supported only
for Xaml components. None of the methods in Object
are virtual
. So
you might see a ToString
in Object
and think that you
could override it to return something nice. Nope, won't work, can't do! If you
really wanted something similar, you may probably want to consider having your
own root-base class and then implementing your object hierarchy from there. The
way I think of the use of Object^
in C++/CX is like a
void*
(there is no such thing as a
void^
by the way). My
strictly unofficial opinion here is that
Object
ending up as the
base class for all classes was a design decision to make it convenient for
managed languages like C# and VB to consume and create WinRT components.
Boxing
The VC++ team and boxing have a bit of a history. When they first released
Managed C++, they used __box
as a keyword to perform explicit
boxing. This was when C# already had implicit boxing. Later, when C++/CLI was
first launched, one of the big changes was that boxing was now implicit. Well
with C++/CX, we now come back to explicit boxing. And it's not even that
straightforward at the moment.
void Foo()
{
int n = 12;
Object^ boxedObj = PropertyValue::CreateInt32(n);
IReference<int>^ refInt = dynamic_cast<IReference<int>^>(boxedObj);
int x = refInt->Value;
}
It's not possible to do this with the current preview version with custom
value types (like those you create).
value struct V
{
public:
int x;
};
void Foo()
{
V v;
v.x = 15;
Object^ boxedObj = %v;
The error thrown is : error C3960: 'V' : illegal usage of
not-yet-implemented feature: Boxing. So, maybe they'll get that fixed by
beta!
Type conversions between WinRT and C++ types
Basic conversions are fairly simple, as with say converting between C++ and
WinRT strings.
String^ s = "WinRT/Metro";
std::wstring ws = s->Data();
s = nullptr;
s = ref new String(ws.c_str());
For collections, you can use <collection.h>
(authored by Stephan
T. Lavavej (STL) of the VC++ team) for a bunch of stuff. Here's some example
code that converts between std::map
and Platform::Map
.
std::map<int, String^> map;
map[1] = L"hello";
map[3] = L"C++";
map[4] = L"world";
auto rtMap = ref new Platform::Map<int, String^>(map);
String^ s = rtMap->Lookup(1);
Of course that worked because I used a String^
with my
std::map
. Normally you'd expect a
wstring
there. If that
were so, then you'd need to write the code to manually convert the type as you
copy over the items.
std::map<int, std::wstring> map;
map[1] = L"hello";
map[3] = L"C++";
map[4] = L"world";
auto rtMap = ref new Platform::Map<int, String^>();
for (auto it = map.begin(); it != map.end(); it++)
{
rtMap->Insert(it->first, ref new String(it->second.c_str()));
}
By the time VC++ 11 RTMs, I suspect there will be more helpers/wrappers added
that will make most of these conversions a little easier. But either way, it's
not very complicated to do on your own.
Don't care for the component extensions?
A few C++ devs I talked to say that they cannot stand the C++/CX syntax and
that it's not really C++. Well you don't need to use the C++/CX syntax if you
don't want to. You can just directly use regular C++/COM to write WinRT
applications. You could think of C++/CX being high-level WinRT access and
regular COM as low-level access. But it's going to be a fairly large amount of
work to get that working. You'll have to do all the stuff that the compiler does
for you in high-level mode, such as creating the activation factory,
implementing IUnknown
and IInspectable
. Manually deal with
HSTRING
. Make sure
you handle ref-counts correctly. The QueryInterface
part of doing things will be
the least of your worries. And lastly, since COM does not really do inheritance
the C++ way, WinRT uses a form of composition to emulate inheritance. So
when you have multi-level object models, you'd end up writing even more code
just to get it all to compile and run. Outside of an academic exercise, it makes
no sense to subject any developer to those extremes.
There is an alternative though. It's called the Windows Runtime Library (or
WRL for short). At the moment there is very little (zero in fact) documentation
on MSDN (or anywhere else). It's basically the ATL equivalent for WinRT. WRL
seems to be owned by the SDK team rather than the VC++ team, and you'll find the
header files in this location (in the dev preview) :
Program Files
(x86)\Windows Kits\8.0\Include\winrt\wrl
. I'd guess that at least parts
of WinRT was developed using WRL, so it should be fairly bug-free by the time it
RTMs. So if you absolutely want to avoid C++/CX then WRL should be your best
option (straight COM most certainly would be an utter impracticality).
Conclusion
These are exciting times if you are a C++ programmer or if you were one in
the past and moved to C# or VB to better consume modern frameworks like WPF and
Silverlight. Because now, C++ is once again the first choice language for
writing Windows apps (at least Windows Metro apps). I am not saying there is no
reason to use C# or VB. For quick app development and for better IDE support,
those languages probably have an edge, specially when you add in all the
benefits of .NET and the vast Base Class Library. But for faster applications
with smaller memory footprints where performance is the key focal point, using
C++ to write Metro apps is the way to go because when you do that it's metal on
metal! The renaissance is here, finally.
History
- September 29, 2011 - Article first published