. Analysis of loading principle of CLR program under NET Platform

Source: Internet
Author: User
Tags execution garbage collection include mscorlib thread thread class versions win32
Program Flier Lu <flier_lu@sina.com.cn>

Note: This series of articles in the Water Wood Tsinghua BBS (smth.org) of the. NET version of the start,
Reprint please keep the above information, please contact the author

Unlike native code in a traditional Win32 executable program (Native),
Microsoft's launch. NET schema, the code for the executable program is in a Java Byte
IL (intermediate Language) pseudo code form exists. After the. NET executable is loaded,
The IL code is removed from the executable file by the CLR (Common Language Runtime).
Referred to the JIT (Just-In-Time) compiler, based on the corresponding metadata (Metadata),
Executed after just-in-time compiling of the cost machine code.
As a result, the startup process for a CLR executable can be divided into three steps.
First, Windows executable loader (OS Loader) loads
The executable file image (PE image) of the PE (portable executable) structure,
Passes execution to the unmanaged Code in the CLR's support library.
Second, start or use the existing CLR engine to create a new application domain (application domains),
Load the accessories (Assembly) into this application domain.
Finally, the execution power is passed from unmanaged code to managed codes, which executes the accessories.
I will explain the above steps in detail below.

Since the release of Win95, there have been no major changes to the PE structure of the executable program.
The. NET platform, but also only take advantage of the existing reserve space in the PE structure,
In order to maintain the stability of PE structure, the greatest degree to maintain backward compatibility.
(Please refer to the author's MS for details.) NET platform under the CLR expanded PE structure analysis of the article)
After compiling, the CLR program imports the executable entry directly to an indirect jump instruction
Point to the _CorExeMain function in Mscoree.lib (the DLL points the entry to the _CorDllMain function).
Therefore, after the CLR executable is loaded by the OS loader, the CLR engine will be processed by the _CorExeMain function
Startup matters. This function will start or use an existing CLR host to load the IL code.
The common CLR host has asp.net, IE, Shell, Database engine, and so on,
Their role is to start a CLR instance that manages CLR programs running in this CLR instance.

Let's go on to see how a CLR host actually works.
The CLR, as an engine, can have multiple versions on the same machine,
Different versions can be configured to coexist well with each other. In
%windir%\Microsoft.NET\Framework
(%windir% represents the location of the Windows system directory),
We can see multiple versions of the CLR with the version number as the directory name,
such as%windir%\microsoft.net\framework\v1.0.3705 and so on,
can also be in the registry
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\. netframework\policy\v1.0
Key to view detailed version compatibility. Name is the build number, and value is a compatible build number.
Each CLR version is divided into two types of runtime libraries, server and workstation,
We'll talk about it in detail when we create the CLR.
The CLR host must operate through a startup Shim library before starting the CLR.
It's actually mscoree.dll, and he provides a version-independent action function, as well as the need to start the CLR
Support, such as the CorBindToRuntimeEx function.
The CLR host loads the CLR engine into the process through the Shim Support Library. The specific functions are as follows
Stdapi CorBindToRuntimeEx (Lpcwstr pwszversion,
LPCWSTR Pwszbuildflavor, DWORD startupflags,
Refclsid Rclsid, Refiid riid, lpvoid FAR);
Parameter pwszversion Specifies the version number of the CLR to load, note that you must precede with a lowercase "V",
such as "v1.0.3705", you can get the different CLR for the current system installation by looking up the registry key mentioned earlier
version, or specify a fixed version of the CLR. You can also pass NULL to this parameter, the system will automatically select the latest
Version of the CLR load.
The parameter pwszbuildflavor specifies the loaded CLR type, "SRV" and "wks".
The former is suitable for multiprocessor computers and can improve parallel performance with multiple CPUs. To single CPU
System, the same is true for passing null, regardless of which type is to be loaded with "wks".
The parameter startupflags is a combination parameter. Consists of multiple flag bits.
STARTUP_CONCURRENT_GC flag Specifies whether to use concurrent GC (garbage Collection)
Mechanism, the use of concurrent GC can improve the efficiency of the user interface of the system, which is suitable for the application of the window interface.
However, the concurrent GC will lose efficiency because of the unnecessary thread context (thread contexts) switching.
The following three parameters are used to specify the fitting load optimization strategy. We'll discuss it in detail.
Startup_loader_optimization_single_domain = 0x1 << 1,
Startup_loader_optimization_multi_domain = 0x2 << 1,
Startup_loader_optimization_multi_domain_host = 0x3 << 1,
The next three parameters are used to get the ICorRuntimeHost interface.
The actual invocation instance is as follows.
Ccomptr<icorruntimehost> SpHost;
CHECK (CorBindToRuntimeEx (NULL, L "wks"),
Startup_loader_optimization_single_domain | STARTUP_CONCURRENT_GC,
CLSID_CorRuntimeHost, IID_ICorRuntimeHost, (void * *) &sphost));
This line of code is loaded into the WKS type runtime of the highest version of the CLR, optimizing for a single application domain and using the concurrency GC mechanism.
As mentioned in the previous part load optimization strategy, to understand this concept, we must first understand the concept of application domain.
In a traditional win program, the allocation Management unit of a resource is a process, and the operating system separates the application instance from the process boundary
,
The collapse of a single process does not have a direct impact on other processes, nor does the process directly use the resources of other processes.
The process is good, but the cost of using the process is too great, for this Win32 introduces the concept of threading. Threads in the same process can
Shared resources, the cost of threading management and switching is far less than the process. But because in the same process, the thread's crash will be straight
Pick up
affect the operation of other threads, and cannot constrain direct access to data between threads, and so on.
To this end, the concept of application domain application domains is introduced in the CLR. application domains are between processes and threads
A logical concept between the two. He has both the advantages of a lightweight, fast management switch, and a process in terms of stability
, the collapse of a single application domain does not directly affect other application domains in the same process, nor does the application domain directly
Accessing resources for other application domains in the same process is exactly the same as the process.
The management of the CLR is fully oriented to the application domain level. The CLR cannot unload (Unload) a type or accessory,
You must start/stop code in the application domain to obtain/release resources.
When the CLR executes an accessory, it creates a new application domain and puts it into the newly applied domain. If more than one application domain
At the same time to use a component, it is necessary to refer to the above mentioned Accessories load optimization strategy. The easiest way to do this is to use the
Startup_loader_optimization_single_domain flag, each application domain has a separate
Parts of the mirror, so the fastest, most convenient management, but occupy more memory. In contrast, all application domains share a
Mirrors of Accessories (using Startup_loader_optimization_multi_domain logo)
This saves memory, but there are data such as static variables in this accessory, because there are separate numbers for each application field
According
So it will affect efficiency to a certain extent. The tradeoff is to use
(using the STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN_HOST flag)
At this point, only those accessories with strong name will be shared by multiple application domains.
This also involves a concept strong Name. He was an accessory to the identity of the proof that he was by accessories of the
Name/version/culture as well as digital signatures. Used to differentiate between different versions when distributing parts.
Also plays an important role in security/versioning, and will have a chance to explain it later. Skip over.
After getting the pointer to the ICorRuntimeHost interface, we can get the current/default application domain with this pointer.
You can enumerate all the application domains in the CLR engine instance.
Ccomptr<iunknown> Spunk;
Ccomptr<_appdomain> Spappdomain;

