Microsoft .NET/COM Migration and Interoperability

 

patterns and practices home

patterns and practices Index

.NET Architecture Center

Application Architecture for .NET: Designing Applications and Services

Steve Busby and Edward Jezierksi
Microsoft Corporation

August 2001

The patterns & practices team has decided to archive this content to allow us to streamline our latest content offerings on our main site and keep it focused on the newest, most relevant content. However, we will continue to make this content available because it is still of interest to some of our users.
We offer this content as-is, without warranty that it is still technically accurate as some of the material is undoubtedly outdated. Note that the content may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

Summary: The interoperability features of the Microsoft .NET Framework will enable developers to continue using traditional Active Server Pages, Component Object Model applications, and Microsoft Win32 DLLs, making it easier to choose if and when to migrate existing managed code to .NET. (34 printed pages)

Contents

Introduction
Understanding .NET Interoperability
Interoperability vs. Migration
Migration Strategy
Migration Guidelines for Developers
Migration Guidelines for Deployment and Operations

Introduction

It is likely that you have traditional Active Server Pages (ASP) and Component Object Model (COM) applications that you will want to use for some time after the Microsoft® .NET Framework and common language runtime (CLR) are released. You will want to take advantage of new functionality exposed by the CLR, and reuse existing components from the managed code that you develop.

The interoperability features of .NET allow you to work with existing unmanaged code (that is, code running outside the CLR) in COM components as well as Microsoft Win32® DLLs. It also allows you to use managed components from your unmanaged, COM-based code. These features allow you to choose if and when to migrate existing unmanaged code to .NET.

Understanding .NET Interoperability

This section briefly introduces the various types of interoperability supported by .NET. This will help you understand the tradeoffs that are described in the migration scenarios later in this document. This section also discusses:

  • Calling a COM component from .NET.
  • Calling a .NET component from COM.
  • Calling unmanaged application program interface (API) functions from .NET.

The .NET CLR enables interoperability by hiding the complexity associated with calls between managed and unmanaged code. The runtime automatically generates code to translate calls between the two environments. .NET manages the following aspects of interoperability between managed and unmanaged code:

  • Object binding
    Both early and late bound interfaces are supported.
  • Data marshaling and translation
    Data type conversion is handled between managed and unmanaged data types.
  • Object lifetime management
    Object references are managed to ensure that objects are either released or marked for garbage collection.
  • Object identity
    COM object identity rules are enforced.
  • Exception and error handling
    The runtime translates COM HRESULT values to .NET exceptions and vice versa.

For more information about how .NET interoperability works, see Interoperating with Unmanaged Code.

Calling a COM Component from .NET

When a COM object is called from .NET, the runtime generates a runtime callable wrapper (RCW). The RCW acts as a proxy for the unmanaged object. Figure 1 illustrates an RCW generated by the runtime for a COM object.

Ee957911.cominterop01(en-us,MSDN.10).gif

Figure 1. .NET runtime callable wrapper

The RCW is responsible for handling all interaction between the .NET client code and the COM component, including (but not limited to):

  • Creating and binding to the underlying COM object.
  • Consuming COM interfaces and factoring the interfaces into a managed form.
  • Translating and marshaling data between environments.
  • Managing the lifetime of the wrapped COM object.
  • Translating COM HRESULT values into .NET exceptions.

The RCW is a managed object and is allocated from the heap maintained by the CLR. As with any other managed object, references to the RCW are traced by the runtime, and the RCW is subject to garbage collection.

For more detailed information, see Exposing COM Components to the .NET Framework.

For information about writing .NET-friendly COM components, see Building COM Components for Interoperability.

Calling a .NET Component from COM

When a .NET component is called from COM, the runtime again generates a wrapper object to bridge the gap between the environments. In this case, the runtime generates a COM callable wrapper (CCW). The runtime reads the type information for the component from its assembly metadata and generates a compatible CCW. Similar to the RCW, the CCW acts as a proxy between the unmanaged COM code and the managed .NET code. Figure 2 illustrates the CCW's role in the interaction between a .NET component and COM client.

Ee957911.cominterop02(en-us,MSDN.10).gif

Figure 2. COM callable wrapper

The CCW is responsible for handling all interaction between the COM client and the managed object, including (but not limited to):

  • Creating and binding to the underlying managed object.
  • Synthesizing several important COM interfaces (such as IUnknown and IDispatch) based on the object's type information.
  • Marshaling and translating data between environments.
  • Managing the lifetime of the .NET component.
  • Translating .NET exceptions into COM HRESULT values.

The CCW is an unmanaged COM object that is allocated from the standard Microsoft Windows® heap and is reference-counted similarly to a traditional COM object. The CCW is not garbage collected, but rather destroyed upon the release of the last client reference. When the CCW is destroyed, the managed object that it wraps is marked for garbage collection.

For more detailed information, see Exposing COM Components to the .NET Framework.

COM+ services

.NET components can participate in COM+ applications and share context, transactions, synchronization boundaries, and so forth with COM+ components. .NET components that participate in COM+ applications (referred to as Enterprise Services in .NET) are called Serviced Components.

Serviced Components must be registered in the COM+ catalog, typically by using the regsvcs tool provided with the .NET Framework SDK. You can specify the exact service requirements for your .NET component by annotating your managed code with service-related attributes. For more information about registering and deploying .NET Serviced Components, see Migration Guidelines for Deployment and Operations in this document and Writing Serviced Components in the .NET Framework SDK.

For information about writing COM-friendly .NET components, see Building .NET Framework Components for Interoperation.

Calling Unmanaged APIs from .NET

