The executive summary of this post is as follows:
Application().Run(%Window());
That line of code is all you need to make a blank window open in today's Visual C++. And if you're a veteran of the dark days of Petzold and Win32, that has to be viewed as a major improvement. (Even if that % operator does look like gibberish.)
In fact from here on in, I'll assume you are such a veteran but are only vaguely aware of .NET.
Win32 API has a native language - C, and by extension, C++. Other languages lack the equivalent of all the .h and .lib files that are the doorway to Win32. Even C# only provides the syntax for declaring entry points, and the user has to then use that syntax to manually declare them (although community efforts like http://pinvoke.net/ have naturally sprung up to fill this gap).
And some APIs require direct manipulation of native memory just to call them. This is notoriously difficult to get right in C and C++. Try the security or Spooler or Lan Manager APIs for example. Of course C# can't really do anything to make it easier, as every case is slightly different, but at least in C++ there is sample code to guide you, and it can be tricky to translate it into the equivalent unsafe C#.
So Visual C++ still has a place in 2008. And then there's legacy code that needs new features. The question is, how easy is it to start from an old-style C++ app and then seamlessly mix today's CLR libraries into it?
To test this in Visual Studio 2008, create a C++ Win32 Console Application. This serves as our example starting point, all old and crummy, and the IDE has written the crummy old main function for us like this:
#include "stdafx.h" int _tmain(int argc, _TCHAR* argv[]) { return 0; }
Obviously we can include <windows.h> and do anything we like with Win32. But then we look at Petzold and see how many pages of code are needed just to get a blank window to open on the screen, and we think to ourselves: there must be an easier way.
Go to the project's Properties, and under Configuration Properties change the Common Language Runtime support setting to have the value: Common Language Runtime Support (/clr).
Then right-click the project and choose References. Click Add Reference and choose the following magical ingredients:
- PresentationCore
- PresentationFramework
- WindowsBase
Note that just by adding the references, our C++ language namespace already contains all the types we need. There's no need to include any extra headers. But we can still use a using namespace declaration to help us to abbreviate our code, same as in standard C++. So put this between the #include statement and the _tmain function:
using namespace System::Windows;
(It has to be after the stdafx.h header, because of the weird behaviour of Visual C++ precompiled headers, sigh.)
Directly above the _tmain function, paste this magical incantation:
[System::STAThread]
This is an attribute, and it has to be there for WPF to work. Finally, we're ready to enter a new world! The excitement is electric here at the Stroustrup Stadium.
Put this inside the _tmain function.
Application().Run(%Window());
Now hit F5. A blank window opens! When you close it, the program terminates (in fact, control returns from the Run function, so the Window behaves like a modal dialog box).
As a C++ veteran, you'd assume that Application is either a function that returns an object (or a reference to an object) or else its the constructor of a type, being used here to make a temporary instance. Well, I can tell you it's the latter: constructor of a temporary. Same for the Window type. We construct an Application, construct a Window, and then call Application::Run, passing it... something windowy. If we tweaked the code a little, you'd understand it immediately:
Application().Run(&Window());
The % operator is very much the CLR equivalent of &. As always in C++, we distinguish between an object and a pointer-to-object. When declaring a pointer-like variable, we use ^ instead of * (and we call the variable a handle instead of a pointer, and we use gcnew instead of new to construct a free store instance). It's almost as if a whole separate peer language has been dropped into C++, but the new language still has the characteristic flavour of the old.
So both of these have the same outcome as the original snippet:
Window w; Application().Run(%w);
or...
Window ^w = gcnew Window(); Application().Run(w);
The analogy even extends to C++ references, which are like pointers that can only be assigned to once and then are syntactically used like objects instead of pointers. For example,
Window ^w = gcnew Window(); Application ^a1 = gcnew Application(); a1->Run(w);
Because a1 is a handle, we have to use the arrow (->) operator to call functions on it, instead of the dot, just like we do with pointers. But just as in standard C++, we can avoid that:
Application ^a1 = gcnew Application(); Application %a2 = *a1; a2.Run(w);
Again, note how in the declarations, ^ is like * and % is like &. But in the usage, the syntax is just like Standard C++: asterisk to dereference, and arrow to access members - this extra bit of uniformity was suggested by Stroustrup to allow templates to be written such that they would work on pointers or handles.
The part I really like is the way destructors work, and I wish C# had the same thing (it's currently only part of the way there, with the using statement). But anyway, to finish off the simple example, add this extra namespace declaration:
using namespace System::Windows::Controls;
Then try this code:
TextBlock b;
b.Text = "Hello, world.";
Window w;
w.Content = %b;
Application().Run(%w);
Here's the complete source:
#include "stdafx.h" using namespace System::Windows; using namespace System::Windows::Controls; [System::STAThread] int _tmain(int argc, _TCHAR* argv[]) { TextBlock b; b.Text = "Hello, world."; Window w; w.Content = %b; Application().Run(%w); return 0; }
1 comment:
Thank you for shaaring
Post a Comment