COM Interop & P/Invoke - .NET at JKU



COM Interop and P/Invoke

Overview

[pic]

Objectives

This module introduces to the concepts and mechanics of the interoperation of .NET managed code with COM unmanaged code and platform services.

The listener/reader is presumed to have at least some knowledge of the .NET framework, COM and the differences between managed and unmanaged execution.

What You Will Learn

• Overview of interoperability

• Samples of interacting between .NET, COM and platform services

Related Topics Covered in This Lesson

• Framework services

Section 1: Overview

[pic]

Existing stuff must coexist

Transition to .NET will be a lasting process

It cannot be expected that with the advent of .NET all existing software systems will turn to this new paradigm overnight. It can also not be expected that all developers will adopt the new platform immediately. To provide a convenient and easy migration path for whole systems, .NET offers a broad interoperable functionality between managed code on the one side and existing (and of course) new COM components or other building blocks.

Portions of systems will remain as they are now

Since there is a huge pile of existing software systems or building blocks written in unmanaged code that actually do their work they even will be maintained in their current form. The .NET framework provides infrastructure to make use of the elder stuff or to use .NET components from existing COM systems, so that they can be maintained and leverage the power of .NET at the same time.

Interoperability increases the momentum of .NET

This interoperability delivers an immediate benefit with switching to .NET. Components can be substituted with managed counterparts one after another or new components can be added without rewriting the whole system.

Goal is to achieve total transparency

The actual interoperation between the managed and the unmanaged world is constructed in a way that the programmer is not forced to deal with too much technical details, actually in most cases there is no need to worry about anything.

If a more fine-tuned interoperation is needed or certain features have to be dealt with, a broad set of attribute classes is provided in the framework to handle these issues.

Mere presence of .NET must not interfere with COM

Installing the .NET runtime on a computer system must not affect the functionality of any existing system based on COM or the platform itself. This design goal also implies that new versions of either the .NET runtime or the COM runtime should not affect one another.

Performance should not be hit

In spite of an additional overhead that can be expected with calling .NET components from COM or vice versa, the superior performance of a .NET component should compensate for it, given that the component is of reasonable complexity.

On the other hand: leaving an existing component as it is and integrate it via the interoperable layer there will be always a performance tradeoff against the convenience of using an existing component.

Section 1: Overview

[pic]

What is different in the two worlds?

Managed Code vs. Unmanaged Code

The Common Language Runtime manages all code that runs inside the .NET Framework. Code that executes under the control of the runtime is called managed code. Conversely, code that runs outside the runtime is called unmanaged code. For example, COM components, ActiveX interfaces, and Win32 API functions are all unmanaged.

One of the most important differences is the lifetime management. Object creation is almost the same in all environments, but freeing objects is basically different. Objects under the .NET runtime are garbage collected, COM objects underlie a reference counting scheme and other unmanaged objects are freed manually.

Another important new concept in managed code is the concept of Metadata: Static and runtime information about an object emitted by the compiler. This information goes far beyond of what concepts like Runtime Type Information (RTTI) or COM Type Libraries could provide.

Common Type System

.NET introduces the Common Type System (CTS). All language compilers that produce code for the .NET platform share the types that the CTS defines. Types are primitive types like Integers and Strings, classes and structures both framework and user defined, and concepts like exceptions, threads etc.

Inheritance concept

COM only knows single interface inheritance, .NET adds implementation inheritance. .NET classes can derive from a single class and implement multiple interfaces. The interoperation layer provides mechanisms to use COM classes in .NET like they were implemented in .NET and vice versa.

Threading model

COM objects use an apartment threading approach, .NET uses free threading. Several considerations have to be taken into account when crossing the boundaries between different threading models.

Error handling mechanisms

COM methods yield a result code while .NET objects throw exceptions. The framework provides a mechanism for translating the different approaches into one another.

Registration

.NET usually does not use registry to store information about objects but rather use metadata. To access unmanaged code from managed code the entries in the registry must be converted to metadata and for unmanaged code to access managed code the metadata must be converted and registered.

Section 2: Calling COM Services from .NET

[pic]

Calling COM Services from .NET

Metadata for COM class must be available

In order to use a COM class from managed code metadata must be available for the called class. This metadata can be constructed from the type library of the component if one exists, or can be created manually, i.e. a .NET wrapper class can be written.

Use COM class like .NET class

Once metadata for the COM class is available the class can be used as any other .NET class.

Early and late bound activation

.NET applications can bind to COM classes in an early or late bound fashion. Early binding is a much more natural way of using a class. Early bound instances are typically created with the new operator and members are accessed directly through the object instance.

When early binding isn’t possible (or desirable) COM classes can also be created in a late bound fashion

COM classes that only support late binding through IDispatch are only accessible through reflection from within managed code.

COM component must be registered locally

The wrapped component must be registered locally, i.e. on the machine where the wrapper is executing.

Section 2: Calling COM Services from .NET

[pic]

Early Bound Activation

Instantiate like creating a .NET object

Assuming that you have already generated metadata and reference the types, you can perform early-bound activation with little effort. Just instantiate the (wrapper) class as you would with any other .NET class.

Metadata must be available at compile time

Early binding requires complete type information for the COM class at compile-time and requires the underlying COM class to support early binding as well.

At runtime, Metadata and COM library must be available

At runtime, when a managed application tries to instantiate the class, the runtime again needs to locate the metadata for the class. The situation is the same regardless of whether the class is a .NET class or a COM class. Since the metadata for COM classes is always packaged in a module other than the module containing the COM code, activating a COM class usually requires two separate modules.

Once metadata for the class is located, the runtime loads the metadata assembly and begins to activate the class. The metadata contains various flags to indicate how the class should be constructed. If the runtime recognizes that the class is a COM class (flagged with the ComImportAttribute), it fabricates a so called Runtime Callable Wrapper (RCW) for the class and delegates the actual construction to COM’s CoCreateInstance API using the CLSID specified in the metadata. If the metadata was generated with the TlbImp tool, the CLSID was carried forward from the original type library.

Section 2: Calling COM Services from .NET

[pic]

Runtime Callable Wrappers

Preserving object identity

An individual RCW may appear to implement several interfaces. When the RCW is cast from one interface to another, internally the wrapper makes a matching QueryInterface call to the COM server. If the Query succeeds, the returned interface is cached within the wrapper. All calls on the wrapper are delegated to the underlying object through interfaces it exposes. Regardless of whether the RCW is created early bound, late bound, or even in response to a method call, there will never be more than one instance of the RCW for each instance of the underlying COM object. The RCW preserves the identity of the underlying COM object by comparing the objects IUnknown interface to determine whether the same object is exposing two different interfaces. Therefore, each RCW wraps a unique IUnknown interface.

Maintaining object lifetime

The RCW is also responsible for keeping the COM object alive as long as the RCW is being used. The RCW itself is garbage collected like any other managed object but internally it holds a reference on the COM object it wraps. When the RCW is garbage collected, the wrappers finalizer is called which, in turn, calls Release() on any interface pointers it’s caching.

Proxying interfaces

The RCW class appears to implement all methods of all interfaces implemented by the class. This allows users of the RCW to call methods directly on the class without having to cast to the proper interface before making the call. A user of an RCW can naturally cast from one interface implemented by the RCW to another. The interface definition needs to be available in order for the cast to succeed.

Marshalling method calls

The methods defined on the interfaces the RCW implements look like .NET methods and follow .NET conventions. This implies that the .NET view of these methods differ from the COM view. The static conversion of the interfaces can be done automatically with TlbImp as described above but RCW is responsible for marshalling calls from one view to another at runtime.

The marshalling process involves the following steps:

• Managing the transition to unmanaged code.

• Handling errors that occur within managed code and converting HRESULTS to exceptions

• Converting [out,retval] parameters to return values

• Marshalling data types from one format to another

Consuming selected interfaces

The primary goal of the RCW is to make a .NET client think that it is using a .NET object, and to make a COM object believe that it's being used by a COM client. To accomplish this charade, the RCW consumes selected COM interfaces without exposing them to the managed client, as shown in the figure on the slide above.