In addition to interoperability with COM-based unmanaged code, the .NET platform supports calling unmanaged code in native Win32 DLLs. This interoperability, called Platform Invocation (commonly abbreviated as P/Invoke), allows managed code to call into C-language-style API functions, handles the marshaling of data types between managed and unmanaged types, finds and invokes the correct function in the DLL, and facilitates the transition from managed to unmanaged code.

A good example of P/Invoke functionality is calling one of the many Win32 API functions exposed by the Windows operating system. Through P/Invoke functionality, the runtime also supports callbacks from API functions. The current release of .NET, however, does not support calling from a Win32 DLL into .NET managed code. To call directly from unmanaged code to managed code, you must use COM interoperability.

Declaring unmanaged code

To call an unmanaged API from .NET code, you must declare the API to the .NET runtime. Although the syntax for the declaration varies from language to language, the declaration includes a list of the parameters and the return value for the function to be called. For an example of how to declare and use unmanaged APIs from .NET, see the shellcmd sample application in the .NET Framework SDK.

Data type translation

By default, the runtime generates code for converting from the managed type to the unmanaged type for each parameter as necessary. You can control the translation, if required, by using custom marshaling with the MarshalAs attribute. For more information about custom marshaling in P/Invoke calls, see Custom Marshaling and Interop Marshaling for COM in the .NET Framework Developer's Guide.

Unmanaged code security

Managed code uses code access security. The runtime checks the code before accessing a resource or performing other potentially dangerous tasks. However, when calling into unmanaged code, the runtime loses the ability to perform the necessary security checks for ensuring that the unmanaged code is not performing harmful activities. Therefore, before allowing any P/Invoke call to unmanaged code, the runtime checks for the necessary security on all callers in the call stack. All managed code in the call chain must be signed with Full Trust permissions, and the administrative policies must allow code to run on the system with full trust.

For more information about P/Invoke interoperability, see Consuming Unmanaged DLL Functions.

Interoperability vs. Migration

The first thing to consider in terms of a migration is whether to migrate the code at all. The COM interoperability features of the .NET Framework are very powerful and, in nearly all cases, allow you to continue to use your existing code without migrating it to managed code. As you develop new parts of your application or reuse components of your application from newer managed code applications, in most cases you can simply call your existing components through the COM interoperability functionality provided by .NET.

There are distinct advantages to interoperating with existing code, rather than migrating it. Interoperability allows you to preserve the investment that you have already made in developing and stabilizing the code, familiarizing developers with it, and learning how to deploy and operate the code safely and effectively.

It is expected that the majority of existing code will be utilized through interoperability instead of being migrated. In a few instances, however, migration may be a better choice for your application. You should weigh the cost of migration in terms of the developer resources required and the time spent rewriting code against some of the reasons to migrate described in the following sections. In most cases, the time savings and convenience of being able to interoperate with existing code from new managed code outweigh any reasons to migrate the existing code.

Performance

Overall, the overhead of calling from managed code to unmanaged code through COM interoperability is minimal. If your method performs any substantial tasks, it is likely that the overhead from the interoperability layer will be a negligible percentage of the overall method call time. However, if your method does nothing more than set a value for a property or perform some other small task, the overhead of the interoperability layer may be a significant portion of the method call. If your interface is made up of a number of these property sets and gets, known as a chatty interface, the interoperability cost may be unacceptably high. You should consider either migrating such components to managed code, or, as discussed in Component Design later in this document, writing a managed wrapper around your component and moving this functionality to the wrapper.

The CLR does not use COM apartments to provide call synchronization for managed objects; it joins a COM apartment only when it is necessary to interoperate with COM components. When the CLR does enter a COM apartment for interoperability, by default, it joins the multithreaded apartment (MTA) for the process. This means that all apartment-threaded objects, including all COM objects written in Microsoft Visual Basic® 6.0, will be called by means of a proxy/stub combination, thus necessitating a costly thread switch for the call and the return. In some cases you can override the behavior of the CLR and cause it to join an STA, avoiding the need for a proxy and stub. (For more information, see STAThreadAttribute in the .NET Framework documentation.) However, in some cases this behavior cannot be overridden, as when Web services are implemented by means of a .asmx file. If you intend to call a business component from a Web service that is implemented in a .asmx file, you should consider migrating the component to managed code to avoid the proxy/stub-based call. If the called method performs a lot of work, the overhead may be minimal.

You should stress test your component in your environment to determine whether performance improves significantly enough to offset the cost of migration.

Enhancing Development Productivity

The .NET development environment provides significant improvements to the COM-based development model for distributed applications and can significantly enhance developer productivity. If your application is expected to undergo a number of changes and development cycles in the future, you should consider migrating the application to take advantage of the higher developer productivity inherent in the .NET development platform.

Using a Managed Object Model

If most clients of your existing components will be written in managed code, you should consider either migrating your component to managed code or writing a managed wrapper as discussed in Component Design later in this document. Your managed code clients will expect your component to look and act like a managed object. Although the RCW makes the component look somewhat like a managed component, it does not change the underlying interfaces to the component. When developing against your unmanaged component through COM interoperability, managed code developers will not be able to use parameterized constructors, static methods, inheritance, and other features they are accustomed to working with in managed code. Migrating your component or writing a managed wrapper will make your component easier to use for managed code developers.

Taking Advantage of .NET Features

In some cases, you will want to migrate parts of your application to .NET so that you can take advantage of the new features that the .NET Framework offers. For example, ASP .NET provides advanced data binding, browser-dependent user interface generation, and improved configuration and deployment. You should evaluate when the value of bringing these new features to your application outweigh the cost of code migration.

Migration Strategy

After you have decided to migrate part or all of an existing application to .NET, you will need to decide how best to approach the migration. This section introduces the horizontal and vertical approaches to application migration. The issues you need to consider in forming a migration strategy are discussed together with some common migration scenarios.

