How COM Entities Are Converted

team lib

This section describes how the entities that can appear in a COM type library are converted when processed by the type library importer.

Dealing with Attributes

Entities in type libraries can be decorated with many attributes. Some of these are directly equivalent to .NET attributes (for example, the [in] and [out] directional attributes used on method arguments). Many others have no .NET equivalent, though, so the type library importer will include them in the RCW by using three .NET attributes:

  • TypeLibTypeAttribute contains details of COM attributes for classes, interfaces, structures, unions, and enumerations.

  • TypeLibFuncAttribute contains details of COM attributes for interface methods and properties.

  • TypeLibVarAttribute contains details of COM attributes for fields in structures.

If .NET client code needs to know about the underlying COM attributes, it can use reflection to query these attributes and retrieve the data associated with them.

Importing Libraries

As I mentioned, the basic unit of conversion is the type library, and a single-file interop assembly is produced for each type library processed. If a type library includes references to other libraries, additional interop assemblies might be generated for the included libraries.

If youre using Visual Studio .NET to import components , the name given to the interop assembly will be of the form Interop.lib_name.dll , where lib_name is the library name. The exact library name depends on the tool used to generate the library. If the library was specified in IDL, the name will be the name associated with the library keyword. If the library was generated by Visual Basic 6, the project name is used as the library name.

You can use the custom IDL attribute to specify the namespace that will be used by the type library importer. The custom IDL attribute specifies custom behavior using a GUID as its first parameter, which can be recognized by client tools. Here is an example, showing how a namespace can be specified for an interface:

 //Theinterfacewillbeplacedinthenamespace //MyCompany.MyComponent.IMyInterface [ object,dual, uuid(3a014c8a-3772-49bb-a8c8-b84d9bf6db72), custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9,  "MyCompany.MyComponent.IMyInterface") ] interfaceIMyInterface:IUnknown{ ... }; 

The first parameter, 0F21F359-AB84-41E8-9A78-36D110E6D2F9 , shows that this custom attribute is defining an interop namespace. You might want to use the custom attribute in two cases:

  • When you want to prevent the namespace from being overridden during the import process. If a programmer attempts to use the /namespace option with TlbImp.exe on a type library that uses this attribute, conversion will fail. This is useful when generating PIAs, where the namespace is fixed.

  • When specifying a period-delimited namespace for a component. Type library names cannot contain periods, so using the custom attribute provides a way to specify a period-delimited namespace for a component.

Importing Data Types

Simple data types are marshaled according to Table 3-4, which shows the types used in IDL and Visual Basic 6. Note that COM value types and reference types (that is, pointers to value types) map to the same .NET type.

Table 3-4: Conversion Between COM IDL, Visual Basic 6.0, and .NET Types

IDL Type

VB6 Type

.NET Type






char , small



8-bit signed integer.




16-bit signed integer.

int , long



32-bit signed integer.

hyper , int64 , __int64



64-bit signed integer. Types hyper and __int64 become int64 inside libraries.

unsigned char , byte



8-bit unsigned integer. Byte is represented as unsigned char inside libraries.

wchar_t , unsigned short



16-bit unsigned integer. Type wchar_t is represented as unsigned short inside libraries.

unsigned int , unsigned long



32-bit unsigned integer.

unsigned hyper



64-bit unsigned integer.




Single-precision floating point.




Double-precision floating point.








32-bit integer representing a pointer.



System.Int16 or

HRESULT s are converted to signed integers because unsigned integers are not CLS-compliant.




















Generic object type.




96-bit fixed-point value
representing a number.
















Generic object type.




Generic object type.




Managed array of type.

A quick word about string data types: an LPSTR is a null- terminated string of ANSI (single byte) characters ; an LPWSTR is a null-terminated string of Unicode (multibyte) characters; a BSTR is a length-prefixed string of Unicode characters and is used to represent strings in COM, especially those passed to and from Visual Basic 6. All of these map to the .NET System.String type.

Handling void* Arguments

You might have noted from the table that a void* pointer is not converted to a System.Object reference. The reason for this is that a void* pointer can point to anythingcode or dataand this would not be a safe conversion. You obviously need to know what the pointer represents before you can do anything with it.