The consumed interfaces include IUnknown (for object identity, type coercion, and lifetime management), and IDispatch (for late binding to COM objects through Reflection). These interfaces factored into a different form, i.e. the information these interfaces provide is retained by delivering otherwise unavailable services.

Section 2: Calling COM Services from .NET

[pic]

Consuming Selected Interfaces

ISupportErrorInfo

If the COM Server being wrapped returns an error HRESULT from a method call, by default, the RCW will throw a corresponding exception to the .NET client: an result code of E_NOTIMPL results in a NotImplementedException. If the COM Server provides additional error information via ISupportErrorInfo, the extended error information will also be propagated into the exception object.

To get additional information about the exception, the .NET client would typically examine the fields of the Exception object that’s thrown. In order for the runtime to provide meaningful information about the error, the COM server must implement the IErrorInfo interface. If the COM server supports IErrorInfo, the runtime will use the information provided through IErrorInfo to initialize the Exception object as shown in the table below. If the COM server does not support IErrorInfo, the Exception object is initialized with default values.

The additional fields are:

|ErrorCode |HRESULT returned from call |

|HelpLink |If IErrorInfo.HelpContext() is non-zero, the String formed by |

| |concatenating IErrorInfo.GetHelpFile() and “#” and |

| |IErrorInfo.GetHelpContext(). Otherwise the string returned from |

| |IErrorInfo.GetHelpFile(). |

|InnerException |Always Null |

|Message |String returned from IErrorInfo.GetDescription() |

|Source |String returned from IErrorInfo.GetSource(). Only used in |

| |external Exceptions. |

|StackTrace |The stack trace |

|TargetSite |The name of the method that returned the failing HRESULT |

IProvideClassInfo

If the COM Server being wrapped implements IProvideClassInfo, the RCW will use this type information to provide support for Reflection as well as interrogating the type information for it’s own purposes.

An additional benefit that the managed runtime gets from this interface is, that it can provide a typed wrapper instead of a generic one.

Type information may also be obtained from the Registry, or from an IDispatch implementation.

ITypeInfo

The runtime also provides a custom marshaller for marshalling the ITypeInfo interface to the System.Type class. Any unmanaged method that uses the ITypeInfo interface will automatically use this marshaller when the method is exposed to .NET. The TlbImp utility will convert all references to ITypeInfo to System.Type on import.

Section 2: Calling COM Services from .NET

[pic]

Identity and Lifetime Control

COM lifetime control is wrapped by RCW

Every instance of a COM object has a single unique wrapper, regardless of how many interfaces the object implements.   In other words, a single COM object that implements five interfaces has only one wrapper.   The same wrapper exposes all five interfaces. If two instances of the COM object are created then two instances of the wrapper are created.   

COM object released when RCW garbage collected

The RCW is also responsible for keeping the COM object alive as long as the RCW is being used. The RCW itself is garbage collected like any other managed object: The wrappers finalizer is called which, in turn, calls Release() on any interface pointers it’s caching. Garbage collection for a RCW is triggered under the same circumstances as collection for a .NET object thereby allowing managed clients to treat a COM object like a .NET object without worrying about COM reference counting.

Non-deterministic finalization issue

Under most circumstances, the scenario described above would work just fine but in some cases it does present a problem. The RCW is automatically garbage collected when the last reference to the wrappers is destroyed. However, there are no guarantee about how long it will take for the RCW to be collected. In fact, it may be some time before the RCW is actually garbage collected. If the underlying COM object is holding resources opened (like a database connection for example) this can be a serious problem as the resources are left opened for prolonged periods. This problem is often referred to as the non-deterministic finalization problem.

Section 2: Calling COM Services from .NET

[pic]

Converting Type Library to Metadata

Use SDK tool TlbImp

The type library importer is responsible for converting the type definitions found within a COM type library into equivalent definitions in a .NET assembly. Conversions are done for an entire type library at one time. You cannot use TlbImp to generate type information for a subset of the types defined within a single type library. The output of TlbImp is an assembly that contains .NET metadata for the types defined within the original type library. This file can be examined with tools such as ildasm.exe.

The most convenient way for the developer to create the metadata needed for interoperation is to add a reference to the registered COM server in Visual ’s solution explorer. This does work for those cases where no manual intervention by the programmer is needed.

Type library can be imported from executable

Actually you do not need to have the source of the type library. The importer tool works on executables.

Reference resulting metadata file in project

In order to use the imported definitions add a reference to your project.

Custom IDL attributes are preserved

Type libraries often contain user-defined IDL attributes (also known as custom attributes). IDL attributes are identified by their GUID; there is no textual name for these user-defined IDL attributes. IDL attributes can be applied to any type library element including libraries, types, members, or parameters. Attributes can also have a value associated with them. The keyword “custom” is used to add an IDL attribute to IDL source code.

IDL attributes are similar to Custom Attributes in the .NET Framework. They both provide a way to attribute the elements that make up the type information. However, whereas IDL attribute are identified by a GUID, Custom Attributes are identified by a strongly typed class.

During import, the type library importer captures IDL attributes in order to preserve the information found in the type library. IDL attributes found in the type library are captured in the IdlAttributeAttribute and applied to the corresponding element in the resulting assembly. The IdlAttributeAttribute has a Guid property and a string property called Value; both the Guid and the Value are copied from the IDL attribute in the type library. All values are converted to strings.

Section 2: Calling COM Services from .NET

[pic]

Conversions by TlbImp (1/2)

Library Level

When the TlbImp utility imports a type library, it automatically places the types defined within the library in a namespace of the same name as the type library. For example, if you run TlbImp on the type library listed below, the utility imports all types defined within the COMLib type library into the COMLib namespace.

You can use a type library attribute to explicitly control the namespace for the type library import process. Because type library name cannot contain periods, this is the only technique that you can import types into a period-delimited namespace.

Module Level

A type library can have one or more modules containing the definitions of constants and methods. Constants defined within modules are imported as public constant static members of a class with the same name as the original module. Constants defined outside of a module are not imported.

Methods are also imported as public static members of a class with the same name as the original module. The members are decorated with the necessary attributes to allow them to directly be called from managed code.

Section 2: Calling COM Services from .NET

[pic]

Conversions by TlbImp (2/2)

Interfaces

When the TlbImp utility imports an interface into a .NET assembly, it strips out all IUnknown and IDispatch methods. The imported interface is attributed with the GuidAttribute to retain the IID assigned in the type library and with the InterfaceTypeAttribute if the interface extends IDispatch instead of IUnknown.

Type Definitions

Type definitions (typedef) within a type library are not imported. Instead, parameters and fields are imported as the underlying types. For example, a parameter of type BUTTON_COLOR is imported as type integer since BUTTON_COLOR is merely an alias for integer.

Once imported, parameters and fields do carry information that associates them with their original type in the ComAliasNameAttribute. The ComAliasNameAttribute is used to attribute a field, parameter or return value with the name of the type library and the type within the library that was used as an alias.

Properties

A COM developer may declare properties and methods on a dual interface. All properties have corresponding accessor methods for setting or getting the property values. When the TlbImp utility converts a type library description of an interface with properties to metadata, it creates a property and one or more accessor methods for that property.

The type library conversion process transforms property accessor methods in the following ways:

• Properties with the [propget] attribute become managed properties of the same type, along with a corresponding method called get_propertyname.

• Properties with the [propput] or [propputref] attribute become managed properties of the same type, along with a corresponding method called set_propertyname.

Events

A COM type library can define interfaces used for events. Within the library, a coclass that sources events can identify the event interface by locating the [source] attribute. An event sink implements the interface and an event source consumed it. The COM connection-point interfaces, which are not described in the type library, connect the event source to the event sink.

Section 2: Calling COM Services from .NET

[pic]

Marshalling

Isomorphic types

Most data types have common a representation in both the managed and unmanaged memory and therefore do not require any special handling by the marshaller. These types are considered Isomorphic because they do not require conversion when passed between managed and unmanaged code. These isomorphic types include all integer types, both signed and unsigned, floating point types and pointer types.

Non-isomorphic types

Non-isomorphic types have different or sometimes ambiguous representations in the managed and unmanaged worlds. These types may require conversion when marshalled between managed and unmanaged code.