CHECK (Sphost->getdefaultdomain (&spunk));
Spappdomain = spunk; Spunk = NULL;
Wcout << L "Default AppDomain is" << (wchar_t
*) Spappdomain->getfriendlyname () << Endl;

CHECK (Sphost->currentdomain (&spunk));
Spappdomain = spunk; Spunk = NULL;
Wcout << L "AppDomain is" << (wchar_t
*) Spappdomain->getfriendlyname () << Endl;

Hdomainenum Henum;
CHECK (Sphost->enumdomains (&henum));
Spunk = NULL;
while (Sphost->nextdomain (Henum, &spunk)!= S_FALSE)
{
Spappdomain = spunk; Spunk = NULL;
Wcout << (wchar_t *) spappdomain->getfriendlyname () << Endl;
}
CHECK (Sphost->closeenum (henum));
The current application domain is the application domain where the current thread is running. Note that the thread belongs to the process, but does not belong to an application domain.

A thread can operate across an application domain. You can get the thread's current location through the thread.getdomain of the thread class
application domain.
The default application domain is an application domain that is automatically built after the CLR engine is loaded, and its lifetime runs through the CLR engine's lifetime.
The CLR host's managed code-side management codes are typically executed in this application domain without executing user code.
Next, it's time to load the user code's accessories. There are two ways to do this, one is to use the complete
Native code or unmanaged code is operated through the COM wrapper interface of BCL, and the second is to operate
The CLR host code that is transferred to the managed Code section executes. The latter is simple to implement and faster.
The author will then introduce the design of the managed code part of the CLR host separately in an article.
Here is a brief introduction to the first implementation.
The complete implementation of CLR host with unmanaged code is cumbersome but more powerful. But because of the operation
To continuously switch between unmanaged/managed code, the efficiency is affected by some degree. (The switch calls
is implemented through the IDispatch interface and is inherently inefficient, plus a CCW (COM callable Wrapper)
The packaging is less efficient than the direct use of managed code.
To unmanaged code to call accessories, you must know parts of the information, such as the name of accessories,
The name of the class to invoke, the function to invoke, and so on. You can specify the parameters to use, or you can
The IL entry entrypointtoken and metadata information of the CLR headers in the PE image to obtain
(Please refer to the author's MS for details.) NET platform under the CLR expanded PE structure analysis "a text metadata article)
Here for the example simple, using the parameter transfer method.
if (ARGC < 4)
{
Cerr << "Usage:" << argv[0] << "<assembly name> <class name> <main
Function name> <Parameters> "<< Endl;
}
Else
{
_bstr_t Bstrassemblyname (argv[1]),
Bstrclassname (argv[2]),
Bstrmainfuncname (Argv[3]);
...
}
Example to pass the accessory/class/function name information in the command line.
Spunk = NULL;
CHECK (Sphost->getdefaultdomain (&spunk));
Spappdomain = spunk; Spunk = NULL;
First gets the default application domain, in which the specified class is created in the specified accessories. Here for example concise
Loading accessories directly in the default application domain should be avoided in actual development, with the creation of new application domains
Way to load the accessories. With regard to the new application domain and the configuration when it was set up, there are many design problems, and then specialized
Write an article detailing, omitted here.
_objecthandleptr spobj = spappdomain->createinstance (Bstrassemblyname,
Bstrclassname);
Ccomptr<idispatch> spdisp = Spobj->unwrap (). Pdispval;
After building an instance of an accessory class, get a _objecthandleptr type value,
Get the IDispatch interface through the unwrap () call, and then you can pass this interface to the traditional COM
method to control the classes in the CLR.
int argcount = argc-4;
DISPID DISPID;
Lpolestr rgszname = bstrmainfuncname;
Variantarg *pargs = new Variantarg[argcount];
for (int i=0; i<argcount; i++)
{
VariantInit (&pargs[i]);
PARGS[I].VT = VT_BSTR;
Pargs[i].bstrval = _bstr_t (Argv[4+i]);
}
Dispparams Dispparamsnoargs = {PArgs, NULL, Argcount, 0};

CHECK (Spdisp->getidsofnames (Iid_null, &rgszname, 1,
Locale_system_default, &dispid));
CHECK (Spdisp->invoke (DispID, Iid_null, Locale_system_default,
Dispatch_method,
&dispparamsnoargs, NULL, NULL, NULL);
Delete[] PArgs;
The above example code, put the command line in the argument array parameters, Idispatch->invoke call to specify the name
Of the method. Its background operations are delivered by the CCW. If you want to run a assembly directly, you can use the
Iappdomain.executeassembly is more convenient.
CHECK (spappdomain->executeassembly (Bstrassemblyname, NULL));
At this point, a simple but complete CLR host program is complete, and he can use the full unmanaged Code
Launches the CLR engine, loading the specified assembly to specify a method for running the specified class.
The following is a complete sample program, VC7 compiled, VC6 modification should also be no problem.

Hello.cs

Using System;

Namespace Hello
{
<summary>
Summary description for Class1.
</summary>
public class Hello
{
public void SayHello (string Name)
{
Console.WriteLine ("Hello" +name);
}
}
}

ClrHost.cpp

CLRHost.cpp:Defines the entry point for the console application.
//

#include "stdafx.h"

#include <mscoree.h>

#import <mscorlib.tlb> Rename ("ReportEvent", "Reportevent_")
using namespace mscorlib;

#include <assert.h>

#include <string>
#include <memory>
#include <iostream>
using namespace Std;

typedef HRESULT (__stdcall * getinfofunc) (LPWStr pbuffer, DWORD Cchbuffer,
dword* dwlength);

#define CHECK (v) \
if (FAILED (v)) \
Cerr << "COM function call failed-" << GetLastError () << "at" <<
__file__ << "," << __line__ << Endl;

Wstring GetInfo (Getinfofunc Func)
{
wchar_t Szbuf[max_path];
DWORD dwlength;
if (SUCCEEDED (Func) (Szbuf, MAX_PATH, &dwlength))
Return wstring (Szbuf, dwlength);
Else
return NULL;
}

int _tmain (int argc, _tchar* argv[])
{
Ccomptr<icorruntimehost> SpHost;

CHECK (CorBindToRuntimeEx (NULL, L "wks"),
Startup_loader_optimization_single_domain | STARTUP_CONCURRENT_GC,
CLSID_CorRuntimeHost, IID_ICorRuntimeHost, (void * *) &sphost));

Wcout << L "Load CLR" << GetInfo (getcorversion)
<< L "from" << GetInfo (GetCORSystemDirectory)
<< Endl;

CHECK (Sphost->start ());

Ccomptr<iunknown> Spunk;
Ccomptr<_appdomain> Spappdomain;

#ifdef _DEBUG
CHECK (Sphost->getdefaultdomain (&spunk));
Spappdomain = spunk; Spunk = NULL;
Wcout << L "Default AppDomain is" << (wchar_t
*) Spappdomain->getfriendlyname () << Endl;

CHECK (Sphost->currentdomain (&spunk));
Spappdomain = spunk; Spunk = NULL;
Wcout << L "AppDomain is" << (wchar_t
*) Spappdomain->getfriendlyname () << Endl;

Hdomainenum Henum;
CHECK (Sphost->enumdomains (&henum));
Spunk = NULL;
while (Sphost->nextdomain (Henum, &spunk)!= S_FALSE)
{
Spappdomain = spunk; Spunk = NULL;
Wcout << (wchar_t *) spappdomain->getfriendlyname () << Endl;
}
CHECK (Sphost->closeenum (henum));
#endif//_DEBUG

if ((ARGC < 2) | | (argc = 3))
{
Cerr << "Usage:" << argv[0] << "<assembly name> <class name> <main
Function name> <Parameters> "<< Endl;
}
Else
{
Spunk = NULL;
CHECK (Sphost->getdefaultdomain (&spunk));
Spappdomain = spunk; Spunk = NULL;

_bstr_t Bstrassemblyname (argv[1]);
if (argc = 2)
{
CHECK (spappdomain->executeassembly (Bstrassemblyname, NULL));
}
Else
{
_bstr_t Bstrclassname (argv[2]),
Bstrmainfuncname (Argv[3]);

_objecthandleptr spobj =
Spappdomain->createinstance (Bstrassemblyname, bstrclassname);
Ccomptr<idispatch> spdisp = Spobj->unwrap (). Pdispval;

DISPID DISPID;
Lpolestr rgszname = bstrmainfuncname;
Dispparams Dispparamsargs = {null, NULL, 0, 0};

int argcount = argc-4;
if (Argcount > 0)
{
Dispparamsargs.cargs = Argcount;
Dispparamsargs.rgvarg = new Variantarg[argcount];
Variantarg *pargs = Dispparamsargs.rgvarg;
for (int i=0; i<argcount; i++)
{
VariantInit (&pargs[i]);
PARGS[I].VT = VT_BSTR;
Pargs[i].bstrval = _bstr_t (Argv[4+i]);
}
}

CHECK (Spdisp->getidsofnames (Iid_null, &rgszname, 1,
Locale_system_default, &dispid));
CHECK (Spdisp->invoke (DispID, Iid_null, Locale_system_default,
Dispatch_method,
&dispparamsargs, NULL, NULL, NULL);
Delete[] Dispparamsargs.rgvarg;
}
}

CHECK (Sphost->stop ());

return 0;
}


Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.