共用方式為


Chapter 3: Choosing Windows Development Technologies

There are several factors to consider when choosing the right set of tools and technologies to use when developing an application. Clearly the requirements of the application are important to consider, as are the skills of the development team and the time available for developing the project. However, just as important to consider are the flexibility and capabilities of the programming languages, development tools, and libraries that will be used to implement the application. The design criteria of the Hilo applications are that they must be lightweight and high performance, yet be compelling fully-featured Windows 7 applications with minimal installation requirements. This article describes the rationale for the choice of the development technologies used to implement the Hilo applications.

Choosing the Right Language

There are many different languages to choose from, each with their own advantages and disadvantages. The first decision to make is whether to use a .NET-based language. While .NET-based languages are getting more sophisticated, they do lack the power and flexibility of more established languages. Any code written in a .NET-based language will access the operating system features through one of the .NET Framework libraries or through the .NET Platform Invoke or Component Object Model (COM) interop layer. Although these libraries and interop layers have been designed to be as efficient as possible they will always add extra machine cycles to any call. In an application that needs the highest performance possible, these extra layers will make a difference.

When you don't want to use a managed runtime environment to develop on the Microsoft Windows platform there are few candidates. The best candidates are C and C++. C gives the developer an experience that gives the developer a relatively simple, powerful programming language, but it lacks the benefits of higher level languages like encapsulation, abstraction, and inheritance. Furthermore, much of the Windows 7 Application Programming Interface (API) is accessible only through a COM interface. COM interface pointers are essentially pointers to virtual function pointer tables, and the double dereferencing that is required to access a COM method make the C code complicated and difficult to read.

So overall C++ is a good choice for writing Windows 7-based applications. It has the correct balance of the raw power and flexibility of C, and the sophisticated language features of C#. C++ as a compiled language produces executable code that is as close to the Windows API as is possible and, indeed, the C++ compiler optimizer will make the code as efficient as possible. The C++ compiler has many of options to allow the developer to choose the best optimization, and Microsoft’s C++ compiler is one the best ways to produce small, fast code.

With such power and flexibility there are potential pitfalls, of course. The chief problem with C++ is the issue of memory management, but since it is a mature language, standard solutions have been developed, for example smart pointers. There are many established standard libraries and their longevity and maturity means that they are broadly used and tested and so can be trusted to be stable.

The advantage of using a standard library is that there is a lot of documentation and examples illustrating how to use it and you have the confidence that it will deliver. For example the Standard Template Library (STL) for C++ has existed for two decades and has been an ANSI standard since 1994.

Finally, C++ has been the language of choice for Windows development for two decades, this means that many companies have a huge investment in C++ source code, and yet the great flexibility of the language means that source code from two decades ago can be integrated into code that uses the most up-to-date Windows 7 features.

Choosing the Right Development Tools

Visual Studio has long been the development tool of choice for Windows developers. Visual C++ 2010 adds productivity tools that make writing C++ code much easier and faster. One new feature is Live Semantic Errors. Users of Microsoft Office are familiar with this productivity tool, but this is a new feature for Visual C++. The code editor displays compiler-quality syntax and semantic errors as wavy underlines as you type. When you hover the mouse over that code a tooltip appears indicating the error.

Visual C++ 2010 also contains standard C++ language features, known as the C++0x features. Some of these features make C++ code a more readable, but others will have a significant effect on the code using them. For example, one C++0x keyword is nullptr, this value is used to assign a null value to a pointer, and makes your code a bit more readable. However, lambda expressions, that allow you to define anonymous function objects bring a level of functional programming to native C++ and are a powerful feature. Lambda expressions are initially quite alien to untrained eyes, but they can make code less verbose, and therefore more readable.

Choosing the Right Graphics Library

The images that you see on your monitor are put there by the computer’s graphics card. There are two fundamentally different ways to tell the graphics card what color each pixel should be displayed, and crucially, how that pixel changes. These two technologies are called Graphics Device Interface (GDI) and DirectX.

