Export (0) Print
Expand All
9 out of 10 rated this helpful - Rate this topic

Converting Managed Extensions for C++ Projects from Pure Intermediate Language to Mixed Mode

Visual Studio .NET 2003

Managed Extensions for C++ projects that are created as DLLs by default contain Microsoft intermediate language (MSIL) code that does not link to native C/C++ libraries such as the C run-time (CRT) library, ATL, or MFC, and does not use any static variables. Their code targets only the common language runtime.

This is done because linking with an entry point causes managed code to run during DllMain, which is not safe (see DllMain for the limited set of things you can do during its scope).

A DLL without an entry point has no way to initialize static variables except for very simple types such as integers. You should not normally have any static variables in a /NOENTRY DLL.

The ATL, MFC and CRT libraries all rely on static variables, so you also cannot use these libraries from within this DLL.

If your mixed-mode DLL needs to use statics or libraries that depend on statics (such as ATL, MFC, or CRT), then you must modify your DLL to have an explicit entry point.

To modify your DLL to have an explicit entry point, convert the managed DLL to mixed mode.

To convert the managed DLL to mixed mode

  1. Link with /NOENTRY. In Solution Explorer, right-click the project node and click Properties. In the project's Property Pages dialog box, click Linker, and then click Command Line. Add this switch to the Additional Options field.
  2. Link msvcrt.lib. In the project's Property Pages dialog box, click Linker, and then click Input. Add msvcrt.lib to the Additional Dependencies property.
  3. Remove nochkclr.obj. On the Input page (same page as previous step), remove nochkclr.obj from the Additional Dependencies property.
  4. Link in the CRT. On the Input page (same page as previous step), add __DllMainCRTStartup@12 to the Force Symbol References property.

Modifying Components That Consume the DLL for Manual Initializiation

After converting to mixed mode, you must modify components that consume the DLL for manual initialization, depending on the way that your DLL is implemented:

  • Your DLL is entered using DLL exports (__declspec(dllexport)), and your consumers cannot use managed code if they are linked statically or dynamically to your DLL.
  • Your DLL is a COM-based DLL.
  • Your DLL's consumers can use managed code, and your DLL contains either DLL exports or managed entry points.

To modify your DLL that is entered using DLL exports (__declspec(dllexport)) and consumers that cannot use managed code

  1. Add two new exports to your DLL:
    // init.cpp
    // Add these headers before the header with the using namespace System
    // directive, or add them in a .cpp file that does not have a 
    // using namespace System directive.
    #include <windows.h>
    #include <_vcclrit.h>
    
    // Call this function before you call anything in this DLL.
    // It is safe to call from multiple threads, is not reference
    // counted, and is reentrancy safe.
    
    extern "C" __declspec(dllexport) void __stdcall DllEnsureInit(void)
    {
       // Do nothing else here. If you need extra initialization steps,
       // create static objects with constructors that perform 
       // initialization.
       __crt_dll_initialize();
       // Do nothing else here.
    }
    
    // Call this function after this whole process is totally done
    // calling anything in this DLL. It is safe to call from multiple
    // threads, is not reference counted, and is reentrancy safe.
    // First call will terminate.
    
    extern "C" __declspec(dllexport) void __stdcall DllForceTerm(void)
    {
       // Do nothing else here. If you need extra terminate steps, 
       // use atexit.
       __crt_dll_terminate();
       // Do nothing else here.
    }
    

    Add the following to the DLL .def file in the exports section:

    DllEnsureInit   PRIVATE
    DllForceTerm   PRIVATE
    

    Without these lines, if you have two DLLs that export functions, then the application linking to the DLL will have link errors. Typically, the exported functions will have the same names.

    In a multiconsumer case, each consumer can be linked statically or dynamically to your DLL.

  2. Your DLL can have several consumers.
  3. If the consumer is statically linked to the DLL, before the first use of your DLL or anything that depends on it in your application, add the following call:
    // Snippet 1
    
    typedef void (__stdcall *pfnEnsureInit)(void);
    typedef void (__stdcall *pfnForceTerm)(void);
    
    {
       // ... initialization code
       HMODULE hDll=::GetModuleHandle("mydll.dll");
       If(!hDll)
       {
          // exit, return; there is nothing else to do
       }
       pfnEnsureInit pfnDll=( pfnEnsureInit) ::GetProcAddress(hDll, 
       "DllEnsureInit");
       if(!pfnDll)
       {
          // exit, return; there is nothing else to do
       }
       
       pfnDll();
       
       // ... more initialization code
    }
    
  4. After the last use of the DLL in your application, add the following code:
    // Snippet 2
    
    {
       // ... termination code
       HMODULE hDll=::GetModuleHandle("mydll.dll");
       If(!hDll)
       {
          // exit, return; there is nothing else to do
       }
       pfnForceTerm pfnDll=( pfnForceTerm) ::GetProcAddress(hDll, 
       "DllForceTerm");
       if(!pfnDll)
       {
          // exit, return; there is nothing else to do
       }
       
       pfnDll();
       
       // ... more termination code
    }
    
  5. If the consumer is dynamically linked to the DLL, insert snippet 1 immediately after the first LoadLibrary for the DLL and insert snippet 2 immediately before the last FreeLibrary for the DLL.

