Saturday, July 05, 2008

In order to solve the problems happen in my application, I decided to dig into the AppDomain. I found some interesting information, so I write them down.

Keys of AppDomain

The CLR scopes all objects, values, and object references to a particular AppDomain. An object resides in exactly one AppDomain, as do values. Moreover, object references must refer to objects in the same AppDomain. Like objects, types reside in exactly one AppDomain. If two AppDomains need to use a type, one must initialize and allocate the type once per AppDomain. Additionally, one must load and initialize the type’s module and assembly once for each AppDomain the type is used in.

Besides, unloading an AppDomain is the only way to unload a module or assembly. Unloading an AppDomain is also the only way to reclaim the memory consumed by a type’s static fields.

I also found some interesting things about the Thread class in .Net framework. System.Threading.Thread represents a schedulable entity in an AppDomain. It is soft thread; it is not recognized by the underlying OS. OS threads are referred to as hard threads. There is no one-to-one relationship between hard threads and CLR soft thread objects. A CLR soft thread object resides in exactly one AppDomain. In the current implementation of CLR, a given hard thread must have at most one soft thread object affiliated with it for a give AppDomain. And it maintains a per-AppDomain thread table to ensure that a given hard thread is affiliated with only one soft thread object per AppDomain.

We can use SetData and GetData to share information between the AppDomains. AppDomain.DoCallBack method allows you to specify a method on a type that will be executed in the foreign domain. This method must comply with the CrossAppDomainDelegate’s signature and must be static.

Table 1 lists several useful AppDomain events.

Table 1 AppDomain Events

Event Name

EventArg Properties

Description

AssemblyLoad

Assembly LoadedAssembly

Assembly has just been successfully loaded

AssemblyResolve

string Name

Assembly reference cannot be resolved

TypeResolve

string Name

Type reference cannot be resolved

ResourceResolve

string Name

Resource reference cannot be resolved

DomainUnload

None

Domain is about to be unloaded

ProcessExit

None

Process is about to shut down

UnhandledException

bool is Terminating, object ExceptionObject

Exception escaped thread-specific handlers

Table 2 lists the properties of an AppDomain that are used by the assembly resolver.

Table 2 AppDomain Environment Properties

AppDomainSetup Property

Get/SetData Name

Description

ApplicationBase

APPBASE

Base directory for probing

ApplicationName

APP_NAME

Symbolic name of application

ConfigurationFile

APP_CONFIG_FILE

Name of.config file

DynamicBase

DYNAMIC_BASE

Root of codegen directory

PrivateBinPath

PRIVATE_BINPATH

Semicolon-delimited list of subdirs

PrivateBinPathProbe

BINPATH_PROBE_ONLY

Suppress probing at APPBASE ("*" or null)

ShadowCopyFiles

FORCE_CACHE_INSTALL

Enable/disable shadow copy (Boolean)

ShadowCopyDirectories

SHADOW_COPY_DIRS

Directories to shadow-copy from

CachePath

CACHE_BASE

Directory the shadow-copy to

LoaderOptimization

LOADER_OPTIMIZATION

JIT-compile per-process or per-domain

DiablePublisherPolicy

DISALLOW_APP

Suppress component-supplied version policy

AppDomain Property

Description

BaseDirectory

Alias to AppDomainSetup.ApplicationBase

RelativeSearchPath

Alias to AppDomainSetup.PrivateBinPath

DynamicDirectory

Directory for dynamic assemblies (/)

FriendlyName

Name of AppDomain used in debugger

AppDomain can also affect the way the JIT compiler works. When the CLR initializes an AppDomain, the CLR accepts a loader optimization flag (System.LoaderOptimization) that controls hwo code is JIT-compiled for modules loaded by that AppDomain. As shown in Table 3, this flag has three possible values.

Table 3 LoaderOptimization Enumeration/Attribute

Value

Expected Domains in Process

Each Domain Expected to Run ...

Code for MSCORLIB

Code for Assemblies in GAC

Code for Assemblies not in GAC

SingleDomain

One

N/A

Per-process

Per-domain

Per-domain

MultiDomain

Many

Same Program

Per-process

Per-process

Per-process

MultiDomainHost

Many

Different Programs

Per-process

Per-process

Per-domain

Marshal and Unmarshal

As I have mentioned before, all objects, values and object references are scoped to a particular AppDomain. When one needs to pass a reference or value to another AppDomain, one must first marshal it. .Net provides RemotingServices.Marshal to do this job. It takes an object reference of type System.Object and returns a serializable System.Runtime.RemotingServices.ObjRef object that can be passed in serialized form. to other AppDomains. Upon receiving a serialized ObjRef, one can obtain a valid object reference using the RemotingServices.Unmarshal method. When calling AppDomain.SetData on a foreign AppDomain, the CLR calls RemotingServices.Marshal. Similarly, calling AppDomain.GetData on a foreign AppDomain returns a marshaled reference, which is converted via RemotingServices.Unmarshal just prior to the method's completion.

