COM Apartments

[Previous] [Next]

COM objects manufactured on the Win32 platform cannot ignore the possibility of multiple threads attempting access, so you must take certain steps to ensure that COM objects are thread-safe. Lucky for us, Microsoft anticipated this requirement. Microsoft's COM implementation provides transparent support for thread-safe access to all COM objects. Multithreaded clients can safely access thread-oblivious COM object servers, and single-threaded clients can safely access multithreaded COM object servers. In fact, any variation of these two scenarios is guaranteed to work. Neither the client nor the server needs to consider the other's use of threads. COM has assumed responsibility by requiring all COM objects to belong to a specific category of thread safety. This category is formally referred to as an apartment.

COM defines two types of apartments: single-threaded apartments (STA) and multithreaded apartments (MTA). A process can contain zero or more STAs, but it can have only one MTA. A thread can enter only one apartment at a time, and the thread must explicitly leave an apartment before entering another. COM objects must reside in an apartment, and they can reside in only one apartment. An apartment can contain many COM objects. STAs, as their name suggests, allow only a single thread at a time to enter an apartment. All invocations made to COM objects in that apartment are synchronized through the apartment's Windows message loop; I'll cover this concept later in the section "Single-Threaded Apartments in an ActiveX EXE." Unlike their single-threaded counterparts, MTAs allow multiple threads to enter a single apartment. As a result, multiple threads can access the same COM object concurrently. COM does not provide synchronization for MTA; therefore, the programmer is responsible for serializing access to objects where needed by employing one or more of the Win32 primitives for synchronizing thread access. Neglecting to serialize access will eventually lead to catastrophic results.

All classes defined in a Visual Basic ActiveX component project are COM classes. Visual Basic provides no inherent support for MTA. It does, however, allow you to create multithreaded STA COM out-of-process servers, or COM in-process servers that are either thread-oblivious or that support STA. Although Visual Basic insulates the programmer from the details of COM programming, it does not protect COM components written in Visual Basic from the potential impact on performance and behavior when mixed with other COM clients and servers that support a particular COM threading model. On that note, let's peel back the Visual Basic layer and examine more closely the COM threading models it supports.

Single-Threaded Apartments in an ActiveX EXE

Multithreaded out-of-process COM components (ActiveX EXE projects, in Visual Basic terminology) allow concurrent access to multiple COM objects. A multithreaded COM server process can have multiple threads. In the STA model, only one thread can enter an apartment—each thread enters a unique apartment containing objects that it alone can access directly. Remember that an object can belong to only one apartment, and a thread in an STA can enter only one apartment. Once a thread enters an apartment, it owns the apartment: no other threads can enter. Therefore, a multithreaded STA out-of-process COM component allows concurrent access to multiple objects, but those objects must reside in separate apartments. The number of threads cannot exceed the number of objects. To achieve maximum concurrency, each object must live in its own apartment.

When a thread is created, it must enter an apartment before attempting to use COM. To do so, it must call one of the following three COM API functions from within its ThreadFunc function:

 HRESULT CoInitialize(void * pvReserved ); HRESULT CoInitializeEx( void * pvReserved, DWORD dwCoInit ); HRESULT OleInitialize(void * pvReserved ); 

The first parameter in all three API functions is reserved by COM and must be set to NULL. To enter an STA, each thread must call CoInitialize or OleInitialize, or call CoInitializeEx with the dwCoInit parameter value set to COINIT_APARTMENTTHREADED. Before terminating, each thread that enters an apartment should explicitly exit the apartment by calling one of the following COM API functions. Failure to exit explicitly might delay the reclamation of resources by the operating system.

 void CoUninitialize(); void OleUninitialize(); 

CoUninitialize must be called from each thread that entered an apartment by calling either CoInitialize or CoInitializeEx. OleUninitialize must be called from each thread that entered an apartment by calling OleInitialize.

A thread that enters an apartment has direct access to the COM objects that reside in that apartment. In an STA, only the original thread that created and entered the apartment is allowed to execute on objects in that apartment. All objects are thread-safe because there is only one thread of execution.