|BOOLEAN |Converted to either a 2 or 4 byte value with true be 1 or -1. |

|CHAR |Converted to a Unicode or ANSI char. |

|STRING |Converted to either a Unicode or ANSI character array or as a |

| |BSTR |

|OBJECT |Converted to a variant or an interface. |

|CLASS |Converter to a class interface. |

|VALUETYPE |Converted to a structure with fixed memory layout. |

|ARRAY |Converted to either an interface or a SafeArray |

Copying vs. Pinning

As data is marshalled it may need to be copied or pinned. Copying involves copy of the data from one memory location to another. Pinning temporarily locks the data in its current memory location keeping it from being relocated by the runtimes garbage collector. Whether data is copied or pinned during the marshalling process depends on the type of the data and its InAttribute and OutAttribute.

• Isomorphic types have fixed layout (formatted) and common data representation in both managed and unmanaged memory. When these types require marshalling, a pointer to the actual object is passed to the callee. The callee is free to change the contents of the memory location being referenced (by the pointer).

• Non-isomorphic types have fixed layout (formatted) but the data representation is different in managed and unmanaged memory. The data therefore may require transformation. When a non-isomorphic type is marshalled by reference, the callee receives a pointer to a copy of the data structure. If the InAttribute is set, this copy is always initialized (marshalled as necessary) with the instances state. If the OutAttribute is set, the state is always copied (again marshalled as necessary) back in to the instance on return. If both is set, both copies are required. If either attribute is omitted, the marshaller may choose to eliminate either copy as an optimisation.

• When a String or StringBuilder is marshalled to unmanaged code (by value or by reference) the marshaller typically copies data of either type to a secondary buffer (possibly converting character sets during the copy) and passes a reference to the buffer on to the callee. The reference is always allocated with CoTaskMemAlloc.

As an optimization, when the string type is being marshalled by value as a Unicode character string, the marshaller will pass the callee a direct pointer to managed strings internal Unicode buffer instead of copying it to new buffer. When a string is passed by value, the callee should never alter the reference passed by the marshaller. Doing so could cause corruption of the runtimes managed heap. When a System.String is passed by reference, the marshaler will copy the contents the string to a secondary buffer before the call is made then copy the contents of the buffer into a new string on return from the call. This ensures that the immutable managed string is never altered. When a System.Text.StringBuilder is passed by reference, the marshaller passes a reference to the StringBuilder's internal buffer directly to the caller. The caller and callee must agree on the size of the buffer. The caller is responsible for creating a StringBuilder of adequate length. The callee must take the necessary precautions to ensure that the buffer is not overrun.

Section 2: Calling COM Services from .NET

[pic]

Marshalling a Union

Use attributed struct in .NET

Since there is no concept of a union in C# and Visual this needs a different approach. In this case you define a struct (or Structure resp.) and assign a special attribute to it. By default the members of a struct are laid out sequentially – using some sequence. The runtime makes no warranties concerning the actual layout. The layout may vary.

If the attribute StructLayout(Layout.Union) is specified on the struct, this is equivalent to a union in C/C++.

StructLayout attribute

The StructLayoutAttribute class allows the user to take advanced control over the physical layout of the data members of a class. Explicit control of a class layout is important if the class is to be passed to unmanaged code that expects a specific layout. The LayoutKind value Sequential is used to force the members to be laid out sequentially in the order they appear. Explicit is used to control the precise position of each data member. With Explicit each member must use the FieldOffsetAttribute to indicate the position of that field within the type.

Section 2: Calling COM Services from .NET

[pic]

Inheritance

COM uses containment/aggregation

COM model for reusing existing objects is based on containment and aggregation.

A .NET object can contain a COM object by importing its metadata into a .NET component file, then declaring a data member of that type within another class. As with normal COM containment, you can call the COM object's interfaces in your own interface implementations, but the contained object is not exposed outside the class. Containment is simpler than aggregation. Containment is typically used when the outer object needs to modify the behavior of the inner object. To do so, the outer object simply creates an instance of the inner object during constructor and delegate calls to the inner object as necessary. The outer object can choose which calls to delegate and which calls to handle directly. There are no special requirements of the runtime in order for objects to support containment.

COM aggregation is typically used when the outer object does not modify the behaviour of the inner object.

.NET adds implementation inheritance

In .NET implementation inheritance is the preferred way to reuse existing objects. Inheriting from another object means effectively inheriting interfaces and the corresponding implementation. Therefore there is no use for aggregation in the COM style.

Mixed-mode objects

A managed object can inherit from a COM class. Strictly speaking it inherits from the Runtime Callable Wrapper.

To accomplish this, there are three requirements that the unmanaged base type must satisfy:

• There must be metadata that defines the unmanaged type.

• The unmanaged type must be creatable.

• The unmanaged type must be aggregatable (in the COM sense).

If the requirements are met, a managed type can then extend the RCW for that unmanaged type and override the methods provided by the base object. The inheritance is done exactly the same as it would if the base object were managed.

Interfaces can be inherited

When importing an interface from a COM type library, all inheritance relationships are preserved with the exception of IUnknown and IDispatch. The inheritance from IUnknown and IDispatch are automatically removed during import because equivalent functionality is provided through other mechanisms.

Section 2: Calling COM Services from .NET

[pic]

Calling a COM Server

The above sample shows the most simple way to use a COM server object. It assumes, that metadata for the COM server is available. That could have been accomplished by running TlbImp against the type library of the server.

The namespace of the COM server is imported by the statement using COMServerLib;. (The name of the server library can of course be chosen deliberately.)

In the client application the server object can be instantiated like any other managed object. In fact the new operator constructs a Runtime Callable Wrapper and a instantiates the COM server object via the CoCreateInstance-API. The usage of the COM server object is completely transparent through the Runtime Callable Wrapper.

Section 2: Calling COM Services from .NET

[pic]

Late Bound Activation

Accomplished by Reflection API

Late bound references do not require a type library for the COM class although you can still late bind to classes that do have metadata. Late bound instances are created and their member are accessed through the reflection API.

Type can be obtained from ProgID or ClsID

A Type object for a specific class can be obtained through the static methods Type.GetTypeFromProgID or Type.GetTypeFromCLSID that return a System.Type object that represents the type specified by a specific ProgID or CLSID. With the type object you can create an instance of the COM class by passing the type to one of the CreateInstance methods of the Activator class.

InvokeMember to access methods and properties

You can pass the object instance to one of the InvokeMember functions on the Type object to actually call methods and access properties of the object through the objects IDispatch interface (if the object supports IDispatch). Arguments are packed into an object array, which is then passed to InvokeMember along with the method name, a set of binding flags, a Binder, and the object instance. When using InvokeMember with a COM object, the following rules apply:

• The name passed to InvokeMember is case insensitive. This is a limitation of the underlying IDispatch interface.

• The binding flags passed to InvokeMember are translated into flags passed to IDispatch.Invoke as shown in the table below. Notice that the SetProperty binder flag causes both the DISPATCH_PROPERTYPUT and the DISPATCH_PROPERTYPUTREF flags to be set when IDispatch.Invoke is called. To do an explicit PropertyPut or PropertyPutRef, use the PutDispProperty or PutRefDispProperty binding flags respectively. These flags are only for use with COM objects.

• The Binder argument is not used.

• The arguments packed in the object array are converted to COM variants and passed to IDispatch.Invoke. The Object to Variant conversions rules are described in the Data Marshaling Specification.

• In order to pass one or more arguments by reference (in a variant with the VT_BYREF flag set), an array of ParameterModifiers must be passed to InvokeMember. The ParameterModifier array should contain a single ParameterModifier element. The ParameterModifier has a default indexer property. The nth index of the default property should be set to true to indicate that the nth parameter should be passed by reference. For example, the following code passes the second argument in a variant with the VT_BYREF flag set.

Metadata is not needed, but useful

You can also inspect the COM type by calling methods on the Type object. If the COM type has accompanying metadata (produced by importing the type library with TlbImp), complete information is available about the type through the Type object. If the COM type does not have accompanying metadata, the Type object will provide relatively little information. COM types that have no metadata are wrapped with a generic Runtime Callable Wrapper with the name __ComObject. __ComObject is a private type that is used solely for wrapping COM objects that don’t have associated metadata.