Horizontal migration involves replacing a whole tier of your application. For example, you may choose to initially replace the ASP code within your Web-based presentation tier, or you may replace the COM code within your middle tier as the initial migration step.

Vertical migration involves isolating and replacing a piece of your application through all n tiers.

Figure 3 illustrates the difference between a Web-tier horizontal migration and a vertical migration.

Ee957911.cominterop03(en-us,MSDN.10).gif

Figure 3. Horizontal and vertical migration approaches

The guidelines that follow are intended to help you to:

  • Choose a migration strategy that minimizes the risk of migration.
  • Choose migration activities that allow you to continue to use your existing COM code base as you move to .NET.
  • Quickly take advantage of the new features provided by the .NET environment.

Note   The subsequent discussion and figures presented in this document discuss a logical three-tier application design, and do not necessarily represent a physical three-tier design. The approach you use should reflect the design goals for your application.

Horizontal Migration

In a horizontal migration, you migrate an entire tier of your Windows DNA–based application to .NET without immediately migrating the other tiers. By migrating a single tier at a time, you can take advantage of the features of the .NET Framework specific to a particular tier (for example, ASP .NET on the presentation tier), in many cases without modifying application code or affecting operations on another application tier.

Choosing a horizontal migration strategy

The first step in a horizontal migration is to decide which tier to migrate first.

To replace the Web tier, you replace ASP code with code developed using ASP .NET. You may also externally expose much of your middle-tier functionality with .NET Web services. Ideally, the Web tier can be replaced with few or no changes to the middle tier. If you migrate your Web tier to ASP .NET you can make use of the following features:

  • Advanced data binding functionality
  • Easier configuration management
  • Improved session state management
  • Advanced caching capabilities
  • Easy development of Web services
  • Compiled code, which results in better performance
  • The Web Forms programming model with server-side event handling
  • Server-side controls, which make it easier to develop solutions targeting multiple browsers

To replace the middle tier, you migrate your middle-tier COM components to .NET with few or no changes to the Web tier.

In deciding whether a horizontal migration strategy is appropriate, and if so, which tier is the most appropriate for initial migration, consider whether your existing Windows DNA–based solution has the following characteristics:

  • Large number of Web servers
    Deployment of an ASP .NET application requires that the CLR be present on each Web server. This can be an issue if your application is deployed on a large number of Web servers in a Web farm configuration. If you have considerably fewer middle tier boxes, consider migrating the middle tier first in a horizontal migration.
  • Shared code migration
    If your ASP code uses a large amount of shared code and a large number of constants in ASP include files, you can avoid having to convert all this code early in your migration by starting with a horizontal migration of your middle tier code.
  • Heavy use of ASP Application or Session state
    In many traditional ASP and COM based applications, the ASP pages share application state and session state using the ASP Application and/or the ASP Session objects. ASP and ASP .NET cannot share state across the two environments using these intrinsic objects. In cases in which you make heavy use of these objects, you should consider a horizontal migration.
  • Complex middle tier
    Complex object hierarchies in the middle tier should be kept as a unit. Deciding where to isolate an application with a complex middle-tier object hierarchy is difficult, and migrating only parts of a complex middle tier typically necessitates numerous interoperability calls between environments, resulting in performance degradation.

Considerations for replacing the Web tier

When replacing the Web tier, you must consider the following:

  • You must translate ADO recordsets returned from the middle tier to ADO .NET datasets required by ASP .NET code, typically for data binding.

  • If desired, you must enable the use of role-based security between an ASP .NET front end and a COM middle tier by properly configuring impersonation in your ASP .NET application.

  • You need to be aware of performance issues when communicating with STA-based COM components from managed code. .NET does not use COM apartments natively and joins a COM MTA by default when interacting with COM. This results in the intervention of a thread-switching proxy.

  • You must consider the interoperability and translation of managed and unmanaged data types.

  • You must deploy generated interoperability assemblies for your middle tier COM components.

  • You must deploy the CLR on all Web servers.

    Note   Many of the considerations in this list are addressed in more detail in "Migration Guidelines for Developers" later in this document.

Figure 4 illustrates replacing the Web tier in a horizontal migration.

Ee957911.cominterop04(en-us,MSDN.10).gif

Figure 4. Horizontal migrationreplacing the Web tier

Considerations for replacing the middle tier

When replacing the middle tier, you must consider the following issues:

  • To transparently replace middle tier components with .NET components without affecting client code, you will need to maintain the original GUIDS and/or ProgIds of your COM components.
  • When attempting to transparently replace a COM component, you must properly handle replacement of the class interface generated by Visual Basic components.
  • You will need to translate the ADO .NET datasets returned from your migrated middle-tier components to ADO recordsets used in your original ASP code.
  • You must deploy the interoperability assemblies for the middle tier components.

Note   Many of the considerations in this list are addressed in more detail in "Migration Guidelines for Developers" later in this document.

Figure 5 illustrates replacing the middle tier in a Horizontal migration.

Ee957911.cominterop05(en-us,MSDN.10).gif

Figure 5. Horizontal migrationreplacing the middle tier

Vertical Migration

Another migration approach is to migrate a portion of your application vertically through all application tiers. This essentially involves carving out a piece of your application that has minimal interaction with other pieces and migrating it. This includes both ASP code and COM components. An example might be converting the search functionality of your Web site to .NET, including the presentation tier, business tier, and data tier. The remaining functionality of the site is left in traditional COM and ASP until the time is right for migration to .NET, based on your project schedules, resources, and current system architecture. Any remaining interfaces between the new managed code and the unmanaged code function through COM interoperability. You will need to do some development and testing work to ensure that the new and old pieces of the site work together, share data, and provide a seamless experience to the end user or client developer. Figure 6 illustrates an example of a vertical migration.