To allow incoming calls from other threads, each STA must contain a GetMessage/DispatchMessage loop in its ThreadFunc function. Incoming calls are put on the thread's message queue. When a thread enters an STA by calling one of the three COM API initialization functions mentioned above, COM creates a hidden window of the class OleMainThreadWndClass. The thread's message loop retrieves each incoming call message and then dispatches it to this hidden window. The window procedure of the hidden window then calls the associated interface method of the target object. Just as in the typical Windows application, each message is processed one at a time. While a thread is processing a message, all incoming calls are added to the queue and will remain there until the thread's message loop retrieves the message.

If a thread from one STA wants to access an object in another STA, the thread must obtain a proxy to that object. The interface of the proxy is semantically identical to that of the real object. The difference is that the proxy must delegate the request across thread boundaries to the real object in the other apartment. This process, called marshaling, is frequently referred to in cross-process interaction of COM objects. Marshaling also applies to cross-thread interaction of COM objects that reside in different apartments. It involves the transparent packaging and sending of a request from the client proxy in one apartment to the real object in another apartment.

COM objects that are instantiated based on classes written in Visual Basic use COM's universal marshaler. The universal marshaler is an inherent part of the Windows operating system setup and is located in oleaut32.dll. To gain the full benefit of COM using Visual Basic 6, a current version of oleaut32.dll must be installed. It can be obtained by installing Windows 98, Windows NT 4 with Service Pack 4 or later, Windows 2000, or any of the Microsoft Visual Studio 6 products. The universal marshaler does all the work of transporting requests. These requests might travel between apartments owned by different threads in the same process, across processes on the same machine, or across machine boundaries on a network.

The STA model affords thread affinity, but this affinity is not obtained without a price. As you can imagine, it is much more efficient to access an object through an object reference, which is a direct pointer to an address in memory, rather than marshaling across threads. Yet the ability to create a multithreaded COM server without having to deal with thread safety is appealing.

Creating an ActiveX EXE project in Visual Basic is synonymous to constructing a multithreaded STA COM out-of-process server. In keeping with its theme, Visual Basic has shielded the programmer from the gory details of COM. No COM programming activity is necessary, but having been exposed to the COM layer will allow you to understand some of the previously inexplicable performance and behavioral issues of your Visual Basic applications.

Single-Threaded Apartments in a Standard EXE

Client applications that want to access COM components must select the type of COM threading model the application supports, because clients, like servers, can be multithreaded. And like servers, clients too must prevent race conditions and deadlocks. A client thread must enter an apartment before it can access a server-side COM object by calling CoInitialize, CoInitializeEx, or OleInitialize, as described for ActiveX EXE components. Prior to terminating, a thread should also explicitly leave the apartment by calling one of the associated uninitialize COM functions. Failure to do so might delay the reclamation of system resources by the operating system.

All the rules of COM that apply to server processes hold true for client processes. The only distinction between the two is the obvious one—servers create objects and serve them up to the clients that use them. Given this distinction, infrastructure differences exist between clients and servers that, as you can imagine, are a bit more complicated on the server side. Regardless, threads invoke behavior whether they exist in a client process or in a server process. Said another way, the concept of a client and a server is an implementation detail of COM. The operating system sees only processes and threads, which is why the rules of COM cannot discriminate between clients and servers.

It is feasible for a client to support MTA, STA, or a combination of one MTA and several STAs. Visual Basic 6 supports only STA clients that contain one thread and apartment where all references to COM objects reside. As I have mentioned earlier in this chapter, extending the thread support of a Visual Basic application is possible using the Windows API, though I do advise you to proceed with caution. An alternative is to create an ActiveX EXE project that contains forms. As mentioned, an ActiveX EXE can contain multiple threads, each of which enters a unique STA. It is not difficult to imagine a server being a client of another server. I will leave it to those of you interested in multithreaded COM clients to take this information as a hint on how to accomplish this in Visual Basic. For those of you who are not in immediate need of this functionality, it is a fairly safe bet to assume that support for threads will increase in future versions of Visual Basic. Eventually, no fancy workarounds will be necessary.

Threading Models for an ActiveX DLL

Unlike their ActiveX EXE counterparts, ActiveX DLLs are loaded into the client process's memory space. COM classes defined in an ActiveX DLL do not declare their support for a COM threading model by calling a variation of CoInitialize, because the client will have already done so. Instead, threading model support for each COM class that is creatable by a COM client is stored in the Windows Registry under the class's associated class identifier (CLSID), as shown in the following code extract:

 [HCR\CLSID\{1E9E3F20-3E84-11D2-BDB4-00805F9BDA1C}\InprocServer32] @="C:\MyActiveX.dll" ThreadingModel="Apartment" 

