Mixing Your Own Managed and Unmanaged Code

All of the sample code presented to you in this chapter has been managed code that compiles to intermediate language. Some of it calls unmanaged code in the C runtime library, but the assembly produced in all the sample projects is 100% IL.

When you combine managed and unmanaged code, you must keep in mind that there is a slight performance cost to call unmanaged code from managed code. One way to optimize the performance of your application is to move the boundary between managed and unmanaged code. Consider the system represented in Figure 6.4, with three classes. Class A must be written in managed code (for example, it's a garbage-collected public class that will be exposed to other code running on the runtime) and makes one call into Class B. Class B could be written in managed or unmanaged code. It makes multiple calls to Class C, which is an existing unmanaged class, part of a legacy library.

Figure 6.4. A class that is called by managed code, and calls unmanaged code, can be written in either.

graphics/06fig04.gif

If Class B is written in managed code, your application will pay the performance cost of calling unmanaged code from managed code hundreds of times each time the application is run, as calls go from Class B to Class C. Alternatively, if Class B is written in unmanaged code, that performance penalty will only be incurred once, when Class A calls Class B. After that, the calls from Class B to Class C will all be unmanaged-to-unmanaged. Clearly, writing Class B in unmanaged code will make your application faster.

Ideally, you could write Class B in unmanaged code using the same tool you use for Class A, and with the two classes in the same project. You can do just that with Visual Studio .NET 2003. In this section, you see how to compile one source file to unmanaged code in a project that builds to managed code.

Using ILDASM to Explore Assemblies

You can examine the contents of an assembly using a tool called ILDASM, intermediate language dissassembler. The easiest way to start ILDASM is from a Visual Studio command prompt. Go to Start, Programs, Visual Studio .NET 2003, Visual Studio .NET Tools, and then Visual Studio .NET 2003 command prompt. Type ildasm at the prompt to start the tool, and open the release version of ManagedMathLibrary.dll.

Figure 6.5 shows this assembly in ILDASM. The ManagedMathLibrary namespace has been expanded to show the two classes, Arithmetic and DLL , and each class has been expanded to show its methods. Static methods are marked with a yellow S in the pink square before each method name .

Figure 6.5. ILDASM shows the contents of the ManagedMathLibrary assembly.

graphics/06fig05.gif

If you double-click a method, such as Add() or Subtract() , you can see the intermediate language for the method in a new window. This intermediate language is similar to assembly language. The IL for Subtract is simple:

 
 .method public instance float64  Subtract(float64 num1,                                           float64 num2) cil managed {   // Code size       6 (0x6)   .maxstack  2   IL_0000:  ldarg.1   IL_0001:  ldarg.2   IL_0002:  sub   IL_0003:  br.s       IL_0005   IL_0005:  ret } // end of method Arithmetic::Subtract 

Even with no assembly language background, you might be able to guess what the numbered lines do:

0000:

Loads argument #1 into a register

0001:

Loads argument #2 into a register

0002:

Subtracts the registers

0003:

Copies the answer into the return value

0005:

Returns

The IL for Add is a little more complex, because it has calls to the << operator, but it is still intermediate language. It makes calls to unmanaged code without being unmanaged code.

A managed class, with the __gc keyword, can only be defined in managed code. A source file that doesn't define any managed classes can be written in either managed or unmanaged code, as you prefer. Consider the Simple class, defined in the same project as Arithmetic . It's not a garbage-collected class and it's not in a namespace. It has a constructor and a getx() method, which have been defined as in-line functions. Here's the class definition again:

 
 class Simple { private:    int x; public:    Simple(int xx): x(xx){};    int getx() {return x}; }; 

Because all the member functions of this class have been defined in-line, they don't appear in the disassembly of the intermediate language. They'll be expanded into the code that uses them and compiled in that location. To serve as a demonstration of unmanaged data in managed code, Simple can be changed so that the functions are no longer defined in-line. The header file, Simple.h, becomes

 
 class Simple { private:    int x; public:    Simple(int xx);    int getx() ; }; 

The implementation file, Simple.cpp, becomes

 
 #include "StdAfx.h"  #include "simple.h"  #using <mscorlib.dll> Simple::Simple(int xx): x(xx) { } int Simple::getx() {    return x; } 

When you build this project, the assembly contains IL for the constructor and the getx() function. You have to look hard for them among all the other methods from the CRT that are displayed in the ILDASM tool, but Simple.__ctor and Simple.getx are listed. The IL for the constructor looks like this:

 
[View full width]
 
[View full width]
.method public specialname static valuetype Simple* modopt([mscorlib]System.Runtime graphics/ccc.gif .CompilerServices.CallConvThiscall) Simple.__ctor(valuetype Simple* modopt([Microsoft.VisualC] Microsoft.VisualC graphics/ccc.gif .IsConstModifier) modopt([Microsoft.VisualC] Microsoft.VisualC.IsConstModifier) A_0, int32 xx) cil managed { .vtentry 66 : 1 // Code size 5 (0x5) .maxstack 2 IL_0000: ldarg.0 IL_0001: ldarg.1 IL_0002: stind.i4 IL_0003: ldarg.0 IL_0004: ret } // end of method 'Global Functions'::Simple.__ctor

It basically just stores the argument that was passed to it. The first line is long and strange because this is a constructor, and specifically a constructor for a value type.

Turning Off /clr for a Single File

Why does Simple compile to intermediate language? Because the compiler was directed to compile it in that way. The directive is available on the property pages for the project as a whole, but also for each individual file. You can change the directive to compile to intermediate language for Simple.cpp while the rest of the project remains managed code by following these steps:

  1. Right-click Simple.cpp in solution view and choose Properties. Click the Configuration box and choose All Configurations.

  2. Expand the C/C++ folder and select General.

  3. Click next to Compile As Managed to reveal a drop-down box.

  4. Change Assembly Support ( /clr ) to Not Using Managed Extensions.

  5. Still on the property pages, switch to Precompiled Headers under C/C++.

  6. Change Create/Use Precompiled Headers from Use Precompiled Header (/Yu) to Not Using Precompiled Headers.

  7. Still on the property pages, switch to Advanced, and click next to Force #using . Click the ... that appears, and then clear the Inherit From Project check box. Click OK.

  8. Click OK to close the property pages.

  9. Remove the #using directive from Simple.cpp because unmanaged code cannot use an assembly.

  10. Remove the #include directive that brings in stdafx.h because the precompiled headers in this project are for managed code.

At this point you have changed the Simple.cpp properties so that it does not compile to intermediate language, does not use precompiled headers, and does not automatically bring in all the assemblies used by the project. But because this project has references to other assemblies, there is still work to do. Turning off the Force #using option does not override references. As a result, you must convert references to #using directives. You can do this file-by-file in each source file of the project, but it's more maintainable to use the Force #using property of the project. Here's how to do so:

  1. Expand the References node of the ManagedMathLibrary project in Solution Explorer.

  2. The references added when the project were created are not always ones that you need. For example, in this project the reference to System.Data is unused. Note which references the project is actually using: System and mscorlib .

  3. For each reference the project needs, click it and then switch to the Properties window. Write down the name of the assemblySystem.dll, for example.

  4. Bring up the Properties page for the ManagedMathLibrary project by right-clicking it and choosing Properties (not by switching to the Properties window).

  5. Expand the C/C++ folder and switch to Advanced.

  6. Click next to Force #using and enter the assembly names , separated by semicolons. For this sample, enter system.dll; mscorlib.dll , as shown in Figure 6.6.

    Figure 6.6. Use the Force #using property instead of references when some files are compiling to native code.

    graphics/06fig06.jpg

  7. Click OK to close the property pages.

  8. Click the first reference under ManagedMathLibrary and press Del to delete it. Repeat until all the references have been deleted.

Now build the project. There should be no errors or warnings. All the files in the project except Simple.cpp are using system.dll and mscorlib.dll and compiling to IL. Simple.cpp is not using any assemblies or precompiled headers, and is compiling to native code.

By just declaring a class and function without using them, you leave the opportunity for the optimizer to not bother putting them into the assembly. Add some lines to the Add method in Arithmetic that use Simple , so that it looks like this:

 
 System::Double Add(System::Double num1, System::Double num2) {    Simple s(2);    cout << endl << "Simple is " << s.getx() << endl;    cout << endl << "I am in Add"  << endl;    return num1 + num2; } 

Build the project, and open the assembly in ILDASM. Find the Simple member functions, and double-click them to examine the code. Instead of the IL that was shown to you when you disassembled the Simple constructor before, you will see something like this:

 
[View full width]
 
[View full width]
.method public specialname static pinvokeimpl(/* No map */) valuetype Simple* modopt([mscorlib] System.Runtime.CompilerServices.CallConvThiscall) Simple.__ctor(valuetype Simple* modopt( [Microsoft.VisualC]Microsoft.VisualC graphics/ccc.gif .IsConstModifier) modopt([Microsoft.VisualC]Microsoft.VisualC.IsConstModifier) A_0, int32 A_1) native unmanaged preservesig { .custom instance void [mscorlib]System.Security. SuppressUnmanagedCodeSecurityAttribute: graphics/ccc.gif :.ctor() = ( 01 00 00 00 ) // Embedded native code // Disassembly of native methods is not supported. // Managed TargetRVA = 0x4c80 } // end of method 'Global Functions'::Simple.__ctor

This constructor has compiled to native code, not IL. You have succeeded in creating a class written in unmanaged code within a project that creates managed code. There are several important applications for this technique. One is to create a wrapper for a complex legacy class with a "chatty" interface. Like Class C in Figure 6.4, many legacy classes expose 10 or 20 functions, each setting a single parameter, and then expose another function that actually does something using those parameter values. An unmanaged wrapper class would have one method that takes all the parameters at once, and makes multiple calls to the legacy object, setting all the parameters and then calling the action function. This would reduce the impact of the switch from managed to unmanaged code, because the switch would happen only once instead of tens or hundreds of times. Remember, this capability is unique to Visual C++.



Microsoft Visual C++. NET 2003 Kick Start
Microsoft Visual C++ .NET 2003 Kick Start
ISBN: 0672326000
EAN: 2147483647
Year: 2002
Pages: 141
Authors: Kate Gregory

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net