If a type derives from System.MarshalByRefObject, then it’s AppDomain-bound, which means the type will marshal by reference and the CLR will give the receiver of the marshaled object (reference) a project that remotes all member access back to the object’s home AppDomain. Note, the proxies never remote static methods. If types don’t derive from MarshalByRefObject but do support object serialization (System.Serializable), they are considered unbound to any AppDomain. They will marshal by value. And the CLR will give the receiver of the marshaled object (reference) a disconnected clone of the original object.

Marshaling typically happens implicitly when a call is made to a cross-AppDomain proxy. The CLR marshals the input parameters to the method call into a serialized request message that the CLR sends to the target AppDomain. When the target AppDomain receives the serialized request, it first deserializes the message and pushes the parameters onto a new stack frame. After the CLR dispatches the method to the target object, the CLR then marshals the output parameters and return value into a serialized response message that the CLR sends back to the caller's AppDomain where the CLR unmarshals them and places them back on the caller's stack.The following diagram show the architecture.

Figure 1. Cross-Domain Method Calls

When one use cross-AppDomain proxies, then both AppDomains must have access to the same assemblies. Moreover, when the two AppDomains reside on different machines, both machines must have access to the shared types’ metadata.

CreateInstance

Now, let’s see what happens when we use AppDomain.CreateInstance method to create remote object in another AppDomain. This method only returns an object handle, which is similar to marshaled object references. And this method returns object handle rather than a real object reference, this can avoid requiring metadata in the caller’s AppDomain, in other words, in the caller’s AppDomain, it does need to load the assembly. An attempt to call CreateInstance on a target application domain that is not the current application domain will result in a successful load of the assembly in the target application domain. Since an Assembly is not MarshalByRefObject, when this method attempts to return the Assembly for the loaded assembly to the current application domain, the common language runtime will try to load the assembly into the current application domain and the load might fail. Actually, AppDomain.CreateInstance method delegates the work to Activator.CreateInstance; Internally, Activator loads the assembly first, then it tries find the class’s public constructors; after that, it checks whether the type can be marshaled. Then, it continues to use RuntimeType (internal class) to create the object. Finally, it wraps this object in an ObjectHandle object, and returns this ObjectHandle object back to the parent AppDomain.

Unload

Another thing I am concerned is how to unload an AppDomain. AppDomain.Unload method is used to unload the whole AppDomain. In the .NET Framework version 2.0 there is a thread dedicated to unloading application domains. This improves reliability, especially when the .NET Framework is hosted. When a thread calls Unload, the target domain is marked for unloading. The dedicated thread attempts to unload the domain, and all threads in the domain are aborted. If a thread does not abort, for example because it is executing unmanaged code, or because it is executing a finally block, then after a period of time a CannotUnloadAppDomainException is thrown in the thread that originally called Unload. If the thread that could not be aborted eventually ends, the target domain is not unloaded. Thus, in the .NET Framework version 2.0 domain is not guaranteed to unload, because it might not be possible to terminate executing threads. I try to use Reflector to dig into the .net code. I find that it needs to get the AppDomain ID first. In this method, it uses RemotingServices.IsTransparentProxy to see whether the AppDomain is a transparent proxy or a real object. If it is, it gets the AppDomain ID from the proxy by invoking RemotingServices.GetServiceDomainIdForProxy method; else, it gets the ID directly from the AppDomain.Id. After that, it can use this ID to unload the AppDomain. But, I can’t find out what really happens internally.


Tuesday, July 01, 2008