ActiveX DLLs support four types of threading models: Free, Apartment, Both, and thread-oblivious. Setting the ThreadingModel name value to Free indicates that an instance of the specified class can be created only in an MTA. When the ThreadingModel name value is set to Apartment, class instances can be created only in an STA. If the ThreadingModel name value is set to Both, the class instances can be created in either an MTA or an STA. Not setting the ThreadingModel name value implies to COM that the class is thread-oblivious.

Let us not forget that COM classes defined in an ActiveX DLL are instantiated within a COM client thread. This leads to some very interesting scenarios. If the threading model entered into by the client thread and the class the client wants to create are compatible, the instance of the class will be created in the client thread's apartment. This will give the client direct access to the instance, resulting in the best possible performance. If the client thread's apartment type is not compatible with the type defined for the class in the ActiveX DLL, COM will interpose and spawn another thread from the client process that enters an apartment supported by the class. A class instance is then created in that apartment, and a proxy to that instance is returned to the apartment of the thread that initiated the request. This guarantees thread safety, but performance is affected because the client thread that initiated the request does not have direct access to the object it created. Its requests are marshaled across thread boundaries.

COM even guarantees thread safety for thread-oblivious objects by spawning a main STA thread in which all thread-oblivious objects are created. If the client thread that initiated the request is not the main STA thread, it will receive a proxy to the object. Table 3-1 illustrates the actions taken by COM for all possible scenarios.

Table 3-1. ActiveX DLL Object Interactions with a Client Process

Threading Model Defined for an ActiveX DLL Class
Threading Model Entered Into by a Client Thread STA MTA Thread-Oblivious
STA Class instances are created in the client thread's apartment. The client thread has direct access to the class instance, which results in optimal performance. COM spawns a thread that enters an MTA, creates a class instance in the MTA, and returns a proxy to the client thread's STA. Object requests are marshaled across thread boundaries. Performance due to marshaling can potentially degrade to unacceptable levels. COM designates one of the client STA threads as the main STA. COM creates all oblivious class instances in the main STA. If the thread initiating the request resides in the main STA, it will have direct access to the class instance. If not, a proxy is returned from the main STA to the apartment of the client thread that made the request.
MTA COM spawns a thread that enters an STA, creates a class instance in the STA, and returns a proxy to the client thread's MTA. Object requests are marshaled across thread boundaries. Performance due to marshaling can potentially degrade to unacceptable levels. Class instances are created in the client thread's apartment. The client thread has direct access to the class instance, which results in optimal performance. The programmer, however, must make use of Win32 thread synchronization primitives to ensure class instances are thread-safe. COM designates one of the client STA threads as the main STA. If no STAs exist, COM spawns a thread that enters an STA. This now becomes the main STA. COM creates all thread-oblivious class instances in the main STA and returns proxies to the MTA of the requesting client thread.