Ee957911.cominterop06(en-us,MSDN.10).gif

Figure 6. Vertical migration

Choosing a vertical migration strategy

You might choose to adopt a vertical migration strategy for a number of reasons:

  • Good application isolation
    If parts of your application are well isolated from other parts of your application, you have an excellent candidate for vertical migration. Parts of an application that are well isolated share very little state information with the rest of the application and can easily be migrated with little impact on the rest of the system.
  • Adding new functionality to an existing application
    When adding new functionality to an existing application, you should strongly consider using the .NET Framework to develop the new functionality.
  • Heavy use of ADO recordsets between tiers
    Many applications pass disconnected ADO recordsets from the data and business tiers to the presentation tier. They then iterate through the recordsets and generate HTML tables. This type of application is well suited to a vertical migration. ADO. NET to ADO migration and interoperability requires special consideration, as explained later in this document in Migration Guidelines for Developers. Migrating vertically would minimize the work involved in achieving interoperability with ADO.
  • Planning to re-architect
    If you plan to re-architect your application, vertically migrating part of your application to the new architecture provides a good test bed for the new design. The .NET Framework also makes it easier to provide the functionality that newer architectures are built on. For example, you can use HttpHandlers to perform many of the tasks for which you would previously use ISAPI extensions, but with a much simpler programming model.

Vertical migration considerations

Before undertaking a vertical migration, you must consider the following issues:

  • Application-slicing for a vertical migration

  • Migration of shared code

  • Session and application state management in a mixed ASP/ASP .NET environment

  • Interaction between ASP and ASP .NET code (redirects, security, and so forth) in a mixed ASP/ASP .NET environment

  • Interoperability and translation of managed and unmanaged data types

  • CLR deployment

  • Application deployment

  • Configuration of COM+ based applications and .NET Enterprise Serviced Components

  • Optionally using Application Center 2000 to ease deployment

    Note   Many of these items are addressed in more detail with specific guidance for developers and operations in the following section, Migration Guidelines for Developers.

Migration Guidelines for Developers

This section discusses the issues you will face as an application developer as you develop your migration and interoperability plan. It also presents guidance on how to address these issues early in the migration cycle. The section discusses the following topics:

  • Choosing which pieces of your application to migrate
  • Migration to and interoperating with ASP .NET
  • Component design

Choosing Pieces of Your Application To Migrate

Choosing the right pieces of your existing application to migrate is an essential task in ensuring a successful vertical migration. It is recommended that you perform a code path analysis on your current application.

Code path analysis

A typical Windows DNA–based application consists of one or more ASP pages with which a user interacts. Those ASP pages call one or more COM components, which in turn may call additional COM components. The COM components eventually access a database and either store data or retrieve data on behalf of the user. The ASP pages and components that are used during the user's single interaction would be considered a code path. Figure 7 illustrates a code path in a Windows DNA–based application.

Ee957911.cominterop07(en-us,MSDN.10).gif

Figure 7. A single code path

In many cases, the objects used in a code path neither depend on nor access other components in the application. Distinct code paths are a natural place to consider isolating a piece of your application for migration to .NET. Code path analysis of the application typically results in discovering pieces of your application with minimal interaction with other parts of the application, thereby minimizing interoperability needs. You can use code paths to help identify which pieces of an application are suited to vertical migration.

Interoperability for shared components

An analysis of your Web site in terms of the code paths is likely to identify components that are shared among multiple code paths. In a vertical migration, these components can be accessed through the COM interoperability functionality until all the code paths that interact with the component have been migrated. Code paths that share components between them are good candidates for migrating concurrently, minimizing the COM interoperability required.

Migrate read-only, nontransactional functionality first

In a typical Windows DNA–based application, a large percentage of the code paths are associated with nontransactional activity such as retrieving data for display. The data is read, displayed (typically in HTML), or sent to another application or process, and is subsequently discarded. For example, a bookstore's Web site might display a list of books in a particular category.

Transactional activities such as adding a book to the user's shopping cart or placing an order tend to be performed using different components, often making use of the transactional support services provided by COM+. An optimized Windows DNA–based application separates transactional components from nontransactional components. In this way, components that perform read operations do not incur the unnecessary overhead of COM+ transactions.

By migrating the nontransactional code early, you can quickly take advantage of the advanced data-binding and caching capabilities of ASP .NET, and easily expose this data to other applications that want to retrieve the data through Web services.

Also, as described in Migration Guidelines for Deployment and Operations later in this document, migrating components that do not take advantage of COM+ Services eases the burden of deployment.

ASP .NET Migration Guidelines

This section discusses migration guidelines specific to migration from ASP to ASP .NET and interoperating between the two environments.

Avoid or replace the ASP session object

As previously mentioned, you cannot share session state across a mixed ASP/ASP .NET environment using the intrinsic Session object. In a vertical migration, the best way to avoid this problem is to not use the intrinsic Session object in your ASP application. Numerous methods exist for working around server-based session storage and allowing information to be passed from page to page. All these methods work with both ASP and ASP .NET:

  • Using cookies
  • Using hidden form fields
  • Encoding session information in URL strings (URL munging)
  • Manually storing and retrieving session information from the database through direct ADO (from ASP) and ADO .NET (from ASP .NET) calls
  • Using a custom session object that stores state in a database (This method is discussed in the following section.)

If your application does not already make heavy use of the Session object, you should generally use one of the first three methods in the previous list, all of which avoid sharing session state on the server.

Sharing state using a database