System.IntPtr is a CLS-compliant type that represents a generic pointera void* pointer, in C++ terms. The type is called IntPtr because it is an integer type large enough to hold a pointer, in the same way the Int32 is an integer type large enough to hold a 32-bit value. Working with IntPtr is a topic that could take several chapters, so this section provides only an overview of how IntPtr works and how you work with it.

When the type library importer finds a void* pointeror any type it cant marshalit will represent it with an IntPtr . When this happens, youll see the following warning message:

 Atleastoneoftheargumentsfor'xxx'cannotbemarshaledbythe runtimemarshaler.Suchargumentswillthereforebepassedasa pointerandmayrequireunsafecodetomanipulate. 

If youre working in managed C++ or Visual Basic .NET, you need to use the methods in the System.Runtime.InteropServices.Marshal class to work with IntPtrs . If youre programming in Visual C#, you can use the Marshal class or a language feature specific to C# called unsafe code .

The Marshal class contains a number of methods that let you read and write to the memory pointed at by an IntPtr . These methods are listed in the Table 3-5.

Table 3-5: Marshal Class Methods for Reading and Writing Using IntPtrs



ReadByte , WriteByte

Read or write a single byte via an unmanaged pointer

ReadInt16 , WriteInt16

Read or write a 16-bit integer via an unmanaged pointer

ReadInt32 , WriteInt32

Read or write a 32-bit integer via an unmanaged pointer

ReadInt64 , WriteInt64

Read or write a 64-bit integer via an unmanaged pointer

ReadIntPtr , WriteIntPtr

Read or write a pointer- sized integer via an unmanaged pointer

Listing 3-2 shows how you would read a 32-bit integer from an IntPtr in managed C++. You can find this sample in the Chapter03\ReadPtr folder in the books companion content.

Listing 3-2: ReadPtr.cpp
start example
 #include<iostream> usingnamespacestd; #using<mscorlib.dll> usingnamespaceSystem; usingnamespaceSystem::Runtime::InteropServices; voidmain() { //Allocatememoryforanint IntPtrip=Marshal::AllocHGlobal(sizeof(int)); //Grabthepointerandwritetothememory int*pInt=(int*)ip.ToPointer(); *pInt=43; //Readit... Int32n=Marshal::ReadInt32(ip); cout<< "nis " <<n<<endl; } 
end example

When the IntPtr points to an array, overloads to these methods allow you to write particular elements. The following line of code writes newValue at a given byte offset from the location pointed to by myIntPtr :


IntPtr supports equality and inequality operators so that you can check whether two IntPtrs are referring to the same location. In C# and managed C++, you can use the overloaded == and != operators; because Visual Basic .NET doesnt support overloaded operators, you need to call the operator functions explicitly:

 //C#code if(myIntPtr1==myIntPtr2) Console.WriteLine("They'repointingtothesameplace..."); 'VisualBasic.NETcode IfIntPtr.op_Equality(myIntPtr1,myIntPtr2)Then Console.WriteLine("They'repointingtothesameplace...") EndIf 

You can also perform pointer arithmetic on IntPtrs , using the + , - , ++ , and -- operators, provided you convert them to the right- size integer type before you do any arithmetic. For example, look at this code:

 //C#code //ConvertanIntPtrtoaninteger.ForWin32,thiswillbea //32-bitinteger Int32ip=myIntPtr.ToInt32(); //CreateanewIntPtrpointing10byteson IntPtrnewPtr=newIntPtr(ip+10); 

If the IntPtr is pointing to a string, you can use the PtrToStringAnsi , PtrToStringAuto , PtrToStringBSTR , and PtrToStringUni methods to read all or part of the string; these methods will return a .NET System.String object. The Auto version reads either ANSI or Unicode, whichever is the platform default. The StringToHGlobalAnsi , StringToHGlobalAuto , StringToBSTR , and StringToHGlobalUni perform conversion the other way, taking a System.String , writing the data to unmanaged memory in the appropriate format, and returning an IntPtr . Note that these functions allocate the unmanaged memory, so client code will need to free the memory after use.

Using IntPtr from Visual C#

The unsafe code feature in C# lets you specify unsafe blocks of code within which you can declare and use pointers. Such code is called unsafe because by using pointers, the programmer can do things that might go against the rules established by the common language runtime. In addition, marking blocks where pointers can be used tells the garbage collector it must not move or collect any objects referred to by pointers while the code in the block is being executed.


