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
msvcrt.lib
to the Additional Dependencies property. nochkclr.obj
from the Additional Dependencies property. __DllMainCRTStartup@12
to the Force Symbol References property. 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:
To modify your DLL that is entered using DLL exports (__declspec(dllexport)) and consumers that cannot use managed code
// 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.
// 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 }
// 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 }
To modify your DLL that is COM based
// 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.
// 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 */
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 转换为混合模式?
修改使用DLL的代码部分,进行手动初始化
转换成混合模式之后,你必须修改使用DLL的代码部分,根据你的DLL实现方式进行手动初始化:
用 __declspec(dllexport) 输出且调用者无法使用托管代码的 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 进行静态或动态链接。
// 代码段一 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(); // ... 更多的初始化代码 }
// 代码段二 { // ... 终止代码 HMODULE hDll=::GetModuleHandle("mydll.dll"); If(!hDll) { // 退出,返回,再没有什么要做的了 } pfnForceTerm pfnDll=( pfnForceTerm) ::GetProcAddress(hDll, "DllForceTerm"); if(!pfnDll) { // 退出,返回,再没有什么要做的了 } pfnDll(); // ... 更多的终止代码 }
基于 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 输出或者托管入口点,修改方式如下:
// 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 */
#using <mscorlib.dll> #using "ijwdll.dll" using namespace System; int main() { int retval = 0; retval += ManagedWrapper::minitialize(); retval += ManagedWrapper::mterminate(); return retval; }