The ASP .NET Session object has the ability to store session data in a Microsoft® SQL Server™ database automatically. However, you cannot access this data from ASP (it is stored in a binary format, which is not easily read) without considerable work and custom code on the ASP side. You also cannot directly instantiate the ASP .NET Session object through COM interoperability and use it to retrieve the session state. So, abstracting the storage and retrieval of session information away from the ASP/ASP .NET layer involves replacing the use of the built-in session object with a custom implementation for storing session state.

Any replacement used for the Session object should follow the dictionary pattern adopted by the intrinsic session objects, and should be capable of storing and retrieving named value pairs, for example:

mysessionobject("somekey") = "somevalue"

By adopting this pattern, you can replace the intrinsic session object in your existing ASP code quickly and easily. When the remaining parts of your application are converted to .NET, you can easily replace the custom implementation with the native ASP .NET session object.

Remember that if you use a custom session object (written as a COM object), each call to the object from ASP .NET will go through the COM interoperability layer, so you should try to minimize the amount of interaction you have with the session state object. Another option is to simply have separate code in each environment that reads and writes session information to the same external database.

Avoid or replace the application object

In a vertical migration, application state (in addition to session state) cannot be directly shared between the two environments using the intrinsic Application objects. Typically, the majority of information stored in application state is either data or objects that are expensive to create for each page, and is created and initialized in the Application_OnStart event in the application's global.asa file.

The simplest method for dealing with this issue is to recreate the data and objects in both environments. This allows you to maintain your existing ASP application environment and also take advantage of the equivalent and enhanced ASP .NET features such as improved caching, tracing, and deployment. This approach works well if the data is fairly static; however, if you change the data frequently, you will need to do additional work to keep the data synchronized between the environments. If you do not want to store the data in both ASP and ASP .NET, or if you change the data frequently, you will need to find an external place to store it that is accessible from both environments such as a SQL Server database.

Use Response.Redirect

You cannot use Server.Transfer and Server.Execute between ASP and ASP .NET pages. For interactions between the two environments, use Response.Redirect instead.

Calling apartment-threaded (STA) components

Managed code does not enter a COM apartment until it needs to call COM code. Unless specified otherwise, the managed code enters the process-wide MTA. If you create apartment-threaded (STA) components, for example those created with Visual Basic 6, this will result in a cross-apartment call and a thread switch through a COM proxy/stub pair, which can substantially degrade the performance of your application. To avoid this problem, use the aspcompat attribute on your ASP.NET page, which causes your page to enter a COM STA when calling your apartment-threaded components.

Starting with Beta 2 of the .NET Framework, if you create an STA COM object directly from ASP .NET either by using the <object> tag or by calling Server.CreateObject and you do not include the aspcompat attribute, the runtime throws an exception and does not allow the object to be created. In these cases, the runtime automatically generates late-bound RCWs that are derived from __ComObject. As a result, the runtime has the opportunity to intercept calls, determine the component's threading model, and throw an exception if needed. However, if you create an STA COM object through a type-specific assembly that you generate with tlbimp, the runtime is not aware of the threading model of the component and does not throw an exception. It is up to you to make sure you use this attribute in your application.

You should also use the aspcompat attribute on your ASP .NET pages if you need access to the ASP intrinsic objects from your COM components. Without aspcompat, the ASP .NET environment does not make these context objects available through COM interoperability.

Using the aspcompat attribute causes your page to run in the ASP .NET STA thread pool and requires the runtime to create and initialize the ASP intrinsic objects. Because of the associated performance costs, you should use aspcompat only where necessary; that is, where you need to call STA COM components or need access to the ASP intrinsic objects.

The aspcompat attribute is not available to a Web service exposed through an ASMX file. Additionally, the STAThread attribute, which is used to instruct the CLR to run your code in an STA, is ignored. All calls to an STA COM component from a Web service implemented using an .asmx file incur the overhead of a proxy/stub pair.

Security

ASP .NET does not enable impersonation by default. If you use impersonation within the presentation tier to enable role-based security in the middle tier, you must explicitly enable impersonation in ASP .NET. The impersonate attribute of the identity tag in your web.config file should be set to true. For example:

<identity impersonate="true"/>

Component Design

This section presents guidelines relating to .NET/COM migration and interoperability component design issues.

Use blittable data types

When calling between .NET and COM environments through the interoperability layer, the CCW or the RCW (depending on the direction of the call) must translate the data on the call stack between the two environments. Certain data types (referred to as blittable types) do not require translation. Examples of blittable types include integers, longs, and floats (Singles and Doubles). Nonblittable types require translation by the wrappers. A Visual Basic BSTR is an example of a nonblittable type. As you migrate your application to .NET, you should minimize the use of nonblittable types between managed and unmanaged code because the associated conversion overhead affects performance.

See a list of blittable types and nonblittable types in the .NET Framework Developer's Guide.

Wrap existing COM components with future managed clients in mind

As you move your clients to .NET, consider the interfaces exposed by your existing COM components. Typically, you will want to expose your existing COM components to your new .NET clients while leaving existing COM clients in place. Think about how you want to wrap your existing components for both environments and consider future managed clients as you design your interfaces.

One option to use when migrating your components to .NET is the tlbimp utility to generate an automatic RCW for your application. This RCW, by default, exposes the same interfaces (and by definition the same properties and methods) as your existing component. In many cases, conventional COM-style interfaces exposed by the RCW will not be natural to use from managed code. Managed code developers will expect to be able to take advantage of such features as:

  • Parameterized constructors
  • Inheritance
  • Static methods

To give your managed clients these abilities and to produce interfaces more suited to the managed code environment, you should consider writing a custom wrapper class for your COM object. This wrapper class consumes the RCW for your COM components internally and delegates most calls to your existing COM components. Some calls may perform more complex data type conversions such as mapping between ADO .NET datasets and ADO recordsets. This is discussed further in Working with ADO .NET and ADO later in this document. Over time, you can move more and more of the functionality from the COM component to the wrapper without affecting your managed clients.

