It Just Works (IJW) Interoperability and the Mixed DLL Problem

It Just Works (IJW) Interoperability and the Mixed DLL Problem

The previous chapter, "Writing a Class Library in Unmanaged C++," showed how to write an unmanaged class and, among other things, call it from managed code. The capability of managed C++ code to call into unmanaged code contained in a .LIB file is part of the appeal of managed C++ as a language to use on the .NET runtime. Visual Basic and C# don't have this capability.

This means that you can use not only your own unmanaged libraries, but all the unmanaged libraries that already exist: MFC, ATL, STL, libraries you bought from a vendor, and so on. Working in managed C++ opens up a world of interoperability.

When you write a managed console application that uses IJW to call unmanaged code, you create what's known as a mixed EXEan assembly that contains both intermediate language and native code. Mixed EXEs are simple to create and they work flawlessly. When you create a mixed DLL, you usually need to go to a little trouble to make sure it works just as flawlessly. Otherwise you might run into what's known as the loader-lock bug.

The Loader-Lock Bug

The issue is not mixing native code and intermediate language, but rather a seemingly trivial issue: initializing statics. When you write an unmanaged DLL, global variables and static member variables are initialized in a function called DllMain , which is generated for you when you create the project. If you are using any libraries, DllMain would normally call out to these libraries to initialize them.

At first glance, you can do the same thing in a managed library, with a few lines of managed code in DllMain , using IJW to call the unmanaged libraries. However, because of the architecture of the .NET loader in versions 1.0 and 1.1 of the .NET Framework, it's very important not to call any managed code from DllMain . If you write a DllMain with managed code in it, the .NET loader has to be called from DllMain to load and run the managed code, and under certain circumstances your machine could lock or hang unexpectedly. Code with this issue can run normally until the system is at very high load, and then lock up at a critical time. It's also more likely to happen if your assembly has a strong namethat is, after you've deployed it onto customer computers. (This is called the loader-lock bug and will be fixed in a future version of the Framework.)

Starting in version 1.1 of the .NET Framework, all managed-code DLLs are generated with an option called /NOENTRY , which suppresses the generation of a DllMain function. This prevents the loader-lock issue from hurting you, but leaves you the task of initializing the libraries you will use. Your own unmanaged libraries might not need initializing, but ATL, MFC, the C runtime (CRT), and other popular libraries do have statics that must be initialized before the libraries are used. You must write code to initialize these libraries, and what's more, you must expose this code in methods that users of your libraries must call.

A Sample Mixed DLL that Needs Library Initialization

To demonstrate the issue of library initialization, here is a changed version of the Add() method in the managed Arithmetic class that uses the C runtime library:

 
 System::Double Add(System::Double num1, System::Double num2) {    cout << endl << "I am in Add"  << endl;    return num1 + num2; } 

To tell the compiler about cout and endl , you add these statements:

 
 #include <iostream> using namespace std; 

To tell the linker about cout and endl , you add msvcrt .lib as an additional dependency. You do this by accessing the Properties page for the ManagedMathLibrary project, expanding Linker, selecting Input, and adding msvcrt.lib (for a release build; use msvcrtd.lib for a debug build) to the entries already there, as in Figure 6.2. Microsoft recommends also removing nochkclr.obj, because that additional dependency is for managed-only DLLs that want to emit verifiable code.

Figure 6.2. Adding the C runtime library to your linker dependencies.

graphics/06fig02.gif

If you build the project at this point, you'll get a rather strange set of linker warnings:

 
[View full width]
 
[View full width]
ManagedMathLibrary.cpp Linking... msvcrt.lib(checkclr.obj) : warning LNK4210: .CRT section exists; there may be unhandled graphics/ccc.gif static initializers or terminators msvcrt.lib(secchk.obj) : warning LNK4210: .CRT section exists; there may be unhandled graphics/ccc.gif static initializers or terminators libcpmt.lib(xlock.obj) : warning LNK4210: .CRT section exists; there may be unhandled graphics/ccc.gif static initializers or terminators libcpmt.lib(cout.obj) : warning LNK4210: .CRT section exists; there may be unhandled graphics/ccc.gif static initializers or terminators libcpmt.lib(iosptrs.obj) : warning LNK4210: .CRT section exists; there may be unhandled graphics/ccc.gif static initializers or terminators libcpmt.lib(locale0.obj) : warning LNK4210: .CRT section exists; there may be unhandled graphics/ccc.gif static initializers or terminators

Because these are only warnings, and the build succeeds, you might be tempted to just run the harness application. It will run into a NullReferenceException (see Figure 6.3), and if you choose Break, you'll see that execution was in the ostream code.

Figure 6.3. A NullReferenceException is thrown because the CRT has not been initialized.

graphics/06fig03.jpg

This runtime error is just what the linker was warning about. The cout object is an instance of the ostream class, a static instance that is shared by all the code in a process. That static instance is created when the C runtime library is initialized. And when you write a managed DLL, you don't have a DllMain that would quietly initialize the CRT for you.

Initializing the CRT Library

The solution to errors caused by uninitialized libraries is to add a method to the DLL that will initialize the CRT. And, because the CRT should also be cleaned up when your DLL is not going to be used any more, you should also add a method to clean up the CRT. You could make these member functions of the Arithmetic class, but if you need to use the CRT in the Arithmetic constructor, this won't work: The initializer needs to run before the constructor. Also, if your DLL contains several classes, which is normal, you have the problem of deciding which class should hold these administrative methods. A good solution is to create a separate class, and add these methods as static functions:

 
 public __gc class DLL { public:    static void Initialize()    {       __crt_dll_initialize();    }    static void Cleanup()    {       __crt_dll_terminate();    } }; 

A new header file, _vcclrit.h, is provided in Visual Studio .NET 2003 and you should include it in the library. It needs windows .h, so add these lines at the top of the source file, after the other include statements:

 
 // windows.h is for _vcclrit.h #include <windows.h> #include <_vcclrit.h> 

INITIALIZE IN CONSTRUCTOR? CLEANUP IN DESTRUCTOR?

It's tempting to make these methods the constructor and destructor of the DLL class, but garbage-collected classes don't get deterministic destruction; you don't know when the destructor will be called. These have to stay as regular methods.

How do you know the names of the functions to call? The help for that linker warning, LNK4210, explains the loader-lock bug and library initialization, and provides the names of these functions, along with the related header file.


Rebuild the library and you should see that the linker warnings have disappeared. Unfortunately, the runtime errors will still occur unless you change the calling code (the test harness) to use the new functions:

 
 ManagedMathLibrary::DLL::Initialize(); ManagedMathLibrary::Arithmetic* a = new ManagedMathLibrary::Arithmetic(); Console::Write("2.3 + 4.5 is "); Console::WriteLine(a->Add(2.3, 4.5)); Console::ReadLine(); ManagedMathLibrary::DLL::Cleanup(); 

Now the code not only builds without warnings, it runs without errors, producing this output:

 
 2.3 + 4.5 is I am in Add 6.8 

Use this technique to initialize any libraries your managed code is using through IJW, and you'll never be bitten by the loader-lock bug.



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