For the unsafe code feature to work, you also have to specify the /unsafe compilation option. In Visual Studio .NET, youll find this option on the solutions property pages, under Configuration Properties.

Within an unsafe block, you can operate on pointers much as you would in C++. Here is an example that shows an unsafe block in operation:

 //Asimplereferencetypethatwrapsanint classIntWrapper{ publicintval; publicIntWrapper(intn){ val=n; } } ... //AllocatefourbytesusingtheMarshalclass IntPtrip=Marshal.AllocHGlobal(4); //Createareferenceobject IntWrapperiw=newIntWrapper(4); //Createavalueobject intn=5; //Declareanunsafeblocksothatpointerscanbeused unsafe{ //DereferencetheIntPtrtogetapointer int*pi=(int*)ip.ToPointer(); *pi=10; //A'fixed'statementletsyougetapointertoa //referencetype,andmeansthatthegarbagecollector //willnotmoveorcollecttheunderlyingobject fixed(int*pn=&iw.val){ *pn=5; } //Obtainapointertoavalueobjectonthestack. //Thisisnotgarbagecollected,sonofixedstatementisneeded int*pn2=&n; *pn2=6; } 

You can also, in true C style, use array notation where an IntPtr points to an array.


For more details on how to use unsafe code in Visual C#, consult a book such as Inside C# , Second Edition, by Tom Archer and Andrew Whitechapel, published by Microsoft Press.

Converting Arrays

Arrays in COM are a surprisingly complex topic. This section explains how COM arrays are mapped to .NET arrays and the limitations surrounding such conversions. The first thing to note is that there are two basic types of COM arrays: SAFEARRAY s and C-style arrays.

Ill consider both of these types, starting with the SAFEARRAY . This type was introduced into COM so that arrays could be exchanged with Visual Basic, which uses array objects that contain bound information as well as data. SAFEARRAY s can have any number of dimensions; they also contain details of bounds and can hold any data that can be placed in a Variant .

If you import a COM component using Visual Studio .NET, SAFEARRAY s will be converted into references to System.Array objects.


System.Array is the base class for all .NET arrays, even the built-in ones that look like theyre part of the language. It provides a lot of functionality and can do anything a SAFEARRAY can do.

Two consequences of this conversion might not be desirable. First, System.Array is typeless, so code needs to discover the type of elements at run time. Second, this class can be awkward to work with because youll need to use explicit methods and properties (such as GetValue and SetValue ) instead of the shortcuts available with built-in language array types.

The /sysarray option lets users of TlbImp.exe choose whether or not to import SAFEARRAY s as System.Array references. If this option isnt specified, the SAFEARRAY will be imported as a one-dimensional array of the appropriate type, with a zero lower bound. If the SAFEARRAY used by the COM object matches this spec, the default behavior will work fine and generate typed arrays for use in client code. Note that if you have SAFEARRAY s that use a lower bound other than zero, these can be imported only as System.Array types.

The second basic type of COM array is the C-style array, which is simply a block of memory containing the array data. Unlike SAFEARRAY s, C-style arrays contain no extra dimension or bound information. COM supports no fewer than four types of C-style arrays, designed to let the marshaling of array data take place as efficiently as possible. Ill look at each of these four types, starting with fixed-length arrays.

Fixed-length arrays , as the name suggests, have a fixed length, which is specified in IDL using familiar C-style array syntax:


The import process converts fixed-length arrays to a one-dimensional .NET array, stored in row-major order. The MarshalAs attribute is added to the converted array, with the SizeConst parameter giving the size of the array. If the array is part of a structure, MarshalAs specifies UnmanagedType.ByValArray; if the array is a method argument, MarshalAs specifies UnmanagedType.LPArray (a Pointer to an array). The preceding sample IDL would therefore be converted into C# as follows :


Note that the size is nine because the three-by-three two-dimensional array is converted into a one-dimensional array.

Varying arrays let you deal with part of an array; for example, you could pass only elements 10 through 50 of a 100-element array in a method call. Note that varying arrays must use contiguous array elements: you cant leave holes! Varying arrays are specified using combinations of the [first_is] , [last_is] and [length_is] IDL attributes. For example, look at this code:

 HRESULTPassVarying([in]shortstart,[in]shortlength, [in,start_is(start),length_is(length)]longarr[100]); 