There are a number of factors to consider when deciding whether to use an RCW or a custom-managed wrapper class.

Value of Existing Interfaces

If your components have a large numbers of clients who are accustomed to your existing object model, creating an RCW and exposing the existing interfaces may be an appropriate strategy. For example, the object model in Microsoft® Excel is widely used by Excel developers using Microsoft Visual Basic for Applications. The object model is highly structured and maps well to the features and user interface presented by Excel. Customers are very familiar with the existing object model and would require significant retraining if the object model were changed substantially. In this instance, using the standard RCW might be appropriate.

Optimizing Property-Based Access and Remoting

Consider writing custom-managed wrappers for COM interfaces that make heavy use of get and set properties. When these interfaces—previously described as chatty interfaces—are used from managed code through an RCW, each property call crosses the interoperability boundary and incurs overhead. For a simple interface with minimal marshaling work, the interoperability overhead will be roughly 30 to 50 assembly instructions. This overhead is minimal for a method that performs a significant amount of work internally, but it would represent a large percentage overhead for a simple property access.

If you expect the clients of your COM components to move to .NET soon, consider writing a custom-managed wrapper and move the functionality represented by the chatty interface from the COM component to the wrapper. Other, less chatty interfaces could be left implemented in the COM object and could be delegated to by the managed wrapper. This repartitioning of code enables you to minimize the interop call overhead and provides for a more natural interface to your object from managed code.

An additional benefit of writing a custom-managed wrapper is that it allows you to move the remoting boundary as illustrated in Figure 8. This figure illustrates both a standard RCW and a custom-managed wrapper for a COM component. It shows how you can provide an interface more suited to managed clients, as well as how you can move the remoting boundary so that you can make .NET remoting calls.

Ee957911.cominterop08(en-us,MSDN.10).gif

Figure 8. Standard RCW and custom-managed wrapper

Firewall Changes and Managed Wrappers

Developing a custom-managed wrapper provides you with an additional benefit in that it allows you to use .NET remoting in place of DCOM after moving the presentation tier to .NET in a horizontal presentation tier migration. If your middle tier is physically separated from your Web tier, by wrapping your middle-tier components with custom-managed wrappers, you can use .NET remoting between computers using either the TCP or HTTP transport instead of DCOM. This enables you to close the DCOM ports in the firewall.

Use interoperability attributes to support existing clients

The COM interoperability system provides a number of attributes to support existing COM clients. These attributes allow you to replace an existing COM component with its .NET equivalent without having to recompile the existing COM client, even if the client is early bound. Several of the important attributes are listed in the following table.

ProgIdAttribute Use this attribute on your class to have it assigned a specific programmatic identifier (progid) when the COM entries are added to the registry (using the regasm utility).
GuidAttribute Use this attribute on your class or interface to have it registered under a specific class identifier (CLSID) or interface identifier (IID).
InterfaceType Identifies how the interface should be exposed to COM (that is, Dual, Custom, or Dispatch).

One implication of migrating your middle tier to .NET occurs if you employ physically separate Web servers and application servers. In this scenario, you would like to replace the middle tier with .NET components without touching the front-end Web servers. Creating your .NET components with the attributes described in the previous table allows you to replace the middle tier components without having to change the application proxies deployed to the Web servers. When replacing an existing COM component, you should use these attributes to support existing clients. For more information, see Applying Interop Attributes.

Implement the class interface

COM components implemented in Visual Basic contain a hidden class interface. Visual Basic automatically creates the class interface to expose all the public properties and methods of your Visual Basic class module. The interface name is derived from the class name (with a leading underscore). By default, Visual Basic marks the class interface as the default interface for the generated COM class (coclass) and is used by scripting clients as well as by Visual Basic when you create an instance of the class.

When you run the tlbexp utility against a .NET class, a class interface with the properties and methods of the class is generated by default. However, this interface differs from the original class interface generated by Visual Basic in that it has a different interface ID. When your goal is to replace your existing COM server transparently without recompiling your client, you should explicitly implement the class interface using the GuidAttribute attribute to expose the original interface ID for the interface. To do this, perform the following steps:

  1. Generate a .NET assembly for your component.
  2. Reference the assembly from your new .NET component.
  3. Inherit your .NET component from the class interface.
  4. Use the [ClassInterface(ClassInterfaceType.None)] attribute on your .NET component to keep the compiler from automatically generating a class interface.

Use primary interop assemblies

An interop assembly, unlike other .NET assemblies, contains no implementation code. Interop assemblies contain only the type definitions of types that are already implemented in COM components. It is from these type definitions that the CLR generates the RCW to allow managed code to bind to the types at compile time and provides information to the CLR about how the types should be marshaled at run time. Any number of assemblies can be generated for a COM type (using tlbimp is one method for generating an interop assembly). However, only one assembly is known as the Primary Interop Assembly (PIA). The PIA contains the software publisher's description of the types and is signed and authorized for use by that publisher. Because the PIA is signed by the software publisher and contains the PrimaryInteropAssembly attribute, it can easily be distinguished from other interop assemblies that define the same COM types.

Primary Interop Assemblies are important because they uniquely identify a type. Types defined within an interop assembly that was not provided by the component publisher are not compatible with types defined in the primary interop assemblies. For example, consider two developers in the same company who are writing managed code that will interoperate with an existing third-party supplied COM component. One developer acquires the PIA from the component publisher. The other developer generates his own interop assembly by running tlbimp on against the COM object's type library. Each developer's code works properly until one of the developers (or worse yet, a third developer or customer) tries to pass the object to the other developer's code. This results in a type mismatch exception; although they both represent the same COM object, the type checking functionality of the CLR recognizes the two assemblies as containing different types.