To modify your DLL that is COM based

  • Modify the DLL export functions DllCanUnloadNow, DllGetClassObject, DllRegisterServer, and DllUnregisterServer as demonstrated in the following code:
    // Implementation of DLL Exports
     
    STDAPI DllCanUnloadNow(void)
    {
       HRESULT hrReturn=S_FALSE;
       // Function as usual
       // At this point hrReturn is S_OK if you can unload
       if(hrReturn == S_OK)
       {
          __crt_dll_terminate();
       }
       return hrReturn;
    }
     
    STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
    {
       // Do nothing here
       __crt_dll_initialize();
       // Continue with DllGetClassObject as before
    }
    
    STDAPI DllRegisterServer(void)
    {
       if ( !( __crt_dll_initialize() ) )
       {
          return E_FAIL;
       }
       // Call your registration code here
       HRESULT hr = <registration code>
       __crt_dll_terminate();
       return hr;
    }
    
    STDAPI DllUnregisterServer(void)
    {
       if ( !( __crt_dll_initialize() ) )
       {
          return E_FAIL;
       }
       // Call your unregistration code here
       HRESULT hr = <unregistration code>
       __crt_dll_terminate();
       return hr;
    }
    

To modify your DLL that contains consumers that use managed code and DLL exports or managed entry points.

  1. Implement a managed class with static member functions for initialization and termination. Add a .cpp file to your project, implementing a managed class with static members for initialization and termination:
    // ManagedWrapper.cpp
    
    // This code verifies that DllMain is not called by the Loader
    // automatically when linked with /noentry. It also checks some
    // functions that the CRT initializes.
    
    #include <windows.h>
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <math.h>
    #include "_vcclrit.h"
    
    #using <mscorlib.dll>
    using namespace System;
    
    public __gc class ManagedWrapper {
    public:
       static int minitialize() {
          int retval = 0;
          try {
                __crt_dll_initialize();
          } catch(System::Exception* e) {
             Console::WriteLine(e);
             retval = 1;
          }
          return retval;
       }
       static int mterminate() {
          int retval = 0;
          try {
                __crt_dll_terminate();
          } catch(System::Exception* e) {
             Console::WriteLine(e);
             retval = 1;
          }
          return retval;
       }
    };
    
    BOOL WINAPI DllMain(HINSTANCE hModule, DWORD dwReason, LPVOID 
    lpvReserved) {
       Console::WriteLine(S"DllMain is called...");
       return TRUE;
    } /* DllMain */
    
  2. Call these functions before you refer to the DLL and after you have finished using it. Call the initialization and termination member functions in main:
    #using <mscorlib.dll>
    #using "ijwdll.dll"
    
    using namespace System;
    
    int main() {
          int retval = 0;
          retval += ManagedWrapper::minitialize();
          retval += ManagedWrapper::mterminate();
          return retval;
    }
    

See Also

Compiling to MSIL | Producing Verifiable Components with Managed Extensions for C++ | Converting Managed Extensions for C++ Projects from Mixed Mode to Pure Intermediate Language

Did you find this helpful?
(1500 characters remaining)
Thank you for your feedback
Show:
© 2014 Microsoft. All rights reserved.