将 C++ 托管扩展项目从纯中间语言转换为混合模式请参见
编译为 MSIL | 产生可验证的 C++ 托管扩展组件将 C++ 托管扩展项目从混合模式转换为纯中间语言项目在默认情况下,作为 DLL 创建的 C++ 托管扩展项目所包含的 Microsoft 中间语言 (MSIL) 代码不会与 C 运行时 (CRT) 库、ATL 或 MFC 等本机 C/C++ 库建立任何链接,也不会使用任何静态变量。它们的代码只用于公共语言运行库。
这样做的原因是:如果托管代码与一个入口点链接,则会导致它们在 DllMain 期间运行,而这样是不安全的(有关在 DllMain 范围内可以执行的操作限制,请参见 DllMain)。
如果一个 DLL 没有入口点,则无法初始化静态变量(像整数这样非常简单的类型除外)。通常情况下,在 /NOENTRY DLL 中不应有任何静态变量。
由于 ATL、MFC 和 CRT 库都依赖于静态库,因此,您也无法从此 DLL 内使用这些库。
如果混合模式的 DLL 需要使用静态变量或依靠静态变量的库(如 ATL、MFC 或 CRT),则必须修改该 DLL,使之具有显式的入口点。
若要修改 DLL,使之具有显式的入口点,需将托管 DLL 转换成混合模式。
将托管 DLL 转换成混合模式
使用 /NOENTRY 开关进行链接。在解决方案资源管理器中,右击该项目节点,然后单击“属性”。在项目的“属性页”对话框中,单击“链接器”,然后单击“命令行”。在“附加选项”字段中添加此开关。
链接 msvcrt.lib。在项目的“属性页”对话框中,单击“链接器”,然后单击“输入”。在“附加依赖项”属性中添加 msvcrt.lib。
移除 nochkclr.obj。在“输入”页(上一步的同一页)上,从“附加依赖项”属性中移除 nochkclr.obj。
在 CRT 中链接。在“输入”页(上一步的同一页)上,在“强制符号引用”属性中添加 __DllMainCRTStartup@12。
修改使用 DLL 的组件以进行手动初始化
在转换为混合模式后,必须修改使用 DLL 的组件以进行手动初始化,具体方法取决于实现 DLL 的方式:
DLL 是使用 DLL 导出 (__declspec(dllexport)) 输入的,使用者如果是静态或动态地链接到该 DLL,则无法使用托管代码。
DLL 是基于 COM 的 DLL。
DLL 的使用者可以使用托管代码,并且该 DLL 或者包含 DLL 导出,或者包含托管入口点。
修改通过 DLL 导出 (__declspec(dllexport)) 输入的并且使用者无法使用托管代码的 DLL
在 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.
}
在 DLL .def 文件的导出部分添加下列两行代码:
DllEnsureInit PRIVATE
DllForceTerm PRIVATE
如果不添加以上的代码行,那么,一旦出现两个需导出函数的 DLL,链接到该 DLL 的应用程序就会出现链接错误。通常情况是,导出的函数将具有相同的名称。
在出现多个使用者的情形中,每个使用者都可以静态或动态地链接到 DLL。
DLL 可以有多个使用者。
如果使用者是静态地链接到 DLL,那么,在应用程序中第一次使用该 DLL 或依赖它的任何对象的代码之前,添加下面的调用代码:
// Snippet 1
{
// ... 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
}
{
// ... 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
}
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;
}
// 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.