Intercom enables the user to write reusable components in Rust, that are binary compatible with the Component Object Model interface standard. These components can be used in any language that supports static COM components, including C++, C# and VB.Net.
Rust COM server:
pub use intercom::*;
#[com_library(Calculator)]
#[com_class(Calculator)]
struct Calculator {
value: i32
}
#[com_interface]
impl Calculator {
pub fn new() -> Calculator { Calculator { value: 0 } }
pb fn add(&mut self, value: i32) -> ComResult<i32> {
self.value += value;
Ok(self.value)
}
}
C# COM client:
class Program
{
[STAThread]
static void Main( string[] args )
{
var calc = new Calculator.Interop.Calculator();
calc.Add( 10 );
var result = calc.Add( 100 );
Console.WriteLine( result );
}
}
Intercom isn't the first time Rust is playing with COM interfaces. There are at least two other crates that are related to COM support.
winapi-rs
contains various COM interface definitions for Microsoft's Windows APIs.winrt-rust
provides support for Windows Runtime (WinRT) APIs for Rust.
Ideally these crates would play well together. If you encounter usability issues in using these crates together with Intercom, feel free to create an issue describing the problem.
While COM is traditionally a Windows technology, Intercom is aimed to provide a platform independent way to perform method calls between Rust and other languages. This gives us two guiding principles for Intercom:
- There exists a subset of Intercom APIs/conventions, that allows users to write Intercom components that can be compiled and used on any platform.
- On platforms that have their own APIs for COM, Intercom components are compatible with the platform APIs and expectations.
In practice this means that on Windows, for example, Intercom will allocate
strings using SysAllocString
. This allows Intercom components to be used
with Windows technologies, such as .Net COM interop.
However the platform independent subset will require strings to be allocated
and deallocated using IIntercomAllocator
COM is based on the use of virtual tables (also known as dispatch tables).
There is one exported symbol, DllGetClassObject
, which the clients (pieces of
code using the COM module) use to acquire the initial IClassFactory
interface. Once the client has the IClassFactory interface available, the rest
of the method calls can be done through the COM interfaces.
Each COM interface is represented by an object containing a virtual table and
any data that the server (the library implementing the interfaces) requires.
When the client tries to invoke a method on the interface, it will use the
virtual table to resolve the correct method address and performs the call
specifically using pascal calling convention (__stdcall
in most C++ compilers
and "stdcall"
in Rust).
The Intercom libraries are built over Rust proc macro attributes. Currently there are four attributes available:
com_library!(LIBID, Classes...)
- A required attribute that implements the exportedDllGetClassObject
entry point as well as theCoClass
for theClassFactory
.[com_interface(IID)]
- An attribute that specifies atrait
or animpl
as a COM interface. The attribute results in the virtual table struct to be defined for the interface.[com_class(CLSID, Itfs...)]
- An attribute defined on astruct
. This attribute implements the necessaryCoClass
for the struct, which allows constructing the reference countedComBox<T>
instances on the object.