Visual Basic supports the creation of in-process COM components (ActiveX DLLs) that are either thread-oblivious or STA-bound. You can select a threading model for COM classes defined in Visual Basic from the drop-down list in the General tab of the Project Properties dialog box. (See Figure 3-1.) Select Single Threaded for thread-oblivious classes, or select Apartment Threaded for classes that support STA. The threading model selection applies to all classes creatable by a client process. Creatability is determined in Visual Basic by setting the Instancing property value of a class to MultiUse or GlobalMultiUse. (I'll talk more about Instancing property values later in this chapter, in the section "Instancing Property Value.") When you compile the ActiveX DLL, Visual Basic will automatically add a ThreadingModel name value to the Registry as described previously. Once again, Visual Basic takes care of the COM details and lets the programmer concentrate on developing object-oriented solutions.

In spite of this fact, the programmer is still exposed to unwelcome performance and behavioral issues that can be avoided to a certain extent if the programmer understands how to determine in which apartment a Visual Basic object will reside.

Apartment Living Options for Visual Basic Objects

Visual Basic objects are COM objects; therefore, they must live in an apartment. You do not have to make any native COM calls in Visual Basic code, but several features are available that determine which apartment an object will live in and how objects will interact with other objects and resources in the same apartment or across apartments. What follows is an explanation of these features.

Thread Management

When you create an ActiveX EXE project, you can select one of two approaches for managing threads and apartments on the General tab of the Project Properties dialog box. The server can either create a Thread Per Object, or it can maintain a Thread Pool.

Creating a thread per object allows each instance of a class with an Instancing property value of MultiUse to be created in its own thread that has entered a unique apartment. As I mentioned previously, this is the highest level of concurrency possible in a multithreaded STA COM out-of-process component.

Creating a thread pool allows each instance of a class with an Instancing property value of MultiUse to be created in an apartment of a thread from the thread pool. New threads are created until the maximum size limit set by you in the Project Properties dialog box is reached. At this point, each new instance is created in the apartment of the next available thread from the pool in a round-robin fashion. Consequently, multiple objects can live in the same apartment owned by a single thread.

Global Variables and Methods

Publicly defined variables, functions, and subroutines in Visual Basic modules are globally accessible to all objects of an ActiveX component that live in the same apartment. These variables, functions, and subroutines are completely inaccessible to objects that do not live in that apartment, even if they are a part of the same component or client process.

Friend Class Members

A friend is someone you trust; therefore, friends have access to information about you that the rest of us are not privileged to have. In Visual Basic, you can define properties and methods of a class with the keyword Friend. With respect to these friend properties and methods, an instance of a class is considered friendly (accessible) to all other objects that reside in the same apartment, but remains unfriendly (inaccessible) to objects that do not live in that apartment, even if they are a part of the same component or client process. Properties and methods defined as public in the same class as the friend properties and methods will remain accessible to all COM objects.

Instancing Property Value

A class's Instancing property value plays a significant role in determining the residence of a Visual Basic object. Several options for setting the Instancing property are listed below:

  • Private Objects of this class are globally accessible to all objects of an ActiveX component that live in the same apartment. These objects are completely inaccessible to objects that do not live in that apartment, even if they are a part of the same component or client process.
  • PublicNotCreatable Objects of this class are accessible from any apartment in the same component or client process, or across processes. However, only apartment members can create this object.
  • SingleUse Clients can create instances of this class, but each new instance will launch a new component process that contains a thread and an STA in which this instance will reside. This option applies only to ActiveX EXEs.
  • GlobalSingleUse This option is the same as the SingleUse option, except the client does not need to explicitly instantiate this class. Visual Basic will automatically instantiate the class upon the first object request. In addition, you can call a property or method of this class without an object prefix, which is syntactically equivalent to calling a global method. This option applies only to ActiveX EXEs.
  • MultiUse Clients can create instances of this class. When the thread management selection in an ActiveX EXE is a thread per object, the component will spawn a new thread, enter a unique STA, create an instance of this class in the STA, and then return a proxy of the instance to the calling client. If the thread management selection is a thread pool, the component will do as just described until the maximum number of threads set by you have been created in the pool. At this point, further object creations will be added by the component process to a thread and an STA selected from the thread pool in a round-robin fashion. Unlike the SingleUse option, MultiUse will allow multiple instances of a class to reside in the same component process and, depending on the thread management configuration, the component could contain multiple STA threads, each containing one or more class instances. If the component is an ActiveX DLL, a client process can create an instance of this class. All class instances of an ActiveX DLL component will reside in an STA. The STA in which the instance resides is determined by the factors described in the section "Threading Models for an ActiveX DLL."
  • GlobalMultiUse Same as MultiUse, except GlobalMultiUse does not require an explicit instantiation of this class. Visual Basic will automatically instantiate it upon the first object request. In addition, you can call a property or method of this class without an object prefix, which is syntactically equivalent to calling a global method.

Using Keyword New vs. the CreateObject Function

Before you can use an object in Visual Basic, it must be explicitly constructed (with the exception of classes with Instancing property values of the Global variation). To construct an object, you can use either the keyword New or the CreateObject function. The following source code extract illustrates the usage of both:

 Dim myEspressoMaker As New CoffeeLib.EspressoMaker 

or

 Set myEspressoMaker = New CoffeeLib.EspressoMaker 

or

 Set myEspressoMaker = CreateObject("CoffeeLib.EspressoMaker") 

When a Visual Basic client of an ActiveX EXE component uses the keyword New, and depending on the thread management approach selected, a new thread might be spawned that enters a unique apartment, creates the object, and returns a proxy to the client. In an ActiveX EXE component, using the keyword New to construct an object will result in Visual Basic verifying that the type of object to be created is defined locally in the component. If the object is defined locally, Visual Basic will create the object in the apartment that initiated the request. This scenario can occur if a client has a proxy to an object in an existing apartment and then makes a request that will in turn preempt that apartment object to create an instance of another class defined in the component.

Using the CreateObject function from a Visual Basic client will have the same effect as using the keyword New. However, this is not the case in an ActiveX EXE. If you have selected a thread per object, or a thread pool that contains more than one thread, creating an object using CreateObject will result in Visual Basic bypassing the internal verification of whether the class is locally defined. Instead, CreateObject will create the object in another thread and apartment in the same component process and then return a proxy to the apartment that initiated the request.

The Cost of Living for Visual Basic Objects

You have found the apartment of your dreams, so now it's time to return to reality and find out how much it's going to cost you. Many factors will contribute to the cost, and if you don't conduct the proper research, you might run into some unwelcome surprises later on. For instance, consider just the location factor. The distance from your dream apartment to your place of employment is 50 miles. The distance from the apartment to a large international airport is only 5 miles. The rent might be affordable, but when you add in the transportation cost, the time spent in travel, and—maybe most important—the noise of airplanes between the hours of midnight and 4:00 a.m., you might decide it's not a price you're willing to pay.

Apartment hunting for Visual Basic objects is no different. You should fully research and understand the apartment-living options described above. The performance and behavior of COM objects is determined largely by whether collaborating objects reside in the same or different apartments.

Visual Basic objects are either thread-oblivious or support the STA model. STA allows the Visual Basic programmer to create thread-safe classes that can coexist in a multithreaded process. This process can be a multithreaded out-of-process component (ActiveX EXE written in Visual Basic) that serves up objects on multiple threads to numerous clients to prevent the processing of one client request from blocking the request of another client. In general, the intention of making Visual Basic objects support STA is to create an ActiveX server that scales when the usage volume increases. Furthermore, the process can be a client process written in C++ (such as Microsoft Internet Explorer) that spawns multiple threads, each invoking requests on class instances created from an in-process component (an ActiveX DLL written in Visual Basic).

Objects that live in the same STA are considered thread-safe because only the thread that owns the apartment can directly reference all objects in its apartment. This is the optimal situation because a direct reference implies that a thread refers to an address in memory where a given object is loaded. When a thread makes a request to an object in another STA, it cannot do so directly. All requests are marshaled across thread boundaries by proxy. There is significant overhead with this approach, but it exists to provide thread-safe access to COM objects. In addition, Visual Basic has some features of its own that further determine the behavior.

In Visual Basic, classes with an Instancing property value of Private, global variables, global functions and subroutines, and friend class properties and methods are accessible to all objects that reside in the same apartment. If another STA is created as a result of spawning a new thread, another instance of all globals will be created in that apartment. Objects in that apartment with friend members and private class instances are accessible only to other objects within the apartment.

Setting a Visual Basic class's Instancing property value to MultiUse indicates that a client of a given ActiveX component can create an instance of this class. This also results in the creation of an instance of this Visual Basic class within an STA. Depending on the threading model, each Visual Basic MultiUse class could potentially end up residing in its own thread and STA.

It should be clear to you as a component designer how the implementation of your component will function. For instance, if you've designed each class instance in the component to be completely self-reliant, feel free to set the Instancing property value to MultiUse. If you intend minimal collaboration, be aware that objects residing in different apartments that interact with one another must marshal their requests across thread boundaries, which degrades performance. On the other hand, if your intention is to share information stored in global variables among multiple objects or to make use of the Friend declaration, you'll need a deeper understanding of COM threading apartment models.

For predictable results with optimal performance, set the Instancing property value to MultiUse for all classes in an STA ActiveX DLL component that employs globals and friends, and then use that DLL in a Visual Basic Standard EXE project. Using the same ActiveX DLL in Internet Explorer will result in unpredictable results, because the content of global variables is inconsistent between objects from the same component. Also, run-time errors will occur if an object with friend members is accessed by another object in the same component that is friendly when used in the Standard EXE project.

These errors might startle and frustrate you, but the reason they occur is because of incompatible apartment types between the client process and the ActiveX DLL component. In short, the Internet Explorer client process supports MTA, but the ActiveX DLL supports STA. Because the threading apartment models are incompatible, the COM objects instantiated from the ActiveX DLL cannot live in the same apartment as the client thread that initiated the request. COM therefore interposes by spawning a new thread in the client process that enters an STA, creates the object there, and then returns a proxy to the thread requesting service in the MTA. Each class with an Instancing property value of MultiUse in the ActiveX DLL will result in COM spawning a new thread and STA. Each apartment will contain a separate instance of globals. Objects residing in different apartments cannot share global references; friendships can exist only within an apartment. Visual Basic will raise a run-time error if an object attempts to access friend methods or properties of another object in a different apartment. If proper exception handling does not exist, the Internet Explorer client process will terminate.

To overcome this problem, here are three solutions that I've listed from least elegant to most elegant:

  1. Avoid using globals and friends as a means for sharing information between objects.
  2. Set the threading model for the ActiveX DLL to Single Threaded.
  3. Use an Object Factory design pattern (as shown in Chapter 8), and set the threading model to Apartment Threaded.

Although the first solution is obvious, we often discount it. As programmers, we sometimes forget that not every problem requires an elaborate, sophisticated solution. Assuming the problem does require extra effort, you are left with solutions 2 and 3.

Solution 2 suggests making the COM objects created from your ActiveX DLL thread-oblivious. In another context, this would mean the objects created from this DLL are not thread-safe. In COM, all objects are thread-safe. Because the COM classes defined in this DLL support no threading model, COM will also intervene when a request is initiated from a thread in the Internet Explorer client process. In this case, however, COM will create all the thread-oblivious objects in Internet Explorer's main STA, and then return proxies to the client thread in the MTA. If no STA exists, COM will spawn a thread that enters an STA. This STA will now be the designated main STA for all subsequent thread-oblivious objects. Each class in the Single Threaded ActiveX DLL with an Instancing property of MultiUse will be instantiated in the main STA. As a result, a single instance of globals will be shared between all objects, and friend object members will be accessible to all objects in the apartment. Behavior will remain as expected, but performance will degrade significantly because of cross-thread marshaling and object-request blocking.

Multiple threads that have entered into the client's MTA could feasibly reference objects from the ActiveX DLL. For each reference, a proxy will be maintained in the MTA. Because all objects reside in the same thread and STA, Internet Explorer will behave more like a single-threaded application, because all object invocations are synchronized through the main STA thread (if the ActiveX DLL is a spreadsheet component, for example). Performance will diminish if a request from one MTA thread causes a lengthy spreadsheet recalculation. Other requests from other threads will be queued in the main STA message queue to be sequentially processed by the single thread that owns the apartment. These threads will be unable to execute further until their requests are processed.

Solution 3 is the most elegant approach. Implementing an Object Factory design pattern (Chapter 8) within an STA-defined ActiveX DLL gives the component designer the desired behavior with the best possible performance. An object factory defers object creation from the client code to a factory instance designed to manufacture the object requested by the client. In one factory class, you can define a create method for each class that you want to make available to a client. The factory class's Instancing property value should be the only one set to MultiUse. The Instancing property value of the remaining classes that have corresponding create methods in the factory should be set to PublicNotCreatable. Because the factory class is the only one labeled as MultiUse, COM will spawn a new thread and STA in the Internet Explorer client process only when an instance of the factory is created. All objects created by the factory instance will share the same apartment. Consequently, these objects will be able to share global references and maintain friendships. Performance will still be an issue to the extent that requests are submitted from other client threads, but sets of objects living in unique STAs can simultaneously execute tasks. For example, modifying the spreadsheet component in the example in solution 2 to support this model will allow one instance of the spreadsheet to recalculate without affecting the performance of another.

Conducting the proper system analysis that leads to the employment of various design patterns is not enough for a successful Visual Basic implementation. You must also understand COM technology well enough to make the appropriate cost-effective design decisions. Most chapters in Part Two include a section titled "COMments" that will emphasize significant consequences due to implementation decisions of the design pattern at hand.



Microsoft Visual Basic Design Patterns
Microsoft Visual Basic Design Patterns (Microsoft Professional Series)
ISBN: B00006L567
EAN: N/A
Year: 2000
Pages: 148

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net