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!

Reference Types

Reference types are either interfaces or classes. Some classes contain explicit layout information with the StructLayoutAttribute. be either formatted or unformatted. Interfaces and classes are both marshaled through a proxy interface generated by the marshaler. Formatted class can also be marshaled as structures if marked with UnmanagedType.Struct.

Unmanaged Type Description of unmanaged format
UnmanagedType.Interface A COM Interface.
UnmanagedType.Struct A C style structure

Table 10 – Class Types

Because the layout of formatted types is fixed, the marshaler is able to safely and predictably move data between the managed and unmanaged representation of these types. The marshal does not support marshaling of unformatted types as structures. Doing so would cause problems if the layout of the type changed over time. Instead, unformatted type can only be accessed via and interface.

Passing an unformatted value type through the marshaler will cause an exception.

Marshaling Interfaces

Managed classes and interface are always marshaled through a proxy created by the runtime marshaling service. Every interface has both a managed and an unmanaged representation. The runtime SDK provides tools for generating one representation form another representation. The proxy is then responsible for marshaling calls from on representation to the other. This includes any necessary conversion of data that is passed along with the call.

While the managed and unmanaged representations of the interface share the same identity (based on GUID), their layout and method signatures differ quite a bit. The unmanaged definition of the interface is usually described in a type library while the managed definition of the interface is described in NGWS runtime metadata. See the Interop Spec for details on how the two interfaces differ.

Interfaces are marshaled between managed and unmanaged code as interface. Every interface has both a managed and an unmanaged representation. All references to an interface from managed code refer to the managed representation while references from unmanaged code refer to the unmanaged representation:

For example, consider the following managed code with the IPoint interface:

interface IPoint {
   void SetXY(int x, int y);
}

class Graphics {
   void SetPoint (IPoint p);
   void SetPointRef (ref IPoint p);
   IPoint GetPoint ();
}

When exported to a type library, the IPoint interface is converted to a COM Interface. All references to the Point interface are maintained as they were in managed code.

interface IPoint : IUnknown {
   HRESULT SetXY(int x, int y);   
}

interface _Graphics {
   …
   HRESULT SetPoint ([in] IPoint *p);
   HRESULT SetPointRef ([in out] IPoint **p);
   HRESULT GetPoint ([out retval] IPoint **p);
}

public coclass Graphics {
   [default] interface _Graphics;
}

Marshaling Classes

Classes can only be marshaled by COM Interop and are always marshaled as interfaces. In most cases the actual interface used to marshal the class is what is known as the “class interface” (see the Interop Spec for details on overriding the class interface with an interface of your choice).

Every managed class has an implicit class interface that contains all the members of the class. The class interface can only be used from COM; managed client never see or know about the class interface and there is never a need to explicitly define a class interface in managed code. The class interface contains all the methods and properties of the class and carries the same name as the class with a prepended underscore. The fields of the class are also available through getters and setters on the class interface.

Clients can late bind against the class interface because it always derives from IDispatch. Like any other interface, the class interface is exposed via a proxy. The proxy is responsible for marshaling the calls between the class interface and the actual members of the managed class.

Importing and Exporting

When an assembly is exported to a type library, a class interface is produced for each accessible class with the assembly. A coclass is also created for the class and the class interface is marked as the default interface for the coclass. All parameter and field references to the class are replaced with references to the class interface for that class. For example:

Managed Types

class Foo {…}
class Bar {
   Void SetFoo(Foo f);
   Void SetFooRef(ref Foo f);
   Foo GetFoo();
}

Generated Type Library

[uuid(…), …]
interface _Foo : IDispatch {…}

[uuid(…), …]
coclass Foo {
   [default] interface _Foo;
}
[uuid(…), …]
interface _Bar {
   HRESULT SetFoo([in] _Foo *f);
   HRESULT SetFooRef([in, out] _Foo **f);
   HRESULT GetFoo([out, retval] _Foo **f)
}

[uuid(…), …]
coclass Bar {
   [default] interface _Bar;
}

When a type library is imported to produce an assembly, the coclasses within the type library are converted to managed classes that have all the member of the coclass’es default interface. For example, the managed class Foo below has the same method m() as the IFoo interface. All references to the coclass that appear as parameters or fields are converted to references to the managed class (as in the SetFoo() method below).

Type Library

[uuid(…), …]
interface IFoo {
   void m{};
}
[uuid(…), …]
coclass Foo {
   [default] interface IFoo;
}

[uuid(…), …]
interface IBar {
   HRESULT SetFoo([in] Foo *f);
   HRESULT SetFoo([in, out] Foo **f);
   HRESULT GetFoo([out, retval] Foo **f)
   HRESULT SetIFoo([in] IFoo *f);
   HRESULT SetIFoo([in, out] IFoo **f);
   HRESULT GetIFoo([out, retval] IFoo **);
}

Generated Assembly

interface IFoo {
   HRESULT m()
}

class Foo {
   HRESULT m()
}

interface IBar {
   void SetFoo(Foo f);
   void SetFooRef(ref Foo f);
   Foo GetFoo();
   void SetIFoo(IFoo f);
   void SetIFooRef(ref IFoo f);
   IFoo GetIFoo();
}

Passing Classes to the NGWS Runtime

When a managed class is passed to COM, the Interop marshaler automatically wraps the class with a COM proxy and passes the class interface produced by the proxy on to the COM method call. The proxy then delegates all calls on the class interface back to the managed object. The proxy also exposes other interfaces that are not explicitly implemented by the class. Interfaces like IUnknown and IDispatch are automatically implemented by the proxy on behalf of the.

Passing Classes to the NGWS Runtime

Coclasses are not typically used as method arguments in COM. Instead, a classes default interface is usually passed in place of the coclass itself.

When a interface is passed into managed code, the Interop marshaler is responsible for wrapping the interface with the proper wrapper and passing that wrapper on to managed method. Determining which wrapper to use can be difficult.

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 5 interfaces has only one wrapper. The same wrapper exposes all 5 interfaces. If 2 instances of the COM object are created then 2 instances of the wrapper are created.

In order for the wrapper to maintain the same type throughout its lifetime, it’s important that the Interop marshaler determine which is the correct wrapper the first time an interface exposed by the object is passed through marshaler. That means that the marshaler must try to determine the identity of the object by look at one of the interfaces the object implements. Consider the example below where the marshaler needs to determine that the Foo wrapper should be used to wrap the I3 interface that was passed into managed code.

When the interface I3 is first passed through the marshaler, the marshaler first checks to see if the interface is coming from a known object. This would occur in two possible situations.

  1. The interface I3 is being implemented by another managed object that happened to be passed out to COM elsewhere. The marshaler can readily identify interfaces exposed by managed objects and is able to match the interface up with the managed object that provides the implementation. The actual managed object is then passed to the method and no wrapper is needed.
  2. The interface I3 is being implemented by an object that has already been wrapped. To determine if this is the case, the marshaler queries the object for its IUnknown interface and compare the returned interface to the interfaces of other objects that are already wrapped. It the interface is the same as that of another wrapper, the objects are determined to have the same identity and the existing wrapper is passed to the method.

If interface is not from a know object, the marshaler then needs to determine how a new wrapper should be created:

  1. The marshaler will first query the object for the IProvideClassInfo2 interface. If provided, the marshaler will use the CLSID returned from IProvideClassInfo2.GetGUID() to identify the coclass providing the interface. With the CLSID, the marshaler can potentially locate the wrapper class in one of two possible ways
    1. If the assembly containing the metadata for the class has already been loaded, the marshaler will use the CLSID to locate the metadata for the class and create the proper wrapper. This approach assumes that the metadata for the class was generated with TlbImp prior to the interface being passed in.
    2. If the assembly containing the metadata has not been loaded, the marshaler will check the registry to see if metadata for the CLSID has been registered. In order to register the metadata for the class, TlbImp would have to be run prior to the interface being passed in.
  2. The marshaler will then query the interface for the IProvideClassInfo interface. If provided, the marshaler will use the ITypeInfo returned from IProvideClassInfo.GetClassinfo() to determine the CLSID of the class exposing the interface. With the CLSID the marshaler will use to process described above to locate the metadata for the wrapper.
  3. Finally, if all attempts to identify the class fail, the marshaler will wrap the interface with a generic wrapper class called System.__ComObject.