COM Interfaces

While mostly useful on the Windows sub-platform, Interface types on any Island sub-platform, optionally, be declared as COM-compatible interfaces for interaction with other COM-compatible environments.

Most interfaces are native Island (or Cocoa or Swift) interfaces, and not compatible with COM, by default. If an interface is declared to descend (directly or indirectly) from IUnknown, it automatically becomes a COM-compatible interface. Alternatively, it can also be marked with the [COM]/@COM Aspect (and will then automatically descend from IUnknown).

COM interface must specify a unique GUID, using the Guid Aspect.

type
  [COM, Guid('4daa7c37-aa4d-4c39-9c49-7a97cc0129f1')]
  IMyInterface = public interface 
    method DoSomething(i: Integer);
  end;
[COM, Guid("4daa7c37-aa4d-4c39-9c49-7a97cc0129f1")]
public interface IMyInterface
{
    void DoSomething(int i);
}
@COM, @Guid("4daa7c37-aa4d-4c39-9c49-7a97cc0129f1")]
public interface IMyInterface {
    func DoSomething(i: Int);
}
@COM, @Guid("4daa7c37-aa4d-4c39-9c49-7a97cc0129f1")]
public interface IMyInterface
{
    void DoSomething(int i);
}

On Windows, pre-defined COM interfaces declared by the Windows SDK are automatically imported as COM interfaces and available for use; this includes IUnknown, IDispatch and other well-known APIs. On other platforms, IUnknown is declared in Island RTL.

Implementing COM Interfaces

When implementing a COM interface on a class, the compiler will automatically emit the necessary infrastructure to let the class be used in a COM-compatible manner. This includes adding (hidden) implementations for the three IUnknown members.

  MyClass = public class(IMyInterface)
  public
    method DoSomething(i: Integer);
  end;
public class MyClass : IMyInterface
{
    public void DoSomething(int i) {}
}
public class MyClass : IMyInterface {
    public func DoSomething(i: Int) {}
}
public class MyClass : IMyInterface
{
    public void DoSomething(int i) {}
}

Instances of the object can then freely be cast to COM-compatible interfaces, and assigned to fields/variables or passed to API parameters of those types, including external Windows APIs or APIs created in other programming languages such as C, C++ or Delphi.

COM Interfaces can only be implemented by native Island classes, not by classes other Object Models such as Cocoa or Swift, nor on records.

Working with COM Interface references

A COM Interface reference can be obtained mainly in two ways:

  1. by casting an Island object to a COM interface and/or assigning it to a variable/field of a COM Interface type
  2. by calling an external API that creates or returns COM interfaces

In the second case, it will be likely that the obtained interface is not implemented by an Island class, but via some other development too chain, such as C, C++ or Delphi. But this does not matter, as COM is specifically designed as a tool-independent binary standard.

When working with a COM Interface referendes, the compiler automatically activates the COMRC Life-Time Strategy, which will use calls to the AddRef/Release methods of IUnknown to manage the lifecycle of the object.

The comiler will also use QueryInterface, when casting to from one COM interface to another.

Casting Back to Island Objects

For COM Objects implemented as native Island Objects, it is possible to cast back from a COM Interface to the underlying object (be it to Object, or a more concrete type). Of course, this cast will fail of the object in question is not an Island object (but was implemented in a different development tool).

var x: IUnknown := ...;;
var y: Object := x as Object;
IUnknown x = ...;
object y = (Object)x;
let x: IUnknown = ...
var y: Object = x as Object
IUnknown x = ...;
object y = (Object)x;

Under the hood, this implemented using a special Guid ({5b9e00e5-c1da-4f0d-8d92e06842bd5df5}) being passed to QueryInterface, that will be handled only by Island objects. But that is an implementation detail that should not affect the developer.

Note that casting back to from COM Interfaces to native Island Objects should be done sparingly, and only when you can be sure the original object was instantiated within your code base.

Objects obtained from external sources (such as a COM Object instantiated via a CLSID stored in the registry) might be implemented using Island, but may have been created using a different/binary-incompatible version of Elements, or linked against a different version of core libraries such as Island RTL.

Implementation Details

The infrastructure for COM support is shared between special logic handled by the compiler and helper types and APIs in Island RTL.

  • ElementsCOMInterface is the record used at runtime for COM Interfaces; it contains a reference count, the VMT and the original Island Object reference.
  • COMHelpers contains functions to convert COM to Island and back
  • __elements_Default_AddRef, __elements_Default_Release and IUnknown_VMTImpl_QueryInterface are the default implementations for the three IUnknown members.
  • COMRC is the compilers Life-Time Strategy for COM support.

Since COM uses reference counting, the ElementsCOMInterface has it's own reference count for that instance, independent of Garbage Collection. Additionally, the class itself has global reference count to make sure the object does not get garbage collected until all COM references are released. This works by creating a GC handle for the object when reference count becomes larger then zero, and releasing it when it goes down to zero again.

Note: The new COM support described here supersedes the old ComInterface and ComClass aspects, as they conflict with the new model.

See Also