Section 2: Calling COM Services from .NET

[pic]

Late Bound Call to COM Server

In this sample no metadata is needed for the COM server. The only precondition is that the COM server has to be registered with the Windows registry (and of course that the COM server supports IDispatch).

A generic type object (__ComObject) is constructed. The Activator class from the reflection namespace then instantiates the object using this type object.

Once the object is instantiated, methods and properties can be accessed via the InvokeMember method of the type object. This method needs the name of the method or property, a binding flag - stating the type of access - and a reference to the actual object.

Section 2: Calling COM Services from .NET

[pic]

Calling an Automation Server

If not only the COM server supports IDispatch but there is also metadata available, the creation of the object is simpler.

The assembly containing the metadata of the COM server must be referenced in the build process. Then the namespace of the COM server can be imported.

After that the COM server object can be created like any managed object. But since this object only supports IDispatch, early binding is not possible. Therefore invoking members of this object must be accomplished using the Type.InvokeMember scheme.

Section 2: Calling COM Services from .NET

[pic]

Threading

Most COM objects are single threaded

Unlike COM, .NET does not use apartments to synchronize access to managed resources. Managed objects themselves are responsible for ensuring that all shared resources are used in a thread safe manner. The CLR provides several mechanisms for protecting access to shared resources such as synchronized regions, synchronization primitives such as mutexes, locks and completion ports and synchronized contexts.

When using COM Interop however, developers need to be aware of the threading characteristics of the COM classes they’re using in order to write the most efficient code. COM uses apartments to ensure that all objects are used in a thread safe manner. Most COM objects are designed to live only in single threaded apartments while other objects are built for multithreaded apartments. Some object can work in either type of apartments. Classes indicate the type of apartment they were built for through the registry.

.NET thread must initialize COM apartment

Since .NET doesn’t use apartments, managed threads typically do not initialize a COM apartment unless and until the thread calls a COM object. When a call is first made from managed code to unmanaged code, CoInitializeEx is called to initialize the COM apartment as either an MTA or an STA apartment. The type of apartment created can be controlled by setting the threads ApartmentState field. The ApartmentState must be set before any calls to unmanaged objects are made.

If the Thread.ApartmentState field is set to ApartmentState.MTA or ApartmentState.Unknown, a multithreaded apartment is initialized just before the call is made. If the Thread.ApartmentState field is set to ApartmentState.STA, an single threaded apartment is initialized. Unless set otherwise the ApartmentState of all managed threads is ApartmentState.Unknown and a multithreaded apartment is created.

Runtime provides transparent use of COM threads

If the apartment model of the object and the thread calling the object are compatible, COM allows calls on the object to be made directly by the calling thread. If the apartments are incompatible, COM creates a compatible apartment and marshals all calls through a proxy in the new apartment. But beware, calling through a proxy adds significant overhead to the call and can seriously degrade performance. Calling through a proxy also requires the object to have a registered proxy and stub or a registered type library if the types are OLE Automation compatible and type library marshalling is to be used. For additional information on compatible apartments and type library marshalling see MSDN.

To avoid calling through a proxy to access apartment threaded COM objects, managed threads can set the ApartmentState of the thread before the first call is made. See next slide for an example.

A given thread can only initialize a COM apartment once. The apartment is initialized before the very first call is made into unmanaged code on that thread and therefore the ApartmentState should be set as early as possible. Changing the ApartmentState after the apartment has been initialized has no effect. Apartments cannot be un-initialized or re-initialized.

In some situations, the thread may have already made calls into unmanaged code before the ApartmentState could be set. In those scenarios, there is no way of changing the apartment type after the thread is initialized. The only alternative is to create a new thread.

Section 2: Calling COM Services from .NET

[pic]

Using ApartmentState

Let the runtime initialize the default MTA

In this first example the object is created without any precaution regarding threading models. In this case a multithreaded apartment is initialized to host the COM object.

This is perfectly fine if the COM object is multithread enabled. If not then the second sample would be the better way to instantiate the object.

Setting ApartmentState explicitly eliminates proxy

Setting the ApartmentState member of the current thread to ApartmentState.STA effectively initializes a single threaded apartment. This avoids the construction of a proxy.

It is important to set the ApartmentState before the object is created, otherwise setting the state would have no effect.

Section 2: Calling COM Services from .NET

[pic]

Results and Exceptions

COM reports errors via result codes

COM methods report errors by returning HRESULTs, while .NET methods report them by throwing exceptions. The runtime handles the transition between the two.

HRESULT specifies exception class

Each HRESULT that the COM server might return is mapped to an exception class in the .NET Framework. For a complete list corresponding result codes and exceptions see .NET Framework SDK documentation.

Extended error information via IErrorInfo

To retrieve extended error information, the managed client must examine the fields of the exception object that was thrown. For the exception object to provide useful information about an error, the COM object must implement the IErrorInfo interface. The runtime uses the information provided by IErrorInfo to initialize the exception object.

The following fields of the exception object are initialized:

|ErrorCode |HRESULT returned from call. |

|HelpLink |If IErrorInfo.HelpContext() is non-zero, the string is formed|

| |by concatenating IErrorInfo.GetHelpFile() and “#” and |

| |IErrorInfo.GetHelpContext(). Otherwise the string returned |

| |from IErrorInfo.GetHelpFile(). |

|InnerException |Always Null. |

|Message |String returned from IErrorInfo.GetDescription(). |

|Source |String returned from IErrorInfo.GetSource(). |

|StackTrace |The stack trace. |

|TargetSite |The name of the method that returned the failing HRESULT. |

If the COM object does not support IErrorInfo, the runtime initializes an exception object with default values. The following table lists each field associated with an exception object and identifies the source of default information when the COM object supports IErrorInfo.

PreserveSigAttribute in custom wrapper

To prevent the translation of HRESULTs to exceptions, attach the PreserveSig(true) attribute to the method. The HRESULT will then be returned by the call as an Int32.

Section 2: Calling COM Services from .NET

[pic]

Design for Interoperability

Provide and register Type Libraries

The runtime requires metadata for all types, including COM types, in most situations. The TlbImp utility, which is included in the SDK, can convert COM type libraries to .NET Framework metadata. Once the type library is converted to metadata, managed clients can call the COM type seamlessly. For ease of use, always provide type information in a type library.

You can pack a type library as a separate file or embed it within a .dll, .exe, or .ocx file as a resource.

Further, you can generate metadata directly, which allows you to sign the metadata with the publisher's key pair. Metadata signed with a key has a definitive source and can inhibit binding when the key is wrong.

To marshal calls correctly, the runtime may need to locate the type library that describes a particular type. A type library must be registered first before the runtime can see it, except in the case of late binding.

Use Automation Compliant Data Types

The runtime marshaling service automatically supports all automation compliant data types. Other non-compliant types may or may not be supported

Use Chunky Calls

Marshalling data between managed and unmanaged code has a cost. You can mitigate the cost by making fewer transitions across the boundary. Chunky interfaces that minimize the number of transitions generally perform better than chatty interfaces that cross the boundary often, performing small tasks with each crossing.

Explicitly Free External Resources

Some objects use external resources during their lifetime: a database connection, for example, might update a recordset. Typically, an object holds onto an external resource for the duration of its lifetime, whereas, an explicit release can return the resource immediately. For instance, you can use the Close method on a file object instead of closing the file in the class destructor or with IUnknown.Release. By providing an equivalent to the Close method in your code, you can free the external file resource even though the file object continues to exist.

Avoid Using Members of System.Object

Managed clients and COM coclasses interact with the help of wrappers provided by the runtime. When you import a COM type, the conversion process adds all methods on the coclass’s default interface to the wrapper class, which derives from System.Object. Unless you take care in naming the members of a coclass's default interface, you may encounter naming conflicts with the members of System.Object. If a conflict occurs, the imported method overrides the base class’s method.

This action might be favorable if the method of the default interface and the method of System.Object provide the same functionality. It can be problematic, however, if methods of the default interface are used in an unintended fashion. To prevent naming conflicts, avoid using the following names in default interfaces: Object, Equals, Finalize, GetHashCode, GetType, MemberwiseClone and ToString.

Section 2: Calling COM Services from .NET

[pic]

Restrictions and Limitations

void* Parameters need marshalling

There are situations in which the information available with a type library may not be sufficient for TlbImp to produce accurate metadata. This includes void* as parameters. These need manual marshalling to be interpreted in the correct manner. See the example on the “Showcase for the Standard Marshaller” slide.

Success HRESULTS cannot be distinguished

Since in case of a successful return of the COM method no exception is thrown there is no way to determine the exact result of the call.

Typedef do not get through

Type definitions in the type library are suppressed by TlbImp. To make use of such definition, you would have to counter these types with a managed definition.

Variable length arrays cannot be imported

COM SafeArrays are self-describing. By examining the SafeArray, the runtime marshaller can determine the rank, size, bounds, and usually the type of the array contents at runtime. Variable length arrays do not have the same self-describing quality. For example, the unmanaged signature above provides no information about the array parameter other than the element type.

In fact, the array is indistinguishable from any other parameter passed by reference. As a result, the TlbImp utility will not convert the array parameter of the DoSomething method correctly. Instead, the array appears as a reference to a Byte.

Section 3: Calling .NET Services from COM

[pic]

Calling .NET Services from COM

Use .NET class like COM class

As with the interoperational support with using COM servers with managed code the opposite approach tries to deliver as much transparency as possible.

In order for COM clients to create a managed type, the type must be public and it must have a public default constructor. Further, only public methods, properties, fields, and events are available for COM clients to call. Non-public type are omitted from the resulting type library during the assembly to type library conversion.

Assuming a type is public, it will be visible to COM. However, without a public default constructor, COM clients cannot create the type. A default constructor has no arguments.

The rules for creation are not always the same in COM and the .NET Framework. For example, in COM when a managed type lacks a default constructor, it cannot be created. A managed client can use another constructor on the same type, if there is one, to create the type. The managed client can even return an instance of the non-creatable type to a COM client as a return value or an out parameter (provided the non-creatable type is public and visible to COM). Since abstract types lack constructors, you cannot create managed abstract types from COM.

Managed components expose features to other managed code that might not be exposed to COM. For instance, parameterized constructors, static methods, and constant fields are not exposed to COM clients. Further, as the runtime marshals data in and out of a type, the data might be copied or transformed.

When a managed type is exposed to COM, the type's inheritance hierarchy is flattened. Versioning, too, differs between managed and unmanaged environments. Types exposed to COM will not have the same versioning characteristics as other managed types.

Early and late bound activation

COM applications can bind to managed classes in either an early bound or late bound fashion. Late binding in this context refers to using the IDispatch interface exposed by the object. Early binding (also called v-table binding) implies binding through an interface other than IDispatch. Early binding requires compile time type information while late binding does not.

Convert Metadata to Type Library

The runtime generates type information in the form of a COM type library for COM applications to bind to. The type libraries generated can then be imported into most development tools to provide complete compile-time type information about managed types. A COM type library can be generated by converting the metadata within the assembly. This conversion is explained later in detail.

Wrapper of .NET component must be registered

In order to be able to create managed types from COM applications the appropriate entries must be made in the Windows registry. The runtime ships with a registration utility called RegAsm for just this purpose.

Section 3: Calling .NET Services from COM

[pic]

COM Callable Wrappers

Responsibilities of the COM Callable Wrapper

During the activation process, the runtime instantiates the object as well as a COM callable wrapper (CCW) for the object. The object is allocated from the garbage-collected heap while the CCW is allocated from a non-collected heap. This ensures that the runtime is free to move the object around in memory as necessary. The wrapper holds a single reference to the object in order to keep the object alive as long as the wrapper is in use. Since the wrapper is allocated in non-collected memory, the runtime is free to hand out interface pointers on the wrapper to the unmanaged client. The wrapper is reference counted like any other COM object. When the reference count reaches zero, the wrapper releases its sole reference on the object and the object is freed during the next garbage collection.

The CCW has the following responsibilities:

• Preserving object identity

• Maintaining object lifetime

• Proxying custom interfaces

• Marshalling method calls

• Synthesizing selected interfaces

How these responsibilities are fulfilled is explained in the following chapters.

Section 3: Calling .NET Services from COM

[pic]

Identity and Lifetime Control

Single CCW for .NET class insures identity

Activation is not the only way wrappers can be created. A CCW is also created as a side effect when one managed object returns a reference to another managed object from a method call (either as a return value or as an out parameter on a method call). In order to honor COM’s identity rules, it’s important that only one wrapper ever exists for a given managed object. This ensures that all interfaces on the same object have a common IUnknown. Therefore, when a CCW is needed, the runtime always checks for an existing wrapper on the object before creating a new one. This ensures that no more than one wrapper will ever exist for a given managed object.

Each interface of the CCW is reference counted

Each COM interface that is returned from the CCW is reference counted in the usual manner. The managed object is not reference counted in any way. The CCW in turn maintains a traced reference the managed object.

Managed objects lives at least while the CCW lives

That way the CCW can ensure that that the underlying managed object remains alive as long as unmanaged references exist on the wrapper. It also allows other managed objects to maintain a reference to the same managed object as the CCW.

However, unlike COM objects, managed object do not have deterministic lifetimes. That means that even though all reference to a CCW have been released, it may be some time before the managed object it wraps is actually freed. COM clients that make assumptions about when the objects they reference will be delete may experience unexpected behavior.

Notes: Under some circumstance, leaks can also occur when an unmanaged process is shut down. Image an unmanaged process that holds a reference on a managed object implemented in another DLL. A CCW is created to wrap the managed object being referenced. Once the reference is released, the object being wrapped by the CCW becomes eligible for collection. If the process is shut down before the collection takes place, the object will leak. While you might think that the runtime could perform a collection before it shuts down (within DLLMain for example), there are several reasons why that isn’t possible:

• Performing a garbage collection during DllMain execution would surely result in a deadlock as object attempt to acquire the loader lock.

• The order in which DLL’s are notified of shut down is unpredictable and therefore, during shut down, objects may inadvertently attempt to execute code in other DLLs that have already been unloaded.

To ensure a leak-free shut down under these circumstances the unmanaged process must explicitly call CoEEShutDown. By explicitly calling CoEEShutDown, the runtime is given an opportunity to perform one last collection before the process is destroyed. However, CoEEShutDown collects all outstanding objects in any order. There can be no assumptions about the order in which objects are destroyed.

Section 3: Calling .NET Services from COM

[pic]

Synthesizing Selected Interfaces

MyClass implementing MyItf derived from MyBase

In order for COM client to be able to access the members of a managed class, a new interface is defined when a class is exported to a type library with TlbExp. This interface is commonly referred to as the “class interface” and carries the same name as the class itself but is prefixed with an underscore. The class interface contains all public members of the managed class and its implicit and explicit base classes, so that unmanaged users have access to the class members directly. The interface is automatically generated by TlbExp and is exposed to COM in addition to any other interfaces that the class implements explicitly. The class interface is always dual so COM clients can either early bind or late bind to the interface.

Managed clients never use (or even know about) the class interface. Instead, managed clients can simply access the class methods directly.

Additional interfaces implemented on CCW

In addition to the interfaces that are explicitly implemented by a .NET class, the CCW synthesizes an implementation of some interfaces. The implementations of these interfaces are based on the metadata provided by the class and are completely implemented by the CCW on behalf of the object. A .NET class is never required to implement any of these interfaces.

The IUnknown implementation is provided for the lifetime management of COM as already discussed.

There are two separate IDispatch implementations that the runtime can use.

• OLE Automation compatible implementation

• Internal runtime implementation

For the highest level of backward compatibility, the OLE Automation compatible implementation should be used. This is the same IDispatch implementation that earlier versions of Visual Basic used and it is the one that most COM applications are designed to use. The downside of using this implementation is that it requires static type information. The runtime can, and will, automatically generate the type information for managed types as necessary, but the process is time consuming. See the section below on on-the-fly type library generation.

As an alternative, the runtime provides its own internal implementation of IDispatch. This implementation does not require static type information unless it is specifically asked for by calling IDispatch.GetTypeInfo. Other methods in the IDispatch interface can be called without requiring any form of static type information. If GetTypeInfo or GetTypeInfoCount is called, the same on-the-fly type library generation process described below is used.

The IDispatch implementation can be controlled with the IDispatchImplAttribute. This attribute can be set at the assembly level or on an individual class. When applied to an individual class, the attribute overrides any assembly level setting for that class. If the attribute is missing, the internal implementation is used.

Individual interfaces can also use the InterfaceTypeAttribute to control whether the interface is exposed to COM as IUnknown derived, IDispatch derived or dual.

Section 3: Calling .NET Services from COM

[pic]

Exporting a Type Library

Use SDK tool TlbExp

The TlbExp command-line utility converts the classes and interfaces contained in a .NET Framework assembly to a COM type library. Once the class' type information is available, COM clients can create an instance of the .NET class and call the instance’s methods, just as if it were a COM object. TlbExp converts an entire assembly at one time. You cannot use TlbExp to generate type information for a subset of the types defined in an assembly.

Use the TypeLibConverter class

Like TlbExp, the TypeLibConverter class converts the classes and interfaces contained in an assembly to a COM type library. Both methods produce the same type information.

Leave the conversion to RegAsm utility

You can generate a type library and register the types contained within an assembly with COM by using the /tlb: option to the RegAsm utility. That approach takes to two steps at once.

Export process involves a lot of conversions

Since type libraries cannot accommodate all the information found in assemblies, the conversion process may discard some data during the export process

Type A.B.IList exports with the name IList, allowing COM applications to refer to the type as simply List instead of A.B.IList. One limitation of this approach is that type names within an assembly can potentially collide since types within different namespaces may have the same name. When such a collision occurs, the TlbExp utility exports the type using its complete name (including namespace) to ensure its uniqueness. Since periods are not allowed in type library names, the utility replaces each period with underscores.

Section 3: Calling .NET Services from COM

[pic]

Sample Conversion

There are several things to notice about the class interface:

• It has the same name as the class but is prepended with an underscore. Had the interface _MyClass been defined elsewhere, the class interface would be named _MyClass_2.

• A unique IID was generated for the interface which is unrelated to the CLSID generated for the MyClass class. There is not way of explicitly setting the IID of the class interface

• It derives from IDispatch. The class interface is always dual.

• It has the odl, dual, hidden, nonextensible and oleautomation attributes.

• It contains all the public members of it’s base class (in this case System.Object)

• It does not contain the private or protected members of the class.

• Each member is automatically assigned a unique dispid. The DispIds can be set explicitly with the DispIdAttribute. (not shown on slide)

• Like other interface methods, the method signatures are transformed to return HRESULTS and have [out, retval] parameters.

• The properties and fields are transformed into [propget] and [propput] and [propputref].

There are several things to notice about the MyClass coclass:

• It does no contain the methods of the managed class. Coclasses can not have members.

• The class interface is identified as its default interface.

• A unique CLSID was generated for the class automatically.

• It also implements the class interfaces of all its base classes (in this case _Object)

Section 3: Calling .NET Services from COM

[pic]

Special Attributes

Attributes control the conversion process

These attributes mentioned here are only a subset of the large set of attributes, that can be used to control the conversion process. For a full list see the framework documentation.

The InAttribute is used on a parameter or field to indicate that data should be marshaled in to the caller.

The OutAttribute is used on a parameter or field to indicate that data should be marshaled out from callee back to caller.

The MarshalAsAttribute is used on fields or parameters to indicate how the data should be marshaled between managed and unmanaged code. You can apply this attribute to parameters or fields.

The attribute is always optional as each data type has a default marshaling behavior. The attribute is only necessary when a given type can be marshaled to a number of other possible types. For example, a String could be marshaled to unmanaged code as either a LPStr, LPWStr, LPTStr or as a BStr. By default the string would be marshaled as a BStr. The MarshalAsAttribute attribute can be applied to an individual field or parameter to cause that particular string to be marshaled as a LPStr instead of a BStr. For complete details on using this attribute, see the data type marshalling specification.

In most cases, the attribute simply identifies the format of the unmanaged data using the UnmanagedType enumeration.

The GuidAttribute supplies an explicit Guid when an automatic Guid is undesirable. You can apply this attribute to assemblies, classes, interfaces, structures, enumerations, or delegates.

The string passed to the attribute must be in a format that is an acceptable constructor argument for the type Guid. To avoid conflicts with the type Guid, use the long name GuidAttribute explicitly. Only use explicit Guid 's when a type must have a specific Guid. If the attribute is omitted, a Guid will be assigned automatically.

The static function Marshal.GenerateGuidForType generates a stable Guid based on the fully qualified type name, i.e. two calls to this function with the same type name generate the same Guid.

The COMVisibleAttribute controls whether an individual type or all types within an assembly are visible to COM. You can apply this attribute to assemblies, interfaces, classes, structures, delegates, enumerations, fields, methods, or properties. This attribute is not needed to make all public managed types visible, they are visible to COM by default. Setting the attribute to false on the assembly hides all public types within the assembly. Setting the attribute to false on a specific type hides that specific type. Settings applied to an individual type overrides any assembly settings. Therefore, setting the attribute to false on the assembly and true on an individual type causes only that type to be visible from the assembly.

Only types that are public can be made visible. The attribute cannot be used to make an otherwise internal or protected type visible to COM. If you want a nonpublic class or interface to be visible to COM, use the COMImportAttribute.

Section 3: Calling .NET Services from COM

[pic]

Registration (RegAsm)

Adds assembly information to the registry

In order to be able to create managed types from COM applications the appropriate entries must be made in the Windows registry. The runtime ships with a registration utility called RegAsm for just this purpose. RegAsm reads the metadata within an assembly and adds the necessary entries to the registry so COM clients can create the .NET classes transparently.

Once a class is registered, any COM client can use it as though the class were a COM class. The registration process is only done one time when the assembly is installed. The classes within the assembly are not COM creatable until they are actually registered.

The stringized assembly reference found under the InProcServer32 key is the string form of the name of the assembly containing the class. The string is obtained from AssemblyName.FullName and is used to locate and load the assembly when the class is being created.

Section 3: Calling .NET Services from COM

[pic]

Results and Exceptions

You know: COM uses HRESULTs, .NET exceptions

COM interfaces return HRESULTs to convey error information to the caller. .NET introduces the ability to throw exceptions across object interfaces but only from one managed object to another. When COM clients are using .NET objects, the stub must maintain HRESULT semantics that these clients are expecting. Therefore, exceptions that occur during calls made by COM clients must be mapped to the equivalent COM HRESULTs and returned to the COM client.

Can’t propagate exceptions to unmanaged code

User-defined exception classes can specify whatever HRESULT is appropriate, and they can dynamically change the HRESULT to be returned when the exception is thrown by setting the HResult field on the exception object. Additional information about the exception is provided to the client through the IErrorInfo interface, which is implemented on the .NET object in the unmanaged process.

If you create a class that extends System.Exception, you must set the HResult field during construction. Otherwise, the base class assigns the HResult value. You can map new exception classes to an existing HRESULT by supplying the value in the exception’s constructor. The example shows how to create a new exception class called NoAccessException and map it to the HRESULT E_ACCESSDENIED.

Section 3: Calling .NET Services from COM

[pic]

Aggregation and Containment

Simple requirements to be met

COM reusability is accomplished through containment and aggregation. Managed objects can participate in both models as the inner object in the relationship.

Aggregation is used to enable the outer object to expose the managed object's implementation of an interface without modification.

Containment is used to enable the outer object to modify the behaviour of the managed object

In order for a COM client to contain or aggregate a managed object, only two requirements have to be met: The managed class must be public and it must have a default, i.e. parameterless constructor.

Containment

Containment is the simpler of the two reuse models and is supported by managed objects. Containment is typically used when the outer object needs to modify the behavior of the inner object. To do so, the outer object simply creates an instance of the inner object during construction and delegate calls to the inner object as necessary. The outer object can choose which calls to delegate and which calls to handle it self. There are no special requirements of the runtime in order for objects to support containment.

Aggregation

Aggregation is slightly more complex. It is typically used when the outer object wants to expose another objects implementation of an interface without modification. All managed objects automatically support COM style aggregation with the managed object being used as the inner object (or aggregatee). In order to aggregate a managed object, the unmanaged outer object creates the managed inner object by calling CoCreateInstance passing the outer object’s IUnknown as a OuterUnknown parameter. When an outer IUnknown is passed to a managed object during construction, the managed object caches the interface and uses it as follows:

• The outer IUnknown is not reference counted since the inner objects life is scoped within the lifetime of the outer object.

• If the inner object is queried for an interface that it does not support, the inner object delegates the call to the outer objects IUnknown interface.

• All calls to the inner objects AddRef and Release methods are delegated to the outer objects IUnknown.

These three behaviors make it possible to aggregate any managed object. With this type of aggregation relationship, it’s possible to have a single COM object that is partly implemented in managed code (the inner portion) and partly in unmanaged code (the outer portion).

Section 3: Calling .NET Services from COM

[pic]

Exposing Inheritance

Managed interfaces provide inheritance

When managed interfaces are exposed to COM, they always extend IUnknown or IDispatch even when the interface is inherited from another interface on the managed side. The same applies for the class interface that’s generated for managed classes. This interface flattening approach allows managed interfaces that inherit from several other interfaces to be exposed in a predictable way.

Advantages with versioning

Further it insulates users of the derived interface from changes in the base interface. In other words: Users of an derived interface are not affected by changes made to the base interface. Applications that use the derived interface will continue to work without recompilation even if the base interface(s) change.

Disadvantages

The one downside of this approach is that it prevents COM clients from using the type polymorphically. For example, IDerived cannot be passed to methods that take arguments of type IBase. In order for a COM client to use IBase, it must explicitly query the coclass for the IBase interface before making the call.

Section 3: Calling .NET Services from COM

[pic]

Converted Inheritance Tree

This sample shows the conversion concerning the inheritance tree of a managed component. The interface inheritance is flattened: All interfaces derive from IDispatch, which is the default.

The CoClass inherits from all interfaces plus the _Object interface, since it is implicitly derived from System.Object.

Section 3: Calling .NET Services from COM

[pic]

Restrictions and Limitations

Parameterized constructors are not exposed

These constructors are not visible to COM. Therefore the default constructor must be used.

Static methods are not exposed

Static methods can only be called by wrapping the static method with an instance method which can be called from COM.

Section 4: Calling Platform Services from .NET

[pic]

Calling Platform Services from .NET

Calling static functions in DLLs

Platform Invocation Services (P/Invoke) allows managed code to call unmanaged functions that are implemented in a DLL. P/Invoke takes care of finding and invoking the correct function, as well as marshalling its managed arguments to and from their unmanaged counterparts (integers, strings, arrays, structures, etc).

P/Invoke was intended primarily to allow managed code to call existing, unmanaged code, typically written in C. A good example is the several thousand functions that comprise the Win32 API.

P/Invoke provides services

When P/Invoke calls an unmanaged function, it locates the DLL where that function lives, loads that DLL into process memory, finds the function address in memory, pushes its arguments onto the stack (marshaling if required), enables pre-emptive garbage collection, and transfers control to the address for the unmanaged code.

P/Invoke looks up the name of the unmanaged function in the DLL you specify, in order to load and call that code. However, in order to make things easier for the programmer, P/Invoke actually carries out a fuzzy match of the name provided. For example, you may say you want to call the function MessageBox in the user32.dll, but P/Invoke will actually call MessageBoxA (the version that accepts ANSI strings) or MessageBoxW (the version that accepts wide, or Unicode, strings), depending upon whether you are executing on a platform where strings are ANSI or Unicode. In fact, there is no such function as MessageBox in user32.dll at all.

Although it is possible to export methods of a class from a DLL, P/Invoke does not support calling such methods. P/Invoke only supports calling global functions exported from the DLL. P/Invoke knows what to do by examining metadata, stored into the image file. This metadata, in turn, was generated by the compiler in response to attributes that the programmer writes into his source code.

You can start executing managed code, and call unmanaged code. And from that unmanaged code, you can even return to managed code via a callback (see later).

Section 4: Calling Platform Services from .NET

[pic]

Code Sample

This is another "Hello World" example. Rather than "puts" which writes to a console window, this example calls the windows MessageBox function, provided as part of the Win32 API in USER32.DLL.

The function name provided in the [DllImport] directive can be in one of two forms: either a regular name, such as MessageBox, or the ordinal of the target function in the DLL, specified as a decimal number preceded by #. For example, "#123".

If the function 'name' is its ordinal, then P/Invoke simply calls a GetProcAddress for that ordinal number.

The sample makes use of the name matching algorithm. If ExactSpelling=True is would have been specified, the matching would be exact – given MessageBox there would be only a search for MessageBox. Since this function does not exist, there would be an exception thrown.

Section 4: Calling Platform Services from .NET

[pic]

Marshalling

Strings are copied

P/Invoke copies string arguments, converting from the .NET Framework format (Unicode) to the platform format. Strings are treated as immutable, so they are not copied back from unmanaged to managed space when the function returns.

On the other hand, P/Invoke does copy back any argument of type StringBuilder, since this is a mutable string. Notice that this simulates "by-reference" semantics by using copy-in/copy-out.

Interfaces supported

P/Invoke handles arguments that are marked as a .NET Framework interface type by extracting the "COM" interface pointer and passing it to the unmanaged code. Interfaces are immutable and are therefore not copied back out.

Arrays of primitive types can be used

Arrays can be passed, by copy-in/copy-out, if they have elements of any of the "Primitive Types": boolean, char, integer (1, 2, 4, or 8 bytes, signed or unsigned), real number (4 or 8 bytes), or string.

Beware: if you call an unmanaged function, passing an array argument by reference, P/Invoke copies all its elements to an unmanaged buffer. However, when copied back, P/Invoke no longer knows the size of that unmanaged buffer, so it copies back just 1 element. On return, the array has been resized to 1.

Custom marshalling using attributes

P/Invoke allows you to over-ride the default marshaling provided for function return values, for function arguments, and for fields within structures used as function arguments. For example, if you want a managed String marshaled, not to an LPTSTR (the default), but to a LPBSTR, this is possible by saying so in the metadata. The attributes that are used for this purpose are basically the same as in COM Interop.

Section 4: Calling Platform Services from .NET

[pic]

Showcase for the Standard Marshaller

Given a method in some DLL

This sample assumes that a DLL with the name “some.dll” contains a function named “SomeFunc” with the signature above.

There is an issue with the second parameter that is typed as a void pointer. This type is not available in .NET so we have to deal with it in a special way.

Standard declaration may need special decoding

The natural counterpart of a void pointer which is basically a pointer to some area in memory is an array of bytes. To express this the parameter is typed as byte[] and a marshalling attribute marking this parameter as a pointer to an array is used.

If this scheme is used it is up to programmer to make sure that the correct conversions are made. A string as the actual parameter has to be converted into the correct format using the correct character set, an integer value has to be formatted into the expected size and byte order etc.

May use tailored versions

The standard marshaller offers functionality to simplify this task. One can write wrappers for the original function using specialized formal parameters.

The function SomeFunc_String() mentioned above is such a spezialized wrapper. It uses a StringBuilder typed parameter instead of a byte array. (String would do if this were an input parameter, StringBuilder must be used in case of an output parameter.)

The mapping to the original function is accomplished by the EntryPoint member of DllImportAttribute.

The marshaller does the necessary conversion without any further specification.

Section 4: Calling Platform Services from .NET

[pic]

Callbacks

Unmanaged code can call back to managed code

A callback function is code within a managed application that helps an unmanaged DLL function complete a task. Calls to a callback function pass indirectly from a managed application, through a DLL function, and back to the managed implementation.

First, you have to create the callback function in your managed application. Finally, you call the DLL function, passing a pointer to the callback function as an argument.

To supply the callback parameter a delegate must be defined. This delegate will then be used as the actual parameter in the .NET application.

P/Invoke creates a callback thunk since the DLL function has no concept of delegates but of function pointers. This behavior is completely transparent to the programmer.

Section 4: Calling Platform Services from .NET

[pic]

Code Sample

The example below demonstrates how a managed application, using platform invoke, can print the handle value for each window on the local computer. Specifically, the example uses the EnumWindows function to step through the list of windows and a managed callback function (named CallBack) to print the value of the window handle.

Step 1

Before going further with the implementation, you must look at the signature for the EnumWindows function. EnumWindows has the following signature:

BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARMAM IParam)

One clue that this function requires a callback is the presence of the lpEnumFunc argument. It is common to see the lp (long pointer) prefix combined with the Func suffix in the name of arguments that take a pointer to a callback function. For documentation on Win32 functions, see the Microsoft Platform SDK.

Step 2

Create the managed callback function. The example declares a delegate type, called CallBack, which takes two arguments (hwnd and Iparam). The first argument is a handle to the window; the second argument is application-defined. In this release, both arguments must be integers.

Callback functions generally return nonzero values to indicate success and zero to indicate failure. This example explicitly sets the return value to true to continue the enumeration.

Step 3

Finally, you create a delegate object and pass it as an argument to the EnumWindows function. Platform invoke converts the delegate object to a familiar callback format automatically.

Section 4: Calling Platform Services from .NET

[pic]

Security

Suppressing Demands for Unmanaged Code Permission

A special optimization is available to code that has permission to call unmanaged code. This optimization allows your managed code to call into unmanaged code without incurring the overhead of a stack walk. Asserting unmanaged code permission can shorten the stack walk, but the optimization discussed in this topic can eliminate it entirely.

Normally, a call into unmanaged code triggers a demand of unmanaged code permission, which results in a stack walk that determines whether all callers have permission to call into unmanaged code. Applying the custom attribute SuppressUnmanagedCodeSecurity to the method that calls into unmanaged code suppresses the demand, which eliminates the stack walk that the demand would have caused. In essence, using this attribute creates an "open door" into unmanaged code. Only code that has the unmanaged code permission can use this attribute; otherwise, it has no effect.

Warning: Use the SuppressUnmanagedCodeSecurity attribute only with extreme care. Incorrect use of this attribute can create security weaknesses.

The SuppressUnmanagedCodeSecurity should never be used to allow less trusted code (code that does not have unmanaged code permission) to call into unmanaged code. This attribute is best applied only to privately declared entry points to unmanaged code so that code in other assemblies cannot access and take advantage of the security suppression. Typically, the highly trusted managed code that uses this attribute first demands some permission of callers before invoking the unmanaged code on the caller’s behalf.

Calling class must be fully trusted

The calling .NET Framework class must be fully trusted. This means that the class must be signed with "Full Trust" access and that the administration policies have allowed to code to run on the system with full trust. This level of security is essential since the unmanaged code runs with no runtime checks.

Any code calling that class must be fully trusted

Any code calling a class that makes P/Invoke calls must be fully trusted. Since the Common Language Runtime walks up the stack to ensure that all code in the call chain has the right level of trust, all .NET Framework code that is on a chain that ultimately makes a P/Invoke call must also be fully trusted.

Section 4: Calling Platform Services from .NET

[pic]

Component Services

Fundamental building blocks

Component services are the fundamental building blocks of distributed, scalable applications. Just-In-Time (JIT) Activation, synchronization, object pooling, object construction, transaction processing, and shared properties are examples of well-known services for components. Loosely coupled events, queued components, and security are services that enable developers to write flexible components for Web-based applications.

Both .NET Framework and COM+ components can be configured with component services. Services, like transactions, can flow among managed objects just as transactions can flow among COM+ objects in Windows 2000. The Common Language Runtime and COM+ Services work together to generate type-library information on the fly and perform dynamic registration on managed components for inclusion in the COM+ Catalog.

Mostly restricted to Windows 2000

Most of these features are only available on Microsoft Windows 2000.

COM+ generates and services context object

COM+ can host a managed component by creating and servicing a COM+ context object on the managed object's behalf. COM+ uses context objects to store services-related information on each instantiated object in the Catalog. The Common Language Runtime performs the actual implementation and execution of the managed component, as the figure shows.

Given the close cooperation between the runtime and COM+ Services, managed and unmanaged objects can share common services. Each environment controls the implementation and execution of its native code, while COM+ Services always creates and manages the context objects.

Section 4: Calling Platform Services from .NET

[pic]

Using COM+ Services

Derive class from System.ServicedComponent

A COM+ application can host managed components as either an in-process servers or out-of-process servers.

A serviced component is a .NET Framework component that derives from the System.ServicedComponent class and is automatically serviced by Microsoft Windows 2000 Component Services. A serviced component is typically hosted in a COM+ library as an in-process server or as an out-of-process server that is called by an unmanaged client.

Add attributes to parameterized services

These components also include service-related custom attributes set at the class and method level.

Assemblies which contain components using COM+ services are recommended to specify an assembly header with attributes like:

[assembly:ApplicationActivation(

ActivationOption.Library)]

[assembly:ApplicationID(

"4fb2d46f-efc8-4643-bcd0-6e5bfa6a174c")]

[assembly:ApplicationName("Services")]

[assembly:Description("Services .NET

Automatic registration helper.")]

An assembly name is obtained from the metadata, either from the assembly's FullName (with version removed), or the value obtained from an ApplicationName attribute. If the assembly is tagged with an ApplicationID or Guid attribute, all searches for the application are based on that guid, not on the application name.

Use regsvcs to register assembly as server

The .NET Services Installation tool performs the following actions:

• Loads and registers an assembly.

• Generates, registers, and installs a type library into a specified COM+ 1.0 application.

• Configures services that you have added programmatically to your class.

regsvcs requires a source assembly file. The names of the target application and the type library file are optional. The actual name of the application (i.e. what the COM+ application name will be) can be generated from the source assembly file and will be created by regsvcs, if it does not already exist. If you do not specify a type library name, Regsvcs.exe uses the assembly name as the default.

Using regsvcs relieves you from the need to use tlbexp and regasm, since their work is already included in regsvcs.

If application code tries to create a new instance of a managed code ServicedComponent, the CLR detects if it is not already registered in a COM+ Application. If it has not been registered, then all the ServicedComponents in the assembly are registered in the COM+ Application. The Service custom attributes (e.g. Transaction) on the ServicedComponent are used to configure the COM+ Catalog.

This includes all children of ServicedComponent. So MyAccount which is child of Account (which is a child of ServicedComponent) is lazy registered into a COM+ Application.

Section 4: Calling Platform Services from .NET

[pic]

Transactions Sample

This code fragment shows the Account class, which performs a database update under the protection of an automatic transaction. Notice that the Account class derives from the ServicedComponent class. Other service-related coding elements used in this example include:

Namespaces

System includes the ComponentServices class.

System.pilerServices contains custom attributes for registration.

Services contains the custom attributes that apply to setting up component services.

Registration-Related custom attributes

[assembly:ApplicationName(“TestApp”)] to associate the assembly with a COM+ application.

Service-Related custom attributes

[Transaction(TransactionOption.Required)] to require a transaction.

[AutoComplete] to cast an exception-based vote on the transaction.

Developers do not need to explicitly vote in a transaction within a Web Service method, if all cases where a Web Service method would call ContextUtil.SetAbort are confined to an exception. If an exception occurs within a Web Service method, the transaction is automatically aborted. Conversely, if no exceptions occur, the transaction is automatically committed.

Summary

[pic]

Summary

Preserving investments and moving on

We have seen how COM components can be integrated in .NET applications, effectively saving development time and preserving massive programming efforts. This means can be a migration path for your applications, since you can upgrade existing components at the pace that fits your needs.

But also the other way round helps you with stepping up: Write new components in the .NET framework using the power and ease of development while continuing to drive your application from unmanaged code.

The .NET Common Language Runtime helps you Windows Component Services

Prepare COM components for use with .NET

If you continue to develop native COM applications and want to switch to .NET later, a few thoughts should be taken and a few guidelines should be followed to ease the upgrade at a later time.

.NET offers maximum transparency

No matter which way you choose: the .NET framework has a solution. The framework provides maximum transparency to not bother you with tedious programming tasks to accomplish compatibility.

................
................

In order to avoid copyright disputes, this page is only a partial summary.

Google Online Preview   Download