Overview / Strategies to Migrating
When a new technology emerges, companies and developers begin to wait anxiously for answers to their questions. Programmers need to learn about the new stuff because they want to use the new tools to develop their projects. Companies always want to move to new technology and build new products therefore; developers need to improve their knowledge and ability constantly to maintain their job against the new technology developers. Now, to learn and move to .NET environment is the answer to all these necessities. Our first objective is Visual C++ and migrating with .NET. The important thing is to find the right strategies to migrate your products, codes, projects with Microsoft's .NET technology.
Interoperability or Migrating: The Best Way to Use COM+ with .NET
Probably you have the traditional Visual C++ and Component Object Model (COM) applications, which you liked to use for a short period of time following the Microsoft .NET Framework and common language runtime's (CLR) release. Now, you may like to have the improvement of the newest functionality enclosed by the CLR, and reuse existing components from the managed code that you developed.
With Microsoft .NET' interoperability features, you are able to work with existing unmanaged code (which is, code running outside the CLR) in COM components plus Microsoft Win32 DLLs. It also gives you the advantage of using managed components from your unmanaged, COM-based code. These features let you to decide if and when existing unmanaged code migrate to .NET.
Understanding .NET Interoperability and Migration
Here this article I will introduce you to special types of interoperability provided by .NET in a few words. This will help you to be familiar with the tradeoffs that are described in the migration scenarios later in this article. This article also discusses calling unmanaged application program interface (API) functions from .NET.
The .NET CLR provides interoperability by defeating the difficulty related with calls among managed and unmanaged code. The runtime without human intervention generates code to convert calls between the two environments. Microsoft .NET manages the next aspects of interoperability involving managed and unmanaged code. Object binding gives support simultaneously early and late bound interfaces. Data marshaling and translation means that data type conversion is handled between managed and unmanaged data types. Object references are managed to ensure that objects are either released or marked for garbage collection. That is called object lifetime management. With object identity COM object identity rules are enforced. In Exception and error handling area, the runtime translates COM HRESULT values to .NET exceptions and vice versa.
First of all lets decide about the conditions of a migration, whether to migrate the code or interoperability. The COM interoperability features of the .NET Framework are very authoritative, which means in nearly all cases you can 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 different advantages to interoperating with existing code, rather than migrating it.
- Interoperability gives you the advantage of preserving the investment that you have already made in developing and stabilizing the code,
- Also gives you the advantage of familiarizing developers with it,
- And learning how to deploy and operate the code safely and effectively.
More likely greater part of the existing code will be utilized through interoperability instead of being migrated. However, in a few cases, 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 interoperation with existing code from new managed code is much more important than any reasons to migrate the existing code.
Generally, the operating cost of calling from managed code to unmanaged code through COM interoperability is minimal. If your method performs any extensive tasks, it is likely that the operating cost from the interoperability layer will be a tiny gain of the overall method call time. on the other hand, if your method does nothing more than setting a value for a property or executing some other small job, the operating cost of the interoperability layer may be an important magnitude 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. If that is the case than you should consider to migrate such components to managed code or, writing a managed wrapper around your component and moving this functionality to the wrapper.
We should remember CLR does not utilize COM apartments to make obtainable call synchronization for managed objects; it joins a COM apartment simply when it is required to interoperate with COM components. Once 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.
Ways to Migration: Vertical and Horizontal
You will need to decide how to handle the migration after you have decided to migrate part or all of an existing application to .NET. Now we will focus on the horizontal and vertical approaches to application migration. The issues you need to bear in mind in forming a migration strategy are talked about together with some common migration situations.
Horizontal migration involves replacing a whole tier of your application. For instance, you may replace the COM code within your middle tier as the preliminary migration step or you may choose to above all replace the C++ code within your Web-based presentation tier. Vertical migration involves isolating and replacing a piece of your application through all n tiers.
Here are the guidelines to help you: First choose a migration strategy that minimizes the risk of migration. Than choose migration activities that allow you to continue to use your existing COM code base as you move to .NET. Finally Quickly take advantage of the new features provided by the .NET environment.
Note The following 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.
Vertical Migration
Initially we will talk about migrating a portion of your application vertically through all application tiers. This fundamentally involves carving out a piece of your application that has minimal interaction with other pieces and migrating it. This includes both C++ 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 C++ until the time is right for migration to .NET, depending on your project schedules, resources, and current system architecture.
Any outstanding 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.
Choosing a Vertical Migration Strategy
For decide to adopt a vertical migration strategy there are different reasons: High-quality application isolation; if parts of your application are healthy isolated from other parts of your application, you have an outstanding runner 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, you should strongly consider using the .NET Framework to develop the new functionality once adding new functionality to an existing application.
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. Migrating vertically would minimize the work involved in achieving interoperability with ADO.
Planning to redesign; vertically migrating part of your application to the new architecture provides a good test bed for the new design if you plan to redesign your application. 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
There are some issues such as application slicing for a vertical migration, migration of shared code and session and application state management in a mixed C++/C++.NET environment that you should consider before carrying out a vertical migration.. Also you need to consider about the interaction between C++ and C++.NET codes in a mixed C++/C++.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 and optionally using Application Center 2000 to ease deployment.
Horizontal Migration
In a horizontal migration, you can 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, in many cases without modifying application code or affecting operations on another application tier.
Choosing a Horizontal Migration Strategy
Deciding which tier to migrate first is the first step in a horizontal migration. You need to replace C++ code with code developed using C++.NET to replace the Web tier. You may also externally expose much of your middle-tier functionality with .NET Web services. Ideally, you can replace the Web tier with few or no changes to the middle tier. You can make use of the subsequent features if you migrate your Web tier to C++.NET. Such features are; 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.
For migrate your middle-tier COM components to .NET with few or no changes to the Web tier to replace the middle tier. Consider whether your existing Windows DNA-based solution has the following characteristics in deciding a horizontal migration strategy is appropriate, and if so, which tier is the most appropriate for initial migration.
Large number of Web servers
The CLR be present on each Web server is required by Deployment of a C++.NET application. If your application is deployed on a large number of Web servers in a Web farm configuration this can be an issue. Consider migrating the middle tier first in a horizontal migration if you have considerably fewer middle tier boxes.
Shared code migration
You can avoid having to convert all this code early in your migration by starting with a horizontal migration of your middle tier code if your C++ code uses a large amount of shared code and a large number of constants in C++ include files.
Heavy use of ASP Application or Session state
The ASP pages share application state and session state using the ASP Application and/or the ASP Session objects in many traditional ASP and COM based applications. ASP and ASP.NET cannot share state across the two environments using these intrinsic objects. You should consider a horizontal migration in cases which you make heavy use of these objects.
Complex middle tier
In the middle tier complex object hierarchies should be kept as a unit. It is difficult to deciding where to isolate an application with a complex middle-tier object hierarchy, and migrating only parts of a complex middle tier typically necessitates numerous interoperability calls between environments, resulting in performance degradation.
Horizontal Migration Considerations
You must consider the following issues when replacing the middle tier: 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. You must properly handle replacement of the class interface generated by Visual Basic components when attempting to transparently replace a COM component,. You will need to translate the ADO .NET datasets returned from your migrated middle-tier components to ADO record sets used in your original C++ code. You must deploy the interoperability assemblies for the middle tier components.
More about the Migration
In conclusion, if you are already started a new development with the pre-released versions of .NET, than, you are ready for the conversion. The .NET Framework and languages make writing component-oriented code quite simple. The rich Framework libraries probably will increase your coding productivity significantly. They also provide benefits for software houses developing in multiple languages because using different languages you can share in-house knowledge that develops for the Framework classes among developers. Depending on the language, the developers will all be using the same libraries with slightly different syntax. On the contrary, your VB developers, scriptwriters, and C++ developers all use different libraries and techniques in current multi-language development.
If you did not have the chance to start developing .NET yet, you should try to package your functionality as COM or COM+ components. That way, you can use them directly through COM interoperability in .NET. Also it makes the code much easier to port to a .NET component later if you have your code structured as a component for COM. cross language development is another key future in .NET. You can now choose from the languages that will ship with Visual Studio.NET, C#, C++, VB, and Jscript as well as a host of other languages being developed to run on the .NET runtime. However, even if you are not going to change to a whole new language, such as C#, you still need to learn some new knowledge to write code for .NET. C++ developers can use managed extensions for C++ to write .NET code. Unfortunately, to use MC++, you must learn new language extensions, keywords, and attributes, as well as which syntax features in C++ are legal for a .NET application. This means you will practically learn a new language and all that extra bits and pieces in your code will make the project pretty unreadable.
After using C++ as my primary programming language for more than 10 years, I find C# much more useful and easy than other languages to write .NET application. But MC++ does provide more power, as well as the ability to portion your application into native and managed code more easily and more flexibly than in C#. In addition, you have to write completely native applications in C++. If you need to write native applications and COM components in C++, you might want to stick with MC++ for your .NET development. ATL Server is a new technology for creating high-performance Web Services. Using ATL is still C++-based along with the rest of the ATL library.
In the .NET C# stands out as the new language. It is created by taking all the best features and capabilities of previous object-oriented languages such as C++, Java, and Smalltalk, and blending them into one best-of-breed language with C++-like syntax. C# is quickly becoming the de facto .NET language for many C++ developers because it is intuitive and easy to use. Microsoft's decision to develop a large percentage of the .NET Framework classes in C# also indicates the importance of the language. First, you need to decide what to choose for your primary .NET development language. Then you should start training people on using that language and the new features relevant to .NET. This step is as essential to become productive in .NET. You have greater flexibility than ever in allowing your developers to use the language they are best with to develop applications. However, you still need to think through the configuration management aspects of this decision.
For many companies using a mixture of languages for their code base is troublesome no matter how interoperable they are. But you will find new opportunities to share your talent across projects. For example, if you have a surge of work for a project that consists mainly of component-oriented C++, it would be difficult with current technologies to use your VB Web developers in a supplemental effort for that project. With .NET, that becomes a perfectly feasible solution.
Introduction to Managed Extensions for C++
C# is the preferred language for .NET development; however Microsoft has not left C++ coders out of its new Web services framework. You may want to take along our guide to managed extensions to the language if you want to support existing C++ applications. The recently released beta 2 of Microsoft's Visual Studio .NET (dot-NET) continues to support traditional C++ development but you would not know it from the publicity that initially surged around the C# (C sharp) programming language, It also adds managed extensions to C++ to enable execution in the Common Language Runtime (CLR), which is the .NET execution engine.
This version of C++ is called "managed C++" because the CLR is a managed environment. Just what are these extensions? You have some basic familiarity with .NET, such as knowledge of meta-data and attributed programming. But first of all, we will discuss the reasons you might choose managed C++ instead of C# or Visual Basic. The principle reason is to support existing applications written in C++. The .NET runtime can execute unmanaged code; therefore these applications can be brought into the framework with minimal effort.
What are Managed Extensions for C++?
You can still write traditional Visual C++ code In Visual Studio 7, which means that version 7.0 does not break applications developed in 6.0. Developing C++ code that works in the Common Language Runtime, though, requires a few steps. First, add the following two lines at the top of your source file:
#using <mscorlib.dll>
using namespace System;
The first line imports the type information from the core library and the second makes types within the System namespace visible. The next step is to specify that the compiler should generate a .NET executable (intermediate language byte codes instead of native instructions). This is done by passing the /CLR command-line option to the compiler. To start a new "Managed C++ Application" project in the Visual Studio 7 IDE within the "Visual C++ Projects" project type is another way to cause compilation to target the CLR. This automatically adds /CLR to the compiler options.
When You Will Need Managed Extensions for C++
In fact managed extensions for C++ are a set of language extensions to C++ that helps to VC++ developers to write .NET Framework applications. If you need rapid migration of unmanaged C++ applications (or products) to the .NET Framework, managed extensions are best choice. Another choice is accessing a C++ component from a .NET Framework-compatible language. Because of Managed Extensions support calling a C++ class from any .NET Framework language.
Now we can discuss why you might choose managed C++ instead of C# or Visual Basic. The principle reason is to support existing applications written in C++. The .NET runtime can execute unmanaged code; therefore these applications can be brought into the framework with minimal effort. In addition, you can write a wrapper around unmanaged code to allow its invocation from programs targeted for the .NET environment. Or you can go the opposite route, calling .NET classes from unmanaged C++ with some of the managed extensions. All this means that you can easily mix unmanaged and managed code.
Managed Types
There are different Managed types such as interfaces, garbage-collected classes, including structures and value classes. Using managed types in C++ has many benefits. All types are allocated on the CLR heap and they are garbage collected and every language targeted for the CLR can use them. New versions that consist of only additions of data members and/or functions are binary-compatible with previous versions. The meta-data stored in binary output files enables direct usage of managed types in C++ source code. A managed class:
- Can inherit from any number of managed interfaces, but at most one managed class
- Can declare a static constructor (a class level constructor)
- Can contain properties
- Can have a visibility specifier (public, private)
- Can have any number of constructors and a single destructor
- Can contain pointers to unmanaged classes
- Cannot inherit from unmanaged types, nor act as a parent class of an unmanaged type
- Does not support friends
- Cannot be used with the sizeof or offsetof operator
- Cannot contain an overridden new or delete operator.
Managed Arrays
Literal strings and arrays are two language level features that have managed counterparts. Literal strings are instances of the .NET String class, and managed arrays are implicitly derived from the .NET Array class. A literal string, such as S"Hello, World", uses the capital S to specify it is of type String *, the .NET string type. Two exact same String * literals created in different parts of the source code will point to the same instance of the string. The managed string literal (S-prefixed) and wide-character string literal (L-prefixed) are interchangeable anywhere that the program expects a String *.
However, you can not use managed string literals when the code expects a C++ string type,. Managed arrays allocated on the CLR runtime heap and are marked with the __gc keyword (described below). There are five important properties that you should pay attention:
Sizes can not be specified in the array definition, instead, specify size when allocating the array using the new operator.
Array indices are zero-based, just like unmanaged arrays.
Managed arrays implicitly inherit from System::Array, so you can invoke any functions of System::Array directly on a managed array.
The environment usually performs bounds checking; if the array's bounds are exceeded, an IndexOutOfRangeException will be thrown.
Array elements are automatically initialized to 0 or a default constructed object for arrays of objects.
Multidimensional managed arrays can also be created, but the syntax is slightly different from C++. Here's an example of a one-dimensional managed integer array and a multidimensional managed character array:
int intThisArray __gc[];
char charThisArray __gc[,];
intThisArray = new int __gc[20];
for(int i=0; i<10; i++) {
intThisArray[i] = i;
}
charThisArray = new char __gc[10,200];
for(int y=0; y<5; y++) {
for(int x=0; x<20; x++) {
charThisArray[y,x] = 'A';
}
}
After all, you can declare a managed array using the __gc keyword of containing a managed object type. Because managed arrays are allocated from the managed heap, these arrays have additional criteria and features for declaration and usage when compared to standard unmanaged arrays.
Using Assemblies and Namespaces
Assemblies are the physical units in .NET containing types (classes, interfaces, etc.). The #using statement makes types inside an assembly accessible. Next, the using statement must be used to make types within a namespace visible. An example follows, showing "Hello World" in a message box.
#using <mscorlib.dll>
#using "System.Windows.Forms.dll"
using namespace System::Windows::Forms;
int main(void)
{
MessageBox::Show("Hello John");
return 0;
}
Note: That the scope resolution operator (::) is used instead of the dot operator when using the Windows::Forms namespace, and for accessing the Show function from the MessageBox class.
Managed Interfaces
In managed C++ interfaces are similar to traditional abstract base classes and also interfaces in C#. The rules for interfaces in managed C++ are mainly similar to interfaces in C#. Specifically, managed interfaces:
- Cannot contain data members, static members or other class declarations
- Can only have a public section (public is default if not specified)
- Cannot provide any method implementations
- Can inherit only from other managed interfaces or the System::Object class
- Cannot have the __sealed keyword (to be described in Part 2) applied to them.
- An example specification of a managed interface:
__gc __interface IBanking {
void Account();
float checking();
};
The developer can explicitly specify which interface's function he is supplying when a class inherits from two base interfaces and the name of a function overlaps. For example:
__gc __interface IBanking {
void createAccount();
};
__gc __interface IOnlineBanking {
void createAccount();
};
__gc struct CHomeBanking: public IBanking, public IOnlineBanking {
void IBanking::createAccount() {
Console::WriteLine("createAccount, IBanking override");
}
void IOnlineBanking::createAccount() {
Console::WriteLine("createAccount, IOnlineBanking override");
}
};
In object pointing to CHomeBanking must first be cast to the specific interface needed in order to invoke an implementation specific to one of the interfaces. Attempting to call createAccount through the CHomeBanking object causes a compiler error which states that the function call is ambiguous. This happens even if one version of createAccount is not explicitly associated with an interface.
Also, when creating a new instance of a managed class, for example, CHomeBanking, you must use the new operator. The class can not be allocated on the stack, as is permitted with normal C++ classes. This is consistent with the fact that a managed type is actually a pointer to the object allocated in the CLR heap.
The following illustrates correct instantiation of a managed class and casting a managed class to its interface:
CHomeBanking HomeBankingBad; // WILL NOT COMPILE
CHomeBanking *HomeBanking = new CHomeBanking();
IOnlineBanking *is = static_cast<IOnlineBanking *>(HomeBanking);
HomeBanking->createAccount(); // THIS WILL NOT WORK:
// AMBIGUOUS
is->createAccount(); // THIS WORKS
Assemblies and Namespaces
In .NET containing types such as, classes, interfaces, etc., assemblies are the physical units. The #using statement makes the assembly accessible and types inside. In Addition, you must use the using statement to make types within a namespace visible. The following example shows "Hello World" in a message box.
#using <mscorlib.dll>
#using "System.Windows.Forms.dll"
using namespace System::Windows::Forms;
int main(void)
{
MessageBox::Show("Hello John");
return 0;
}
Note: That the scope resolution operator (::) is used instead of the dot operator when using the Windows::Forms namespace, and for accessing the Show function from the MessageBox class.
Delegates in Managed Code for C++
Delegates provide the underlying mechanism for events in the .NET. Delegates are implemented as abstract classes within the .NET Framework. There are two types of delegates, single cast and multi cast. In addition delegates are roughly equivalent to C++ function pointers. However, a delegate can point to a function only within a managed class.
Binding a function pointer to only one method is enabled by the single cast delegates. However, the multicast delegate can bind a function pointer to a single method or many methods. The difference between delegates C++ function pointers is that they can only point to members of managed classes and bind to an object to support calling method of that type of instance.
Here is an example to single cast delegates:
__delegate int GetMonthOfYear
Single cast delegate objects are implemented by the Delegate base class (System.Delegate).
And here is an example to multi cast delegates:
__delegate(multicast) void MonthOfYearModified();
Multicast delegates are able to bind to multiple methods. Multi cast delegates are declared by
__delegate(multicast) instead of _delegate.
The following code declares a single cast delegate and illustrates that the delegate is actually implemented by the Delegate base class:
__delegate int GetMonthOfYear();
__gc class MyCalendarOfYear
{
public: int MyGetMonthOfYear()
{
// Put some code for implementation
}
};
void ViaDelegate()
{
MyCalendarOfYear* pCalendar = new MyCalendarOfYear;
GetMonthOfYear* pGetMonthOfYear = new GetMonthOfYear(pCalendar, &MyCalendarOfYear::MyGetMonthOfYear);
Int MonthOfYear = pGetMonthOfYear->Invoke();
}
Properties
Although a property has the appearance of a normal data member it is actually implemented with a pair of functions. One of those functions is to set the property and the other one is to retrieve it. Client code can access the property by name, but the functions are actually executed. There are two types of properties: 1- scalar and 2- indexed. For a scalar property, the get function must take no parameters and have a return type. Also the set function must return void and take a single parameter that is of the same type that the get function returns. For an indexed property, the get function must return a type and accept a parameter for each dimension of the index. The set function must accept the same set of dimensions as the get function. However for the final parameter, it must accept the same type as the get function returns. For all properties, the get function must start with get_ and the set function must start with set_.
Following example is a class that associates integers with strings. I have defined the Size property as scalar and used it to retrieve the number of strings currently stored. Keep in mind that by supplying only a get function, this property is read-only. On the other hand, Count, is an indexed property; it gets and sets the number associated with a given string. Following is a good example for the class:
__gc class CMyStringCounter {
private:
int m_size;
Int32 m_mystringCount[];
String* m_mystrings[];
public: CMyStringCounter()
{
m_size = 0;
m_mystrings = new String*[100];
m_mystringCount = __gc new Int32[100];
}
__property int find_Size()
{
return(m_size);
}
__property Int32 find_Count(String *str)
{
for(int i=0; i<m_size; i++) {
if(m_mystrings[i]-7gt;Equals(str)) {
return(m_mystringCount[i]);
}
}
return(0);
}
__property void set_Count(String *str, Int32 c)
{
if(m_size==100) return;
for(int i=0; i<m_size; i++) {
if(m_mystrings[i]->Equals(str)) {
m_mystringCount[i] = c;
return;
}
}
m_mystrings[m_size] = str;
m_mystringCount[m_size] = c;
m_size++;
}
};
Example usage:
CMyStringCounter *mystringCounter = new CMyStringCounter();
Console::Write("Total Size of string counter: ");
Console::WriteLine(mystringCounter->Size);
mystringCounter->Count[S"John"] = 2;
Console::Write("Total Size of string counter: ");
Console::WriteLine(mystringCounter->Size);
Console::Write("Value of Tommy: ");
Console::WriteLine(mystringCounter->Count[S"Tommy"]);
Console::Write("Value of Bob: ");
Console::WriteLine(mystringCounter->Count[S"John"]);
Attributes
Managed classes and other elements in C++ have associated meta-data just as in C#. Attributes are additional declarative information stored within the meta-data. Attributes are used same way as they are in C#, therefore, I will describe creating custom attributes in managed C++, followed by an example briefly illustrating how to use attributes in general. The base attribute class is used with a class or struct to designate it a custom attribute. There are three parameters to this attribute,and each one of them has a default value. The first parameter is positional, and the last two are named.
For the AllowOn parameter there are 15 different possibilities(from the AttributeTargets enumeration): All, Assembly, Class, Constructor, Delegate, Enum, Event, Field, Interface, Method, Module, Parameter, Property, ReturnValue and Struct. To make an attribute applicable to more than one target several targets can be bitwise-OR'ed., The parameters passed to it need to be compile-time constants when you use an attribute, and also need to be one of the following types:
- bool
- char, unsigned char
- short, unsigned short
- int, unsigned int
- long, unsigned long
- float, double
- wchar_t
- char *, wchar_t *, System::String *
- System::Type *
- enum
You will find an example below, a custom attribute that tracks changes performed on classes, structures and interfaces. Please note that it can be used multiple times on the same source code element because AllowMultiple is set to true.
[attribute(Class | Struct | Interface, AllowMultiple=true)]
__gc class First_ClassModifiedAttribute {
public:
ClassModifiedAttribute(String *first_modifiedBy, String * first_modifiedOn)
{
m_ first_modifiedBy = first_modifiedBy;
m_ first_modifiedOn = first_modifiedOn;
}
private:
String *m_ first_modifiedBy;
String *m_ first_modifiedOn;
};
Example usage
[First_ClassModified(S"jgodel", S"11/16/2001")]
__gc class MyTestClass {
public:
void showMsg() { Console::WriteLine("Msg"); }
};
The __typeof keyword can be used to return a value of System::Type *. For example, the following usage of the attribute could be used to pass in a type if a custom attribute has a constructor that includes System::Type * as a parameter,
[MyFirstCustomAttribute(__typeof(CSomeClass))]
You can use one of the following syntaxes to set assembly level or module level attributes,:
[assembly:attribute]
[module:attribute]
Events
The Unified Event Model is introduced by COM code and non-COM native C++, Visual Studio .NET. This model provides a uniform way to handle events from managed code. This subject is very complex and it deserves another article. However, I will describe and detail the keywords added to support this event model in a few words. In the Unified Event Model, Event handling begins with an event source that is denoted with the event_source attribute. The event is a function defined in an event source and also marked with the __event keyword. In fact, a delegate-a class that holds references to functions-associates handler functions with an event. The object that called an event sink, receives events, also, is denoted by the event_receiver attribute. The actual function is called the event handler is invoked when an event fires. The __unhook detaches a previously associated event handler from an event in contrary __hook attribute associates an event handler with an event.
The event_source attribute accepts specifying type of the event source as a parameter. Managed com and native are the valid values. Unmanaged classes has native for defoult, and managed is default for managed classes. The symbol _ATL_ATTRIBUTES must be defined when using COM and atlbase.h and atlcom.h must be included. The following example for both the native event model and managed classes. Delegate using managed events are simple to develop as in the above delegate example.
[event_source(managed)]
__gc class CThisEventSource {
public:
__event void ThisConnectionEstablished(String *ipAddress);
void ThisFireEvent(String *ipData)
{
ThisConnectionEstablished(ipData);
}
};
[event_Thisreceiver(managed)]
__gc class CThisEventReceiver {
public:
void ThisNotifyConnectionEstablished(String *ipAddress) {
Console::Write("A connection has been established successfully");
Console::WriteLine("With this IP Address {0}", ipAddress);
}
void ThisHookEvent(CThisEventSource* pSource) {
__hook(&CThisEventSource:: ThisConnectionEstablished, pSource,
&CThisEventReceiver:: ThisNotifyConnectionEstablished);
}
void ThisUnhookEvent(CThisEventSource* pSource) {
__unhook(&CThisEventSource:: ThisConnectionEstablished, pSource,
&CThisEventReceiver:: ThisNotifyConnectionEstablished);
}
};
Whenever the ThisConnectionEstablished event is invoked, all event handlers associated with it will also be invoked. An example invocation of the event follows.
CThisEventSource* pSource = new CThisEventSource();
CThisEventReceiver* pReceiver = new CThisEventReceiver();
pThisReceiver->ThisHookEvent(pSource);
pThisSource->ThisFireEvent("24.132.247.100");
pThisReceiver->ThisUnhookEvent(pSource);
Using the __raise keyword (replacing ThisFireEvent in the CThisEventSource class is another way to invoke the ConnectionEstablished event:
void ThisFireEvent(String *ipData)
{
__raise ThisConnectionEstablished(ipData);
}
Although its use is optional the __raise keyword clearly marks this as raising an event. If you use __raise with a non-event, that will cause a compilation error. You can only use this keyword only within the class where the event is defined.
Garbage Collected Classes
The most important part of managed extensions is Garbage Collected Classes. Because garbage collected classes are allocated from the Common Language Runtime heap. A garbage collected class has just one base class. There are no multiple inheritances of garbage collected classes. It is assumed to be the .NET Framework root class System if no base class is specified,.Object. Using the built-in operator new all instances of a garbage collected class are created on the .NET Framework garbage collected heap. The __gc keyword cannot be used to qualify an unmanaged class type in an object declaration. Also, a garbage collected class cannot have a copy constructor. Finally, a garbage collected class cannot override operators & and new.
The managed state of classes, interfaces and structures is not implicit with other managed C++ keywords. Therefore, the __gc keyword must be applied to mark a class as managed.
The __nogc keyword explicitly marks a class, structure or interface as unmanaged. Unmanaged classes are allocated from the standard C++ heap and are not subject to any of the restrictions or benefits of managed classes.
The keyword __pin is used to prevent the address of a variable from changing during the garbage collection process. Thus, addresses of managed variables that are pinned can be manipulated in unmanaged code without the address changing unexpectedly. An example of pinning a local variable follows:
__gc class CThisScoreResults {
public:
float This_score;
};
void process(CThisScoreResults *sr)
{
CThisScoreResults __pin *pSR = sr;
Console::WriteLine("Total Score is: {0}", __box(pSR->This_score));
}
If you decide to try an experiment, you will need a raw COM client that created a COM object, accessed some interfaces, called some methods, and used the results for a project. Of course, along the way you will need to explicitly release the resources and you will need acquired, such as interface pointers, BSTRs, and so on. To complete the experiment, you can ported your code to use smart types-C++ classes that knew how to manage their own resources-so that you will not have to release the resources manually. By using smart types like CComPtr and CComBSTR, you can reduce the number of lines of code in your COM client by approximately 50 percent. Of course, you can draw any line you like through a single data point, but you probably would agree that resource management code in C++ is a significant percentage of the code you write.
The .NET CLR uses a garbage collector to periodically walk the list of all objects that have been created, letting them know if they are no longer required by calling their Finalize method, and returning the memory to the managed heap from where it came. When I used the new keyword in the Talker client, I was allocating memory from the .NET managed heap, which means I never have to remember to call delete. And, because objects are no longer tracked with reference counts, you do not have to be concerned about reference cycles. With .NET and garbage collection, Microsoft has taken memory leaks and reference cycles off the list of potential bugs in your components.
Managed Extensions for C++ to an Existing Application
Simplicity in converting existing C++ applications to Managed Extensions is one of the strongest reason to use Managed Extensions for C++. The procedure is straightforward and easy and consists of two steps:
- Modifying the Existing Project Settings
- Employing New Managed Extensions Functionality in Existing Application
Modifying the Existing Project
Adding the /CLR option and recompile the target application is the first step for modifying the compiler options. The /CLR option forces a link to the proper library and enables support for Managed Extensions. To modify the project settings, follow these steps:
- Load the target project into Visual Studio 7
- From the Solution Explorer, right click the project node and click Property Pages
- Click the C/C++ folder in the left pane
- Click General folder under C/C++
- Set the Enable Common Language Runtime property to Assembly Support (/CLR)
- Click OK and rebuild the project
Once the project has been rebuilt support for Managed Extensions is available. All .NET Framework features, from managed objects to .NET Framework base classes can be easily added when the target application has been built with Managed Extensions support. It is sufficient to demonstrate this step using the .NET Framework base classes String and Console for the purposes of this section. The following code declares two instances of the String class and a managed array of int type.
String FirstLiteral1 = L"Current array values";
String FirstLiteral2 = L"Current array values (later than initialization)";
__gc int arr1[] = new __gc int[20];
An instance of the System.Console class is created once the variables have been declared and the current values in the array is printed using the Write method of the System.Console class. Here is the Length method of the System.Array class:
Console::Write(FirstLiteral1);
Console::Write('\n');
for (n=0; n< arr1->Length; n++);
{
Console::Write(array[n]);
Console::Write(L" ");
}
Finally, the array is reinitialized and the values are again printed.
for(n=0; i<20; ++n)
arr1[n] = n;
for(n=0; n<arr1->Length; n++)
{
Console::Write(arr1[n]);
Console::Write(L' ');
}
Some of the benefits of using Managed Extensions in a C++ application while examining the example code reveals. Single-Dimensional arrays are dynamic until they are created. Also, the array is automatically destroyed and all resources freed by the managed heap. In addition, the array values are automatically initialized to 0 and .NET Framework base classes are easily accessible and managed classes themselves.
Managed-to-Unmanaged Transitions
At this time, we will see in more detail how managed-to-unmanaged transitions are accomplished. We will not talk about COM-Interop, because it is an extensive topic of its own. Instead we will focus on PInvoke transitions. First of all we to be able to call an unmanaged function from managed code. Which is, of course, it's signature, that is stored as part of the metadata (usually of the caller), marked with the native and unmanaged method specifiers. For example, consider this rather simple piece of code:
#pragma unmanaged
int IncrementIt(int a)
{
return
++a;
}
which produces the following metadata signature:
.method public static pinvokeimpl(/* No map */) int32
modopt(['mscorlib']System.Runtime.InteropServices.CallConvCdecl)
IncrementIt(int32 A_0) native unmanaged
{
.custom instance void System.Security.SuppressUnmanagedCodeSecurityAttribute::.ctor() = ( 01 00 00 00 )
}
The signature has many interesting details; It contains formal descriptions of all arguments and the return value of the function, in this case Int32, both. Through the modopt() method modifier it implicitly specifies the native calling convention of the method. In this case it's __cdecl, the C native calling convention. More interestingly at the same time, it specifies which class in the InteropServices namespace will be used to call the method at runtime, in this case CallConvCdecl. It specifies the PInvoke map to be used to call the function. In this case, there is no need for that because the name of the real method is the same as the one appearing on the signature, and the function was compiled in the same module as the calling method. It has one custom attribute, which suppresses a potential security warning during the transition.
More specifically, there are two signatures associated with an unmanaged method. One of them describes to the managed side how the function should be called (the one we examined above), and another one that describes to the unmanaged side how the function is implemented. This is important to know because the differences between both signatures specify how the system needs to marshal arguments for the method invocation at runtime.
The developer, can help the runtime to decide how the marshaling should be done by adding [MarshalAs] attributes to the argument declarations in the method signature. This needs to be done while writing PInvoke signatures to import unmanaged functions in other modules (using the DllImport attribute). Unfortunately, you can not currently do it without using an explicit PInvoke map. In other words, you can not do it this way now for unmanaged code that lives in the same module as the calling managed code.
In these cases, you need to explicitly use the system::Runtime::InteropServices::Marshal class to get marshaled instances of many common data types you can pass down to unmanaged code. Or else you need to marshal them back into Managed data types. Unfortunately, using the Marshal class can be either really easy, or just become a complete nightmare. This code listing presents a simple example of marshalling.
System::String* down to a const char*.
Now, we can think about some important examples. First, reflect on how the function Increment() above would be called from managed code:
#pragma managed
void main() {
int b = 25;
b = IncrementIt(b);
}
which translates into the following IL code:
.locals (int32 V_0)
IL_0000: ldc.i4.s 25
IL_0002: stloc.0
IL_0003: ldloc.0
IL_0004: call int32 modopt(['mscorlib']System.Runtime.InteropServices.CallConvCdecl)
Increment(int32)
IL_0009: stloc.0
IL_000a: ldc.i4.0
IL_000b: ret
The function is called at IL_0004 almost as if it were managed code, directly with the IL call instruction. You should note, however, that just like the original function declaration it uses the modopt() modifier. It is important to know that the call above refers to the stub or thunk that the runtime uses to make the managed to unmanaged transition and possibly marshal parameters if needed. It does not refer to the real unmanaged Increment() function. Now, we will see an example with an explicit PInvoke map declaration to import a function in the windows API, like the simple GetDriveType() api:
#using <mscorlib.dll>
using namespace System;
__gc class Test {
public:
[DllImport("kernel32.dll")]
static unsigned long FindDriveType (String* drive );
};
void main() {
unsigned long r = Test:: FindDriveType ( "C:\\" );
System::Console::WriteLine ( "{0}", __box(r) );
}
The metadata signature generated for Test::GetDriveType() is:
.method public static pinvokeimpl("kernel32.dll" winapi)
unsigned int32 modopt([Microsoft.VisualC]Microsoft.VisualC.IsLongModifier)
FindDriveType(class System.String drive) il managed forwardref
{
}
The most interesting thing about this signature is that it has an explicit PInvoke map that tells the system the function we are importing is located in module kernel32.dll, and that it has a WINAPI calling convention (__stdcall). Another interesting thing is that it also includes a forwardref modifier, which means this signature is a forward reference of a kind, therefore the implementation of the method is provided somewhere else. Notice this is quite different from the previous example, which was marked both as native and unmanaged.
Unmanaged-to-Managed Transitions
This is a kind of transition which fairly more complex than managed-to-unmanaged transitions. Because there is no way that the call can be made directly from the unmanaged code., A thunk that can ask the .NET EE to make the transition is necessary to make this work. And depending on whether the managed and unmanaged codes live in the same module or on a different one, how and where that thunk is built differs. The linker will create a special jump table through which the call is made if both the caller and the callee exist in in the same module. When the module is loaded and the EE started, the table is occupied with the addresses of the transition thunks generated.
If different modules hosts the caller and callee, then it is a different situation. At the caller site, everything is exactly the same as if it was calling a function exported from a dll and it is done through the module's Import Address Table (IAT). The callee's module has to export an unmanaged entry point corresponding to the method we want to call that points to a table containing the transition thunk addresses to make this work. As you see, things at the callee's site are very similar to when both caller and callee exist in in the same module.
Keep in mind this all works out because we are dealing with non-member managed methods, or managed methods members of unmanaged types, and not full managed types. Unfortunately, just as in the Managed-To-Unmanaged-Transitions case, there is no current support to get easy marshaling of unmanaged data types into their managed counterparts. On the bright side, however, you probably will not need this feature as much, and with a careful design you can usually work around.
What is the P/Invoke?
Using P/Invoke (the P stands for platform) makes calling a native Win32-based DLL from the CLR fairly straightforward. P/Invoke allows you to map a static method declaration to a PE/COFF entry point that is resolvable via LoadLibrary/GetProcAddress. P/Invoke uses a managed method declaration to describe the stack frame as the Java Native Interface (JNI) and J/Direct before it, but assumes the method body will be provided by an external, native DLL. However, P/Invoke is useful for importing "heritage" DLLs that were not written with the CLR in mind unlike JNI. You simply mark the static method as extern and use the System.Runtime.InteropServices.DllImport method attribute to indicate that a method is defined in an external, native DLL. When it is time to call the method the DllImport attribute tells the CLR which arguments to pass to LoadLibrary and GetProcAddress. The built-in C# DllImport attribute is simply an alias for System.Runtime.InteropServices.DllImport.
The DllImport attribute takes a variety of parameters. The DllImport attribute requires at least a file name to be provided. This file name is used by the runtime to call LoadLibrary prior to dispatching the method call. Symbolic name of the method will be the string to use for GetProcAddress unless the EntryPoint parameter is passed to DllImport. In kernel32.dll there are two ways to call the Sleep method. The first example relies on the name of the C# function matching the name of the symbol in the DLL. The second example relies on the EntryPoint parameter instead.
You need to set the Unicode/ANSI policy either on the method or on the surrounding type when calling methods that take strings. This is needed to control the way string types are translated for consumption by unmanaged code. The CharSet parameter to DllImport allows you to specify whether Unicode (CharSet.Unicode) or ANSI (CharSet.Ansi) should always be used, or if the underlying platform should automatically decide based on Windows NT or Windows 2000 versus Windows 9x or Windows Millennium Edition (Me) (CharSet.Auto). Using CharSet.Auto is similar to writing Win32-based code in C using the TCHAR datatype, except for that the character type and API is determined when loading, not at compile time, allowing a single binary to work properly and efficiently on all versions of Windows.
To indicate calling convention and character sets, the Windows platform has a variety of name mangling schemes. When the CharSet is set to CharSet.Auto, the symbolic name will automatically have a W or A suffix, depending on whether Unicode or ANSI is used by the runtime. Additionally, the runtime will munge the symbol using the stdcall conventions (for instance, Sleep may be _Sleep@4) if the plain symbol is not found. Using the ExactSpelling parameter to the DllImport attribute this symbolic mangling can be suppressed. Finally, you have two options when you call Win32 functions that use COM-style HRESULTs. By default, P/Invoke treats the HRESULT as simply a 32-bit integer that is returned from the function, requiring the programmer to manually test for failure.
To pass the TransformSig=true parameter to the DllImport attribute is a more convenient way to call such a function. This tells the P/Invoke layer to treat that 32-bit integer as a COM HRESULT and to throw a COMException in the face of a failed result. (Note that the TransformSig parameter from Beta 1 will be renamed PreserveSig in Beta 2, and its meaning is reversed from Beta 1.) For example programmer needs to manually check the result and deal with failed HRESULTs explicitly when the two declarations calling OLE32Wrapper.CoImpersonateClient1. Because the TransformSig parameter was used, calling OLE32Wrapper.CoImpersonateClient2 tells the CLR to map failed HRESULTs to COMExceptions implicitly without programmer intervention.
In the case of OLE32Wrapper.CoImpersonateClient2, the method returns void because there is no returned value from the underlying function to deal with. Had the method been declared to return a typed value (such as double), the P/Invoke layer would have assumed that the underlying native function accepted an additional parameter by reference. This mapping only takes place when the TransformSig parameter is true.
Calling Unmanaged APIs from .NET
The .NET platform supports calling unmanaged code in native Win32 DLLs in addition to interoperability with COM-based unmanaged code. This interoperability, called Platform Invocation (commonly abbreviated as P/Invoke), and it allows managed code to call into C-language-style API functions, handles the marshaling of data types between managed and unmanaged types. It also finds and invokes the correct function in the DLL, and facilitates the transition from managed to unmanaged code. Calling one of the many Win32 API functions exposed by the Windows operating system is a good example of P/Invoke functionality. The runtime also supports callbacks from API functions through P/Invoke functionality. The current release of .NET, however, does not support calling from a Win32 DLL into .NET managed code. You should use COM interoperability to call directly from unmanaged code to managed code.
When declaring unmanaged code, you must declare the API to the .NET runtime to call an unmanaged API from .NET code. The declaration includes a list of the parameters and the return value for the function to be called, although the syntax for the declaration varies from language to language Please see the shellcmd sample application in the .NET Framework SDK for an example of how to declare and use unmanaged APIs from .NET. Data type translation; by default, the runtime generates code for converting from the managed type to the unmanaged type for each parameter as necessary. If required you can control the translation, , by using custom marshaling with the MarshalAs attribute.
Unmanaged code security
Managed code uses code access security. Before accessing a resource or performing other potentially dangerous tasks the runtime checks the code. However, the runtime loses the ability to perform the necessary security checks for ensuring that the unmanaged code is not performing harmful activities when calling into unmanaged code. Therefore, the runtime checks for the necessary security on all callers in the call stack before allowing any P/Invoke call to unmanaged code. The administrative policies must allow code to run on the system with full trust and all managed code in the call chain must be signed with Full Trust permissions.
Platform invoke is a service that enables managed code to call unmanaged functions implemented in dynamic-link libraries (DLLs), such as those in the Win32 API. It locates and invokes an exported function and marshals its arguments (integers, strings, arrays, structures, and so on) across the interoperation boundary as needed. This section introduces several tasks associated with consuming unmanaged DLL functions. In addition to the following tasks, there are general considerations and a link providing additional information and examples.
To consume exported DLL functions
Identify Functions in DLLs.
You must at least specify the name of the function and name of the DLL which contains the function.
Wrap DLL Functions.
Use an existing class. Create an individual class for each unmanaged function. Create one class that contains a set of related unmanaged functions.
Create Prototypes in Managed Code.
[Visual Basic] Use a Declare statement with the Function and Lib keywords. In some sporadic cases, you can use the DllImportAttribute with the Shared Function keywords. These cases are explained later in this section.
[C#] Use the DllImportAttribute to identify the DLL and function. Mark the method with the static and extern modifiers.
[C++] Use the DllImportAttribute to identify the DLL and function. Mark the wrapper method or function with extern "C".
Call a DLL Function.
Call the method on your managed class as you would any other managed method. Passing Structures and Using Callback Functions are special cases.
A Closer Look at Platform Invoke
Metadata locates exported functions and marshal their arguments at runtime for platform invoke relies on. The following figure shows this process.
Figure-1 A platform invoke call to an unmanaged DLL function
When platform invoke calls an unmanaged function, it performs the following sequence of actions:
- Locates the DLL containing the function.
- Loads the DLL into memory.
- Locates the address of the function in memory and pushes its arguments onto the stack, marshaling data as required.
- Transfers control to the unmanaged function.
- Platform invoke returns exceptions generated by the unmanaged function to the managed caller.
Future of Migration Codes with .NET Framework
Smooth migration of existing code to .NET
Managed extensions will help you make a smooth transition to the .NET platform if you have a large investment in C++ code. Because you can mix unmanaged and managed code in the same application-even in the same file- also you can move code over time, component by component, to .NET. Or taking advantage of the full power and flexibility of the language, you can continue to write components in unmanaged C++, , and only use managed extensions to write thin, high-performance wrappers that make your C++ code callable from .NET components.
Accessing a C++ component from a .NET language
With managed extensions you are able to call a C++ class from any .NET language. You need to write a simple wrapper class using the extensions that exposes your C++ class and methods as a managed class. The wrapper can be called from any .NET language and is a full managed class. Between the managed class and the unmanaged C++ class the wrapper class acts as a mapping layer. It plainly passes method calls from the .NET world directly into the unmanaged class. You can use the managed extensions to call any native dynamic-link library (DLL), as well as native classes.
Accessing .NET classes from native code
You can create and call a .NET class directly from C++ code using managed extensions. You also can write C++ code that treats the .NET component like any other managed C++ class. The native Component Object Model (COM) support can be used in the .NET Framework to call .NET classes. Depending on your project you use COM or the managed extensions to access .NET components. In some cases, the best option will be leveraging the existing COM support. In other cases, using the managed extensions it may be possible to increase performance and developer productivity.
Managed and native code in one executable
Between managed and unmanaged contexts the Visual C++ compiler translates data, pointers, exceptions, and instruction flow automatically and transparently. This process is allows managed extensions to interoperate seamlessly with unmanaged C++ code. The developer is given fine-grained control over what data and code needs to be managed.
It allows the developer great flexibility to have the ability to choose whether each class and function is managed or unmanaged. Some types of code or data will perform better in an unmanaged environment. On the other hand, with the features such as garbage collection and class libraries, managed code typically offers enhanced developer productivity. Existing unmanaged C++ code can be converted to managed code one section at a time, therefore preserving your existing investment.