The Principals in Object-Oriented Design
Most of the OO developers should have heard about the principals in OOD: Open-Close, Liskov Substitution, Dependency Inversion, Interface Segregation, and Single Responsibility. Last weekend, I spent two days on reading and thinking of these principals.
This article contains my summarization about these principals.
1. The Open-Close Principal
I think the open-close principal is the basis of the other principals. This principal is at the heart of many of the claims made for OOD.
As Martin said, the modules that conform to the open-close principal have the following two primary attributes:
1. Open For Extension. This means that the behavior of the module can be extended. The module behavior can be changed to new and different ways as the requirements of the application change, or to meet the needs of new applications.
2. Closed for Modification. The source code of such a module is inviolate. No one is allowed to make source code changes to it.
In this principal, the abstraction is the key. Using abstraction can gain explicit closure. It’s possible to create abstractions that are fixed and yet represent an unbounded group of possible behaviors, which is represented by all the possible derivative classes. If a module manipulates an abstraction, that module can be closed for modification since it depends upon an abstraction not the implementation detail.
At the end of Martin’s article, he mentioned that conformance to this principle isn’t achieved simply by using an object-orient language; rather, it requires a dedication on the part of the designer to apply abstraction to those parts of the program that the designer feels are going to be subject to change.
Actually, the primary mechanisms behind the open-close principle are abstraction and polymorphism.
2. The Liskov Substitution Principle
In the object-orient language, one of the key mechanisms that support the abstraction and polymorphism is inheritance. By using inheritance, we can create derived classes that conform to the abstract interfaces, which is the key of this principle. According to the Martin’s article, the definition of this principle is:
Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.
If there is a function that doesn’t conform to this principle, it definitely violates the open-close principle, because it must be modified whenever a new derivative of the base class is created. This principle provides guidance for the use of public inheritance.
In his article, it gives us an example of square and rectangle. Square class inherits from Rectangle class, this seems valid. But if there is a method that takes a reference to one Rectangle object as parameter:
void Func(Rectangle& r)
{
r.SetWidth(5);
r.SetHight(4);
}
If we pass a reference to Square object to this method, it shows us the problem since the width and height should be same for square. This leads us to a very important conclusion. A model, view in isolation, can’t be meaningfully validated. The validity of a model can only be expressed in terms of its clients. Thus, when considering whether a particular design is appropriate or not, one must not simply view the solution in isolation. One must view it in terms of the reasonable assumptions that will be made by the users of that design.
This principle makes clear that in OOD the ISA relationship pertains to behavior. Not intrinsic private behavior, but extrinsic public behavior; behavior that clients depend upon. It’s important when we decide whether or not one class should inherit from another class.
And when redefining a method in a derivative classes, their behaviors and outputs must not violate any of the constraints established for the base class. Users of the base class must not be confused by the output of the derived class.
3. The Dependency Inversion Principle
In traditional software development methods, such as Structured Analysis and Design, tend to create software structures in the way that high level modules depend upon low level modules, and the abstractions depend upon details. Indeed one of the goals of these methods is to define the subprogram hierarchy that describes how the high level modules make calls to the low level modules. But when the lower level modules are changed, they force the high level to change. And it’s hard to reuse the high level modules, because they depend upon the low level modules.
A design is bad if it exhibits any or all of the following three traits:
1. Rigidity. It’s hard to change because every change affects too many other parts of the system.
2. Fragility. When you make a change, unexpected parts of the system break.
3. Immobility. It’s hard to reuse in another application because it can’t be disentangled from the current application.
Conforming to dependency inversion principle can solve these issues. Any module conform to this principle, it has the following requirement.
A. High level modules should not depend upon low level modules. Both should depend upon abstractions.
B. Abstractions should not depend upon details. Details should depend upon abstractions.
In this principle, the high level modules only depend upon the abstractions of the low level modules, when the implementations of the low level modules is changed, the high level modules don’t need to change, since the abstractions of low level modules are fixed. And the high level modules are independent of the low level modules, and then the high level modules can be reused quite simply. It’s impossible that a module that doesn’t conform to this principle comply with open-close principle, since any change to the low level module can force the module to change.
4. The Interface Segregation Principle
The interface segregation principle deals with the disadvantages of “fat” interfaces. Classes that have “fat” interfaces are classes whose interfaces aren’t cohesive. In other words, the interfaces of the class can be broken up into groups of member functions. Each group serves a different set of clients. Thus some clients use one group of member functions, and other clients use the other groups.
This principle acknowledges that there are objects that require non-cohesive interfaces; however, it suggests that client should not know about them as single class. Instead, clients should know about abstract base classes that have cohesive interfaces.There is an example in his article. TimedDoor class extends Door class, which extends TimerClient class, as the following class diagram.






Each time a new interface is added to the base class, that interface must be implemented in the derived classes. Actually, there is an associated practice that is to add these interfaces to the base class as nil virtual methods rather than pure virtual methods; specifically so that derived classes are not burdened with the need to implement them. This solution can lead to maintenance and reusability problems. When a change in one part of the program affects other completely unrelated parts of the program, the cost and repercussions of changes become unpredictable. And the risk of fallout from the change increases dramatically.
ISP provides us a correct solution to this problem:
Clients should not be forced to depend upon interfaces that they don’t use.
When clients are forced to depend upon interfaces that they don’t use, then those clients are subject to changes to these interfaces. It results in an inadvertent coupling between all the clients. In order to avoid such coupling, we need to separate the interfaces. In Martin’s article, he provides us two ways: separation through delegation and separation through multiple inheritances.
4.1 Separation through delegation
We can employ the Adapter pattern to the TimeDoor problem.

In this diagram, there is a new class named DoorTimerAdapter, whose responsibility is to delegates the message from Timer to the TimedDoor. This solution prevents the coupling of Door clients to Timer. But it involves the creation of a new object. It’s better to use this solution when the Adapter needs to translate the message.
4.2 Separation through multiple inheritances
In this solution, the TimedDoor extends from both Door and TimerClient.

I prefer to use this solution. The structure is more meaningful. TimerClient class and Door class take separate responsibilities; TimedDoor class combines these two responsibilities to complete the work.
5. The Single Responsibility Principle
Each responsibility is an axis of change. When the requirements change, that change will be manifest through a change in responsibility among the classes. If a class assumes more than one responsibility, that class will have more than one reason to change.How can two responsibilities be separated correctly? That depends on how the application is changing. If the application changes in ways that cause the two responsibilities to change to change at different times, then these two should be separated; otherwise, they are changed at the same time, there is no need to separate them. There is conclusion here. An axis of change is an axis of change only if the change occurs. It’s not wise to apply this principle or any other principle, for that matter if there is no symptom.

There was an error in this gadget