There is a problem with importing varying length arrays because these attributes are used to help the generation of efficient proxy/stub marshaling code at the time the IDL is compiled by the MIDL compiler. They arent runtime attributes, so they arent included in the type library. This means that the type library importer wont see them, and the array will be converted as a fixed- length array. In the preceding example, the imported method would simply specify a 100-element array for the second argument.

Conformant arrays are arrays whose dimension is specified at run time, via another parameter in the method call, using the [size_is] IDL attribute. For example, look at this code:


Note the use of a pointer in the second argument. Once again, the [size_is] attribute doesnt appear in the type library, so the pointer will be imported as a pointer to a single instance. You can also define multidimensional conformant arrays, which by default will be converted as IntPtr .


Pointers involving more than one level of indirectionsuch as those used when defining a multidimensional conformant arraywill be imported as IntPtr .

The fourth type of C-style array is the conformant varying array , which enables you to pass a part of a dynamic array. As you might expect, these arrays make use of all the attributes Ive just mentioned [size_is] , [first_is] , [last_is] , and [length_is] . And once again, because the array attributes do not appear in the type library, they cannot easily be imported.

The workaround for the missing attributes problem involves changing the signature of the generated methods in MSIL. Search for Editing An Interop Assembly in MSDN for a description of how to edit these methods in MSIL.

Importing Classes

Importing coclasses is a reasonably straightforward process. The most important thing to note is that two .NET entities are created when COM coclasses are imported:

  • A .NET class with the same name as the coclass, with Class appended. So coclass Person would be imported as PersonClass . This class actually implements the RCW and contains the interface methods the coclass implements.

  • A .NET interface with the same name as the coclass. This is known as the coclass interface; it inherits from the coclasss default interface and has no members of its own. Attributes are used to associate the original interface ID (IID) with the .NET interface.

This arrangement is intended to mimic the way Visual Basic 6.0 works. Early versions of Visual Basic wanted to hide the details of COM object implementation from programmers, so it was arranged that the default interface on a coclass would be automatically exposed. The result is that Visual Basic programmers can create an object and directly call default interface methods on it, completely ignoring the fact that COM interfaces exist.

One peculiarity of this mechanism is that you are allowed to use either the RCW class or the coclass interface to instantiate components. Consider the following IDL fragment, which defines a coclass called MyComponent that exposes a default interface IMyInterface :

 //Defaultinterface [ object, uuid(E324E9D1-7A1F-4E8C-AC75-6483B9794F92), ] interfaceIMyInterface:IUnknown{ ... }; [ uuid(EDC3200D-4F09-4888-806E-72630ECA54D5), ] coclassMyComponent { [default]interfaceIMyInterface; }; 

When imported, the component will be represented by

  • A class called MyComponentClass

  • An interface called MyComponent, which inherits from IMyInterface

When youre creating an instance of this coclass, the following two lines of C# code are equivalent:

 //CreationusingtheRCWclass MyComponentClassmc1=newMyComponentClass(); //Creationusingthecoclassinterface MyComponentmc2=newMyComponent(); 

Implementing Multiple Interfaces

COM coclasses will often implement more than one interface. The type library importer will generate a class that implements all the interfaces, provided that all the interfaces are specified in the type library. If more than one interface defines a method with the same name and parameter list, generated method names on nondefault interfaces will be prefixed with the appropriate interface name. There is no problem if methods have the same name but different parameter lists, since they will be imported as overloaded methods. Consider the following IDL fragment, which shows a coclass that implements two interfaces, both of which contain a Print method:

 interfaceIOne:IUnknown{ HRESULTPrint(); }; interfaceITwo:IUnknown{ HRESULTPrint(); }; coclassMyComponent { [default]interfaceIOne; interfaceITwo; }; 

