Chapter 4: Recommendations for Win32, ActiveX, and "Longhorn"
Preface
Chapter 1: The "Longhorn" Opportunity
Chapter 2: Preparing for "Longhorn"
Chapter 3: Recommendations for Managed and Unmanaged Code Interoperability
Chapter 4: Recommendations for Win32, ActiveX, and "Longhorn"
Karsten Januszewski
Microsoft Corporation
January 2004
Sample updated June 2004
Applies to:
Longhorn Community Technical Preview releases (both PDC 2003 and WinHEC 2004)
Note: The downloadable sample code is based only on the Longhorn Community Technical Preview, WinHEC 2004 Build (Build 4074)
Contents of Chapter 4
Migration Strategies
General Considerations
Details
Control and Window Hosting
Code Samples and Further Information
Download the code samples described in this chapter
"Longhorn" represents a major paradigm-shift for Microsoft® Windows® and Windows-based applications. It isn't simply a matter of some new APIs providing access to some new features. Rather, "Longhorn" introduces a new approach to writing Windows applications. This chapter covers the issues that need to be addressed when Microsoft Win32®-based applications must migrate to or interoperate with "Avalon," the presentation and media portion of "Longhorn." While you can accomplish the same things with "Longhorn" that you did with previous versions of Windows, many of the implementation details vary. In addition, "Longhorn" offers functionality that has no counterpart in earlier versions of Windows. Some examples include:
- Managed code. The new API, WinFX, is largely managed code. "Avalon," in particular, is exposed entirely through managed code.
- Control customization. The "Avalon" architecture allows you to customize controls through inheritance. This allows you to maintain a control's appearance and input functionality without rewriting code. For example, the keyboard is simply available, without the developer having to program any part of the infrastructure.
- Layout and localization. In "Avalon," you can resize the layout to accommodate localized content.
- Styles. This is an improved way to give applications a consistent look and feel.
While most existing Win32-based applications should continue to work normally in "Longhorn," they will not be able to take advantage of the new features without some degree of adaptation to the new programming model.
There are several different scenarios where "Avalon" can enhance an existing Win32-based application. These include:
- Using the "Avalon" user interface (UI) for dialog boxes or wizards.
- Hosting the "Avalon" UI as a child of a Win32 window.
- Redesigning the appearance of existing applications so they resemble the "Avalon" UI.
- Hosting a Win32 window as an element in the "Avalon" UI.
- Hosting an ActiveX control as an "Avalon" element.
- Replacing Win32 menus and toolbars with their "Avalon" equivalents.
- Integrating "Avalon" commands with Win32 commands.
- Using drag-and-drop across "Avalon" and Win32.
Migration Strategies
There are two basic approaches for migrating a Win32-based application to "Avalon." They are:
- Porting Win32 code to managed code and the "Avalon" portion of WinFX.
- Modifying Win32 code to interoperate with managed code and the "Avalon" portion of WinFX.
In practice, most applications will use a mix of these two approaches. There are four basic scenarios.
- Minimum changes. Revise an existing application so that it conforms to the "Longhorn" logo requirements. These requirements are yet to be determined, but will probably involve issues such as installation changes, an application manifest, and "Avalon" UI themes.
- Integration. Incorporate selected "Avalon" features into your existing code base, but don't make any major alterations. Conceptually, this option is somewhat similar to incorporating new Win32 or COM APIs into an existing application. The benefit of this approach is that you can take advantage of those aspects of "Avalon" that are of value to your application, without having to do a major rewrite. The new managed code will be isolated from your existing code, but must interoperate with the original Win32-based applications. Examples of integration include:
- Adding "Avalon"-based wizards or dialog boxes.
- Hosting "Avalon"-based controls.
- Using "Indigo" for communication.
- Migration. Write a new application in managed code using the "Avalon" portion of WinFX, but reuse some key parts of your existing code base. Wrap the reused code in managed objects so that they can readily interoperate with the managed code. With this approach, you can eventually rewrite these objects with managed code without affecting how the object interacts with the rest of the application. One example of migration would be to write a new UI with "Avalon," while reusing some ActiveX controls.
- Rewriting. Rewrite the application entirely in managed code using the "Avalon" portion of WinFX. This option provides the maximum access to "Avalon's" features, but also requires the greatest amount of work.
General Considerations
There are a number of general issues to consider when deciding how to migrate an application.
- Managed or unmanaged code. If you are implementing your applications in C++, use the managed extensions (called managed C++, for short). Using managed C++ allows you to interoperate with other managed objects, including WinFX, in a straightforward way. You also get the other benefits of managed code, such as automatic memory management. The upcoming version of managed C++ (v2.0) has a number of syntactic improvements and features that will make implementing managed C++ applications much easier.
- Mixed mode or pure code. Applications will typically mix managed and unmanaged code in a "mixed-mode" module. This has the advantage of allowing incremental additions of managed code to the module without changing the performance of the application. However, there are some caveats to this approach that should be noted. First, reflection over mixed-mode executable files from outside the assembly is not possible. Also, Click-Once can deploy mixed-mode DLLs, but not executables. One way to circumvent these restrictions is to segregate mixed-mode code in DLLs, and have an executable stub written in either managed or unmanaged code.
- Performance tradeoffs. Unmanaged code can be more effectively optimized than managed code with compilation techniques including aggressive inlining of code, whole program optimization, profile guide optimization, and Simple SIMD Extensions (SSE/SSE2) for floating point math. On the other hand, managed code in C++ can be globally optimized at compile time. There may be some small performance improvements over other non-optimized CLR languages, depending on the particular application.
- Compiling standard C++ classes into MSIL. You can choose to compile your standard C++ classes as either managed or unmanaged code. Here are two considerations:
- If you compile C++ classes to MSIL, you can access managed APIs faster than you can by compiling your C++ classes as unmanaged code and then accessing managed APIs through COM interop (COM-callable wrappers). This is because COM interop has an extra level of indirection and extra memory allocation, and often requires two levels of marshalling.
- Pure-mode images have better virtual function calls and less overhead than the same C++ classes compiled into MSIL in a mixed-mode image.
- Loading the CLR. Since the new functionality in "Longhorn" is only accessible through managed code, you should load the CLR in-process and not delay-load it.
Details
The following sections outline the details of the various issues that you will need to consider when migrating your application to "Avalon." Conceptual and programming model changes are discussed and compared, providing a framework for the Win32 developer to understand the "Avalon" model.
Basic UI model Comparison
With Win32, controls and other elements are positioned in a window, but they have no inherent hierarchy beyond tab order. Win32 applications typically treat the client area as a canvas, and redraw it as the user interacts with the UI. As we shall see, "Avalon" operates differently.
A page in "Avalon" consists of elements and other components, collectively called "nodes," that are organized in a hierarchical tree. The structure of this tree essentially reflects the hierarchy defined by the page's "Longhorn" Markup Language ("XAML") file or the equivalent object model relationships. The structure of this tree largely determines how a page renders and behaves. Events such as mouse clicks are routed through the tree hierarchy, somewhat like they are with DHTML. In addition, applications written with Avalon have built-in support for navigation. Consequently, such an application is typically somewhat similar to a Web application. It consists of a collection of pages, and, as users interact with the UI, they navigate from page to page.
Layout Comparison
With Win32, the layout is largely controlled by specifying the relative or absolute position of the elements, either explicitly or indirectly through tools such as the Dialog Box designer. In contrast, the "Avalon" layout is based on panels. Panels are components that control the rendering of elements and objects, including their size and dimensions, their positions, and the arrangement of their content. Rendering is controlled by properties specified on panels. If you are displaying anything in "XAML," you are using panels, either explicitly or implicitly. The Canvas panel, which allows you to absolutely position elements, is probably the closest to the Win32 model. There are also panels that allow you to stack or dock elements, panels for text layout, and panels for grids and tables. Layout is typically specified with "XAML," although you can also create or modify page layout programmatically.
Eventing Models
With Win32, events are handled as Windows messages, such as WM_NOTIFY or WM_COMMAND. Each event source produces one or more of these messages in response to events such as user interactions. The associated message is received by the window's WndProc, which determines which message has been received and responds appropriately.
Each "Avalon" element can typically raise a number of events. For example, a button element raises a Click event when a mouse is clicked within the button. This event is handled by attaching an event handler for that particular event to that particular element. You implement a handler based on the event's delegate. When the event is raised, the handler is called and you can respond appropriately. You can attach handlers to most events with "XAML." Events are typically not restricted to the element that raises them. Much like DHTML, they are routed through the UI tree, and can be detected anywhere along the route. There are two event-routing models:
- The bubble model, where the event instance goes up the element tree.
- The tunnel model, where the event instance goes down the element tree.
Controls
Win32 controls are essentially special purpose windows designed to handle user interaction. They are explicitly positioned in the parent window and communication with the host takes place through Windows messages. ActiveX controls are often similar but communication with the host takes place through COM interfaces.
In contrast, "Avalon" controls are simply extensions of the "Avalon" class hierarchy. In terms of the UI-tree, they are treated like any other element. Most aspects of layout are handled by setting the same properties, such as Height or Width, that are used by other types of elements. You communicate with an "Avalon" control through its properties and methods. The control communicates with the host by raising events.
Graphics
The "Avalon" graphics rendering layer contributes new drawing and animation features to "Longhorn." These features were previously provided by using specialized libraries—specifically, the Windows Graphics Device Interface (GDI) and Windows GDI+. To accommodate developers working with existing unmanaged applications, "Avalon's" Visual API provides limited interoperability between the "Avalon" graphics system and GDI-based rendering services.
This section is for developers who must adapt their legacy applications to draw using the "Avalon" drawing and rendering functionality. Developers who merely want to host GDI-based windows in "Avalon" applications can use the HwndVisual object, discussed below. For more information, see "Using the Visual Layer," in the "Longhorn" SDK documentation. It's important to note that there is limited interoperability between "Avalon" graphics and GDI-based graphics. Developers should not expect full "Avalon" hosting in unmanaged Win32-based applications. Only specially authored "Avalon" graphics components will interoperate with GDI-based applications. More specifically, it is not possible to render GDI primitives and "Avalon" primitives in the same window or to the same surface.
A Win32-based application can use "Avalon" drawing and rendering by specifying a window class style that distinguishes between a window that draws with GDI and one that draws with "Avalon." To make this distinction, the developer must set the CS_MIL class style when creating the window. This specifies that the window is using "Avalon" rendering. A window can render using either GDI or "Avalon"; the two graphics systems cannot be mixed within the same window. One reason for this distinction is that graphics rendering is hardware-accelerated in "Avalon." To make use of hardware-accelerated rendering in applications that use both GDI and "Avalon," the CS_MIL class style must be set on your application's top-level window.
The following steps describe, in general, how windows can use "Avalon" graphics rendering in an unmanaged application. These steps assume that window classes have already been registered.
Here are the steps for a top-level window:
- The application creates a window.
- If the developer passed CS_MIL to the constructor when the window was created, then the application-defined WndProc function is notified.
- The WndProc function loads the "Avalon" graphics system.
- The window rendering target is created and mapped to the window.
- Drawing proceeds using "Avalon" graphics.
A window with the CS_MIL class style will be able to use "Avalon" hardware acceleration so long as all of its ancestors, up to the top-level window object, have the same class flag; otherwise, all windows descending from a window drawn with GDI will be rendered with software acceleration only.
Here are the steps for a child window:
- The application creates a window.
- If the developer passed CS_MIL to the child window's constructor when the window was created, then the application-defined WndProc function is notified.
- The WndProc function loads the "Avalon" graphics system.
- If CS_MIL is set on the parent window:
- The window rendering target is created and mapped to the window.
- The parent is notified.
- Drawing proceeds using "Avalon" graphics, as determined by the parent rendering.
- If the parent window is a GDI window:
- The window rendering target is created and mapped to the window.
- Drawing proceeds using "Avalon" graphics, but without hardware acceleration.
- If the child window is a GDI window and the developer has passed CS_MIL to the parent window's constructor when the window was created:
- The parent is notified.
- Drawing proceeds using "Avalon" graphics, as determined by the parent rendering, but without hardware acceleration.
- If the parent is simply a GDI window, then drawing proceeds normally, using the GDI APIs.
HWNDVisual and Win32-Based Applications
The Visual API, like the rest of the "Avalon" graphics system, is a managed API and uses the features managed code offers, such as strong typing and automatic memory management. It also takes advantage of the "Avalon" hardware acceleration capability for rendering. To accommodate developers working with existing unmanaged applications, the Visual API provides limited interoperability between the "Avalon" graphics system and GDI-based rendering services.
This interoperability allows developers to:
- Host GDI-based windows in "Avalon" applications using the HwndVisual object.
- Write controls and create themes that are based on "Avalon" drawing and rendering capabilities, but still work in legacy GDI applications.
- Modify GDI HWND-based applications to take advantage of the "Avalon" rendering features, including hardware acceleration and the "Avalon" color model. For more information on this feature, see "Vector Drawing for 'Avalon'" in the "Longhorn" SDK.
The HwndVisual object enables hosting of Win32 content in an "Avalon" application. The HwndVisual object inherits from ContainerVisual. Note that it is not possible to mix GDI and "Avalon" drawing models in the same HwndVisual. Instead, this object might be more useful for legacy controls of limited scope.
Windows
"Avalon" takes care of much of the complexity of window management internally, making it simpler and more straightforward than conventional Windows-based applications. In addition, "Avalon" applications can be compiled to run in either a stand-alone window or be hosted by the browser. As long as your application does not access browser interfaces, you should be able to use the same sources for browser-hosted or stand-alone windows. Window management works much the same in either setting. The primary difference is that you have much more control over window style with a stand-alone window.
There are two types of "Avalon" windows:
- A Window object, which supports core window functionality. This means that since it does not support navigation you can only open and close the window.
- A NavigationWindow, which extends the Window object to support navigation. You can navigate to another page and display it in the same window, as well as open new windows. Navigation windows also support a journal, which keeps a record of navigation, and supports forward/back navigation.
Because of the differences in how windows are handled, you generally can't mix "Avalon" and Win32 window management code. Instead, you can host "Avalon" windows in a Win32-based application or host an ActiveX control in an "Avalon" application. These two scenarios are addressed in the next section.
Control and Window Hosting
There are several issues to address when hosting "Avalon" windows in a Win32-based application or when hosting an ActiveX control in an Avalon application.
Hosting an "Avalon" Window in a Win32-Based Application
One of the simplest ways to add "Avalon" functionality to a Win32-based application is to host an "Avalon" window. For instance, you can write a wizard or dialog box in "Avalon" and launch it from a Win32-based application. Here are some of the issues you will need to address:
- Your hosting code must be managed.
- "Avalon" windows use the single-threaded apartment (STA) threading model, which means that your application must use STA in order to work properly with an "Avalon" window.
- On the "Avalon" side, you must explicitly create and enter a threading context, and create a dispatcher for Windows messages.
- You will need to implement a way to pass information from the wizard or dialog box back to the host. A simple way to do this is to implement properties on the "Avalon" object to hold the data. The host can then get the data from those properties when the wizard or dialog box returns.
Another approach is to host an "Avalon" window as an element of the host window, such as a sidebar. One key issue with this scenario is routing commands across the Win32/"Avalon" boundary. However, the technology to support this scenario is not yet available.
Hosting an ActiveX Control in an "Avalon" Application
Many applications depend on ActiveX controls. These controls may be substantial applications in their own right and are often produced by third-parties. Rewriting them entirely in "Avalon" represents a major effort and one that may not be possible if someone else owns the underlying code. An alternative, especially in the short-term, is to host the control in an "Avalon" UI.
The way to incorporate an ActiveX control in an "Avalon" application is to host it in a Windows Form, which has a well-developed API for handling ActiveX interoperability. You can then host that control in your "Avalon" application.
Handling Commands Across the Win32/"Avalon" Interface
A command, such as a menu selection may occur in the Win32 portion of your application but is handled by the "Avalon" part, or occur in the "Avalon" portion and be handled in the Win32 code. This means that you need a way to pass commands across this interface. The technology to support this scenario is currently unavailable. To get an idea of how this issue will be supported, look at the CommandLink collections that are supported by "Avalon" elements.
User Interaction
Mouse and keyboard interaction in Win32 is handled through Windows messages such as WM_MOUSEMOVE. In "Avalon," mouse and keyboard interaction is handled by raising on the affected element, which can then bubble or tunnel through the UI-tree.
In general, keyboard and mouse interaction should be handled natively by whatever window has focus. The main exception to this rule is accelerator keys, which might be directed to a window that does not have focus.
Accelerator keys raise many of the same issues that are encountered handling commands across the Win32/"Avalon" interface. The technology to support this scenario is currently unavailable. To get an idea of how this issue will be supported, look at the CommandLink collections that are supported by "Avalon" elements.
Threading
As we stated earlier, "Avalon" uses the STA threading model, which means that your main thread must be STA in order to interoperate with "Avalon." To do this, set the [System.STAThread()] attribute on the Main method that is your applications managed entry point.
Every control in "Avalon" has a threading context. The thread that created the control has access to that context, but any other thread can also acquire the context. You acquire the context by calling the Context.Enter() method. If you attempt to call a control on a different thread before calling Context.Enter(), a thread exception is raised. This entire threading model is very similar to using the Control.Invoke() command in Windows Forms. See Chapter 3 for further discussion of managed/unmanaged thread synchronization.
Code Samples and Further Information
For further guidance on specific scenarios, there are code samples that provide actual implementations. The accompanying documentation contains a walkthrough of the sample and explains the process in detail.
Hosting an Application Written with "Avalon" in a Win32/MFC Application. This code sample demonstrates how to host an "Avalon"-based library within an MFC application. Specifically, it uses Managed Extensions for C++ within a simple MFC application to invoke an "Avalon"-based wizard. The same approach would be used to host "Avalon" from a pure Win32 (non-MFC) C++ application.
Note The Hosting an Application Written with "Avalon" sample is not available on the Longhorn Community Technical Preview, WinHEC 2004 Build (Build 4074).
Hosting an ActiveX Control in a "Longhorn" Application. This code sample demonstrates how to host ActiveX controls within a "Longhorn" application. Specifically, it illustrates how to host Macromedia's Flash Control using markup and code-behind files.
"Avalon" documentation is available at the MSDN "Longhorn" Developer Center. An introduction to "Avalon" technologies is also included with the "Longhorn" SDK. From the start page, scroll down and select the "Presentation Subsystem (code-named "Avalon")" link in the Technology Roadmap section. That will take you to a page with a roadmap of the "Avalon" documentation, and links to the overview documentation.
For further information on MFC and managed C++, see the documentation in the MSDN® Library, including:
- Microsoft Foundation Class Library
- Porting and Upgrading
- Managed Extensions for C++ Programming
There are several Microsoft Press books that cover the general area of managed C++ and interoperability.
- Microsoft Visual C++ .NET Deluxe Learning Edition
- Microsoft, Microsoft Visual C++ .NET Language Reference, (ISBN: 0735615535)
- Grimes, Richard, Programming with Managed Extensions for Microsoft Visual C++ .NET, (ISBN: 0735617821)
Continue to Chapter 5: Recommendations for Windows Forms and "Longhorn"