GDI has been part of Windows from the very first version. In essence it is a series of C functions that allow you to indicate the color of individual pixels, or collections of pixels as line or filled shapes. GDI allows you to perform simple animation with double buffering: that is, drawing the next frame in an off-screen bitmap buffer and then rapidly copying the frame to the screen in an action known as bit-blitting after the function used to do this copy: BitBlt. GDI allows you to draw lines, rectangles, polygons, and ellipses both as line shapes and filled shapes. These functions are quite primitive, but are simple to use. Lines are drawn using the current selected pen and areas are filled with the currently selected brush, so you simply select a pen or brush and provide the co-ordinates and GDI changes the appropriate pixels. Similarly with text, GDI offers relatively simple text drawing functions where you select the font, provide values like the pitch and point size and GDI draws the non-anti-aliased text on screen. GDI fill routines allow developers to use solid colors, but it does not provide for gradient fills, and there is no facility to use an alpha channel to provide opacity information. (One of the few GDI that uses alpha channel information is the AlphaBlendfunction, which is an alternative to BitBlt for copying bitmaps.)

GDI+, the native code graphics engine beneath the .NET Framework's Windows Forms, extends GDI to allow developers to use alpha blending and gradient fills, and to draw anti-aliased text. Although GDI+ is provided as C functions exported from a DLL, it is far more object-based than GDI and the Windows SDK for Windows 7 provides a C++ class library to encapsulate these GDI+ objects. While GDI+ is an improvement over GDI, it is still implemented with the same technology.

Perhaps the biggest issue with GDI (and therefore GDI+) is that these APIs have not been written for modern Graphics Processor Units (GPU) and consequently the GDI raster operations have to be translated into the texture and vector based operations native to modern graphics processors. Although they seem to be low level primitives, this extra translation layer means that GDI has less performance than an API that talks directly to the GPU.

DirectX technologies are written for modern GPUs, consequently the APIs reflect the capabilities of the GPUs and the programming paradigm of DirectX is as close as can be achieved to the objects used by the GPUs. To allow as many graphics cards as possible to be programmed with DirectX the underlying library will check the capabilities of the card’s GPU and if the GPU does not support a feature DirectX will provide a software implementation. This means that developers write the same code regardless of the graphics cards used. A DirectX application on a fully featured graphics card will use hardware acceleration.

GPUs are designed for 3D work and consequently DirectX provides the Direct3D API which is a collection of objects accessed through COM-like interfaces.

Choosing the Right Application Framework

Using some form of application framework to help with application development is often essential since a fully-featured Windows-based application can be a complicated beast. The aim of an application framework is to allow the developers to focus on the functionality of the application and not have to worry about the underlying plumbing code.

However, choosing an application framework involves a series of trade-offs. On the one hand a framework can provide you with ready-made implementations which you can use as-is to speed up development significantly. On the other hand, a framework can often constrain what you can do or add complexity by requiring you to extend or modify the framework in order to implement the features or behavior you require, or substantially increase the overall size of your application.

Since Hilo was developed using Visual C++ 2010 Express, the use of MFC (and ATL) which are not available with this edition was not an option. Furthermore, it was decided not to use WTL since this library is unsupported. In any case, the ethos behind the Hilo project is lightweight and flexible and so we decided to write a small common library that would be a thin wrapper above the Windows and Direct2d APIs. This provided a base on which we could write all of the Hilo applications. The Hilo Common Library provides numerous features that make writing the Hilo applications easier and faster, while maintaining the performance and size characteristics we require.

Developing For Windows 7

Windows 7 provides many innovative and compelling features and these are all available either through C functions exported from a DLL or through objects with COM interfaces. Furthermore DirectX has been enhanced in Windows 7 and is becoming the API of choice for Windows developers. DirectX and the constituent technologies of Direct2D, DirectX, DirectWrite all have COM-like interfaces. It is clear that to write a compelling modern Windows 7 application you must use a language that makes accessing COM interfaces and C library functions easy and this language is Visual C++.

Windows 7 brings enhancements to the APIs for accessing tablet features and allowing applications to provide a rich user experience. The most important feature of a Tablet PC is the touch sensitive screen. You interact with the application through finger gestures and through stylus movement. Because figure gestures are a natural movement, you expect the application to react in a natural way, so when you drag a finger across the screen, you expect the selected item to move according to your finger movement. When you use a finger gesture to “flick” an item, you expect the item to appear to be “flicked” on the screen . This requires a different type of graphics development than Windows developers have been used to. Users expect to see items in an application to behave in response to their gestures, and developers need a library that provides the facilities to provide this behavior. Enter Direct2D and the animation API.

Direct2D is built over Direct3D which itself is close to the metal of the GPU of a computer’s graphics card. The paradigm is very different to Windows GDI programming. In GDI programming the program code determines how the pixels on the screen will change and the computer’s CPU performs the calculations to change those pixels, at the same time as doing the work that the display is being animated to show. In Direct2D programming the program indicates how the display will change, and then tells the graphics card’s GPU to do the required calculations and update the display. This frees up the computer’s CPU to perform data crunching work, and results in a faster, more responsive application. Since the graphics card’s dedicated GPU performs the graphics calculations it means that more complex, high performance animations can be created.

For more details about how to program Direct2D refer to “Learn to Program for Windows in C++: Module 3 Windows Graphics”.

Componentization

Componentization means encapsulating state and code as objects. GDI is a non-object API, it has no componentization. GDI+ is object-based, but it is supplied through C functions (although the Windows SDK for Windows 7 provides a C++ class library that componentizes the API). The DirectX APIs are all object based and provided through COM-like interfaces.

COM is a platform-independent, object-oriented system for creating binary software components. COM objects can be created with a variety of programming languages and these objects can be within a single process, in other processes, even on remote computers. The COM infrastructure handles the threading, re-entrancy, and inter-process calls and this provides location transparency, that is, the client code is the same regardless of the location of the COM object.

Perhaps the most important aspect of COM is interface programming. This is a common technique in many languages and it allows code to concentrate on the behavior of objects rather than the implementation. An interface is a collection of related methods that represents a behavior, and since a COM object can implement more than one interface it can have more than one type of behavior. All access to a COM object is through an interface pointer.

Every COM object must implement an interface called IUnknown and every COM interface must derive from IUnknown (that is, implement the methods of the IUnknown interface). The IUnknown interface has three methods, two are the IUnknown::AddRef and IUnknown::Release methods and the third is a method called QueryInterface. The IUnknown::AddRef and IUnknown::Release methods are used for reference counting. The rules of COM mandates that when an interface pointer is copied (including the first time it is returned when a COM object is activated) the IUnknown::AddRef method must be called to increment the reference count. The rules also state that when code has finished using a COM interface pointer the code must call the IUnknown::Release method to decrement the reference count. When the reference count reaches zero there are no more users of the interface pointer and typically the IUnknown::Release method deletes the COM object. Reference counting through the IUnknown::AddRef and IUnknown::Release methods allows you to define the lifetime of an object and ensure that the correct cleanup is performed when the object is no longer need. The IUnknown::QueryInterface method is the COM equivalent of the C++ reinterpret_cast<> operator. The IUnknown::QueryInterface method takes the COM name (a 128-bit identifier) of the requested interface and if the object implements the interface the method returns a pointer to the interface.

Strictly speaking, a COM object must run in a COM apartment. Apartments are the mechanism through which COM manages threading and aspects like re-entrancy. Some of the Windows API are provided with COM-like interfaces that derive from the IUnknown interface but do not provide other COM features like activation or run in COM apartments. This is the case for the DirectX objects and the Windows shell objects. Interface programming is a powerful technique and you may want to use it within your own applications. You do not have to use the IUnknown interface to use interface programming, but since the interface is standard and well-known, it is a good place to start.

For more details about how to access COM objects with C++ refer to “Learn to Program for Windows in C++: Module 2 Using COM in Your Windows Program”.

Using C++ COM Keywords

COM objects are accessed through interface pointers, and an interface pointer is a pointer to a table of method pointers. Thus an interface is the same format as a virtual method table for a C++ class. This is fortuitous because it makes it very straightforward to implement an interface using C++ and to access an interface through an interface pointer. Typically developers create an abstract class with pure virtual methods for the interface, derive the object class from this interface class, and implement the interface methods. Visual C++ provides the __interface keyword and this is essentially a typedef for struct with the added behavior that all members are pure virtual.

Methods, like IUnknown::QueryInterface, that return an interface pointer, can return a pointer to the appropriate method pointer table by casting the object pointer to a pointer to the interface class. The C++ compiler ensures that the pointer returned is a pointer to the virtual method function pointer table. If an object implements more than one interface then you can derive the class from more than one abstract interface class.

When you create COM objects using the COM API (CoCreateInstanceEx) you must use the COM name, a 128-bit Globally Unique Identifier (GUID) for the object and for the name of the interface on that object. Since GUIDs are unwieldy structures the Visual C++ compiler gives you an operator and a class modifier to make your code easier to read and simpler to write. The __declspec(uuid) class modifier allows you to attach a GUID to a class, as a kind of tag that you can read later using the __uuidof operator. Neither the C++ compiler nor the COM functions will automatically read this GUID tag from an object, but your code can. However, attaching a GUID to a C++ class with __declspec(uuid) does not magically make your C++ class a COM object, you have to do more work than that.

