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.
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; }
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.
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(); }
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.
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.
If interface is not from a know object, the marshaler then needs to determine how a new wrapper should be created: