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 COM classes

NGWS applications can bind to COM classes in an early or late bound fashion. 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. 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. 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.

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

NGWS Metadata Generation

In order to early bind to a COM class or interface a NGWS runtime metadata definition of that class or interface must be available. It’s this NGWS runtime metadata definition of the type that managed clients bind against at compile. The runtime also uses this metadata definition to generate the Runtime Callable Wrapper that will wrap an instance of the COM object at execution time. The metadata must be available at both compile time and at execution time.

The metadata for a COM type is no different than the metadata for a other managed type. The same API’s are used to generate it (IMetadataEmit or ReflectionEmit). In fact, a compiler using the either API could generate the exact same metadata. The types defined in the metadata are no different then types that would be found in any managed program other than a few flags that are set to indicate that the type is a COM type.

The metadata can be generated in various ways. Usually it’s generated from an existing type library that describes the types although it doesn’t have to be. Once generated, the metadata may be persisted to a file or it may be used once and discarded. If persisted, the metadata can be examined with tools such as metainfo.exe or ildasm.exe. The next section explains the various options for creating metadata type definitions.

A NGWS runtime metadata definition of a COM type can be generated by:

  1. Converting an existing type library.
  2. Compiling a source code definition of the type using a capable compiler.

The simplest and most common technique is to start with a COM type library and the utility called TlbImp.exe to convert it the NGWS runtime metadata. There is also an API exposed by the runtime that does the same thing but is more is suitable for use from within a development environment. The other option of describing the type in source does not require a type library to start with but is a more advanced technique.

Generating Metadata From a Type Library

Given an existing type library there are two techniques for converting the tlb file to metadata. The first choice is to use the TlbImp utility to do the conversion. This technique is described in the following section. The other option is to use the TypeLibConverter class in the System.Runtime.InteropServices namespace to do the conversion. Both techniques are essentially the same; the output is identical. However, since the TypeLibConverter works with an ITypeLib interface, it can be use to convert an in-memory type library whereas tlbimp.exe must have a persisted tlb library. The TypeLibConverter class is described below and in the reference pages for the type.

For details on the actual conversion that TlbImp and ITypeLibConverter perform see section 5.13.

Using TlbImp (Type Library Importer)

The type library importer is responsible for converting the type definitions found within a COM type library into equivalent definitions in a NGWS runtime 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 NGWS runtime metadata for the types defined within the original type library. This file can be examined with tools such as metainfo.exe or ildasm.exe.

It's often useful to be able to strong name NGWS runtime assemblies (it's impossible to make references to weak named assemblies from strong named assemblies, for example). Therefore, TlbImp also include options for supplying enough information to generate strongly named assemblies.

TlbImp has the following command line syntax:

TlbImp TlbFile [/out: file] [/silent][/unsafe][/originator: file] [/keyfile: file][/keycontainer: name]

Where TlbFile can be the name of any file containing a COM type library. This file can be a .tlb, .dll, .odl, .ocx, .exe file or any other file format with a type library embedded as a resource. The file must be able to be opened with using LoadTypeLib() API.

TlbImp only imports one type library at a time. On rare occasions, a single file may contain multiple type libraries. In such cases, the specific type library to be converted can be specified by append \res to the end of the file name where res is the number of the resource containing the library (i.e. “foo.exe\3”)

Options:

/out: file - The name of the output file that the metadata definitions will be written to. If the option is omitted, the metadata is written to a file with the same name as the type library (not the file but the actual library name within the file) defined within the input file with the .DLL extension. If the output file is the same name as the input file and error is generated to prevent overwriting the typelib.

/silent – Suppress all a console output.

/unsafe – Generates metadata that has runtime security checks suppressed. Call any method defined exposed in this way may pose security risks. This option should not be used unless the author is aware of the risk and responsibilities of expose such code.

/keyfile: file – Signs the resulting assembly with a strong name using the public/private key pair found in file. The key file should be in the formatted generated by the SN utility.

/keycontainer: name - Signs the resulting assembly with a strong name using the public/private key pair found in the key container called name.

/originator: file – Uses the originator key found in file to identity the resulting assembly. The originator key is generated from the public/private key pair supplied with the /keyfile or /keycontainer options if this option is not supplied. This allows an alternate originator key to be supplied to support developer key pair scenarios. The file is in the format generated by SN the utility that is also part of the SDK.

The /keyfile and /keycontainer options both allow assemblies to be strongly named. It only makes sense to supply one of them at a time.

Examples

TlbImp mytest.tlb

Generate a metadata DLL with the same name as the typelib found in mytest.dll and with the .DLL extension.

TlbImp mytest.tlb /out: Mytest.dll

Generate a NGWS runtime metadata DLL with the name MyTest.dll.

Using TypeLibConverter to import a Type Library

The TypeLibConverter class in the System.Runtime.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 importing is contained in the reference pages for the TypeLibConverter class..

Using a Compiler to Create Metadata

When a type library describing a COM class or interface is not available, the only other option is to create a duplicate definition of the class or interface in source code. Using this approach requires 3 things:

  1. A compiler that is capable of generating the proper NGWS class definitions.
  2. A precise description of the interface or coclass being defined.
  3. Knowledge of how to convert from the COM type system to the NGWS runtime type system.

Type library to Assembly Conversion

Common conversions

Library level conversion

When the TlbImp imports a type library, the types defined within the library are automatically placed in a namespace with the same name as the type library the classes came from. For example:

Library AcmeLib {
   interface Widget {};
   coclass Slingshot {};
}

The AcmeLib type library is imported into the AcmeLib namespace. The Slingshot class would be referred to as AcmeLib.Slingshot.

namespace AcmeLib {
   interface Widget {};
   class Slingshot {};
}

Module level conversion

A type library can have one or more modules containing the definitions of constants and methods as shown in the type library below.

Constants defined within modules are imported as public constant static members of a class with the same name as the module they were imported from. 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 module they were imported from. The members are decorated with the necessary attributes to allow them to directly be called from managed code.

COM Type library

[
   uuid(12345678-1234-1234-1234-123456789ABC),   
]
Library TestConstants
{
   [
      uuid(12345678-1234-1234-1234-123456789ABC),   
      dllname(“test.dll”),
   ]
   Module Constants
   {
      const long FRAME_COLOR = 0x10;
      const long WINDOW_COLOR = 0x20;
      const long BUTTON_COLOR = 0x40;
      …
   
      [entry(10) pascal double Square([in] double x);
      [entry(“MessageBox”) pascal int MsgBox([in] BSTR s);
   
   }
}

Converted to

public class Constants
{
   public const static long FRAME_COLOR = 0x10;
   public const static long WINDOW_COLOR = 0x20;
   public const static long BUTTON_COLOR = 0x40;
   
   [DllImport(“test.dll”, 
EntryPoint = “10”)]
   public static extern double Square(double d);

   [DllImport(“test.dll”, 
EntryPoint = “MessageBox”, 
CallingConvention = CallingConvention.StdCall)]
   public static extern int MsgBox(String s)
}

Type level conversion

Interfaces

When an interface is imported into an NGWS runtime assembly, the IUnknown methods and the IDispatch methods are stripped from the interface. 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 as opposed to IUnknown.

Unmanaged Interface

[uuid(…), ]
interface IWidget : IUNknown {
   HRESULT Foo()
   HRESULT Bar()
};

[uuid(…), ]
interface IGadget : IWidget {
   HRESULT Baz()
};

Also notice that the methods of the base interface are also added to the derived interface. In this case the methods Foo and Bar are added to the IGadget interface.

Managed Interface

[Guid(…), InterfaceType(ComInerfaceType.InterfaceIsIUnknown)]
interface IWidget {
   void Foo();
   void Bar();
};

[Guid(…), InterfaceType(ComInerfaceType.InterfaceIsIUnknown)]
interface IGadget : implements IWidget {
   void Foo();
   void Bar();
   void Baz();
};

Classes

Add member of default interface

Imported to managed class

How do we handle duplicate names

Structures

Enumerations

Constants

Constants will not be imported from type library in the PDC.

TypeDefs

Type definitions (typedefs) 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. However, 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. For example, in the following type library, the cl parameter is typed as a BUTTON_COLOR, which is an alias for an integer.

In a type library

Library MyLib {
   typedef [public] int BUTTON_COLOR;

   interface IBar {
      HResult SetColor([in] BUTTON_COLOR cl);
      HResult GetColor([out, retval] BUTTON_COLOR *cl);
   }
   
   coclass Bar {
      [default] interface IBar
   }
}

Converted to

interface IBar {
   void SetColor([ComAliasName(“MyLib.BUTTON_COLOR”)]] int cl);
   [ComAliasName(“MyLib.BUTTON_COLOR”)] in GetColor();
}

class Bar {
   void SetColor([ComAliasName(“MyLib.BUTTON_COLOR”)]] int cl);
   [ComAliasName(“MyLib.BUTTON_COLOR”)] in GetColor();
}