You should obtain the PIA for any COM components you use in your applications. Doing so helps to prevent type incompatibilities in code written by developers using the same COM object. You should also provide PIAs for any components you develop that might be used by others, especially if third party developers or customers will use these components in their applications.

Working with ADO and ADO .NET

Give careful consideration to how ADO and ADO .NET will interoperate, particularly in a horizontal migration such as where the presentation tier has been replaced with ASP .NET but is presented with ADO recordsets by the existing middle-tier components.

ADO recordsets

Many applications use middle-tier components to return ADO recordsets as the result of a query that the ASP layer then iterates through to render HTML. Iterating through the ADO recordsets returned from the middle-tier components requires a significant number of interoperability calls (one for each access of a data field and one for each call to MoveNext). The overhead associated with multiple COM interoperability calls will significantly decrease performance over potentially hundreds of rows.

ADO .NET datasets

The sophisticated data binding capabilities of the ASP.NET server controls can only be used with ADO .NET.

Starting with Beta 2 of the .NET Framework, you can generate an ADO .NET dataset based on the data in an ADO recordset. This capability makes use of the System.Data.OleDb.OleDbDataAdapter component. There is an overhead associated with this conversion: if you are only using a few fields from a few rows on a particular page, it may be cheaper to simply access the data through interoperability, rather than translate the ADO recordset. However, for larger volumes of data, conversion to a dataset may provide the most efficient solution. Test the two approaches in your environment to determine when it is cheaper to leave the data in the ADO recordset.

Converting ADO recordsets to ADO .NET datasets

To take advantage of ASP .NET data binding, translate the recordset into a dataset. One way to approach this is to create a utility function to translate the ADO recordset returned from your existing middle tier. You can then call this function from your ASP .NET code. Subsequently, when you migrate the middle tier to return datasets directly, you can simply remove the call to the utility function. This approach allows you to avoid changing the presentation tier code twice—first to migrate ASP to ASP .NET code with direct manipulation of ADO recordsets, and then to adapt the code to use datasets after the middle tier is migrated.

The following sample code in C# demonstrates how to build an ADO .NET dataset from an ADO recordset.

/*
declare dataset and adapter.
Assume for this sample that an ADODB.Recordset has been 
passed to us by some external code in the variable 'rs'
*/

DataSet myDataSet;
System.Data.OleDb.OleDbDataAdapter myDataAdapter;

myDataAdapter = new System.Data.OleDb.OleDbDataAdapter();
myDataSet  = new DataSet();

/*
 copies the contents of the ADODB.recordset rs into the 
ADO .NET dataset
*/
myDataAdapter.Fill(myDataSet, rs);

For a more complete example of translation between ADO and ADO .NET, refer to the ASPXToADO sample in the .NET Framework SDK. If you install the SDK to its default directory, the sample can be found at C:\Program Files\Microsoft.NET\FrameworkSDK\Samples\Technologies\Interop\Basic\ASPXToADO.

Converting ADO .NET datasets to ADO recordsets

In a horizontal middle-tier migration of an application that passes back disconnected ADO recordsets from the middle tier to the Web tier, you must decide how to handle passing back ADO recordsets to the Web tier. Unlike the code given earlier for recordset to dataset conversion, there is no equivalent functionality built into the .NET Framework for converting from an ADO .NET dataset to an ADO recordset. In a middle-tier migration, you have a few options to consider:

  • Continue to use ADO recordsets in your .NET components
    With this approach, you continue to use ADO recordsets internally through COM interoperability within your migrated middle tier .NET components, and they pass them back to the ASP presentation tier.
  • Translate the ADO .NET dataset to an ADO recordset before returning from the middle tier
    With this approach, you use ADO .NET internally in the middle-tier components and translate the dataset to an ADO recordset for COM clients before returning. Because the .NET Framework does not provide functionality for performing this translation, you must do it manually. For example, you can create a new ADO recordset, iterate through the dataset, and transfer the data to the recordset row by row. Or, you can persist the ADO .NET dataset to XML and use an XSLT script to translate the XML to the supported ADO format. This can then be loaded directly into an ADO recordset. Another option is to write .NET custom marshaling code that automatically translates between a dataset and a recordset when a dataset is returned as a parameter. This is the most elegant solution and is transparent to the implementers of the business code in both tiers. For more information about writing custom marshalers, see Custom Marshaling.

If you have minimal interaction with the data you are returning from your middle tier, using ADO internally through interoperability and directly returning the recordset may be your best option. For example, if you simply execute a stored procedure or query and return the resulting recordset, there will be minimal interoperability and the performance degradation should be minimal. If, however, you have extensive interaction with the data in the middle tier (such as iterating through all of the data), you may want to consider moving to a dataset and manually translating to a recordset for your clients.

The performance tradeoffs of these options depend on the size of the recordset. You also need to consider the relative difficulty and skill sets associated with each option. For example, you can only implement a custom marshaler using managed C++.

Migration Guidelines for Deployment and Operations

The guidelines in this section relate primarily to deploying and configuring your applications and associated .NET assemblies.

Deploying the CLR

Any computer that runs managed code requires the common language runtime (CLR) and the .NET Framework libraries. This means that you will need to deploy the CLR to each Web server and middle-tier server that runs managed code. Starting with Beta 2 of the .NET Framework, Microsoft includes with the framework a redistributable package for the .NET runtime files. The redistribution package contains the core CLR engine, as well as .NET Framework pieces and ASP .NET. This package can be used to deploy the runtime and framework to each computer in your application. The redistribution package is a standard Microsoft Installer (MSI) package.

Depending on the current configuration of your computers, a reboot may be needed to update the Microsoft Installer software on the computer. When deploying your application, consider this possibility and coordinate the downtime accordingly, working with your load balancing scheme to ensure that your other servers can keep serving users with the existing site.