Listing 1 Definition of the IUnknown interface using Visual C++ keywords

__declspec(uuid(“00000000-0000-0000-C000-000000000046”))
IUnknown
{
   HRESULT QueryInterface(REFIID riid, void **ppvObject);
   ULONG AddRef(void);
   ULONG Release(void);
};

Listing 1 shows the definition of the IUnknown interface using the Visual C++ keywords Listing 2 shows the same code using standard C++ keywords, illustrating that an __interface is a struct with pure virtual methods.

Listing 2 Definition of the IUnknown interface using standard C++ keywords

struct IUnknown
{
   virtual HRESULT QueryInterface(REFIID riid, void **ppvObject) = 0;
   virtual ULONG AddRef(void) = 0;
   virtual ULONG Release(void) = 0;
};

Using COM with C++

Hilo is not a COM server and so there is no need to implement any COM activated objects in the project. However, Hilo does access the COM-like interfaces on DirectX objects. One of the problems with using COM interfaces is ensuring that reference counting is correctly handled. As mentioned earlier, every time an interface pointer is copied IUnknown::AddRef must be called to increment the object reference count. Whenever the code has finished using an interface pointer IUnknown::Release must be called to decrement the reference count. If these reference counting rules are not followed exactly then resources will not be released early enough, and the server will suffer memory leaks.

The solution to the resource leak problem is smart pointers. A smart pointer class encapsulated the interface pointer and overrides the –> operator to give access to the interface pointer so that the smart pointer object can be used as if it is the interface pointer. However, the most important detail about the smart pointer class is that it has a destructor that will call IUnknown::Release on the interface pointer when the smart pointer goes out of scope. This means that the reference count on the object is controlled by the lifetime of the smart pointer object, so for example, you can create a smart pointer as stack variable and the lifetime of the COM object will automatically be constrained to the time that the function is running. Regardless of how the function is exited: a call to return, an exception, or just reaching the end of the function, IUnknown::Release will be called on the encapsulated interface pointer.

The Hilo application provides a smart pointer class called ComPtr<>. This class can be used on any interface pointer that derives from IUnknown regardless of whether the interface is on a COM object or not. Listing 3 shows the entrypoint function for the Hilo Browser application. The BrowserApplication class (explained in more detail in an upcoming chapter) provides the main window for the application and implements an interface called IWindowApplication. This is not a COM interface but it derives from the IUnknown interface so that reference counting can be used to manage the lifetime of instances of the BrowserApplication class.

The templated method, SharedObject<>::Create, called in Listing 3 creates an instance of the BrowserApplication class on the C++ heap using the new operator and calls IUnknown::QueryInterface on this object for the interface represented by the parameter.

Listing 3 The entrypoint function for the Hilo Browser application

int APIENTRY _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int)
{
   ComPtr<IWindowApplication> mainApp;
   HRESULT hr = SharedObject<BrowserApplication>::Create(&mainApp);
   if (SUCCEEDED(hr))
   {
      hr = mainApp->RunMessageLoop();
   }

   return 0;
}

The ComPtr<> variable is declared on the stack, so when the variable goes out of scope (after the return statement is called) the destructor is called and this calls IUnknown::Release on the interface pointer, which in turn calls the delete operator on a pointer to the BrowserApplication object. This illustrates that the implementation of the IUnknown methods are closely coupled to the implementation of the code that creates the COM object (in this case SharedObject<>::Create) since the code used to delete the object must be appropriate for the code used to create it.

The IWindowApplication interface contains a method called RunMessageLoop, and the implementation of this method on the BrowserApplication class uses this to provide a standard Windows message loop. In Listing 3 it appears that the RunMessageLoop method is called on an interface pointer, however, this is not the complete story. Since mainApp is a ComPtr<> object the code in Listing 3 actually calls operator –> on the smart pointer class, which in turn calls the method on the encapsulated interface pointer.

Conclusion

This article discussed the rationale for choosing the technologies used to implement the Hilo applications. The next article discusses the process by which the user experience for the Hilo Browser application was designed. The design and implementation of the Hilo Common Library will be covered in the following article.

Next | Previous | Home