Original: Relive. NET Under-assembly loading process
Recently involved in the work. NET is an old problem: the loading process of assembly. Although there are many articles on the Internet to introduce this part of the content, many articles are also a long time ago has appeared, but after reading found, and did not solve my problem, some of the points written is not particularly detailed, let people after reading the feeling or foggy. Finally, I decided to review this classic and old question and summarize it, and then there will be an example to illustrate the problem and hopefully help everyone.
. NET Under-assembly loading process
. NET assembly loading, the most important step is to determine the assembly version. Under. NET, managed DLLs and EXE are called assembly,assembly by AssemblyName to uniquely identify, AssemblyName is the familiar assembly.fullname, it is made up of five parts: name, version, The language, the public key token, the processor architecture, this is believed that everybody knows. For a detailed description of assembly name, refer to: Https://docs.microsoft.com/en-us/dotnet/framework/app-domains/assembly-names. The version, then, is an important part of the AssemblyName. The other four parts are the same, if the version is different, it cannot be counted as the same assembly. To design such a assembly version of the strategy, Microsoft itself is to solve the problem of the first DLL Hell, on Wikipedia on the black history of the detailed description, the address is: Https://en.wikipedia.org/wiki/DLL_Hell, There is not much to be wordy about here.
Assembly version redirection and finalization
. NET under the assembly loading process, in fact, is also the assembly version of the determination and assembly file positioning process, the steps are as follows:
- When a assembly is compiled, the full name of the assembly that it refers to (FullName) is forced by the compiler to write assembly metadata, which is dead, and Ilspy can see that each reference has its full name information:
For example, System.Data relies on System.Xml, and the version it needs is 4.0.0.0, so when the CLR loads System.Data, it can be assumed that the System.Xml version that needs to be loaded next is 4.0.0.0. The emphasis here is "think for the moment" because this is just the first step in determining the assembly version, so is the final system.xml using the 4.0.0.0 version? You need to look at the results of this next step, which is the assembly version of the redirect
- First, check the application's configuration file to see if there is a setting for assembly version redirection. We'll discuss the application configuration file in the AppDomain for the time being (if outside the AppDomain, you'll need to download the configuration file first, and then go ahead, not in-depth discussion). Application configuration files are commonly found in. exe.config and Web. config two. In the configuration file, it can be configured in assemblybinding under the runtime node. For example:
In this example, the version number of ASM6 assembly is redirected to 2.0.0.0. So assuming this is the final version number of the ASM6, then the next time the CLR starts loading ASM6, If the version of 2.0.0.0 is not found, the entire assembly loading process is ended by throwing fileloadexception directly (even if the 3.0.0.0 version is present). FileLoadException details are similar to: Could not load file or assembly ' Asm6, version=3.0.0.0, Culture=neutral, publickeytoken= c0305c36380ba429 ' or one of its dependencies. The located assembly ' s manifest definition does not match the assembly reference
- If the corresponding version redirection setting is found in the configuration file, then review the publisher policy file. The publisher policy file is a. NET Assembly that contains only the configuration files and is installed in the GAC. Its assembly version redirection configuration content is the same as the configuration of the application configuration file above, but it is scoped to all applications that use the assembly. This approach is useful for developing assembly upgrades for system-level generic frameworks, such as the. NET Framework. Here's a sample of the publisher policy file installed in the GAC, and note that publisher policy will override the version redirection configuration in the application configuration information, not the other way around. In other words, if ASM6 in the above step is determined to be 2.0.0.0, and the corresponding publisher policy file is determined to be 2.5.0.0, then, for the moment, the CLR should load the 2.5.0.0 version. Similarly, the word "think for a moment" means that the process of version determination is not over yet.
- Next, look for the Machine.config file. Similarly, if there is a version redirection setting in the Machine.config file, then this value in the Machine.config file is used as the version of the assembly that the CLR should be loading
Now that the final version of Assembly has been determined, the next step is to search for the assembly file and load it.
Search and load process for assembly files
Now that the CLR has started loading the deterministic version of Assembly, the next step is to search for the assembly file. This process is also known as assembly probing. The CLR does the following things:
- First, to see if the required assembly has already been loaded, and if it has already been loaded, then use the version of the loaded assembly directly to match the current desired version, and if so, use that loaded assembly, if it does not match, Then throws FileLoadException, execution ends
- Then, see if Assembly has been strongly signed (strongly Named), and if so, go to the GAC to find assembly. If found, it is loaded directly and the entire assembly loading process ends. If it is not found, proceed to the next step and continue searching for the assembly file. Of course, if assembly does not have a strong signature, skip this step and proceed directly
- Next, the CLR starts searching for possible assembly locations (probing), which can be in a variety of situations:
- First, see if there is a specified <codebase>,codebase configuration in the file that allows the application to specify the mount address for different versions of Assembly, following the following rules:
- If the specified assembly file is located under the Startup directory (or its subdirectories) of the current application domain, the value of the HREF is specified using a relative path
- If the specified assembly file is in a different directory, or anywhere else, the href must give the full path, and the assembly must be strongly signed
- The CLR then explores the root directory of the application domain and the associated subdirectories:
- Assuming assembly's name is Abc.dll, the CLR explores the following directories:
- [Appdomain_base]\abc.dll
- [Appdomain_base]\abc\abc.dll
- Assuming Abc.dll also has a language setting (culture is not neutral), the CLR explores the following directories:
- [Appdomain_base]\[culture]\abc.dll
- [Appdomain_base]\[culture]\abc\abc.dll
- If you find a assembly that conforms to the version, load it or go to the next step
- Finally, the CLR checks to see if there are <probling> nodes in the application configuration file and, if so, explores the privatepath values specified by the probling node. This process also takes into account the culture factor, similar to the one above, to search for the corresponding subdirectory. If the corresponding assembly is found, it is loaded, otherwise thrown fileloadexception, the entire loading process ends. Note that the "explore each" process here is not the process of traversing and finding the best match. The CLR looks for assembly files under PrivatePath based on the name of the assembly (without the version number), finds the first name match but the version mismatch, throws the exception and terminates the load, and it does not continue to search for the remaining paths in the PrivatePath
When loading a assembly file fails, the AppDomain triggers the Assemblyresolve event, which in the subscription function allows the client to customize the handling of the assembly that failed to load, for example, The assembly can be loaded into the AppDomain manually by Assembly.LoadFrom or Assembly.loadfile calls.
FUSLOGVW Assembly Binding Log Viewer
There is a Fuslogvw.exe application in the. NET SDK that allows you to see the detailed assembly loading process. Using the method is very simple, start the visual Studio Developer command Prompt with an administrator, and then enter Fuslogvw.exe at the command line to start the log viewer. After startup, click the Settings button to enable logging:
After the log starts, click the Refresh button, and then start your. NET application, you can see the load process log of the assembly that the current application relies on:
Next, I'll do an example program and then use this tool to analyze the assembly loading process.
Implementation of plug-in system and analysis of assembly loading process
Combining theory with practice, see how to interpret the loading process of the above mentioned Assembly by actual code. A good example is the design of a simple plug-in system, and by observing the system loading the plug-in process, to understand the ins and outs of assembly loading. For simplicity and intuition, I call this plugin system the Plugindemo. This plugin is very simple, the main program is a console application, and then we implement two plug-ins: Earth and Mars, in different plug-in Initialize method, will output a different string.
The project structure for the entire application is as follows:
The plug-in system contains 4 C # projects:
- Plugindemo.common: It defines the AddIn abstract class, and all plug-in implementations need to inherit from this abstract class. In addition, the Addindefinition class is a class used to hold plug-in metadata. To demonstrate, the plugin's metadata only contains the plug-in type of assembly qualified Name
- Plugindemo.app: Application of plug-in system. When this program executes, it scans the DLL in the modules directory under the program directory and loads the corresponding plug-in object according to Module.xml metadata information, and executes the Initialize method.
- PluginDemo.Plugins.Earth: One of the plug-in implementations
- PluginDemo.Plugins.Mars: Another plug-in implementation
Note: In addition to the Plugindemo.common three items, there are referential relationships to Plugindemo.common. The Plugindemo.app project will not refer to these two projects only if the project itself relies on PluginDemo.Plugins.Earth and PluginDemo.Plugins.Mars. The goal is to compile and output the remaining two plug-in items to the specified location when the Plugindemo.app is compiled.
In the Customaddin class of the Earth plug-in, we implement the Initialize method and output a string here:
public class customaddin:addin{public override String Name = "Earth AddIn"; public override void Initialize () { Console.WriteLine ("Earth Plugin initialized."); }
In the Customaddin class of the Mars plugin, we also implemented the Initialize method, where we output a string:
public class customaddin:addin{public override String Name = "Mars AddIn"; public override void Initialize () { Console.WriteLine ("Mars AddIn initialized."); }
Then, in the plug-in system main program, will scan the modules subdirectory of the Module.xml file, and then parse each module.xml file to get each plug-in class assembly qualified Name, The plug-in class is then obtained through the Type.GetType method, which creates the instance and calls the Initialize method. The code is as follows:
static void Main () { var directory = new DirectoryInfo ("Modules"); foreach (var file in directory. Enumeratefiles ("Module.xml", searchoption.alldirectories)) { var addindefinition = Addindefinition.readfromfile (file. FullName); var addintype = Type.GetType (addindefinition.fullname); var addIn = (addIn) activator.createinstance (addintype); Console.WriteLine ($ "{addin.id}-{addin.name}"); Addin.initialize (); }}
Next, modify the app. config file to:
<?xml version= "1.0" encoding= "Utf-8"?><configuration> <runtime> <assemblybinding xmlns= "Urn:schemas-microsoft-com:asm.v1" > <probing privatepath= "Modules\earth; Modules\mars,/> </assemblyBinding> </runtime></configuration>
At this point, running the program, you can get:
There are no problems at the moment. Next, make a few changes to the two addin. Let these two addin rely on different versions of Newtonsoft.json, for example, Earth depends on the version of 7.0.0.0, Mars relies on the 6.0.0.0 version, and then modifies the Customaddin method of two initialize respectively, in the method each call Jsonc Onvert. The SerializeObject method is used to trigger Newtonsoft.json of this assembly load. Now run the program again and you will see the following exception:
Now, refresh the Fuslogvw.exe and find the Newtonsoft.json log:
Double-click to open the log, you can see the following information:
As you can see from the whole process:
- PluginDemo.App.exe is trying to load PluginDemo.Plugins.Mars Assembly
- PluginDemo.Plugins.Mars starts calling Newtonsoft.json
- Scan for application configuration files, host configuration files, and machine.config files, no Newtonsoft.json redirection information was found, Newtonsoft.json version is determined to be 6.0.0.0
- GAC Scan failed, continue to find file
- First look for the application in the current directory there is no Newtonsoft.json, and Newtonsoft.json sub-directory There is no Newtonsoft.Json.dll, found no, continue
- Then, with the privatepath setting of the probing in App. Config, first find the Modules\earth directory (because this directory is the first one in PrivatePath), Found a assembly called Newtonsoft.Json.dll, so the version is judged the same. As a result, 7.0.0.0 is found, and it needs to be 6.0.0.0, version mismatch, so throw an exception, exit the program
So next, change the app. config file to change the two values under PrivatePath.
Try again:
At this time, Earth addin again error. So, let's add a version redirection configuration that specifies that when the program needs to load the 7.0.0.0 version of Newtonsoft.json, let it redirect to the 6.0.0.0 version:
Executed again, succeeded:
Look at the log:
The version has been redirected to 6.0.0.0, and the 6.0.0.0 Newtonsoft.json was found in the Mars directory, and the load was successful.
The source code for this case can be downloaded here .
Summarize
This article describes in detail. NET under the assembly version determination and loading process, and finally gives an example of this process is demonstrated.
Review. NET Under-assembly loading process