Notice that the actual type BUTTON_COLOR is not defined in the resulting metadata. The parameters that were typed as BUTTON_COLOR in the type library are typed as the underlying type (int in this case) and are attributed with the ComAliasNameAttribute. Arguments on class methods are also decorated with the attribute. Tools could then use the attribute in whatever way is appropriate.

Member Level conversion

Method Conversions

Property Conversions

Event Conversions

COM type libraries can define interface to be used for eventing. Coclasses that source events identify the event interface as with the [source] attribute. The event interface is implemented by the event sink and consumed by the event source. The COM connection point interface (which are not described in the type library) are used to wire the event source to the event sink. The Button class shown below implements the IButton interface and sources the IButtonEvents interface.

interface IButton {
   HRESULT Init();
}

interface IButtonEvents {
   HRESULT Click([in] int x, [in] int y);
   HRESULT Resize([out, retval] int *pRetval};
}

coclass Button {
   [default] interface IButton;
   [source, default] interface IButtonEvents;
}

The NGWS runtime event model differs considerable from COM connection point model. Managed classes that wish to sink events do so by passing a delegate to the event source. They do not use the COM connection points.

The runtime’s interop service can be used to seamlessly bridge the two models. During import, several types are created that allow managed applications to sink events sourced by unmanaged classes using the NGWS runtime event model.

  1. A delegate type is created for each event in the event interface. The delegate name is created by concatenating the name of the event interface, an underscore, the name of the event, and the word “Delegate”. For example, in the type library above, the Click event produces the delegate called IButtonEvents_ClickDelegate. The signature of the delegate is a direct translation of the unmanaged method signature. See method conversions above.
  2. The default interface is import in the usual way. The interface name remains the same. In this example, the interface is called IButton interface.
  3. The event interface is import in the usual way. The interface name remains the same. In this example, the interface is called IButtonEvent.
  4. In addition, a second event interface with “_Event” append to its name is created that has the events as members along with an “add_event” and “remove_ event” method for each event. The class sinking to the event source uses this interface to add itself to the sources sink list. In the example above, IButtonEvents_Event
  5. The class sourcing the events is imported as any other class would be except the class name is prepended with two underscores. The class implements any interface that is explicitly implemented by the COM coclass. It is also decorated with the ComSourceInterfaces attribute which contains the name of the interface that the class sources events for.
  6. A second class is also created for each coclass that sources events. The second class has the same name as the COM coclass, extends the class described in 3 above and implements the managed event interface described in 2 above.

Other types that are defined during the importing process are for internal use by the runtime.

The following types demonstrate the classes and interfaces that what would be generated for the Button class shown above:

// 1 - A delegate for each event
delegate void IButtonEvents_Click(int x, int y);
delegate int IButtonEvents_Resize();

// 2 - Direct import of original default interface 
public interface IButton {
   void Init(};
}
// 3 - Direct import of original event interface - Not of interest to managed sinks
public interface IButtonEvents {
   void Click(int x, int y);
   int Resize(};
}

// 4 - Modified version of the event interface with NGWS runtime events for managed sinks
public interface IButtonEvents_Event {
   IButtonEvents_Click Click;
   IButtonEvents_Resize Resize;
   void Click(IButtonEvents_ClickDelegate};
   void Click(IButtonEvents_ClickDelegate);
   void Resize(IButtonEvents_ResizeDelegate};
   void Resize(IButtonEvents_ResizeDelegate);
}

// 5 - Direct import of original coclass 
[ComSourceInterface(“IButtonEvents_Events”)]
public class __Button : IButton {
   void Init(};
}

// 6 - Extension of the original coclass with events added
public class Button : __Button, IButtonEvents_Event
{
   void Init(};
   event IButtonEvents_ClickDelegate Click;
   event IButtonEvents_ResizeDelegate Resize;

   add_Click(OnClickDelegate d);
   remove_Click(OnClickDelegate d);
   add_Resize(OnResizeDelegate d);
   remove_Resize(OnResizeDelegate d);
}

Parameter and Field conversion

The parameter and field conversion rules used by the type library exported are documented in the Data Marshaling Spec.