Application Deployment

Components and ASP .NET pages written for the CLR can be deployed by copying the associated pages and assemblies to the chosen deployment location (this method is known as xcopy deployment). Unless the components use .NET Enterprise Services (previously referred to as COM+ configured components) or are called through COM interoperability, they do not require registration or any other configuration. However, the presence of the CLR does not affect the way ASP pages or COM components are deployed. You must still deploy and register COM components as you have in the past, and you will have to stop and start the Web site to replace COM components.

All .NET components are packaged into assemblies. An assembly, typically packaged as one or more DLLs, is a collection of resources and .NET components together with the metadata necessary to describe the components. Unlike COM objects, which are located by means of an entry in the Windows registry, .NET components are located based on the location of their assembly in the file system. Additionally, COM components and component registration are shared across the computer. In contrast, .NET applications can each have a private copy of a .NET component, with different applications able to have a different version of the same component deployed for that application.

Private and shared assemblies

Assemblies that are deployed and used solely by individual applications are known as private assemblies. Private assemblies are usually deployed to the application directory or a subdirectory of the application directory. If a number of different applications will be using the same assembly, the assembly can be deployed as a shared assembly. Shared assemblies are registered in a computer-wide assembly store called the Global Assembly Cache (GAC). Components deployed to the GAC can be used by any .NET aware application on the computer, as long as the application has security permission to use the assembly. Installing an assembly into the GAC requires that the assembly have a strong name.

For information about how to assign a strong name to an assembly, search for "Strong-named assemblies" in the .NET Framework SDK documentation.

Deploy middle-tier interoperability assemblies in the GAC

You should deploy in the GAC the assemblies that you generate for your middle-tier COM components. Loading your assembly into the GAC allows it to be loaded by the assembly loader, avoids putting your specific code into a system directory, and emulates the COM environment, in which objects are globally visible. The .NET assembly loader looks in either the GAC or the application path (or a subdirectory) for any referenced assembly. When an ASP application is run either in process or out of process, the Web Application Manager (WAM) runs in either the Internet Information Server (IIS) process (inetinfo.exe) or an instance of the dllhost.exe surrogate process, respectively. The executable files are both loaded and run from the System32 directory under your Windows directory. It is recommended that you do not place custom components in this path and its subdirectory.

Lazy registration

The regsvcs tool is used to register managed components in COM+ applications. This, however, diminishes some of the benefits of xcopy deployment. To address this issue, the .NET runtime supports a process called lazy registration.

With lazy registration, you derive your component from the System.EnterpriseServices.ServicedComponent class and annotate your code with the necessary attributes, such as [Transactions(TransactionOption.Supported)]. When your assembly is loaded, the runtime automatically creates a COM+ application with the appropriate settings to match the attributes that you have specified. Default values are used for any attributes that you do not specify.

One advantage of lazy registration is that developers can change COM+ settings in a new version of the component and those settings will be automatically applied when the component is deployed and called for the first time.

The primary disadvantage to lazy registration is that in order for the runtime to create the COM+ application, the user account under whose logon session the component will run must have local administrative privileges on the server. Rarely would a user of your Web site or an account used to run your application have administrative privileges on your server.

One workaround is to provide an initialization script that runs before your site begins to serve user requests. Specifically, you must ensure that this script runs before the server begins handling Web requests. You can use Application Center 2000 as described in the following section to accomplish this.

Use Microsoft Application Center 2000 to ease deployment

Microsoft Application Center 2000 is a good vehicle for deploying your .NET applications, particularly because configuration information for your site has been moved to the web.config file and your .NET assemblies can simply be copied to the target application directory. As mentioned earlier, Application Center can be used to simplify deployment and configuration of .NET Serviced Components.

If you use Application Center to deploy your applications, you can run a script after installation, but before returning the Web server to the Web farm, either to initialize the application or run regsvcs. The Application Center deployment typically runs under the credentials of an administrator. If you manually install your application to the cluster controller for deployment, you can run regsvcs against your application on that server and allow Application Center to deploy the COM+ application, eliminating the need for initialization code or regsvcs.

The current version of Application Center cannot replicate GAC settings. Again, this can easily be resolved by creating a post deployment script that is executed by Application Center after the deployment and before returning your Web server to the Web farm.

For more information about Microsoft Application Center 2000, see https://technet.microsoft.com/en-us/library/bb545877.aspx.

Secure your servers

With the xcopy deployment capabilities of .NET, securing your servers is critical. To exploit a compromised server in the .NET environment, a hacker no longer has to copy a component to the server and then find a way to register the component, but only needs to copy the component in place along with the code to call the component. So, as with any server, it is imperative that you keep up with the latest security updates from Microsoft at https://www.microsoft.com/security/. Also, turn off or disable all services on your Web servers that are not used, particularly those services that allow you to access the file system, such as FTP and WebDAV.

Collaborators

Many thanks to the following contributors and reviewers:

Dennis Angeline, Jonathan Hawkins, Omri Gazitt, Michael Thomassy, Chris Brooks, Jeff Kercher, Kenny Jones, Amitabh Srivastava, Doug Turnure (Developmentor), Jim Lundy, Bart Robertson, Joseph Fultz, Alex Mackman (Content Master), Bernard Chen (Sapient), Ken Argo, Jayesh Rege, and Ralph Pieper-Woerle.

Questions? Comments? Suggestions? For feedback on the content of this article, please e-mail us at devfdbck@microsoft.com.

To learn more about .NET best practices, you can work side-by-side with technology experts at the Microsoft Technology Center in your area. For more information please visit the Microsoft Technology Centers Web page.

patterns and practices home