|
In some scenarios a host might elect to delay the initialization and loading of the CLR until it is actually needed rather than doing it upfront. Loading the CLR lazily can reduce both your startup time and startup working set. However, waiting until later to call CorBindToRuntimeEx opens a window in which a copy of the CLR might get loaded into the process without your knowledge and therefore without an opportunity for you to set the startup options you want. The classic case in which this can occur is with COM interoperability. If your host doesn't initialize the CLR at startup and some unmanaged code in your process loads a managed type through COM, the activation of that type brings the CLR into the process. Because the host is out of the loop at this point, the default settings chosen by the default CLR host are used (refer to Chapter 4 to see how the default CLR host chooses these settings). This can result in loading a CLR that does not match what the host would have chosen. For example, a different version of the CLR might be loaded or the wrong build type might be chosen. Fortunately, hosts can use a function on the shim called LockClrVersion to close this window. When you call LockClrVersion, you're essentially stating that you are the only one allowed to initialize the CLR in this process. When the shim receives the first request to run managed code, it will call a function that you provide to enable you to initialize the CLR the way you see fit. In the preceding scenario, the host's function would have been called when the managed type was accessed through the COM interoperability layer, thereby giving the host a chance to initialize and load the CLR. Here's the definition of LockClrVersion as found in mscoree.h: typedef HRESULT (__stdcall *FLockClrVersionCallback) (); STDAPI LockClrVersion(FLockClrVersionCallback hostCallback, FLockClrVersionCallback *pBeginHostSetup, FLockClrVersionCallback *pEndHostSetup); The parameters to LockClrVersion are summarized in Table 3-4.
At first glance, pBeginHostSetup and pEndHostSetup might seem unnecessary. After all, the shim can tell when the CLR has been initialized after the call to hostCallBack returns. The reason these additional parameters are needed is to notify the shim about which thread the initialization is happening on. The shim needs this information mostly for internal implementation reasons relating to the fact that only one thread in the process is allowed to initialize the CLR. The shim also uses the information to block other threads that might enter the process and wish to run managed code from proceeding until the initialization is done. Listing 3-1 from the sample DeferredStartup.exe shows how to use LockClrVersion to delay the loading of the CLR. In this example, I force the CLR into the process by activating a managed type through the COM Interoperability layer as in the scenario described earlier. When the managed type is activated, the host-supplied function is called to initialize the CLR. Listing 3-1. Using LockClrVersion to Load the CLR#include "stdafx.h" #include <mscoree.h> // Declare globals to hold function pointers to call to notify the shim when the // initialization of the CLR is beginning and ending. FLockClrVersionCallback g_beginInit; FLockClrVersionCallback g_endInit; // This function is registered as the host callback provided to the CLR by LockClrVersion. // The shim will call this function the first time it receives a request to run managed code. STDAPI InitializeCLR() { // Notify the CLR that initialization is beginning g_beginInit(); // Initialize the CLR. ICLRRuntimeHost *pCLR = NULL; HRESULT hr = CorBindToRuntimeEx( L"v2.0.41013", NULL, NULL, CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (PVOID*) &pCLR); assert(SUCCEEDED(hr)); // Start the CLR. pCLR->Start(NULL, NULL); // Notify the CLR that initialization has completed. g_endInit(); return S_OK; } int main(int argc, char* argv[]) { HRESULT hr = S_OK; // Call LockClrVersion so the InitializeCLR always get called to set up the runtime. LockClrVersion(InitializeCLR, &g_beginInit, &g_endInit); // Initialize COM and create an instance of a managed type through COM Interop. This // will require the CLR to be loadedInitializeCLR will be called. CoInitialize(NULL); CLSID clsid; hr = CLSIDFromProgID(L"System.Collections.SortedList", &clsid); assert(SUCCEEDED(hr)); IUnknown *pUnk = NULL; hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, (LPVOID *) &pUnk); assert(SUCCEEDED(hr)); pUnk->Release(); CoUninitialize(); return 0; } LockClrVersion can be used for another, less obvious purposeto prevent the CLR from ever being loaded in a process. The scenarios in which you'd want to do this are clearly limited, but if you had a requirement to prevent managed code from running in your process completely, LockClrVersion is the way to do it. As you've seen, LockClrVersion takes a callback function that gets invoked when the CLR needs to be initialized. If you return a failure HRESULT from this callback, the CLR would not be loaded. In this way, you can prevent the CLR from ever entering your process. Note
|
|