Visual C++ NET Tutorials
Visual C++ NET Tutorials
NET Tutorial
Using the ATL CImage Class
Visual Studio.NET introduced Managed C++, but that wasn't the only thing that was new in it. A number of interesting changes to MFC and ATL make life simpler for those who are working in unmanaged C++. In this column I'll introduce you to the CImage class, which has been added to ATL and adds enhanced bitmap support. Figure 1 shows a simple application that displays and converts images. This is an ordinary MFC dialog application. I added a textbox, two buttons captioned "Load" and "Save As JPG," and a picture control. Then I deleted the Cancel button, and changed the caption (but not the ID) of the OK button to Done.
Figure 1 - Displaying and converting images. Because this is a dialog application, AppWizard created a dialog class for me. I added variables by right-clicking on the surface of the dialog in the resource editor and choosing Add Variable. (ClassWizard is gone in this version of Visual Studio, so you have to learn how to get to the Add Member Variable Wizard dialog.) Here's a summary of the variables I added, all private:
I used the Properties Window to disable the "Save As JPG" button by default (there's code later to enable it.) I also changed the Type of the picture control to Bitmap. And finally, I edited the header file for the dialog class, adding a member variable called m_image of type CImage. At the top of the header file go these two #include statements:
#include <afxstr.h> #include <atlimage.h>
It's important that these include statements appear in this order. This is really the only indication you have that CImage is an ATL class rather than an MFC class. Here's how simple it is to load a GIF or JPG from the hard drive into a CImage object and show it in the picture control:
void CImageDlg::OnBnClickedLoad() { UpdateData(TRUE); if (m_filename == "") m_filename = "Please enter a file name first"; else { if (m_image.Load(m_filename) == S_OK) { m_picture.SetBitmap((HBITMAP)m_image); m_picture.Invalidate(); m_savebutton.EnableWindow(TRUE); } else { AfxMessageBox("File not found or invalid format"); } } UpdateData(FALSE); }
I even have room for a little error checking! I also enable the "Save As JPG" button once an image is successfully loaded. If you're adapting this code for your own use, don't skip the call to Invalidate() - it ensures the picture control will redraw itself. What about converting a GIF to a JPEG? All you have to do is save the image. The format is implied by the file name. Here's how to do it:
void CImageDlg::OnBnClickedSave() { CString filename = m_filename.Left( m_filename.Find('.')) + ".JPG"; m_image.Save(filename);
Two lines of code! How hard is that? What else can you do with a CImage object? Well, you don't have to load one from an existing picture file. You can draw on it by creating a device context associated with the bitmap inside the CImage:
CDC* pDC = CDC::FromHandle(image.GetDC());
Be sure to call ReleaseDC afterwards. CImage supports transparency, alpha blends and a variety of other cool effects, on reasonably recent versions of Windows. If you are writing unmanaged (Classic C++) code that needs to work with images, look into CImage. While it is part of ATL, you can't tell, can you? There's no sign of templates or anything tricky at all. And in my next column, I'll show you the Managed C++ equivalent for this application. Using the ATL CImage Class Simple Role-Based Security in Managed C++ <http://www.codeguru.com/system/KG101802.html> The .NET Framework makes things so much simpler for developers who care about security. There are a lot of useful ways to make sure that only authorized users update the database, place orders, or otherwise interact with your system. In addition, code access security ensures that only trusted code can perform certain tasks, no matter who runs the code. In this column I focus only on one small topic: how to establish that the user running your application is authorized to perform some subtask within the application. You can extend the examples here to any application you write in Managed C++.
Who's There?
Here's a simple console application:
using namespace System; using namespace System::Security::Principal; // This is the entry point for this application int _tmain(void) { WindowsIdentity* Identity = WindowsIdentity::GetCurrent(); WindowsPrincipal* Principal = new WindowsPrincipal(Identity); //Print the values. Console::WriteLine("Principal Values for current thread:"); Console::WriteLine("Name: {0}", Principal->Identity->Name); Console::WriteLine("Type: {0}", Principal->Identity->AuthenticationType); Console::WriteLine("IsAuthenticated: {0}", __box(Principal->Identity->IsAuthenticated));
Console::WriteLine(); Console::WriteLine(); Console::WriteLine("Identity Values for current thread:"); Console::WriteLine("Name: {0}", Identity->Name); Console::WriteLine("Type: {0}", Identity->AuthenticationType); Console::WriteLine("IsAuthenticated: {0}", __box(Identity->IsAuthenticated)); Console::WriteLine("IsAnonymous: {0}", __box(Identity->IsAnonymous)); Console::WriteLine("IsGuest: {0}", __box(Identity->IsGuest)); Console::WriteLine("IsSystem: {0}", __box(Identity->IsSystem)); Console::WriteLine("Token: {0}", Identity->Token.ToString()); return 0; }
The first line of this code gets the identity under which the application is running. That should be you, since the application doesn't contain any code to change the identity. Then it gets a principal based on that identity. The remainder of the code illustrates some useful properties of the identity and principal objects. Notice the use of __box to wrap up the Boolean values, and the ToString method to convert non-string values to strings. When I run this application, it prints out:
Principal Values for current thread: Name: GREGORY\kate Type: NTLM IsAuthenticated: True
Identity Values for current thread: Name: GREGORY\kate Type: NTLM IsAuthenticated: True IsAnonymous: False IsGuest: False IsSystem: False Token: 304
You could do a little security code of your own at this point, comparing the name of the identity or principal with a list of names of authorized users that you have stored somewhere. But that will require you to write code to manage user lists: adding and deleting users, changing their authority code, and so on. I prefer to leverage Windows groups. Even if they aren't in use by the people who'll be running the application, it's less work to teach people to use Windows groups than to write your own equivalent administration section.
role = "BUILTIN\\Administrators"; if (Principal->IsInRole(role)) Console::WriteLine("You are an administrator"); else Console::WriteLine("You are not an administrator"); role = "JSERV\\CodeGuru"; if (Principal->IsInRole(role)) Console::WriteLine("You are a Code Guru"); else Console::WriteLine("You are not a Code Guru");
For you, it almost certainly will not. Notice the three different prefixes in the role strings:
GREGORY is my domain. Use this prefix when you're referring to a group that has been created on your domain controller. BUILTIN refers to built-in groups such as Administrators, created when you install Windows 2000 or NT. JSERV is my machine name. Use this prefix when you're referring to a local group.
You can test this code by creating a local group, and adding yourself to it. If you've never done that before, you can use Computer Management. Right-click My Computer on your desktop and choose Manage. Expand the Local Users and Groups section, then click on Groups.
Click here for larger image Choose Actions, New Group to create a group. Name it CodeGuru, and click Add to add users to it. Add yourself. Then log off Windows and log back on again to update your security profile, and run the application again. Now you should be told you are a Code Guru. (And don't forget about the logging off and logging on. You'll do a lot of that when you're testing security code.)
You could use tests of IsInRole() to decide whether to enable or disable a button. Or you could throw a security exception when the wrong kind of user tried to do something. If you'd like to do that, you're better off using permissions sets.
Permission Sets
Permission sets can be used imperatively or declaratively, the documentation likes to say, which means you can either call a function or add an attribute. Let's start with the imperative way. I added a class to my console project. It isn't even a managed class, and it only has a constructor and destructor:
class AccessControlled { public: AccessControlled(void); ~AccessControlled(void); };
(I don't need a destructor, but I right-clicked the project in Class View and chose Add, Add Class and added a generic C++ class, and that gave me a constructor and destructor.) The implementation file looks like this:
#include "StdAfx.h" #using #include "accesscontrolled.h" using namespace System; using namespace System::Security::Permissions; using namespace System::Security::Principal; AccessControlled::AccessControlled(void) { AppDomain::CurrentDomain->SetPrincipalPolicy( PrincipalPolicy::WindowsPrincipal); //String* role = "BUILTIN\\Administrators"; String* role = "JSERV\\CodeGuru"; PrincipalPermission* p = new PrincipalPermission(0,role); p->Demand(); } AccessControlled::~AccessControlled(void) { }
The first line of this constructor is vital: without it the code will compile but will always react as though you are not in the group. The SetPrincipalPolicy instructs the framework to create WindowsPrincipal objects by default. The code creates a PrincipalPermission object that represents being in a particular role. (The role names used in this example follow the same rules as those used for IsInRole().) The first parameter is 0 here, to indicate that any user identity is acceptable. You can pass in a string representing a specific user if you wish. Having created a PrincipalPermissions object, you now demand that the individual running this application meets the conditions
in that object. If the demand fails, a security exception will be thrown. I added a few more lines to _tmain() to create an AccessControlled object, triggering a call to the constructor:
AccessControlled* ac = new AccessControlled(); Console::WriteLine("created the access controlled object");
When I am in the CodeGuru group, this works. When I am not, it throws a SecurityException. The other way to use PrincipalPermissions is as an attribute, declaratively. This requires the class to be garbage collected, since only managed classes can have attributes:
using namespace System::Security::Permissions; [PrincipalPermissionAttribute(SecurityAction::Demand, Role = "BUILTIN\\Administrators")] __gc class AccessControlled { public: AccessControlled(void); ~AccessControlled(void); };
Now the call to SetPrincipalPolicy must be before the attempt to construct the AccessControlled object:
AppDomain::CurrentDomain-> SetPrincipalPolicy(PrincipalPolicy::WindowsPrincipal); AccessControlled* ac = new AccessControlled(); Console::WriteLine("created the access controlled object");
The constructor itself no longer needs any security code. No-one can create or use an AccessControlled object unless they are in the specified role.
Conclusion
When you build .NET applications, you get a lot of security code without writing it yourself. It's easy to determine who a user is, and what role (Windows group) the user is in. You can deny access to specific methods or entire classes based on the user's identity. Now there's no excuse for skimping on security. Oh, and one more tip: if you're administrator on your own machine, stop it! Give yourself another account with less power, and find out what life is like for the users of those applications you write. I assure you, it will be an eye-opener.
If you've been maintaining a large and complex C++ application for several years, it
probably serves as a mini-museum of technology all by itself. There will be code that interacts with the user, business logic, and data access logic - and I hope it's not all together in one giant source file. Chances are that you've used several different techniques to split that code into modules. One popular technique is COM, and creating and using COM components in Visual C++ 6 is relatively easy. But it's not the only way to reuse code: DLLs are relatively easy to write and use from Visual C++ 6, and many developers found them easier than COM for code that was only to be called from one place. Chances are your system has a few COM components and also a few DLLs. The COM Interop story is vital to the acceptance of .NET by today's programmers. You need access to the huge body of working tested code that is in production today, deployed as COM components. Your .NET code can call old COM code after someone (either the COM developer, or you if no-one else is available) creates a Runtime Callable Wrapper for the component. If you write a new component in .NET, it can be used by old COM code after someone (ok, probably you again) creates a COM Callable Wrapper and registers the interop assembly. These techniques are the same across the .NET languages, because they involve typing commands like tlbexp or tlbimp at a command prompt. Once the appropriate wrapper has been created, you're in It Just Works territory: the .NET code acts as though the COM component is really a .NET object, or the COM code calls the new exciting .NET object as though it were a traditional COM component. So let's go on past the COM Interop story to the PInvoke story. The P stands for platform and it's a reference to the underlying platform on which the Common Language Runtime is running. Today that will be some flavor of Windows, but in the future, who knows? There are projects underway to recreate the CLR on non-Windows platforms.
The DllImport attribute tells the runtime that this function is in a DLL, and names the DLL. The extern C takes care of the rest. Now you need to teach the compiler what a SYSTEMTIME pointer is. By clicking on LPSYSTEMTIME on the help page for
If you copy and paste this struct definition into a Managed C++ application, it won't compile - WORD is a typedef that isn't automatically usable in .NET applications. It just means a 16-bit integer, anyway, and that's a short in C++. So paste it in (before the prototype for GetSystemTime()) and change all the WORD entries to short. Now here's some code that calls this function:
SYSTEMTIME* pSysTime = new SYSTEMTIME(); GetSystemTime(pSysTime); Console::WriteLine("Current Month is {0}", __box(pSysTime->wMonth));
The documention is clear that months are 1-based in the SYSTEMTIME structure, so yes indeed September is month 9. Remember, this example is here to show you how to declare the function type and the structures it uses, not how to get the current time and date. A .NET application gets the current time and date with DateTime::Now():
System::DateTime t = DateTime::Now; Console::WriteLine( "Current Month is {0}", __box(t.get_Month()));
(The trick here is to remember, or to be reminded by Intellisense, that DateTime is a value class, which means that C++ applications don't have to work with a pointer to a DateTime, but can just use a DateTime object as you see here. Also, Now is a property, not a function, of the DateTime class. Let Intellisense help you, because these things are non-trivial to keep track of yourself. It shows different symbols for properties and functions.)
world. Many of the Win32 functions that use strings actually come in two versions: one for ANSI strings and one for wide strings. For example there is no MessageBox function really: there is a MessageBoxA and a MessageBoxW. This is hidden from you when you program in Visual C++ 6 by a #define that changes function names based on your Unicode settings. It's hidden from you in .NET by the Interop framework. If you wish, you can declare MessageBox with a DllImport attribute that only specifies the DLL. The declaration from the online help is:
int MessageBox( HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType);
How do I know? Handles and the like generally map to integers. Pointers to various kinds of strings become String*. UINT is an unsigned int. If you're stuck, do a search in the online help for one of the many tables of data types that are scattered about. One of them will have the mapping you need. Here's the prototype for MessageBox:
[DllImport("user32.dll")] extern "C" void MessageBox( int hWnd, String* lpText, String* lpCaption, unsigned int uType);
This is, however, a little wasteful. You know (or you should know) that "Hi There" is a Unicode string, and that MessageBoxW is the function you really want. Here's a revamp of the prototype that makes it clear:
[DllImport( "user32.dll", EntryPoint="MessageBoxW", CharSet=CharSet::Unicode)] extern "C" void MessageBox( int hWnd, String* lpText, String* lpCaption, unsigned int uType);
call the function the same way, but the DllImport attribute is now mapping directly to the right entry point, and using the right marshaling technique, without having to make these decisions on the fly every time you call this function from the DLL. Don't forget that MessageBox() is just an example of a function call that takes a string and that most developers are familiar with. If you really want to display a message box from within your code, use the MessageBox class in System::Windows::Forms (remember to add a #using statement to the top of your source code mentioning
Conclusion
Do you have useful, even vital functionality wrapped up in a DLL? Would you like to be able to access that DLL from your new .NET applications? Well, go ahead. All you have to do is declare a prototype of the function with a DllImport attribute and an extern C linkage. Declare any structures the function uses, and just call it as though it were managed code. (Remember that it isn't, though, and that means your application must have permission to execute unmanaged code.) Give the framework a hand and tell it you're using wide strings, and away you go. Old technologies can never die as long as they can be called from new ones.
Web services are a terrific way to integrate disparate systems: cross-platforms, crossprogramming-languages, cross-everything. No matter what language you work in, you should be able to do an HTTP GET and parse a little XML. As I showed in an earlier column, it's incredibly simple if you're using Visual C++ to both create and consume Web Services. But to drill a little deeper into the problem, you mustn't forget that a Web service is accessed over the network. What if the network is slow? Or what if the Web service has to root around in a database and do all kinds of calculations before returning an answer? It can take quite a while to get a return value from the Web service, and you don't want your application to be blocked while you're waiting for that answer. Luckily the code that's generated for you when you mark a function with <WebMethod> goes beyond SOAP wrappers and HTTP listeners: you also get an asynchronous version of your web method, with no extra coding. This is an extension of a pattern set up throughout the Base Class Library (BCL) of the .NET Framework: potentially slow functions have a companion Begin method that calls the function asynchronously, so that your code is not blocked. The Begin method returns immediately and when the slow function is finished, a function you write (known as a callback) is called to notify your code that the process is complete. Your callback calls a companion End method that returns the value calculated for you.
This does the same job as Add(), but adds a one-second pause to imitate a slow connection or a slow calculation by the web service. Notice that I don't have to add any code to make this service available asynchronously.
Now for the asynchronous work. If I were to call SlowAdd(), it would be a synchronous call. To make an asynchronous call, I call BeginSlowAdd() instead, which was generated for me when I marked SlowAdd() as a Web method. The Begin methods need to be passed a callback object to indicate where the notification will go when the slow method has completed. That's what frees your application to go on and do something more excisting instead of being blocked waiting for the web method to return. You'll find lots of Visaul Basic and C# examples of creating, but C++ examples are a little thin on the ground, and that's a shame because it is a bit tricky. Instead of just passing the address of the function to the delegate constructor, you need to pass two parameters: a pointer to an object instance and a function pointer to the member function. Well here's problem number one: this is a console application, and it's object-free at the moment. That means you have to write an object whose only purpose in life is to hold this member function. It needs to be a managed object, so you'll need the __gc qualifier on the class definition. I added this code before _tmain():
__gc class CallBack { public: void AddCallback(System::IAsyncResult* ar); };
The signature of this callback method is pre-established for you. When you call a Begin method you must pass it not just any old function address, but the address of a function with this signature: it takes an IAsyncResult pointer and returns void. I'll show you the code for this function shortly, but in the meantime here are the lines I added to _tmain():
System::Console::WriteLine("adding 2 plus 2 asynchronously"); CallBack* callbackobject = new CallBack(); System::AsyncCallback* callback = new System::AsyncCallback(callbackobject, &CallBack::AddCallback); Calc->BeginSlowAdd(2,2,callback,Calc); for (int i= 0; i<20; i++) { System::Console::WriteLine("."); System::Threading::Thread::Sleep(100); // 0.1 second }
This code creates an instance of the class and makes an AsyncCallback from that instance. It then calls BeginSlowAdd(), passing in the two parameters that SlowAdd() expects, the callback, and the web service proxy itself - this last parameter will be given to the callback, as you'll see shortly. After the call to BeginSlowAdd(), just to prove that the application can do something else while the slow Web method proceeds, the code goes on to sleep for a tenth of a second at a time, and print out dots. This lets you know it's working. The callback itself is a member function of the CallBack() class I created. The implementation looks like this:
void CallBack::AddCallback(System::IAsyncResult* ar) { CalculatorService * Calc = (CalculatorService*) ar->AsyncState; double answer = Calc->EndAdd(ar); System::Console::WriteLine("result is {0}", __box(answer)); }
The first thing this code does is to get the AsyncState pointer out of the IAsyncResult that was passed to the callback and cast it back to a CalculatorService pointer. You know that's what it is, because that's what was passed as the last parameter to BeginSlowAdd(). I am using an old-fashioned C-style cast here for readability: it gets me a warning that I should use the static_cast template instead. Once this code has a CalculatorService pointer, it uses it to call EndAdd(), which returns the answer to the original question (2+2, in case you've forgotten). It then boxes the answer and passes it to Console::WriteLine(). When this code runs, the exact delay before the answer appears will vary, but generally you will see output like this:
1 plus 1 synchronously is 2 adding 2 plus 2 asynchronously . . . . .
. . . . . . result is 4 . . . . . . . . .
You can see that the result from the slow Web service just pops in among the regular stream of dots from the loop in _tmain(). This demonstrates that the Web service is being called asynchronously. There is no effort for a Web service developer to offer asynchronous versions of all the Web methods in the Web service at all, and little effort required to call a Web service asynchronously. It's worth looking into!
In my last column, I showed you how to use MSXML4, the COM component that parses, searches, and generates XML, to work with XML in a "classic Visual C++" application. This time around I'm going to tackle the same tasks, but use the .NET Framework to process XML. The advantages of using the XML functionality that are built into the .NET class libraries include:
Nothing to install, deploy, or redistribute - your apps need the framework anyway, and once you have the framework you have everything you need Simpler code because you're not working with COM - for example you don't need to call CoInitialize() and CoUninitialize(). Extra functionality that wasn't in MSXML4.
Sample XML
I'm going to use the same sample XML as I did in the previous column. Here's how it looks:
<?xml version="1.0" encoding="utf-8" ?> <PurchaseOrder>
<Customer id="123"/> <Item SKU="1234" Price="4.56" Quantity="1"/> <Item SKU="1235" Price="4.58" Quantity="2"/> </PurchaseOrder>
Loading XML with XmlDocument The classes for working with XML are in the System::Xml namespace. The XmlDocument represents a DOM document, a tree into which your XML can be loaded. It's the same idea as the DOMDocument you worked with using MSXML4. Here's a simple Managed C++ application that loads a file of XML into memory:
#include "stdafx.h" #using <mscorlib.dll> #include <tchar.h> using namespace System; #using <System.Xml.dll> using namespace System::Xml; // This is the entry point for this application int _tmain(void) { XmlDocument* xmlDoc = new XmlDocument(); try { xmlDoc->Load("sample.xml"); System::Console::WriteLine("Document loaded ok." ); } catch (Exception *e) { System::Console::WriteLine("load problem"); System::Console::WriteLine(e->Message); } return 0; }
The #using statement is really important. Without it, you'll get strange compiler errors like 'Xml' : is not a member of 'System' or 'Xml' : a namespace with this name does not exist. When you're working in C# or VB.NET, there's a Project, Add References menu item that takes care of this for you, but as a C++ programmer, you just do it yourself. You can find the assembly to include in the online help for the class or namespace. Notice also that this code doesn't use a return value from Load() as the COM approach did. The Load() method will throw an exception if it can't load the XML. Simple arithmetic with the contents of a document In my last column, I wrote code to flip through all the elements in a document and determine the total price. Here's the equivalent code the .NET way:
xmlDoc->Load("sample.xml"); double total = 0; System::Console::WriteLine("Document loaded ok." ); XmlNodeList* items = xmlDoc->GetElementsByTagName("Item"); long numitems = items->Count; for (int i=0;i<numitems;i++) { XmlNode* item = items->Item(i); double price = Double::Parse(item->Attributes->GetNamedItem("Price")-> get_Value()); double qty = Double::Parse(item->Attributes->GetNamedItem("Quantity")-> get_Value()); total += price * qty; } System::Console::WriteLine("Purchase Order total is ${0}", __box(total));
I just added this code in the try block. If you're converting an application from COM and MSXML4 to .NET, notice that the capitalization is quite different, and the functions just return the values you need rather than taking a pointer to an object that they change for you. That makes it simpler to chain long strings of calls together as I've done here. Because Managed C++ is a little pickier about types, I've had to use Double::Parse() to change strings to numbers, and to box up total before passing it to WriteLine(). When this application runs, it prints out:
Document loaded ok. Purchase Order total is $13.72
If you want to dump the XML out, you just need one line of code:
System::Console::WriteLine(xmlDoc->InnerXml);
InnerXml is a String*, so there's no problem passing it to WriteLine() and no need to go through any intermediate variables.
You mark the class with the Serializable element. Only public variables can be serialized
like this, and the class must have a default constructor. Once you've complied with those restrictions, life gets pretty simple. Here's how to dump out the object to a file:
PurchaseOrder* po = new PurchaseOrder(Int32::Parse(xmlDoc-> GetElementsByTagName("Customer")->Item(0)-> Attributes->GetNamedItem("id")->get_Value())); System::Xml::Serialization::XmlSerializer* ser = new System::Xml::Serialization::XmlSerializer(po->GetType()); System::IO::TextWriter* writer = new System::IO::StreamWriter("po.xml"); ser->Serialize(writer,po); writer->Close();
This would be a lot more readable if you used namespaces, but I wanted you to see the full namespaces associated with the classes I'm using here. The first line, which is very long, roots through the XML document looking for Customer tags, grabs the first one, looks for an attribute called id, and passes its value to the constructor for PurchaseOrder. Then I create a serializer and a writer, and called the Serialize() method of the serializer. The po.xml file comes out like this:
<?xml version="1.0" encoding="utf-8" ?> <PurchaseOrder xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <customer>123</customer> </PurchaseOrder>
If PurchaseOrder was a large and complex class, perhaps using one of the .NET collection classes to hold a collection of items, I would still need just this little block of code to write it out as XML: make a serializer and a writer, then call Serialize(). To rehydrate objects from XML, you just do the same work in reverse:
PurchaseOrder* po2 = new PurchaseOrder(); ser = new System::Xml::Serialization::XmlSerializer(po2->GetType()); System::IO::FileStream* fs = new System::IO::FileStream("po.xml", System::IO::FileMode::Open); po2 = (PurchaseOrder*) ser->Deserialize(fs); System::Console::WriteLine("Rehydrated customer is {0}.", __box(po2->customer));
If you're using DataGrids in either ASP.NET or Windows applications, you can load them from a file of XML (wonderful for quick prototypes) or as part of your debugging, have them dump themselves out to XML when you need to. Just make an empty DataSet object, call its ReadXml method to fill it with XML from a file, then make it the data source for your DataGrid and call DataBind(). Or grab the XML from the DataGrid like this:
xmlstring = DataGrid1->DataSource->GetXml();
There's so much more you can do with XML using the .NET class libraries. In just a line or two, you can generate XML, transform from one layout to another, or find subsets of a document. With the schema support in Visual Studio you can generate a schema that matches a sample XML file, or edit XML with Intellisense that knows the schema you're using and reminds you what elements or attributes are allowed where you're typing. XML is at the heart of the .NET revolution, so you should see what it's all about sooner
XML is at the heart of .NET. You can hardly read a single page of a .NET article, whitepaper, or help entry without coming across those three little letters. But XML was changing everything before .NET came along, and you can work with it from a "traditional" Win32 application. In this column, I'll cover how to read, write, and transform XML using the Microsoft COM component called MSXML4. Next time, I'll tackle the same tasks the .NET way. First, if you don't have MSXML4 installed, you're going to need it. You can get it from <http://msdn.microsoft.com/downloads/default.asp?url=/downloads/sample.asp?url=/msd n-files/027/001/766/msdncompositedoc.xml> and follow the instructions there to install it on your development machine. If you're planning to distribute applications that use MSXML4, you'll want to get the redistributable cab file too. Second, if you have no clue what XML is or why you should care, here's a quick laundry list of XML features. XML is a notation for representing structured and semi-structured data that:
is plain ASCII text that can be read and understood by people is plain ASCII text that can easily be hand-typed can be processed in a just a few lines of code using freely-available (often free) components for essentially any language and operating system can be generated in a just a few lines of code using those same components can be easily transformed into HTML, PDF, Postscript or a variety of other printfriendly and display-friendly formats can be persisted back and forth to almost any database format using widely available components features as few rules and pre-requisites as possible for maximum availability and flexibility
If you'd like to know more, check out www.xml.org <http://www.codeguru.com/columns/Kate/www.xml.org> or www.w3.org/XML/1999/XML-in-10-points <http://www.codeguru.com/columns/Kate/www.w3.org/XML/1999/XML-in-10-points> to see what all the fuss is about.
Sample XML
You can see that I'm using #import to bring in the COM library to make my coding as simple as possible. I call CoInitialize() at the start, to get COM ready for me, and CoUninitialize() at the end. In between I make an instance of the COM object on the stack. It looks like a pointer, but it's really an object using a template set up for me by the #import statement. It will clean up after itself when it goes out of scope, so I've wrapped
it up in an extra set of brace brackets just so that I can send it out of scope before calling CoUninitialize(). That part should be familiar to COM programmers who've used the #import timesavers before. Where's the XML part? The call to load(). This function takes a URL or a relative file name, and reads in the XML from that URL or in that file. It returns false if there's a problem, such as the file not being found or the XML in it not being well-formed. One line of code to get all that XML parsed and into memory. Now you can do things with it.
I put this code in the if (ret) block, replacing the single output statement that was there before. You can see that it uses a variety of handy functions from the DOM API:
getElementsbyTagName() returns a list of all the <Item> elements. get_length() gets the length of the list of <Item> elements. get_item() gets an item from the list. attributes is a property of the item, and it's a list of attributes on the item. The list is held as a map, or lookup table. getNameItem() looks up entries in the map using the string provided getNodeValue() extracts the contents of the attribute so you can use it in simple arithmetic
How did I memorize all that? I didn't. Intellisense helps tremendously. I knew about getElementsbyTagName(), it's one of the standard XML functions that all the XML processors support. The help told me what it returns, an IXMLDOMNodeList, and I tacked Ptr on the end so I could get the helper class created from the #import. Then it was just a matter of typing -> and seeing what Intellisense offered.
There's an interesting convention at work in the helper classes that are created for you when you use #import. Function names that start get_ take an address of something you allocated, and put a value in it, like this:
items->get_length(&numitems);
Function names that start Get (with no underscore) just return what you're looking for, like this:
double qty = item->attributes->getNamedItem("Quantity")->GetnodeValue();
Quite often both sets of functions are supported in a class, but the C++ samples in the help only show you the kind that takes a pointer. I guess C++ people are not supposed to like having answers returned to us, or something. Anyway, remember that both sets are there. When this application runs, it prints out:
Document loaded ok. Purchase Order total is $13.72
That's how simple it is to run through a file of XML and do something with it. But why stop there? You can transform XML from one layout to another -- or to HTML, or to any other number of formats. You can write it out to a file or the screen pretty easily, too:
_bstr_t text = xmlDoc->Getxml(); char* printable = text; cout << printable << endl;
This code uses the helper class _bstr_t which wraps a BSTR and provides a conversion to a char*. You need to ask for the conversion, though - you can't just send the BSTR to cout. Still, this is pretty neat stuff. I encourage you to play with XML whenever you get the chance. It really is changing everything. If you never were a COM programmer, get hives from HRESULTs and shudder at the thought of a BSTR, take heart! Next time you'll see this same work the .NET way.
Web Services are a really exciting part of the .NET initiative - but they are bigger than .NET itself. The concept behind them is simple. Almost any web server you can name has some mechanism for executing server-side code: you type a URL into your browser, and something runs on the server and then writes out HTML in response to your request. It might be ASP, ASP.NET, servlets, even a five-year-old Perl script triggered through CGI. So imagine that the running code returns XML instead of HTML, and that it's called not because a user typed a URL into a browser, but because some code somewhere did a GET over HTTP to ask the web server for that URL. Now you've got an application-toapplication connection happening. Just about every programming language has a class library that makes it easy to do a GET request over HTTP and some easy way of parsing
XML, so this approach gives you a cross-platform, cross-programming-language, crossvendor, cross-everything way to have code in one application call code on a completely different machine, as long as they're both on the Internet or otherwise connected. That's the basic concept behind Web Services. But thinking of that code as a function instead of a page, how do you pass parameters to the function? What if it returns a complex data type? What if a web server has more than one web service on it? How can you find out the names, specify which service you want, and so on? This is where the capital letters are earned. There are standards being developed with names like Web Services Description Language (say wizdle to sound ultra-cool) that cover these technical issues for you. If you build a Web Service with Visual Studio.NET, it will meet these standards. And if you consume a Web Service, no matter how it was built, with Visual Studio.NET, you'll see how amazingly easy it can be to leverage someone else's code.
I was given a method called HelloWorld() and it's simple enough to edit it into Add() - I just changed the name in both the .cpp and .h file, changed the signature so that it takes two floating-point numbers, and added code to return the total. The class declaration ends up looking like this:
using <System.Web.Services.dll> using namespace System; using namespace System::Web; using namespace System::Web::Services; namespace Calculator
{ public __gc class CalculatorService : public WebService { public: [System::Web::Services::WebMethod] double Add(double x, double y); }; }
If you've been following along, you can test the code by choosing Start, Debug. You can't really run a web service, but this starts a web browser and loads Calculator.asmx into it. This is the runs-on-the-web-server file that actually uses your class and methods. If you prefer, open a browser and enter the URL yourself: <http://localhost/Calculator/Calculator.asmx> If you have any trouble, make sure that your web server is started, and that your browser isn't going through a proxy server. On the proxy server, localhost is the proxy server, not your machine. You can follow the link for Add to see some generated documentation for the method, and even test it by entering numbers in the edit boxes and clicking the Invoke button. When you do that, another browser window opens with the result of your call, wrapped up in XML. For example I entered 3 and 2 in the edit boxes, and got this XML:
<?xml version="1.0" encoding="utf-8" ?> <double xmlns="http://tempuri.org/">5</double>
documentation you saw when you ran the web service project: click Add Reference to complete the process. Once the reference has been added, calling a web service is just like using any C++ class. Adding the reference creates a header file that you can include wherever you want to use the web service. I replaced the line that printed Hello World with a line to create the object that represents my web service and another to use it. The edited CalcTest.cpp looks like this:
#include "stdafx.h" #using <mscorlib.dll> #include <tchar.h> #include "WebService.h" using namespace System; // This is the entry point for this application int _tmain(void) { CalculatorService * Calc = new CalculatorService; System::Console::WriteLine("1 plus 1 is {0}", __box(Calc->Add(1,1))); return 0; }
(If the __box keyword doesn't ring a bell, check my previous column on boxing and unboxing fundamental types.) When this application runs, it prints out, not surprisingly I hope:
1 plus 1 is 2
And that's how simple it is to use a web method from within your own code. If you're wondering what happened to the XML and why you didn't have to parse the number out from inside it - that's just one of the cool things that Visual Studio took care of for your when you added the web reference. The possibilities for this are endless. Any code that can run on your server can, if you want, be accessed by other code through the Internet. Security, authentication, encryption and so on are all available to you and supported by SOAP, one of the standards involved in web services. Since those .asmx files are in fact part of ASP.NET, everything you know about ASP.NET pages applies to web services in .NET as well. Why not try your hand at a little application-to-application integration today?
Sometimes things that should be really simple don't feel simple at all when you try to do them. Take, for example, writing the value of a variable to the screen. You know how to do it in "classic" C++, say for example in Visual C++ 6:
int x = 3;
No problem. Whatever "Introduction to C++" you took, I'm willing to bet you saw something very much like these two lines of code less than 10% of the way into the course. Right?
Now, you can paste your cout-using code into this main(), and it will work - after you add the usual include statement:
#include <iostream.h> // ... Console::WriteLine(S"Hello World"); int x = 3; cout << "x is " << x << endl;
You'll get a warning, though: warning C4995: '_OLD_IOSTREAMS_ARE_DEPRECATED': name was marked as #pragma deprecated To get rid of it, just use the iostream code from the STL, and bring in the std namespace:
#include <iostream> using namespace std;
The code will then build and run just fine. But to me, seeing that Console::WriteLine() in the same program as the cout line is a little confusing. What's more, Console::WriteLine() has neat features. It's more like printf in that it uses placeholders in a string to show where variable values should be put. For example, here's some working code from a C# console application:
int x = 3; Console.WriteLine("x is {0}",x);
The {0} is a placeholder, and the value of the second parameter will end up wherever that placeholder appears. So I want to use Console::WriteLine throughout my Managed C++ application, just as it's used in C#. But if you copy these lines into your Managed C++ application, and change the . to a ::, the application won't build. The compiler error is:
error C2665: 'System::Console::WriteLine' : none of the 19 overloads can convert parameter 2 from type 'int' boxpin.cpp(7): could be 'void System::Console::WriteLine( System::String __gc *,System::Object __gc *)' boxpin.cpp(7): or 'void System::Console::WriteLine( System::String __gc *,System::Object __gc * __gc[])'
Now, I'm stubborn, and I expect C++ to be able to do everything the other .NET languages can do - and more. (Some of you may have noticed that already.) So why can't this simple little thing work? Well, if all else fails, read the error messages. I have given it an int for the second parameter, and it would like a pointer. In fact, a pointer to a System::Object (or some class derived from that, of course), a pointer to a __gc object. An int is none of those things. You could try passing &x instead of x, that at least would be a pointer, but it's no help. What WriteLine() wants is a pointer to an object. You can't hand the integer directly to WriteLine(), which (in the interests of genericity) has been written to handle pointers to garbage-collected objects, and nothing else. Why? Everything in the Base Class Library is designed to work with objects, because they can have member functions - not all .NET languages support the idea of casting or of overloading casting operators. For example, objects that inherit from System::Object all have a ToString() method. You don't want to write a class to hold this little not-an-object integer, and write a ToString() overload for it, plus deal with getting your integer into (and possibly out of) the class whenever you pass it to a Base Class Library method like WriteLine(). So how do you get your integer to WriteLine()?
Boxing a value type means putting the value inside a temporary object, an instance of a class that inherits from System::Object and lives on the garbage collected heap, and passing the address of that temporary object to the method call. Whatever was in the original variable is bitwise-copied into the temporary object, and the temporary object provides all the functionality that WriteLine() will want. The __box keyword means that every service the Base Class Library provides will work with value types as well as with managed types.
Alternatives to Boxing
So boxing allows you to use both value types and managed types with Base Class Library methods that are expecting pointers to managed types. That naturally raises the question: what's the difference between a value type and a managed type? A managed type lives on the garbage collection heap and is managed by the runtime. Here's an example:
__gc class Foo
The Foo class is a managed type. You can't create instances of it on the stack, like this:
Foo f2;
If you have a class already, perhaps from some pre-.NET application, it will not be a managed type. It doesn't have the __gc keyword. You could add the keyword (assuming the class meets all the rules for being a managed type - more on that in a future column) but then you would have to find all the places that created instances of the class and make sure they were creating the instance on the heap, like this:
OldClass* poc = new OldClass(); //maybe some parameters //to the constructor
And everywhere your code was calling methods of the class, you'd have to remember to change . to -> -- how tedious! Better to keep the class as an unmanaged class. You can allocate instances on the stack or the unmanaged heap as you prefer:
class notmanaged { private: int val; public: notmanaged(int v) : val(v) {}; }; // ... notmanaged nm(4); notmanaged *p = new notmanaged(5);
This is no big deal: it's just how C++ was before Managed Extensions were added with the release of Visual C++ .NET. But let's say you want to pass one of those instances to good old WriteLine():
Console::WriteLine("notmanaged holds {0}",nm);
You'll get the same error message as before: WriteLine() is no happier with this oldfashioned style class than it was with the integer. "Aha," you think, "I've learned a new keyword, I know what to do:"
Console::WriteLine("notmanaged holds {0}",__box(nm));
This gets you the rather obscure message "only value types can be boxed." Isn't notmanaged a value type? Well it isn't a managed type, but it doesn't inherit from the base class System::ValueType, and that's necessary to be boxed. Now, there is a handy __value keyword, which will cause the class to inherit from System::ValueType and be boxable, but for classes and structures I don't think boxing is the way to go. You don't just want the bits of data that are in your class or structure to be blurted out onto the screen. You want some control over the way your class is represented as a string - or to be accurate, as a System::String. In other words, you want to write a function so that you retain control of the way your class behaves. So why not add a ToString() method?
class notmanaged { private: int val; public: notmanaged(int v) : val(v) {}; String* ToString() {return __box(val)->ToString();} }; // ... notmanaged nm(4); Console::WriteLine("notmanaged holds {0}",nm.ToString()); notmanaged *p = new notmanaged(5); Console::WriteLine("notmanaged pointer has {0}",p->ToString());
Note the clever use of __box inside the ToString() method so that it can return a pointer to the managed type System::String* -- and WriteLine() is happy as can be with that as a parameter. You could easily expand this approach for a class with multiple member variables. Of course, you can call the method whatever you like, but calling it ToString will help other .NET programmers, since that's the name throughout the Base Class Library for a method that represents the internals of a class as a single System::String.
Unboxing
So far, you've only seen the __box keyword used in temporary circumstances, just to pass to a method. But you can create a longer-lived pointer to a managed type, like this:
__box int* bx = __box(x);
It's important to remember that this is a copy. If you change it, the original is unchanged. These four lines:
__box int* bx = __box(x); *bx = 7; Console::WriteLine("bx is {0}", bx); Console::WriteLine("x is still {0}", __box(x));
If you want changes in the boxed copy to go back to the original, just dereference the pointer:
x = *bx;
Then x will contain the new value (7, in this example) that you put into the boxed copy.
Summary
Well, that was a long walk in the park just to get a handful of integers onto the screen, wasn't it? Understanding the differences between managed and unmanaged types is vital to keeping your sanity when working in Managed C++. Boxing an unmanaged type is a useful way to convert it to a managed pointer, but when your unmanaged type is a class, remember that you're in control and can make managed pointers however you choose to do so - and a member function called ToString is an excellent approach. And you can unbox without any special keywords - just get the value back out. So don't let pesky
compiler errors keep you from exploring all the fun of the Base Class Library!
Getting Started
I'm going to start by building a C# application. If you want to follow along, open Visual Studio and choose File, New Project. Select Visual C# Projects on the left and Windows Application on the right. Name the project cs1 to have all your file names and so on match mine. In the forms designer, just quickly drag on a button and a label next to it. (You might have to choose View, Toolbox to have somewhere to drag them from.) The user interface looks like Figure 1.
Figure 1. Click here for larger image I changed the text (caption) of the button to Greet Me and the label to blank, but left their names as generated. For a real application, you would go to town here creating a complex and beautiful user interface. Next, double-click the button to generate an event handler for it. I just added this line of code:
label1.Text = "Hello!";
If you like, build and run it to assure yourself that it works. Click the button and the label text should change to "Hello!". That's all you need this project for, so exit the app and choose File, Close Solution to close it in Visual Studio.
classes, classes from static member functions, and object references from instance functions. C++ uses the scope resolution operator (::) for the first three, and the member dereference operator (->) for the last.
C# returns object references from new; C++ returns pointers. C++ cannot have an instance of a managed object, only a pointer to one, so declarations must be changed from Foo to Foo*.
C# allows you to add references to a project with menus; C++ requires a #using statement. (The online help for each class tells you what assembly it is in, such as System.dll, so you can add the appropriate #using statement.)
C# repeats the access qualifier (private, public, etc) on each declaration. C++ sets up blocks of members with a given access qualifier. C# allows you to initialize a member variable when you declare it. C++ does not. The C# keyword for a null reference is null; the C++ constant for the null pointer is NULL. In C# when you override a function from the base class, you indicate that you are doing so knowingly with the override keyword: this isn't necessary in C++. Although C# doesn't have a pre-processor, it does have some keywords that start with # that are instructions to the IDE more than to the compiler. These are generally not supported in C++ and must be removed. Two very common examples are #region and #endregion, used in outlining view.
C# makes it easy to declare arrays on the fly with brace brackets; C++ does not allow this as an argument to a function. Either create a temporary array to pass or look for another function that doesn't need an array passed to it, and call it multiple times.
C# talks to the base class as base. while C++ uses the actual name of the base class and ::. Some classes in the Base Class Library are actually value types, which means that C++ code doesn't create them with new or refer to them with pointers. Point and Size are good examples of these. If you aren't sure from the online help whether something is a value type or not, try compiling a line that uses it and the compiler will let you know if you've got it right.
The syntax for passing function pointers requires a & in C++ but not in C#. C# lets pointers-to-member functions be specified in a more concise syntax than C++.
Find the closing brace bracket at the end of the class definition (don't be tricked by the closing brace bracket at the end of the namespace block) and add a semicolon after it.
Change public class Form1 : System.Windows.Forms.Form to public __gc class Form1 : public System::Windows::Forms::Form Change the block of lines that start using System to start using namespace System Change all instances of . that separate namespaces such as System.Data to :: as in System::Data Add these lines immediately before the block of using namespace statements:
#using #using #using #using <System.dll> <System.Drawing.dll> <System.Windows.Forms.dll> <System.Data.dll>
Find fully qualified names like System.Windows.Forms.Button and change all the . characters to ::, or better still remove the fully qualified name, in this case just declaring a Button. (For Container, leave the fully qualified name to avoid compiler errors)
Change declarations of references, which would be solid objects in C++, to pointers. For example Button button1 becomes Button* button1. Remove the word private or public from individual declarations and instead place a private: or public: on a line of its own before the set of declarations. Remove the =null on the line that declares components, and add a line to the constructor to set components to NULL. Edit-and-replace this. to this-> or nothing at all, since there aren't any name collisions in this example. Remove any occurrences of the override keyword. Remove any #region and #endregion pre-processor directives Change the signature of the button1_Click() method to take pointers:
void button1_Click(Object* sender, System::EventArgs* e)
Change the single line: Controls.AddRange(new System.Windows.Forms.Control[] { label1, button1}); to the two lines
Controls->Add(label1); Controls->Add(button1);
to
Form::Dispose( disposing );
Find the lines that create a new Point object and take away the new keyword, so that the line reads, for example, label1->Location = Point(184, 96); Do the same for the lines with Size, but call it by its full name, System::Drawing::Size, to avoid conflicts with the Size property of the form. Change the line in the middle of InitializeComponent() that adds button1_Click() to the list of handlers for a click on button1 from
this.button1.Click += new System.EventHandler(this.button1_Click);
to
button1->Click += new System::EventHandler(this, &Form1::button1_Click);
Take the single line in Form1::Main() and move it to _tmain(), editing it slightly so that it reads: Application::Run(new cs1::Form1()); (if you changed the namespace name from cs1, adjust this line.)
Remove Form1::Main() completely, it has no further work to do. Just to show off, change the declaration of _tmain to int __stdcall WinMain() so that you won't get a telltale black box in the background when you run your WinForms application.
That's it! Try building and running it. If you get compiler errors, it's probably because you missed a . somewhere -- let the messages guide you to the place you need to fix. Here's what my code looks like:
// This is the main project file for VC++ application project // generated using an Application Wizard. #include "stdafx.h" #using <mscorlib.dll> #include <tchar.h> #using #using #using #using using using using using using using <System.dll> <System.Drawing.dll> <System.Windows.Forms.dll> <System.Data.dll> namespace namespace namespace namespace namespace namespace System; System::Drawing; System::Collections; System::ComponentModel; System::Windows::Forms; System::Data;
namespace cs1 { /// <summary> /// Summary description for Form1. /// </summary>
public __gc class Form1 : public System::Windows::Forms::Form { private: Button* button1; Label* label1; /// <summary> /// Required designer variable. /// </summary> System::ComponentModel::Container* components; public: Form1() { // // Required for Windows Form Designer support // components = NULL; InitializeComponent(); // // TODO: Add any constructor code after // InitializeComponent call // } /// <summary> /// Clean up any resources being used. /// </summary> protected: void Dispose( bool disposing ) { if( disposing ) { if (components != NULL) { components->Dispose(); } } Form::Dispose( disposing ); } /// <summary> /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// </summary> private: void InitializeComponent() { button1 = new Button(); label1 = new Label(); SuspendLayout(); // // button1 // button1->Location = Point(48, 96); button1->Name = "button1"; button1->TabIndex = 0;
button1->Text = "Greet Me"; button1->Click += new System::EventHandler(this, &Form1::button1_Click); // // label1 // label1->Location = Point(184, 96); label1->Name = "label1"; label1->TabIndex = 1; // // Form1 // AutoScaleBaseSize = System::Drawing::Size(5, 13); ClientSize = System::Drawing::Size(292, 273); Controls->Add(label1); Controls->Add(button1); Name = "Form1"; Text = "Form1"; ResumeLayout(false); }
private: void button1_Click(Object* sender, System::EventArgs* e) { label1->Text = "Hello from C++!"; } }; } // This is the entry point for this application int __stdcall WinMain() { Application::Run(new cs1::Form1()); return 0; }
What if you want to change your user interface later? Add another button and handler, or move one of the controls you already have? Well, you have two choices. Either you edit the C++ code you already have, or you go back to your starter C# app and edit your interface, then look at the code generated for you and do some careful copying and reediting in your C++ code. You can see the way that the position, name, and text of each control is set in InitializeComponent(), and you can edit these lines at will to rearrange your user interface, or copy them to add more controls and handlers. Well, that's all for now. I hope this column has shown you that C++ can do more than appears possible at first glance, and demonstrated some syntax differences between C# and C++. In future columns I'll talk more about managed types and value types, and some of the other subtle distinctions there wasn't space to discuss here.
In the last 6 months, I've done two cross-Canada tours to tell developers about Visual Studio .NET and what makes it so amazing. I've delivered lines like these:
"All those things VB programmers couldn't do -- a service, a snapin, a multithreaded app, a console app -- VB.NET programmers can do." "Visual C++ programmers no longer have a privileged position with the class library that gets all the goodies first. There's one class library for everyone now" "Language is just syntax."
Now, from the perspective of the .NET Framework, those things are certainly true. But Visual C++ .NET has something none of the other Microsoft .NET languages has: it can make ordinary Windows applications. That's right, if a Visual Basic programmer wants to make a "classic" Windows app that runs on a machine without the .NET Framework installed, it's time to dig out that old copy of VB 6. And that means the VB programmer doesn't get to use the fun toys like code outlining, flyover windows, or the server explorer while developing that app. But for Visual C++ programmers like me, there's no need to go anywhere: Visual C++ .NET makes MFC and ATL applications, just like before. It has a privileged position compared to the other .NET languages after all. In fact, the developer team for Visual C++ had a mission statement that included something none of the other teams had: make this a great upgrade even for users who won't be building .NET applications. And they've achieved that mission. The user interface simplies some really common tasks, and reduces frustrations dramatically. (One way for me to measure this is to watch my staff as they switch back to a machine that doesn't have Visual Studio .NET on it yet, and have to do things the old way.) What's more, MFC and ATL have been improved, and so has C++ standards compliance. How do you upgrade a Visual C++ 6 application to a Visual Studio .NET application? Simple, just open your old project file in the new product. Confirm you want to convert to the new format, Visual Studio will make you a .sln file, and nothing else will change. You'll be using the fun new user interface to do your old work. Of course, you'll have to learn your way around that fun new user interface a bit. Some old favorites are gone, and others are hard to find. Probably the toughest thing is that ClassWizard is gone -- you use the Properties window to catch messages now.You can see a dramatically condensed explanation of catching messages with the Properties window in Figure 1. It goes like this (the large red numbers in the figure are the order of the steps):
Figure 2. - Click here for larger image 1. In the Class View, select the class that will catch the message. 2. Bring up the Properties Window if it's not up, and click the Events button -it's a lightning bolt. 3. Find the message of interest, click to expand it, and click next to COMMAND or UPDATE_COMMAND_UI as appropriate. You'll get a drop down with one choice, to add the function. You can't control the name of the function as you could in older versions of Visual C++. 4. After you select the Add choice, the function will appear in the source and header files for the class, and the Class View will be updated to show the function to you. You can then add appropriate message-handling code. What else is different? Well a lot of items have wandered from one menu to another, and some menu items only appear when you've selected the right thing in the Class View or Solution Explorer first. That can be frustrating, to say the least. The first thing you're going to want to do is to change your user profile to "Visual C++ Developer." That arranges the windows in a way that looks more like
Visual C++ 6, and gives you the familiar keyboard shortcuts instead of the new Visual Studio arrangements. Simply switch to the Start Page using the tabs across the top (if you closed the Start Page, bring it back by choosing Help, Show Start Page) and click My Profile at the bottom of the left-hand column. Set your profile to "Visual C++ Developer" and things should get a lot more comfortable right away. The first thing most Visual C++ developers ask me is "where has Project, Settings" gone to? Believe it or not, there are four ways to get to the Property Pages dialog that serves the same purpose as the old Settings dialog:
Select the project in Solution Explorer and choose Project, Properties Select the project in Solution Explorer and choose View, Property Pages Select the project in Class View and choose Project, Properties Select the project in Class View and choose View, Property Pages
There are two distractions to avoid here: don't select the solution, always the project. And if you're on the View menu, don't be fooled by the Properties item -- that's for the usual Properties window. You can do an incredible amount of things once you have the project property pages up: perhaps the most common is to add another directory for include files. To do that, expand the Resources tab and choose General under that. (See Figure 2.)
Figure 2. - Click here for larger image While there are other user interface differences between Visual C++ 6 and Visual C++ .NET, knowing how to catch messages and change your project settings should take you a long way towards feeling comfortable and productive with this
amazing new tool. In the columns to come, I'll be showing you more about how to use Visual C++ .NET to build both .NET applications and classic Windows applications. And for those of you who think that poor Visual C++ has a neglected air to it, or can't do some of the things that C# and VB can do, I have a few surprises in store. See you soon!