When the methods are imported, the RCW will implement them as shown below (using C#):

 publicclassMyComponentClass:IOne,ITwo { publicvirtualvoidPrint();//Printondefaultinterface publicvirtualvoidITwo_Print();//ITwo }; 

Importing Interfaces

On import, COM interfaces are converted to .NET interfaces on a one-to-one basis. So that no COM-specific methods are exposed to .NET clients , all IUnknown and IDispatch methods are removed from the .NET interface.

This means COM interfaces that derive directly from IUnknown or IDispatch will be imported as .NET interfaces that have no base interface. All other inheritance relationships between COM interfaces will be preserved.

Converted interfaces will be given the Guid and ComInterface attributes. The Guid attribute specifies the original interface IID of the COM interface, while ComInterface specifies the type of the original COM interface, which can be one of the following:

  • ComInterfaceType.InterfaceIsDual , which specifies a dual interface

  • ComInterfaceType.InterfaceIsIDispatch , which specifies an Automation dispinterface that must be used through late binding

  • ComInterfaceType.InterfaceIsIUnknown , which specifies a custom interface deriving from IUnknown

The purpose of this attribute is to let .NET know how it should call methods. COM interface methods are called by their offset into the interfaces virtual function table. Its important to know how many of the initial entries in the table are taken up with the methods belonging to standard COM interfaces. If the interface derives from IUnknown , there will be three such entries, whereas for a dual interface there will be seven (the three belonging to IUnknown , plus the four for IDispatch ).

For example, consider the following pair of COM interfaces:

 [ object, uuid(8a2b1366-6832-4cfe-b454-b67d1b15965d) ] interfaceIAnimal:IUnknown{ HRESULTEat(); HRESULTSleep(); }; [ object, uuid(815f6162-af6c-45bd-9352-bc28cadfe344) ] interfaceIDog:IAnimal{ HRESULTBark(); HRESULTWagTail(); }; 

These would be imported as shown by the following C# code:

 [ ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid(8a2b1366-6832-4cfe-b454-b67d1b15965d) ] publicinterfaceIAnimal { voidEat(); voidSleep(); }; [ ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid(815f6162-af6c-45bd-9352-bc28cadfe344) ] publicinterfaceIDog:IAnimal { voidEat(); voidSleep(); voidBark(); voidWagTail(); }; 

Because the COM interfaces are custom interfaces, they are marked as ComInterfaceType.InterfaceIsIUnknown . Note how the derived interface contains the base interface methods. Once again, this is done so that the correct offset into the v-table can be calculated when calling the methods.

Importing Methods

Method arguments that are not pointers are passed by value ( ByVal in Visual Basic .NET), while those that are pointers are passed by reference ( ByRef in Visual Basic .NET, ref in Visual C#). Any pointer with more than one level of indirection (for example, IUnknown** ) will be passed as an IntPtr because .NET doesnt support more than one level of indirection.

Methods that use [out, retval] parameters will be imported as functions with the appropriate return type. For example,


will be imported into Visual Basic as


Importing Properties

COM objects are always accessed via interface methods. Visual Basic programmers, however, are accustomed to using objects that have both methods and properties, so COM interfaces were designed with the ability to expose methods that can be used as properties in client code. Here is a sample property:

 [propget,helpstring("propertyX")]HRESULTX([out,retval]SHORT*pVal); [propput,helpstring("propertyX")]HRESULTX([in]SHORTnewVal); 

Note how the property methods have the same name. Three attributes can be applied to properties:

  • propget is used to return a value to the client

  • propput is used to accept a value from the client

  • propputref is used to accept an object reference from the client

When properties are imported, a .NET property is created with the same name. The propget methods are represented by a .NET method with a get_ prefix; propputref methods are represented by a method with a set_ prefix. How propput methods are converted depends on whether the interface also defines a propputref method. If it does, the propput is represented by a method with a let_ prefix; if it doesnt, the prefix will be set_ .

Here is how the preceding sample property appears in the IL Disassembler, ILDasm.exe:

 .propertyint16x() { .getinstanceint16atl1Lib.IFFF::get_x() .setinstancevoidatl1Lib.IFFF::set_x(int16) } 

.NET properties have two accessor methods, which are used to get and set the property value. You can see how the propget and propput COM methods are implemented by the get_x and set_x methods within the property.

Importing Structures, Unions, and Enumerations

Many COM programmers are not aware of the fact that you can declare and use structures, unions, and enumerations within type libraries. These declarations can be made directly in IDL or by using a higher-level program (for example, using Visual Basic 6 to produce an ActiveX control).


Structures are imported as .NET value types and added to the interop assemblys metadata. Each field of a structure will be represented by a public field in the .NET value type. As an example, here is an IDL struct, together with the resulting .NET type:

 //AnIDLstructure structPoint{ longx,y; }; //C#code publicstructPoint { publicintx; publicinty; } 

If a field within a structure is a reference type (for example, a pointer), the field is imported as an IntPtr and marked with the ComConversionLoss attribute to show that type information has been lost during the import process.


IDL lets the COM programmer define unions so that one piece of storage can be represented in more than one way. .NET does not support unions, so they are imported as value types, in a similar way to structs. The type library importer uses two extra attributes StructLayoutAttribute and FieldOffsetAttribute to import the original layout of the union. Both these attributes are defined in System.Runtime.InteropServices .

StructLayoutAttribute determines how structures are laid out in memory. The runtime is usually responsible for deciding how best to lay out structures in memory, and the order in which members are laid out might not be the same order in which they are defined. StructLayoutAttribute is important in interoperability scenarios, where unmanaged code expects structures to have a fixed layout. The attribute takes one fixed parameter, which determines the layout type. When used for interop, this parameter will take the value LayoutKind.Explicit or LayoutKind.Sequential . Sequential layout mandates that the members are laid out in sequence, as defined. Packing can be specified to control layout.

Explicit layout is used in conjunction with FieldOffsetAttribute and allows the precise position of each member of the structure to be specified. Using an offset of zero for more than one field in a structure makes it possible to mimic an unmanaged union. As an example, consider the following union defined in IDL:

 //Unioncomposedofashortandalong [switch_type(short)]unionMyData{ [case(1)]longl; [case(2)]shorts; }; 

When converted for use in an interop assembly, the value type produced is equivalent to the following C# code:

 [StructLayout(LayoutKind.Explicit)] publicsealedstructMyData { [FieldOffset(0)]publicInt32l; [FieldOffset(0)]publicInt32s; } 


Enumerations are supported in both type libraries and .NET.


Enums are defined at the IL level in .NET, so they are supported by all .NET languages and can easily be used across languages.

There is no significant difference between type library and .NET enumerations, so conversion is straightforward. Here is an enumeration defined in IDL:

 //EnumdefinedinIDL typedef[uuid(46e8ae76-fd1f-4f14-a449-74a8b9b1f632)] enumColors{ Red=1, Green=2, Blue=3 }; 

When converted for use in an interop assembly, this enum is equivalent to the following Visual Basic .NET code:

 <Guid(46e8ae76-fd1f-4f14-a449-74a8b9b1f632)> PublicEnumColors Red=1, Green=2, Blue=3 EndEnum 


Type libraries can also contain constants, but the current version of the type library importer doesnt import such constants. If you need to import constants and are able to modify the type library, you should wrap the constants in an enumeration.

Importing Typedefs

Typedefs in type libraries are imported as the underlying type. For example, consider a type library that defines a typedef COORDINATE :


All arguments of type COORDINATE will be imported as type int . The converted arguments will have a ComAliasAttribute applied to them so that their original type information is not lost. This example shows how a method that took a COORDINATE argument would be represented in C#:


If necessary, client code can use reflection at run time to retrieve the typedef name for the argument.

Importing Modules

Type libraries can contain modules , which are collections of global methods and constants. Modules are converted into sealed classes when imported, with constants as public members. Here is a simple example of a module containing only constants:

 //ModuledefinedinIDL moduledays{ unsignedshortconstMON=1; unsignedshortconstTUE=2; unsignedshortconstWED=3; unsignedshortconstTHU=4; unsignedshortconstFRI=5; unsignedshortconstSAT=6; unsignedshortconstSUN=7; } 

When modules are imported, a class equivalent to the following will be constructed :

 //Importedclass(C#code) publicabstractclassdays{ publicstaticconstunsignedInt16MON=1; publicstaticconstunsignedInt16TUE=2; publicstaticconstunsignedInt16WED=3; publicstaticconstunsignedInt16THU=4; publicstaticconstunsignedInt16FRI=5; publicstaticconstunsignedInt16SAT=6; publicstaticconstunsignedInt16SUN=7; } 
team lib

COM Programming with Microsoft .NET
COM Programming with Microsoft .NET
ISBN: 0735618755
EAN: 2147483647
Year: 2006
Pages: 140

Similar book on Amazon © 2008-2017.
If you may any questions please contact us: