NGWS SDK Documentation  

This is preliminary documentation and subject to change.
To comment on this topic, please send us email at ngwssdk@microsoft.com. Thanks!

Binding to NGWS classes

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. Late binding requires compile time type information while late binding does not.

Early Binding

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 any NGWS runtime assembly. This conversion is explained in section 4.1.4 below.

Late Binding

COM client are free to bind to managed types using the standard late binding mechanism provided by COM, namely IDispatch. All managed types automatically support IDispatch without having to write any additional code. The IDispatch implementation is automatically provided by the runtime by examining the metadata for the type. For details of IDispatch implementation the runtime provides, see section 4.9.2. Under some circumstances, the runtime may require a type library in order to provide an implementation of IDispatch.

Type Library Generation

A type library describing the types contained in a managed assembly may be needed for several reasons.

As section 4.9.2.2 describes, the runtime is capable of generating type libraries on-the-fly as needed. However, a static type library describing the types defined in a NGWS runtime assembly can also be generated in two ways:

Using TlbExp (Type Library Exporter)

The TlbExp utility is provided as part of the NGWS SDK. TlbExp is a command line tool that uses the API described in the following section to export a type library generated from a NGWS runtime assembly.

TlbExp has the following command line syntax:

TlbExp AssemblyName [/out: TlbFile]

The TlbExp utility generates a type library containing definitions of the public types defined in the NGWS runtime assembly named AssemblyName.

Options:

/out: TlbFile - Specifies the name of the tlb file to be generated. If the option is omitted, the type library is generated with the same name as the assembly (the actual assembly name not the name of the file containing the assembly) with the .tlb extension.

Examples

TlbExp mytest.dll

Generates a type lib with the name same name as the assembly found in mytest.dll.

TlbExp mytest.dll /out clipper.tlb

Generates the type library with the name clipper.tlb.

Using ITypeLibConverter to Export a Type Library

The TypeLibConverter class in the System.InteropServices namespace implements the ITypelibConverter interface that can be used by both managed and unmanaged applications that need to convert metadata to type information or vice-versa.

For a complete description of the TypeLibConverter class, see the reference pages for the specific type.

A complete description of the actual conversions made during type library generation is contained in the sections below.

Note on using type libraries

Most development tools are designed to easily consume type information in the form of a type library. Type libraries produced by TlbExp or the TypeLibConverter object are no exception. The Visual Basic 6.0 development environment for example, allows user to directly add a reference a type library (tools references menu). Using a type library from Visual C++ 6.0 is also possible but a little trickier. A C++ source file can reference a type library using the #import statement:

#import <windows.h>
#import <mylib.tlb> 

//  reference types defined in mylib.tlb

When the C++ pre-compiler parses the #import statement it imports the type library and generates a corresponding .tlh file that contains the same type definitions in C++ syntax. The compiler then parses the .tlh file as a normal C++ header file. The .tlh file only needs to be converted once for a given .tlb file. Any changes to the .tlb file will cause a new .tlh file to be generated.

There are several options that can be used with the #import statement (see the Visual C++ documentation for details). Two options are of particular importance when using libraries generated with TlbExp. When importing a library, users must either 1) use the raw_interfaces_only option or 2) import the definitions of the types defined in the base class library (mscorlib.tlb).

Importing a library without mscorlib.tlb

   #import "thedll.tlb" raw_interfaces_only

Importing a library with mscorlib.tlb

   #import "mscorlib.tlb" 
   #import "thedll.tlb"

Another issue to be aware of when using type libraries from within C++ has to do with case sensitivity of assembly names. NGWS runtime assemblies names are case insensitive. During exporting the assembly name is used create the type library name. During #import the type library name is used to create the namespace name. The namespace name then used to reference the types within the given library. Because the types are referenced within C++ source code, the type names are case sensitive. As a result, two assemblies that differ only in the case of their names are considered distinct assemblies from unmanaged C++. Developers should therefore be sure to use the same case when creating naming assemblies. The way the assembly name is provided varies according to the tools being use. In most cases the assembly name is provided as part of the build process. See your compiler documentation for details.

One final point worth mentioning is that names within type libraries are folded into a common, case insensitive string table. This includes types names, member names and parameter names. In situations where a type, member and/or parameter share the same name, the name has common casing. The case of the common name is the same as the case used in the first reference to that name. For example, if the following types were exposed to COM, the type named Foo, the method name FOO and the parameter named foo, would all be exported as Foo because that’s the case used in the first reference to the name foo.

class Bar {
   int FOO();
}

class Foo { …
}

class Baz {
   int Baz(int foo);
}   

Assembly to Type library Conversion

The mechanisms described above convert NGWS runtime assemblies to COM type libraries. During the conversion, certain transformations are made. Because type libraries are unable to accommodate all the information found in assemblies, some data may be discarded. This section explains those transformations and identifies the source of each piece of information emitted to the resultant type library. The transformations described here are those made by the ITypeLibConverter interface.

Common conversions

There are some common pieces of information that are seen throughout an assembly and are converted the same way regardless of where they appear. Information like version and helpfile are examples. This information is always optional and can be provided for an entire assembly, an individual type or member of a type. Rather than repeat the conversion rules in each of the following sections, they appear once here.

The version information in an assembly consists of a 4-part version string similar to “2,1,23,443”. The first two parts are considered the product version while the last two parts make up the build version. In type libraries, a 2-part version string is used (like “2.1”). When converting from assembly to type library, the first two parts of the assembly version (the product version) are transferred to the type library version. The build version is discarded.

Assembly level conversion

At the outermost level of the conversion, the managed assembly is converted to a COM type library. Each individual assembly is converted into a single type library. There is always a one-to-one mapping between assembly and type library. An assembly cannot be broken up into multiple type libraries.

The conversion is deterministic. A given assembly will always generate the same type library regardless of when or where it was converted.

Type libraries are identified solely by their type library identifier (TLBID), version and LCID. The name of the file containing the type information is not part of the libraries identity nor is the friendly name defined within the library. Type Libraries are located through the registry via their TLBID, version and LCID.

Sample Library

[ uuid(0D26FC72-7EB1-4565-AA75-DA5F177EFA66),
  lcid(0x0409), 
  version(2.1), 
  helpstring("Acme Widget Library")
] 
library Acme 
{
…
}; 

Assemblies are identified by the combination of their SimpleName, OriginatorKey, version and locale. The Originator key is the public part of a public/private cryptographic key pair. The SimpleName is the friendly that’s used to refer to the assembly. The version is a four-part numeric string. The first two parts are the major and minor product version while the last two parts are the build numbers. The locale string indicates the locale that the assembly supports. Other information is not part of the assembly identity.

Sample Assembly

SimpleName:       Acme
OriginatorKey:    Public Key of Acme Corp
Version:          2,1,23,443
Locale:          en-us
Title:            Acme Widget Library
Processor:         x86
OS:               NT4

The conversion from assembly to type library preserves the identity of the library. In other words, each uniquely identified assembly converts to a uniquely identified type library. To ensure this uniqueness, the conversion process uses the SimpleName and OriginatorKey from the assembly to produce a unique TLBID. A given SimpleName and OriginatorKey combination will consistently yield the same TLBID. Conversely, no two assemblies with different SimpleName and OriginatorKey combination will every produce the same type library.

SimpleName + OriginatorKey => TLBID

Since the version and locale make up the rest of the type libraries identity, they are not used in the generation of the TLBID. The version information from the assembly is also passed along to the type library although type libraries contain only a two-part version number while assemblies contain a four-part version number. The assembly’s major and minor version numbers are carried forward to the type library. The assembly build numbers are discarded in the conversion. This does imply that two assemblies that differ only by their build numbers will yield the same type library.

The assembly locale id string (i.e. “us-en”) is converted to an LCID (i.e. 0x409) and stored in the LCIS field in the type library. If the locale is not specified the LCID is set to 0.

A type library is typically referred to by its library name (not the file name). The name is copied directly from the assemblies SimpleName. Since an assemblies simple name can (and often does) contain periods which are not permitted in type library names, all periods in the simple name are converted to underscores.

The helpstring (also called DocString) of the typelib is copied from assembly’s Title field. The HelpFile and HelpContext fields in the type library are not set. The LIBFLAGS in the typelib are also not set.

Other assembly fields such as Processor, OSInformation, CodeBase, Alias, AssemblyHash, Configuration and Flags are discarded during the conversion.

Other type library attributes such as hidden, restricted and control are not set.

In general, there are no guarantees about the content of a typelib from version to version. In other words, a type defined in version 1 of a particular library is not guaranteed to be in version 2.

Module level conversions

A NGWS runtime assembly may contain the definition of several external methods that are actually implemented in unmanaged code. The managed definition is provided so that managed applications can take advantage of code implemented in unmanaged dynamic link libraries. During export of a NGWS runtime assembly, a type library module will be created for each dynamic link library externally referenced from within the assembly. Each module will contain all the external methods that reference a single DLL. The module is marked with the dllname attribute to indicate the DLL containing the implementations of the methods defined within the module.

External Methods

class Utility
{
   [DllImport("kernel32", 
EntryPoint=”CreateDirectory”,
CallingConvention=CallingConvention.StdCall]
   public static extern bool Create (string name);

   [DllImport("User32"]
EntryPoint=”MessageBox”,
CallingConvention=CallingConvention.StdCall]
   public static extern int MsgBox (string msg);
}

class MoreUtils
{
   [DllImport("kernel32"]
EntryPoint=”RemoveDirectory”,
CallingConvention=CallingConvention.StdCall]
   public static extern bool Remove (string name);
}

Converted to

   [dllname(“Kernel32.dll”), …]
   Module Kernel32Module
   {
      [entry(“CreateDirectory”) pascal bool Create([in] BSTR name);
      [entry(“RemoveDirectory”) pascal bool Remove([in] BSTR name);
   }

   [dllname(“User.dll”), …]
   Module User32Module
   {
      [entry(“MessageBox”) pascal int MsgBox([in] BSTR name);
   }
}

Type level conversions

Each type within the managed assembly can potentially be exported to the resulting type library. When exported, types carry the same name they had within their assembly (excluding the namespace portion of the their name). So, for example, the type A.B.IList:

namespace A {
   namespace B {
      interface IList {
            …
      }
   }
}

Would be exported with the name “IList”. This allows COM applications to refer to the type as simply “List” as opposed to “A.B.IList”. The using this approach, type names within an assembly can potentially collide since types within different namespaces may have the same name. When such collisions occur, the type will be exported using the its complete name (including namespace) to ensure its uniqueness. Since dots are not allowed in type library names, the dots are replaced with underscores. For example, the following types within the namespaces A, B and C:

Assembly Widgets 
{
   namespace A {
      namespace B {
         class LinkedList : public IList {…}
         interface IList {…}
      }
   }
   namespacee C {
      interface List {…}
   }
}

Would be exported as shown below.

Library Widgets 
{
   […]
   coclass LinkedList 
   {
      interface A_B_IList;
   }

   […]
   interface A_B_IList {…}
   […]
   interface C_IList {…}
}

In addition to the type name, types that are COM creatable are also given a ProgId. The ProgId is generated automatically by combining the namespace combined with the type name. The ProgId of the LinkedList class shown above would be A.B.LiskedList. however, simply combining the namespace and type name can, under some circumstances, result in an invalid ProgId. For example, ProgId’s are limited to 39 characters and cannot contain punctuation characters other than periods. In that case, the ProgId can be specified explicitly with the ProgIdAttribute. If the ProgIdAttribute is applied, the class will be registered with the ProgId specified rather than using the generated ProgId. If for any reason the ProgId is invalid, the RegAsm utility will generate a warning at registration time and the class will not be registered.

There are 4 different type definitions that can be found in an assembly.

Classes

Managed classes are exported to type libraries as a coclass. The coclasses have no methods or properties. The coclass has the same name as the original managed class and implements the all interfaces that the managed class explicitly implemented. In addition, the coclass implements one other interface called the class interface that is generated during the conversion process.

The class interface has all methods and properties available on the original managed class. This allows COM clients to effectively access the methods and properties of the managed class by calling through the class interface. For example, the Button class shown below:

class Button : Control {
   void click();
}

Would produce the following type information:

[
   uuid(…), hidden, dual, odl, nonextensible, oleautomation
]
interface _Button : IDispatch {
   HRESULT ToString([out, retval] BSTR* pRetVal);
   HRESULT Equals( [in] VARIANT obj, [out, retval] VARIANT_BOOL* pRetVal);
   HRESULT GetHashCode([out, retval] long* pRetVal);
   HRESULT GetType([out, retval] _Type** pRetVal);
   HRESULT click();
}

[
   uuid(A03A6562-3425-3288-99AD-9533F36ADB14)
]
coclass Button 
   [default] interface _Button;
   interfce _Object;
}

Notice that the class interface contains all the method implemented by the managed class as well as the methods of its base classes. The class interface always carries the hidden, dual, odl, nonextensible and oleautomation attributes. The uuid attribute assigned to the class interface is generated during export. It cannot be changed. For complete details on the class interfaces see section 4.9.3 below).

A managed class can be assigned an associated uuid by attributing the class with the GuidAttribute. When a managed class is exported, the value of the GuidAttribute is used for the uuid attribute in the type library. If the class does not have a GuidAttribute assigned, a uuid is automatically generated during the export process. Generated uuids are produced from a hash that includes class’s complete name (including the namespace). This ensures that a class with a given name in a given namespace will always generate the same uuid and that no two classes with different names will ever generate the same uuid.

Managed classes that are not COM creatable, such as abstract classes and those without public default constructors, are marked with the noncreatable attribute.

Other type library coclass attributes such as licensed, hidden, restricted, and control are not set.

Interfaces

Managed interfaces are export as to type libraries as COM interfaces. The interface has the same methods and properties as the managed view of the interface but the method signature differ considerable. See member conversions below for details. COM interfaces are identified by their IID. A managed interface can be assigned a fixed IID by attributing the interface definitions with the GuidAttribute. If the interface is not attributed with the attribute and IID is automatically assigned when the type library is exported. The assigned IID is based on the interface name (including namespace) as well as the complete signature of all the methods defined within the interface. This ensures that any changes made to the interface will result in a new IID being defined if the interface definition re-exported. Reordering the methods, change their argument types or return type will all affect the IID generated. Changes to method names do not affect the generated IID.

Interfaces that are not attributed with a fixed IID can still be still be requested through the runtime’s implementation of QueryInterface. QueryInterface will work for both fixed interface IID’s as well as generated IID’s. Generated IID’s are not persisted within the metadata for the type.

Value types

Value types (types that extend System.Value) are exported to type libraries as c-style structures along with typedef. The layout of the structure members is controlled with the StructLayoutAttribute, which is applied to the type. Only the fields of the value type are exported. If a value type has methods, they are inaccessible from COM.

For example:

[StructLayout(LayoutKind.Sequential)]
struct Point {
   int x;
   int y;
   public void SetXY(int x, int y){ 
      this.x = x;
      this.y = y;
   }
};

The Point value type is exported to COM as a point typedef shown below:

typedef struct tagPoint {
   int x;
   int y;
} Point;

Notice that the SetXY method is removed from the typedef.

Enumerations

Managed Enumerations are exported to type libraries as a COM as an enum with the member names altered in order to ensure the uniqueness of each members name. Managed enum members are scoped to the enum in which they belong. In other words, all references to Sunday in the DaysOfWeek enum shown below must be qualified with the name of the DaysOfWeek enum. Referencing just Sunday instead of DaysOfWeek.Sunday is not permitted. Because enum members must always be qualified, multiple enum’s are able to have member with the same name.

In COM and in unmanaged C++ however, enum members are scoped to the entire library. This allows members to be used without qualifying which enum the member belongs to. So, for example, Sunday can be used in place of DaysOfWeek.Sunday. Therefore, each enum member must have a unique name. Have two enums with the same member names is not permitted.

To ensure that each member name is unique, the name of the enum along with an underscore is prepended to each member when the enum is exported to a type library. For example:

Enum DaysOfWeek {
   Sunday = 0;
   Monday;
   Tuesday;
   …
};

Is exported as:

Enum DaysOfWeek {
   DaysOfWeek_Sunday = 0;
   DaysOfWeek_Monday;
   DaysOfWeek_Tuesday;
   …
};

Member are then be referred to as DaysOfWeek_Sunday by unmanaged applications.

COM Visibility of Types

Type Visibility: The COM visibility of managed types is largely determined by the managed visibility of that type. Simply put, public types are visible to COM while private, family and assembly scoped and nested types are not visible to COM.

Visibility of types can also be controlled explicitly with the ComVisibleAttribute. The attribute can be used to reduce the visibility of a type but cannot be used to increase the visibility of a type. In other words, the attribute cannot be used to make a non-public type visible.

When used to attribute an assembly, the ComVisibleAttribute applies to all type within the given assembly. For example, setting the ComVisibleAttribute of an assembly to false hides all the types within the assembly.

When used to attribute an individual type, the ComVisibleAttribute can be used to hide a public type from COM. An attribute applied to an individual type overrides the any attribute applied to the assembly. For example, the attribute can be used to make one type visible within an otherwise hidden assembly.

In the following assembly, only interface IB and class C would be COM visible.

Namespace Widgets 
{
   private interface IA {}
   public interface IB {}
   [COMVisilble(False)] public interface IC {}

   protected class A : IB {…}
   public class C {…}
   [COMVisilble(False)] public class D {…}
}

The COM visibility of a type has the following implications.

Types that are not visible to COM:

Do not appear in the type libraries generated by TlbExp.

Cannot be obtained through the runtime implementation of QueryInterface.

Cannot be created from COM.

Cannot be used in the public interface of any type that is COM visible.

When a non-visible type is used in the public interface of a visible type the type is either replaced by an IUnknown interface or the member is not exported. Consider the following example:

Namespace BillingSystem 
{
   private struct Address {…}
   private class Invoice{…}
   public class Customer {
      public void SendInvoice(Invoice I);
      public void SetAddress(Address A);
   }
}

In this case the public Customer type uses the private types Invoice and Address within its public interface. When exported to a COM type library, the customer class would appear as:

Library BillingSystem 
{
   interface _Customer {
      HRESULT SendInvoice(IUnknown *I);
   }
   coclass Customer {
      interface _Customer;
   }
}

The Invoice type used in the SendInvoice method is replaced with an IUnknown. The SetAddress method is removed from the interface completely. A warning is generated when TlbExp tried to export the method.

Accessibility of members: All members of managed interfaces must be public and therefore are also publicly accessible from COM. There is no mechanism for selectively removing members of a managed interface from the COM representation of that interface.

Classes are also exposed to COM through the class interface. The class interface contains all the public, non-static members of the class. Class members that are private, protected or static are not accessible through the class interface. The same accessibility rules apply the member being access through the IDispatch portion of the class interface.

Users can access the static or protected member of a class from COM by wrapper the access to those member in another public non-static member. For example:

public   class IHaveStatics
{
   public static void StaticMethod() {…}
   public void InstanceStaticMethod() {
      IHaveStatics.StaticMethod();
   }
}

There is no mechanism for limiting the visibility or accessibility of a member through the IDispatch interface that the runtime exposes.

Member level conversion

Managed types may contain methods, properties, fields and events all of which are exported COM in one form or another.

Method Conversions

Classic COM clients expect to make method calls passing familiar COM data types as parameters and receiving HResults in return. NGWS classes on the other hand have no restriction on return types (and in fact don’t use HResults) and prefer to implement methods using familiar NGWS types.

In order to satisfy both models, every method of a managed type has an NGWS signature and an implied COM signature. The two signatures are typically very different. NGWS runtime clients interact with the server using the NGWS runtime signature while (possibly at the same time) COM clients interact with the server using the COM signature. The server implements the method with the NGWS runtime signature and the runtime marshaling service is responsible for providing a stub with the COM signature the delegates the call to the managed method.

HResult Translation

A managed signature is converted to an unmanaged signature by changing the managed return value to an out, retval parameter and changing the type of the unmanaged return value to HResult. For example, the DoSomething method might have the following signatures:

Managed Signature

long DoSomething(long i);

Unmanaged Signature

HRESULT DoSomething([in] long i, [out, retval] long *rv);

Notice that the COM signature returns an HResult and has an additional out parameter for the return value. The return value from the managed implementation is always returned as an out parameter added to the end of the unmanaged signature and the unmanaged signature always returns an HResult. If the managed method has a void return, the out retval parameter is omitted. For example:

Managed Signature

void DoSomething(long i);

Unmanaged Signature

HRESULT DoSomething([in] long i);

Under some circumstances, its preferable the leave the managed signature unchanged. The PreserveSignatureAttribute can be used to do this. For example:

Managed Signature

[PreserveSignature] long DoSomething(long i);

Unmanaged Signature

long DoSomething ([in] long i);

Having two distinct method signatures makes it easy for the class to be used by both COM and runtime clients seamlessly. In fact, both COM and Runtime clients can use a NGWS class simultaneously. The author of the class only ever implements the managed signature. The only place the COM signature is ever used is within the type library generated for the class.

Overloaded Methods

While the NGWS runtime does support overloaded methods, the IDispatch interface relies solely on method name for binding rather than the complete method signature and therefore is not capable of supporting overloaded methods. However, in order to provide access to overloaded methods of a type, TlbExp decorates the names of overloaded methods with an ordinal number so that each method name is unique. For example,

Managed Signature

interface IFoo {
public:
   void DoSomething();
   void DoSomething(short i);
   void DoSomething(long l);
   void DoSomething(float l);
   void DoSomething(double l);
}

Unmanaged Signature

interface IFoo {
   void DoSomething();
   void DoSomething_1(short i);
   void DoSomething_2(long l);
   void DoSomething_3(float l);
   void DoSomething_4(double l);
}

The COM signature for the methods appear as a single DoSomething method followed by a series of decorated DoSomething_x methods where x starts from 1 and continues for each overloaded form of the method. Note that some of the overloaded methods may be inherited from a base type. There is no guarantee that overloaded methods will retain the same number as a give type is versioned.

While Runtime clients can use the overloaded form of the method, classic COM clients will have to access the decorated methods. Object browsers would display all forms of the decorated method along with the method signature so that developers can choose the correct form of the method.

The late bound client can also call IDispatch::GetIdsOfNames() passing in the decorated name to get the dispid of any overloaded method.

Property and Field conversions

Managed types (classes and interfaces) can also have properties and fields. A managed property is of a specific data type that may have an associated get method and set method. The get and set methods are defined separately just like any other method. For example, the following code shows an interface containing a Height property. Classes that implement the interface are required to provide a get and set method for the property.

A managed field is simply a data member of a managed class. Fields can only appear on classes. In Managed C++ for example:

__managed __interface IMammal {
public:
   __property IMammal *Mother;
   __property IMammal *Father;
   __property int Height;
   __property int Weight;
};

__managed class Human : public IMammal {
   public:
      int Age;
      __property IMammal *Mother {get{…;};set{…};};
      __property IMammal *Father {get{…;};set{…};};
      __property int Height {get{…;};set{…};};
      __property int Weight {get{…;};set{…};};
};

When exporting managed properties to a type library, the property set method becomes a [propput], and the get method becomes a [propget]. The name of the property is the same as the managed property name.

If the property type is a class or interface, the property set method becomes a [propputref], and the parameters are given an added level of indirection.

If the property has no get or the set method, the property is omitted from the type library.

Managed fields are also exported to the type library. The runtime marshaling service automatically generates the get and set methods for all public fields. Fields are always have both a get and a set function.

interface IMammal : IDispatch {
        [propget]    HRESULT Mother([out, retval] IMammal** pRetVal);
        [propputref] HRESULT Mother([in] IMammal* pRetVal);
        [propget]    HRESULT Father([out, retval] IMammal** pRetVal);
        [propputref] HRESULT Father([in] IMammal* pRetVal);
        [propget]    HRESULT Height([out, retval] long* pRetVal);
        [propput]    HRESULT Height([in] long pRetVal);
        [propget]    HRESULT Weight([out, retval] long* pRetVal);
        [propput]    HRESULT Weight([in] long pRetVal);
        [propget]    HRESULT Age([out, retval] long* pRetVal);
        [propput]    HRESULT Age([in] long pRetVal);    
};

Event Conversions

Events that are sourced by managed types can be sinked by unmanaged types. Managed types implement events using a delegate base event model. For example, the following Button class implemented in C# sources the Click and the Resize event:

delegate void ClickDelegate(int x, int y);
delegate int ResizeDelegate ();

public class Button
{
   public event ClickDelegate Click;
   public event ResizeDelegate Resize;
   public void OnClick(int x, int y) { if (Click != null) Click(x, y); }
   public int OnResize() { if (Resize != null) return Resize() else return 0; }
   public void Init() {;}
}

When exported to a type library, the exporter makes the following conversions:







The complete listing of the metadata produced for the Button class is shown below:

[uuid(…), …]
interface _ClickDelegate : IDispatch {
   // all member of type _Object
   // all members of type _Delegate
    [id(0x6002000b)] HRESULT Invoke([in] long x, [in] long y);
};

[uuid(…), …]
interface _ResizeDelegate : IDispatch {
   // all member of type _Object
   // all members of type _Delegate
    [id(0x6002000b)] HRESULT Invoke([out, retval] long* pRetVal);
};

[uuid(…), …]
interface _Button : IDispatch {
   // all member of type _Object
    [id(0x60020004)] HRESULT add_Click([in] _ClickDelegate* handler);
    [id(0x60020005)] HRESULT remove_Click([in] _ClickDelegate* handler);
    [id(0x60020006)] HRESULT add_Resize([in] _ResizeDelegate* handler);
    [id(0x60020007)] HRESULT remove_Resize([in] _ResizeDelegate* handler);
    [id(0x6002000a)] HRESULT Init();
};

[uuid(…), …]
coclass Button {
   [default] interface _Button;
   interface _Object;
};

Unmanaged applications that want to sink events that originate in managed code can implement the delegate interfaces defined by the exporter and pass them to the managed objects add and remove methods in order to register as an event sink.

Unfortunately, managed events cannot sink with COM connection points using the metadata shown above. In order to use COM connection points, and additional event interface must be defined and identified as a source interface with the ComSourceInterfacesAttribute as shown below.

using System.Runtime.InteropServices
using “ButtonEventsLib”;  // definition of the ButtonEvents interface

delegate void ClickDelegate(int x, int y);
delegate int ResizeDelegate ();

[ComSourceInterfaces(“ButtonEventsLib.ButtonEvents”)]
public class Button
{
   public event ClickDelegate Click;
   public event ResizeDelegate Resize;
   public void OnClick(int x, int y) { Click(x, y); }
   public int OnResize() { return Resize(); }
   public void Init() {;}
}

When an event source interface is provided, the exported type library will list the interface as a source interface for the coclass that sources the events. The event interface is typically defined in a COM type library. In that case, the generated type library will also import the type library containing the definition of the source interface (see example below).

importlib("ButtonEvents.tlb");

[uuid(…), …]
coclass Button {
   [default] interface _Button;
   [default, source] interface ButtonEvents;
   interface _Object;
};

By providing an event source interface, unmanaged clients can more easily sink to the events sourced by managed classes with COM connection points. For details on this technique, see the section 4.9.10 below.

COM Visibility of members

Parameter and Field level conversion

In the process of converting methods, properties and fields, various data type conversions take place. For example, the managed String field may be converted to a BSTR field when exported to COM. For details on these type conversions see Appendix A.