Click here to Skip to main content
15,946,342 members
Articles / Programming Languages / C++

Visual C++ and WinRT/Metro - Some fundamentals

Rate me:
Please Sign up or sign in to vote.
4.83/5 (57 votes)
29 Sep 2011CPOL13 min read 298.4K   72   78
The fundamentals of using Visual C++ to consume and create WinRT objects

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.

MC++
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.

MC++
auto comp = ref new WinRTComponentDll2::WinRTComponent();

Or I could have totally avoided ref new and used stack semantics.

MC++
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.

MC++
MIDL_INTERFACE("00000035-0000-0000-C000-000000000046")
IActivationFactory : public IInspectable
{
public:
    virtual HRESULT STDMETHODCALLTYPE ActivateInstance( 
        /* [out] */ __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

MC++
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
MC++
IActivationFactory
object is obtained, the ActivateInstance method is called on it. If the call is successful, a pointer to a
MC++
IInspectable
object is returned (via the out parameter).
MC++
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.

MC++
MIDL_INTERFACE("AF86E2E0-B12D-4c6a-9C5A-D7AA65101E90")
IInspectable : public IUnknown
{
public:
    virtual HRESULT STDMETHODCALLTYPE GetIids( 
        /* [out] */ __RPC__out ULONG *iidCount,
        /* [size_is][size_is][out] */ __RPC__deref_out_ecount_full_opt(*iidCount) IID **iids) = 0;
    
    virtual HRESULT STDMETHODCALLTYPE GetRuntimeClassName( 
        /* [out] */ __RPC__deref_out_opt HSTRING *className) = 0;
    
    virtual HRESULT STDMETHODCALLTYPE GetTrustLevel( 
        /* [out] */ __RPC__out TrustLevel *trustLevel) = 0;
    
};

Every WinRT object will implement IUnknown and

MC++
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.

MC++
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

MC++
Release
- so that takes up the first 3 spots, then we have the
MC++
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.

MC++
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 HSTRINGs. 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.

MC++
void Foo()
{
  String^ str = "Hello C++ world";
  // WindowsCreateStringReference is called
  
  auto len = str->Length();
  // WindowsGetStringLen gets called
  
  String^ copy = str;
  // WindowsDuplicateString gets called
  
  str = nullptr;
  // WindowsDeleteString is called
  
  auto raw = copy->Data();
  // WindowsGetStringRawBuffer is called
 
  raw = nullptr;
  // Pointer set to null
  
  copy = nullptr;
  // WindowsDeleteString is called
}

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.

MC++
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.

MC++
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.

MC++
public ref class Ref sealed
{
private:
  void Foo(std::wstring) // <-- compiles
  {
  }

public:
  void Bar(std::wstring) // <-- will not compile
  {
  }
};

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

MC++
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.

MC++
void Foo()
{
  int n = 12;

  // Box to Object^
  Object^ boxedObj = PropertyValue::CreateInt32(n);

  // Unbox to an int
  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).

MC++
value struct V 
{
public:
    int x;
};

void Foo()
{
    V v;
    v.x = 15;

    // Box to Object^
    Object^ boxedObj = %v; // <-- compiler error

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.

MC++
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.

MC++
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

MC++
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.

MC++
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) :

MC++
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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
Nish Nishant is a technology enthusiast from Columbus, Ohio. He has over 20 years of software industry experience in various roles including Chief Technology Officer, Senior Solution Architect, Lead Software Architect, Principal Software Engineer, and Engineering/Architecture Team Leader. Nish is a 14-time recipient of the Microsoft Visual C++ MVP Award.

Nish authored C++/CLI in Action for Manning Publications in 2005, and co-authored Extending MFC Applications with the .NET Framework for Addison Wesley in 2003. In addition, he has over 140 published technology articles on CodeProject.com and another 250+ blog articles on his WordPress blog. Nish is experienced in technology leadership, solution architecture, software architecture, cloud development (AWS and Azure), REST services, software engineering best practices, CI/CD, mentoring, and directing all stages of software development.

Nish's Technology Blog : voidnish.wordpress.com

Comments and Discussions

 
SuggestionMore details would be nice Pin
Elmue29-Jul-16 11:30
Elmue29-Jul-16 11:30 
QuestionHow to consume web service using c++/Metro app? Pin
Gokulnath0077-May-13 2:59
Gokulnath0077-May-13 2:59 
GeneralWhy can I not pass a non WinRT class into a WinRT class Pin
John Gaby30-Jun-12 16:28
John Gaby30-Jun-12 16:28 
QuestionHow updating this for the current version of Windows 8 Pin
sam stokes18-May-12 5:28
sam stokes18-May-12 5:28 
GeneralMessage Removed Pin
10-Apr-12 9:26
professionalN_tro_P10-Apr-12 9:26 
GeneralMy vote of 5 Pin
Dominic Amann9-Apr-12 5:33
Dominic Amann9-Apr-12 5:33 
GeneralMy vote of 5 Pin
bazbal19-Mar-12 6:50
bazbal19-Mar-12 6:50 
GeneralMy vote of 2 Pin
softwareguru_usa7-Mar-12 3:09
softwareguru_usa7-Mar-12 3:09 
Generalnice Pin
softwareguru_usa7-Mar-12 3:08
softwareguru_usa7-Mar-12 3:08 
QuestionCustom Classes Consumable in XAML or Other Lanugae Pin
lok.vikram16-Nov-11 20:12
lok.vikram16-Nov-11 20:12 
GeneralMy vote of 5 Pin
Hongwu Wang4-Nov-11 6:32
Hongwu Wang4-Nov-11 6:32 
QuestionMSDN - Windows Runtime C++ Template Library (WRL) Pin
James S.F. Hsieh2-Nov-11 23:34
James S.F. Hsieh2-Nov-11 23:34 
AnswerRe: MSDN - Windows Runtime C++ Template Library (WRL) Pin
Nish Nishant3-Nov-11 1:00
sitebuilderNish Nishant3-Nov-11 1:00 
GeneralMy vote of 5 Pin
Emile van Gerwen1-Nov-11 2:30
Emile van Gerwen1-Nov-11 2:30 
GeneralRe: My vote of 5 Pin
Nish Nishant3-Nov-11 1:00
sitebuilderNish Nishant3-Nov-11 1:00 
GeneralMy vote of 5 Pin
Luc Pattyn24-Oct-11 7:53
sitebuilderLuc Pattyn24-Oct-11 7:53 
GeneralRe: My vote of 5 Pin
Nish Nishant24-Oct-11 7:56
sitebuilderNish Nishant24-Oct-11 7:56 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA13-Oct-11 19:44
professionalȘtefan-Mihai MOGA13-Oct-11 19:44 
GeneralRe: My vote of 5 Pin
Nish Nishant14-Oct-11 6:34
sitebuilderNish Nishant14-Oct-11 6:34 
QuestionHow to covert the traditional MFT of windows 7 to metro style Pin
kenny_lin11-Oct-11 15:29
kenny_lin11-Oct-11 15:29 
AnswerRe: How to covert the traditional MFT of windows 7 to metro style Pin
Nish Nishant12-Oct-11 4:18
sitebuilderNish Nishant12-Oct-11 4:18 
GeneralMy vote of 5 Pin
Pete O'Hanlon11-Oct-11 9:37
mvePete O'Hanlon11-Oct-11 9:37 
GeneralRe: My vote of 5 Pin
Nish Nishant11-Oct-11 11:40
sitebuilderNish Nishant11-Oct-11 11:40 
QuestionAccess WinRT from Win32 Pin
flippydeflippydebop11-Oct-11 4:25
flippydeflippydebop11-Oct-11 4:25 
AnswerRe: Access WinRT from Win32 Pin
Nish Nishant11-Oct-11 4:45
sitebuilderNish Nishant11-Oct-11 4:45 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.