What is Application Domain?
The primary purpose of the AppDomain is to isolate an application from other applications. Win32 processes provide isolation by having distinct memory address spaces. This is effective, but it is expensive and doesn't scale well. The .NET runtime enforces AppDomain isolation by keeping control over the use of memory - all memory in the AppDomain is managed by the .NET runtime, so the runtime can ensure that AppDomains do not access each other's memory.
Objects in different application domains communicate either by transporting copies of objects across application domain boundaries, or by using a proxy to exchange messages.
MarshalByRefObject is the base class for objects that communicate across application domain boundaries by exchanging messages using a proxy. Objects that do not inherit from MarshalByRefObject are implicitly marshal by value. When a remote application references a marshal by value object, a copy of the object is passed across application domain boundaries.
Definition
Application domain is a construct in the CLR that is the unit of isolation for an application. The isolation guarantees the following:
An application can be independently stopped.
An application cannot directly access code or resources in another application.
A fault in an application cannot affect other applications.
Configuration information is scoped by application. This means that an application controls the location from which code is loaded and the version of the code that is loaded.
What is a default domain?
The default domain is an application domain that is created automatically by the CLR every time it is initialized in a process. The default domain is not unloaded until the process shuts down. Most hosts dont run code in the default domain for two main reasons. First, the default domain cannot be shut down independently of the process. Second, for security and isolation reasons, it is desirable that the user code and hosting code execute in different application domains. Instead, hosts create application domains with a set of properties that control security, isolation, loading, etc.
How does an AppDomain get created?
AppDomains are usually created by hosts. Examples of hosts are the Windows Shell, ASP.NET and IE. When you run a .NET application from the command-line, the host is the Shell. The Shell creates a new AppDomain for every application.
AppDomains can also be explicitly created by .NET applications. Here is a C# sample which creates an AppDomain, creates an instance of an object inside it, and then executes one of the object's methods. Note that you must name the executable 'appdomaintest.exe' for this code to work as-is.
using System;
using System.Runtime.Remoting;
public class CAppDomainInfo : MarshalByRefObject
{
public string GetAppDomainInfo()
{
return "AppDomain = " + AppDomain.CurrentDomain.FriendlyName;
}
}
public class App
{
public static int Main()
{
AppDomain ad = AppDomain.CreateDomain( "Andy's new domain", null, null );
ObjectHandle oh = ad.CreateInstance( "appdomaintest", "CAppDomainInfo" );
CAppDomainInfo adInfo = (CAppDomainInfo)(oh.Unwrap());
string info = adInfo.GetAppDomainInfo();
Console.WriteLine( "AppDomain info: " + info );
return 0;
}
}
What is the purpose of the System._AppDomain interface?
The System._AppDomain interface is meant for use by unmanaged code that needs access to the members of the managed System.AppDomain class. Unmanaged code calls the methods of the System._AppDomain interface through COM Interop. Unmanaged code can obtain a pointer to the _AppDomain interface by calling QueryInterface on the default domain. See Hosting the Common Language Runtime and Common Language Runtime Hosting Interfaces for details.
How do I create an application domain?
The default domain is created automatically when a CLR hosts unmanaged code calls the CLR hosting interface to load the CLR.
A user application domain is created by calling one of the following overloaded static methods of the System.AppDomain class:
1. public static AppDomain CreateDomain(String friendlyName)
2. public static AppDomain CreateDomain(String friendlyName, Evidence securityInfo)
3. public static AppDomain CreateDomain(String friendlyName, Evidence securityInfo, AppDomainSetup info)
4. public static AppDomain CreateDomain(String friendlyName, Evidence securityInfo, String appBasePath, String appRelativeSearchPath, bool shadowCopyFiles)
Overload 1 allows an application domain to be created given its name. Overloads 2-3 allow a System.Security.Policy.Evidence object to be supplied to CreateDomain to define the security policy for the application. Overload 3 allows a System.AppDomainSetup object to be supplied to CreateDomain to configure how assemblies are loaded into the application domain. Overload 4 allows some of the common configuration settings to be specified as parameters without defining a System.AppDomainSetup object.
How do I configure how assemblies are loaded into an application domain?
An application domain is configured by supplying a System.AppDomainSetup object to the following static method of the System.AppDomain class:
public static AppDomain CreateDomain(String friendlyName, Evidence securityInfo, AppDomainSetup info)
The AppDomainSetup class provides a set of properties that can be set or retrieved. The properties control how assemblies are loaded into an application domain. The most important properties are ApplicationBase and ConfigurationFile. The other properties are used mainly by a CLR host and are described in the BCL documentation. A newly created application domain inherits the ApplicationBase property of its creator. No other property is inherited by the new application domain.
The ApplicationBase property defines the root directory for an application. When an assembly needs to be loaded from the disk, the CLR will begin probing for the assembly in the directory specified by the ApplicationBase property. This provides a degree of isolation in that the assemblies that are loaded are privatized to a particular application.
The ConfigurationFile property defines the XML file that contains the configuration settings for the application that executes in the application domain. The configuration file describes information such as versioning rules and policies for locating types. For more information see Configuration Files for the CLR.
The following code snippet creates an application domain with default security policy and specific ApplicationBase and ConfigurationFile properties:
// C# Sample Code
using System;
AppDomainSetup info = new AppDomainSetup();
info.ApplicationBase = C:\\Program Files\\MyApp;
info.ConfigurationFile = C:\\Program Files\\MyApp\\MyApp.exe.config;
AppDomain appDomain = AppDomain.CreateDomain(MyDomain, null, info);
How do I retrieve configuration information about an an application domain?
Use the following property of the System.AppDomain class.
public AppDomainSetup SetupInformation { get; }
How do I define the security policy for an application domain?
The runtime enforces code-access security. In code-access security, what code is allowed to do is based on the characteristics of the code called Evidence. The runtime maps the evidence to a set of permissions when the code is loaded and run. The permissions control specific actions of the code.
A host can control policy in two ways:
First, the host can provide evidence to an application domain when it is created by supplying an Evidence object to System.AppDomain.CreateDomain. This supplied evidence is added to the evidence for the code before the security policy for the code is evaluated. For example, the following code snippet restricts code running in an application domain associated with a Web site to no more permissions than code originating from the site:
// C# Sample Code
using System;
using System.Security.Policy;
Evidence evidence = new Evidence();
Evidence.AddHost(new URL(http:://www.site.com);
AppDomain appDomain = AppDomain.CreateDomain(MyDomain, evidence);
Second, the host can restrict the permissions granted at a specific policy level. The hierarchy of policy levels beginning at the top level are: enterprise wide policy, machine wide policy, per user policy, and application domain level policy. Each policy level can restrict the permissions granted by the level above it. The System.AppDomain.SetAppDomainPolicy is used to specify security policy. For more information and an example of how to use the method, see Hosting the Common Language Runtime.
How do I retrieve the evidence for an application domain?
Use the following property of the System.AppDomain class.
public Evidence Evidence { get; }
How do I execute an application in a remote application domain?
The System.AppDomain class provides the following methods that can be used to run an assembly in an application domain:
1. public int ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
2. public int ExecuteAssembly(String assemblyFile, Evidence assemblySecurity)
3. public int ExecuteAssembly(String assemblyFile)
Overload 1 is the most general form of ExecuteAssembly. The first parameter is the path name of the assembly to be executed. The second parameter allows the caller to supply a System.Security.Policy.Evidence object for the assembly. The last parameter allows the caller to supply an array of parameters to be passed to main entry point of the assembly.
The steps necessary to execute an assembly in a remote application domain are as follows:
Create the remote application domain using System.AppDomain.CreateDomain. Optionally specify the following:
o Configuration information such as ApplicationBase to control where assemblies are searched for during loading.
o Evidence to control specific actions of the code executed in the application domain.
Execute the assembly using System.AppDomain.ExecuteAssembly on the application domain created in the previous step.
Note that the ExecuteAssembly method will not work with executables generated by Visual C++ compiler due to the way the unmanaged CRT startup works.
The following code snippet shows how to execute an assembly named HelloWorld1:
// C# Sample Code
// File: ExecAssembly.cs
// Creates a remote application domain and executes an assembly
// within that application domain.
using System;
using System.Reflection;
using System.Runtime.Remoting;
public class ExecAssembly
{
public static void Main( String[] argv )
{
// Set ApplicationBase to the current directory
AppDomainSetup info = new AppDomainSetup();
info.ApplicationBase = "file:///" + System.Environment.CurrentDirectory;
// Create an application domain with null evidence
AppDomain dom = AppDomain.CreateDomain("RemoteDomain", null, info);
// Tell the AppDomain to execute the assembly
dom.ExecuteAssembly("HelloWorld1.exe");
// Clean up by unloading the application domain
AppDomain.Unload(dom);
}
}
The HelloWorld1 assembly contains the following code:
// C# Sample Code
// File: HelloWorld1.cs
using System;
using System.Threading;
public class HelloWorld
{
public void SayHello(String greeting)
{
Console.WriteLine("In the application domain: " + Thread.GetDomain().FriendlyName);
Console.WriteLine(greeting);
}
public static void Main( String[] argv )
{
HelloWorld o = new HelloWorld();
o.SayHello("Hello World!");
}
}
Compile ExecAssemby.cs and HelloWorld1.cs as follows:
csc ExecAssembly.cs
csc HelloWorld1.cs
How do I invoke a method in a remote application domain?
The steps necessary to invoke a method in a remote application domain are as follows:
Create the remote application domain using System.AppDomain.CreateDomain. Optionally specify the following:
o Configuration information such as ApplicationBase to control where assemblies are searched for during loading.
o Evidence to control specific actions of the code executed in the application domain.
Ensure the type containing the method to be invoked derives from System.MarshalByRefObject. This is necessary to ensure that the type is marshaled by reference across the boundary of the created application domain.
Call System.AppDomain.CreateInstance to create an instance of the parent type of the method in the application domain created in the previous step. CreateInstance will return a wrapped object reference (ObjectHandle) that needs to be unwrapped before it can be used.
Unwrap the ObjectHandle to obtain an Object reference to invoke methods on the type.
The System.AppDomain class provides the following methods for instantiating a type:
1. public ObjectHandle CreateInstance(String assemblyName, String typeName)
2. public ObjectHandle CreateInstance(String assemblyName, String typeName, Object[] activationAttributes)
3. public ObjectHandle CreateInstance(String assemblyName, String typeName, bool ignoreCase, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes, Evidence securityAttributes)
4. public ObjectHandle CreateInstanceFrom(String assemblyFile, String typeName)
5. public ObjectHandle CreateInstanceFrom(String assemblyFile, String typeName, Object[] activationAttributes)
6. public ObjectHandle CreateInstanceFrom(String assemblyFile, String typeName, bool ignoreCase, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes, Evidence securityAttributes)
The difference between CreateInstance and CreateInstanceFrom is that CreateInstances first parameter is the simple name of an assembly while the first parameter to CreateInstanceFrom is the path name of the assembly. The first method calls the static method Assembly.Load while the second method calls the static method Assembly.LoadFrom. See loading of assemblies for additional details.
The System.AppDomain class also provides the following convenience methods that combine the operations of instantiating a type and unwrapping the ObjectHandle:
1. public Object CreateInstanceAndUnwrap(String assemblyName, String typeName)
2. public Object CreateInstance(String assemblyName, String typeName, Object[] activationAttributes)
3. public Object CreateInstance(String assemblyName, String typeName, bool ignoreCase, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes, Evidence securityAttributes)
4. public Object CreateInstanceFromAndUnwrap (String assemblyFile, String typeName)
5. public Object CreateInstanceFromAndUnwrap (String assemblyFile, String typeName, Object[] activationAttributes)
6. public Object CreateInstanceFromAndUnwrap (String assemblyFile, String typeName, bool ignoreCase, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes, Evidence securityAttributes)
The following code snippet shows how to invoke the SayHello method in the HelloWorld2 assembly:
// C# Sample Code
// File: Invoke.cs
// Creates a remote application domain and invokes a method
// within an assembly in that application domain.
using System;
using System.Reflection;
using System.Runtime.Remoting;
public class InvokeMethod
{
public static void Main( String[] argv )
{
// Set ApplicationBase to the current directory
AppDomainSetup info = new AppDomainSetup();
info.ApplicationBase = "file:///" + System.Environment.CurrentDirectory;
// Create an application domain with null evidence
AppDomain dom = AppDomain.CreateDomain("RemoteDomain", null, info);
// Load the assembly HelloWorld2 and instantiate the type
// HelloWorld
BindingFlags flags = (BindingFlags.Public | BindingFlags.Instance | BindingFlags.CreateInstance);
ObjectHandle objh = dom.CreateInstance("HelloWorld2", "HelloWorld", false, flags, null, new String[]{"Hello World!"}, null, null, null);
if (objh == null) {
Console.WriteLine("CreateInstance failed");
return;
}
// Unwrap the object
Object obj = objh.Unwrap();
// Cast to the actual type
HelloWorld h = (HelloWorld)obj;
// Invoke the method
h.SayHello();
// Clean up by unloading the application domain
AppDomain.Unload(dom);
}
}
The HelloWorld2 assembly contains the following code:
// C# Sample Code
// File: HelloWorld2.cs
using System;
using System.Threading;
public class HelloWorld : MarshalByRefObject
{
private String _greeting;
public HelloWorld(String greeting)
{
if (greeting == null) {
throw new ArgumentNullException("Null greeting!");
}
_greeting = greeting;
}
public void SayHello()
{
Console.WriteLine("In the application domain: " + Thread.GetDomain().FriendlyName);
Console.WriteLine(_greeting);
}
}
Compile Invoke.cs and HelloWorld2.cs as follows:
csc /t:library HelloWorld2.cs
csc /r:Helloworld2.exe Invoke.cs
Note. The HelloWorld2 assembly must be locatable in the callers application domain and the remote application domain. The assembly is loaded into the remote application domain by CreateInstance. It is loaded into the callers application domain when the Object obj is cast to the HelloWorld type.
Note. If the metadata for the invoked method SayHello is not available in the callers application domain, you may get errors during JIT compilation.
How do I load an assembly into an application domain?
The System.AppDomain and the System.Reflection.Assembly classes provide many overloads for loading an assembly into an application domain. The System.AppDomain.Load methods are primarily meant for COM Interoperability use. However, they can be used safely to load an assembly into the current application domain.
An attempt to call AppDomain.Load on an application domain that is not the current application domain will result in a successful load of the assembly in the target application domain. Since Assembly objects are not MarshalByRef, when the method attempts to return the Assembly object for the loaded assembly to the current application domain (i.e., the callers application domain), the runtime will try to load the specified assembly into the current application domain. The load may fail if the configuration settings (such as AppDomainSetup.ApplicationBase) for the two application domains are different. Even when the loading into the current application domain succeeds, the Assembly object that is loaded into the current application domain is different from the assembly that was loaded into the target application domain.
The System.AppDomain provides the following methods for loading an assembly into an application domain:
1. public Assembly Load(AssemblyName assemblyRef)
2. public Assembly Load(AssemblyName assemblyRef, Evidence evidence)
3. public Assembly Load(AssemblyName assemblyRef, Evidence evidence, String callerLocation)
4. public Assembly Load(byte[] rawAssembly)
5. public Assembly Load(byte[] rawAssembly, byte[] rawAssemblyStore)
6. public Assembly Load(byte[] rawAssembly, byte[] rawAssemblyStore, Evidence evidence)
7. public Assembly Load(String assemblyName)
8. public Assembly Load(String assemblyName, Evidence evidence)
9. public Assembly Load(String assemblyName, Evidence evidence, String callerLocation)
Note. Overloads 3 and 9 are obsolete and will be removed in a future release.
Overloads 1-3 require the caller to supply an System.Reflection.AssemblyName object that specifies what assembly is to be loaded. See the description of the class in the BCL reference documentation for the details. Overload 2, 6, and 8 allow the caller to supply evidence for the assembly code that will execute in the application domain. Overloads 4-6 are used to load a COFF-based image containing an emitted assembly and optionally specify the raw bytes representing the symbols for the assembly. In overloads 7-9, the assemblyName parameter should be an assembly name and not the path or filename of the requested assembly. assemblyName does not include an extension such as .DLL or .EXE.
Some examples of AssemblyName include the following:
A simply named assembly with a default culture:
com.microsoft.crypto, Culture=
A fully specified reference for strong named assembly with culture en:
com.microsoft.crypto, Culture=en, PublicKeyToken=a5d015c7d5a0b012, Version=1.0.0.0
Partially specified AssemblyName which may be satisfied by either strong or simply named assembly:
com.microsoft.crypto
com.microsoft.crypto, Culture=""
com.microsoft.crypto, Culture=en
The System.Reflection.Assembly class provides the following static methods for loading an assembly into the current application domain:
1. public static Assembly Load(AssemblyName assemblyRef)
2. public static Assembly Load(AssemblyName assemblyRef, Evidence evidence)
3. public static Assembly Load(AssemblyName assemblyRef, Evidence evidence, String callerLocation)
4. public static Assembly Load(byte[] rawAssembly)
5. public static Assembly Load(byte[] rawAssembly, byte[] rawSymbolStore)
6. public static Assembly Load(byte[] rawAssembly, byte[] rawSymbolStore, Evidence evidence)
7. public static Assembly Load(String assemblyName)
8. public static Assembly Load(String assemblyName, Evidence evidence)
9. public static Assembly Load(String assemblyName, Evidence evidence, String callerLocation)
10.public static Assembly LoadFrom(String assemblyFile)
11.public static Assembly LoadFrom(String assemblyFile, Evidence evidence)
Note. Overloads 3 and 9 are obsolete and will be removed in a future release. Overload 1-9 of Assembly.Load are similar to the overloads 1-9 of AppDomain.Load with the difference that the static Assembly.Load methods load the specified assembly into the current application domain while the instance AppDomain.Load methods load the specified assembly into the target application domain. The same restriction on the assemblyName parameter that applied to AppDomain.Load also applies to overloads 7-9, i.e., the assembly name should not be a path name or a file name of the requested assembly and should not contain an extension such as .DLL or .EXE in the name. Overloads 10-11 can be used to load an assembly from a specific location by specifying a file name or path to the assembly.
Note. To ensure that assemblies loaded using Assembly.LoadFrom do not interfere with direct dependencies of the managed application (obtained using Assembly.Load), the assembly cache manager maintains separate load contexts, namely, a default load context and a LoadFrom load context. The default load context records the assembly name and assembly instance information of the transitive closure of dependent assembies that are directly used by the managed application. The LoadFrom load context contains the assembly name and assembly instance information for all assemblies loaded using Assembly.LoadFrom as well as the transitive closure of their dependencies. For more information about load contexts, consult the SDK documentation.
Note. The System.AppDomain class does not provide LoadFrom methods similar to the System.Assembly.LoadFrom methods. If you wish to load an assembly into a target application domain given a file name fro an assembly, you will need to use the following workaround:
Create a stub assembly that calls System.Assembly.LoadFrom using the file name of the assembly to be loaded into the target application domain.
Execute the stub assembly in the target application domain using System.AppDomain.ExecuteAssembly.
How do I dynamically resolve assemblies, types, and resources?
The assembly resolution is controlled by the configuration properties for the application domain such as System.AppDomainSetup.ApplicationBase. The configuration properties may not be sufficient in some hosting scenarios, especially if the host is creating assemblies in memory on the fly using Reflection Emit, since there may not be an assembly on disk to find. In such cases, you can use the System.AssemblyResolve event to hook into the type loading process.
The AssemblyResolve event is defined as follows:
public event ResolveEventHandler AssemblyResolve
where System.ResolveEventHandler is defined as follows:
public delegate Assembly ResolveEventHandler(Object sender, ResolveEventArgs args)
The args parameter to ResolveEventHandler is the identity of the assembly the runtime is seeking. The receipient of the event is free to resolve the reference to the assembly by any means. For example, the receipient may construct an assembly on the fly, find it in a custom location on disk, etc. The only requirement is that the receipient return a instance of System.Reflection.Assembly.
The following sample code shows how to define a handler to resolve an assembly dynamically:
// C# Sample Code
// File AssemblyResolve.cs
// Illustrates how to resolve an assembly dynamically.
using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.Remoting;
using System.Threading;
public class AssemblyResolve
{
// This method invokes the "SayHello" method on the
// type "HelloWorld" of an assemby "DynamicHelloWorld".
// The assembly does not exist on the disk but is created
// on the fly by the assembly resolution handler.
public static void Main( String[] argv )
{
// Define the handler for resolving the assembly
AssemblyResolve assemblyResolve = new AssemblyResolve();
ResolveEventHandler hndlr = new ResolveEventHandler(assemblyResolve.AssemblyResolver);
AppDomain dom = Thread.GetDomain();
dom.AssemblyResolve += hndlr;
// Attempt to load the assembly. This should trigger the
// handler when the assembly is not found on disk.
// Load the assembly HelloWorld
Assembly asm = Assembly.Load("DynamicHelloWorld");
if (asm == null) {
Console.WriteLine("Main: Can't load assembly HelloWorld");
return;
}
// Lookup the type in the loaded assembly
Type t = asm.GetType("HelloWorld");
if (t == null) {
Console.WriteLine("Main: Can't find type HelloWorld");
return;
}
// Instantiate the type HelloWorld
Object o = Activator.CreateInstance(t, null);
// Use Reflection, to invoke the "SayHello" method
t.InvokeMember("SayHello", BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod, null, o, null);
}
// The handler uses Reflection Emit to dynamically build
// the "DynamicHelloWorld" assembly with the type "HelloWorld"
public Assembly AssemblyResolver(Object sender, ResolveEventArgs args)
{
// Write message that we are in the assembly resolver
Console.WriteLine("AssemblyResolver: Building assembly" + args.Name + "...");
// Define the dynamic assembly
AssemblyName asmName = new AssemblyName();
asmName.Name = "DynamicHelloWorld";
AssemblyBuilder asm = Thread.GetDomain().DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Run);
// Define a dynamic module in the assembly
ModuleBuilder mod = asm.DefineDynamicModule("DynamicHelloWorld");
// Define the "HelloWorld" type in the module
TypeBuilder typ = mod.DefineType("HelloWorld", TypeAttributes.Public);
// Define the "SayHello" method
MethodBuilder meth = typ.DefineMethod("SayHello", MethodAttributes.Public, null, null);
ILGenerator il = meth.GetILGenerator();
il.EmitWriteLine("Hello World!");
il.Emit(OpCodes.Ret);
// Complete the type and return the dynamic assembly
typ.CreateType();
Console.WriteLine("Returning dynamic assembly");
return asm;
}
}
Compile AssemblyResolve.cs as follows:
csc AssemblyResolve.cs
A type is resolved dynamically by defining a handler for the TypeResolve event. The TypeResolve event is defined as follows:
public event ResolveEventHandler TypeResolve
A resource is resolved dynamically by defining a handler for the ResourceResolve event. The ResourceResolve event is defined as follows:
public event ResolveEventHandler ResourceResolve
For both types and resources, the handler must resolve the assembly in which the entity is defined. The code for the handler is very similar to the handler shown in the sample above.
How do I unload an application domain?
You should use the following static method in the System.AppDomain class:
public static void Unload(AppDomain domain)
The Unload method gracefully shuts down the specified application domain. During shutdown no new threads are allowed to enter the application domain and all application domain specific data structures are freed. You cannot unload an application domain in a finalizer or destructor.
If the application domain has run code from a domain-neutral assembly, the domains copy of the statics and related CLR data structures are freed, but the code for the domain-neutral assembly remains until the process is shutdown. There is no mechanism to fully unload a domain-neutral assembly other than shutting down the process.
What is the difference between an application domain and a process?
An application domain is lighter than a process. Application domains are appropriate for scenarios that require isolation without the heavy cost associated with running an application within a process. A process runs exactly one application. In contrast, the CLR allows multiple applications to be run in a single process by loading them into separate application domains. Additionally, the CLR verifies that user code in an application domain is type safe.
COM+ Application Domains Introduces application domains and describes how the runtime uses domains to isolate applications running in the same process.
How do I get the process name quickly?
If you want to get Process.ProcessName, there is an alternative:
Process.MainModule.ModuleName.
What’s the use of System.Diagnostics.Process class?
Diagnostics classes in .NET SDK can be used to get information about the running process.
Provides access to local and remote processes and enables you to start and stop local system processes
No comments:
Post a Comment