`

Managed Extensions for C++ Reference

阅读更多
Managed Extensions for C++ Reference
Converting Managed Extensions for C++ Projects from Pure Intermediate Language to Mixed Mode

 

 

 

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;
        }

 

 

 

创建 DLL 的 C++ 托管扩展项目默认包含 MSIL(微软中间语言)代码,这个代码并不与 C 运行时库(CRT),ATL 或 MFC 这样的本机 C/C++ 库链接,也不使用任何静态变量。其代码只面向公共语言运行时。
  之所以要这么做是因为带有入口点的链接导致 DllMain 期间运行托管代码,这样不安全(参见 DllMain 相关文档,了解在它执行期间你不能做那些事情)。
  不带 入口点的 DLL 无法初始化静态变量,非常简单的类型如整型除外。通常,在 /NOENTRY DLL 中,你不能有任何静态变量。
  ATL,MFC 和 CRT 库都依赖于静态变量,所以你也不能在该 DLL 中使用这些库。
如果你的混合模式 DLL必须使用静态变量或者依赖静态变量的库(如:ATL,MFC 或 CRT),那么你必须修改你的 DLL,使之具备外在入口点。
  为此,必须将托管 DLL 转换为混合模式。那么,

如何将将托管 DLL 转换为混合模式?

  1. 用 /NOENTRY 链接:在解决方案管理器中,在项目节点上单击右键并选择“属性”。在属性页对话框中选择“链接器”,然后选择 “命令行”。添加一个开关到“附加选项”编辑框。
  2. 链接 msvcrt.lib:在项目的属性页对话框中选择“连接器”,然后选择“输入”。将 msvcrt.lib 添加到“附加依赖项”。
  3. 删除 nochkclr.obj:在“输入”页面(与前一步骤相同的页面),从附加依赖项属性中删除 nochkclr.obj。
  4. 链接到 CRT:在“输入”页面(与前一步骤相同的页面),将 __DllMainCRTStartup@12 添加到“强制符号引用”属性中。

修改使用DLL的代码部分,进行手动初始化

  转换成混合模式之后,你必须修改使用DLL的代码部分,根据你的DLL实现方式进行手动初始化:

  • 你的 DLL 使用 __declspec(dllexport) 输出,并且 DLL 的使用者与 DLL 之间的链接不管是静态链接的还是动态的,那么 DLL 的使用者都无法使用托管代码。
  • 你的 DLL 是基于 COM 的 DLL
  • 你的 DLL 的调用者可以用托管代码,并且你的 DLL 包含 DLL 输出或托管入口点

用 __declspec(dllexport) 输出且调用者无法使用托管代码的 DLL 的修改方法:

  1. 向 DLL 添加两个新的输出:
    // init.cpp
    // 在 using namespace System 指令头之前添加这些头文件,
    // 或者在没有using namespace System 指令头的 .cpp 文件中添加它们
    #include <windows.h>
    #include <_vcclrit.h>
    
    // 在你调用任何该 DLL 中的东西之前调用该函数。
    // 从多线程中调用才安全,并非引用安全,而是重入安全
    
    extern "C" __declspec(dllexport) void __stdcall DllEnsureInit(void)
    {
    	// 在这里什么也不要做,如果你需要额外的初始化步骤,
    	// 创建带有构造函数的静态对象,在构造函数中完成初始化。
    	__crt_dll_initialize();
    	// 在这里什么也不要做。
    }
    
    // 在整个进程彻底调用完该 DLL 后调用该函数。从多线程中调用才安全。
    // 并非引用安全,而是重入安全。第一次调用将终止。
    
    extern "C" __declspec(dllexport) void __stdcall DllForceTerm(void)
    {
    	// 在这里什么也不要做,如果你需要额外的终止步骤,
    	// Do nothing else here. If you need extra terminate steps, 
    	// 使用 atexit.
    	__crt_dll_terminate();
    	// 在这里什么也不要做。
    }        
    将下面代码添加到 DLL .def 文件的 “exports” 部分:
    DllEnsureInit PRIVATE
    DllForceTerm PRIVATE

    如果没有这两行,那么当你有两个 DLL 都输出函数时,链接到该 DLL 的应用程序将会出现链接错误。典型的错误是输出的函数名字相同。
    在有多个DLL调用者时,每个调用者都可以和你 DLL 进行静态或动态链接。

  2. 你的 DLL 可以有多个调用者
  3. 如果调用者与该 DLL 静态链接,那么在应用程序中首次使用你的 DLL 或者依赖它的任何东西之前,要添加如下调用:
    // 代码段一
    
    typedef void (__stdcall *pfnEnsureInit)(void);
    typedef void (__stdcall *pfnForceTerm)(void);
    
    {
    	// ... 初始化代码
    	HMODULE hDll=::GetModuleHandle("mydll.dll");
    	If(!hDll)
    	{
    		// 退出,返回,再没有什么要做的了
    	}
    	pfnEnsureInit pfnDll=( pfnEnsureInit) ::GetProcAddress(hDll, 
    		"DllEnsureInit");
    	if(!pfnDll)
    	{
    		// 退出,返回,再没有什么要做的了
    	}
    	
    	pfnDll();
    	
    	// ... 更多的初始化代码
    }        
  4. 在应用程序最后一次使用 DLL 之后,添加下面的代码:
    // 代码段二
    
    {
    	// ... 终止代码
    	HMODULE hDll=::GetModuleHandle("mydll.dll");
    	If(!hDll)
    	{
    		// 退出,返回,再没有什么要做的了
    	}
    	pfnForceTerm pfnDll=( pfnForceTerm) ::GetProcAddress(hDll, 
    		"DllForceTerm");
    	if(!pfnDll)
    	{
    		// 退出,返回,再没有什么要做的了
    	}
    	
    	pfnDll();
    	
    	// ... 更多的终止代码
    }        
  5. 如果调用者是动态链接到此 DLL,将代码段一插入到第一次调用 LoadLibrary 之后,同时将代码段二插入到最后一次调用 FreeLibrary 之前。

基于 COM 的 DLL 的修改方法

  修改 DllCanUnloadNow,DllGetClassObject,DllRegisterServer 和 DllUnregisterServer 输出函数的方法如下:

// 实现 DLL 输出

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)
{
	// 在这里什么也不要做。
	__crt_dll_initialize();
	// 像从前那样继续 DllGetClassObject
}

STDAPI DllRegisterServer(void)
{
	if ( !( __crt_dll_initialize() ) )
	{
		return E_FAIL;
	}
	// 在这里调用注册代码
	HRESULT hr = <registration code>
		__crt_dll_terminate();
	return hr;
}

STDAPI DllUnregisterServer(void)
{
	if ( !( __crt_dll_initialize() ) )
	{
		return E_FAIL;
	}
	// 在这里调用注销代码
	HRESULT hr = <unregistration code>
		__crt_dll_terminate();
	return hr;
}
      

你的 DLL 包含调用者,该调用者使用托管代码以及 DLL 输出或者托管入口点,修改方式如下:

 

  1. 实现一个带有静态成员函数的托管类,静态成员函数用于初始化终止例程。往项目中添加一个 .cpp 文件:
    // ManagedWrapper.cpp
    
    // 这个代码验证当使用 /NOENTRY 链接选项时,DllMain 没有被 Loader 自动调用。
    // 它也检查某些 CRT 初始化函数。
    
    #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. 在引用 DLL 之前和使用完 DLL 之后,在 main 中调用这些初始化例程和终止例程成员函数:
    #using <mscorlib.dll>
    #using "ijwdll.dll"
    
    using namespace System;
    
    int main() {
    	int retval = 0;
    	retval += ManagedWrapper::minitialize();
    	retval += ManagedWrapper::mterminate();
    	return retval;
    }        
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics