Merge pull request #49 from microsoft/readme

Add front-page README
This commit is contained in:
Ryan Levick 2019-09-13 13:25:59 +02:00 коммит произвёл GitHub
Родитель 6c234eba78 f30c5e0870
Коммит c6838f2a20
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
2 изменённых файлов: 230 добавлений и 1 удалений

133
README.md
Просмотреть файл

@ -2,4 +2,135 @@
[![Build Status](https://dev.azure.com/microsoft-rust/com-rs/_apis/build/status/microsoft.com-rs?branchName=master)](https://dev.azure.com/microsoft-rust/com-rs/_build/latest?definitionId=1&branchName=master)
A one stop shop for all things related to [COM](https://docs.microsoft.com/en-us/windows/win32/com/component-object-model--com--portal) programming in Rust.
A one stop shop for all things related to [COM](https://docs.microsoft.com/en-us/windows/win32/com/component-object-model--com--portal) programming in Rust.
This library exposes various macros, structs and functions to the user for both producing and consuming COM components in an idiomatic manner.
## Usage
### Defining a COM interface
To both consume or produce a COM component through an interface, you will first need to generate the Rust representation of said interface. The `com_interface` macro is the main tool for automatically generating this Rust representation.
```rust
#[com_interface(00000000-0000-0000-C000-000000000046)]
pub trait IUnknown {
unsafe fn query_interface(
&self,
riid: winapi::shared::guiddef::REFIID,
ppv: *mut *mut winapi::ctypes::c_void
) -> winapi::shared::winerror::HRESULT;
fn add_ref(&self) -> u32;
unsafe fn release(&self) -> u32;
}
#[com_interface(EFF8970E-C50F-45E0-9284-291CE5A6F771)]
pub trait IAnimal: IUnknown {
fn eat(&self) -> HRESULT;
}
```
Short explanation: This generates the VTable layout for IUnknown and implements the trait on ComPtr so that it dereferences the correct function pointer entry within the VTable.
### Consuming a COM component
Interaction with COM components are always through an Interface Pointer (a pointer to a pointer to a VTable). We represent such an Interface Pointer with the `ComPtr` struct, which helps manage the lifetime of the COM component through IUnknown methods.
```rust
use com::Runtime;
// Initialises the COM library
let runtime = Runtime::new().expect("Failed to initialize COM Library");
// Get a COM instance's interface pointer, by specifying
// - The CLSID of the COM component
// - The interface of the COM component that you want
// runtime.create_instance returns a ComPtr<dyn IAnimal> in this case.
let mut cat = runtime.create_instance::<dyn IAnimal>(&CLSID_CAT_CLASS).expect("Failed to get a cat");
// All IAnimal methods will be defined on ComPtr<T: IAnimal>
cat.eat();
```
### Producing a COM component
Producing a COM component is relatively complicated compared to consumption, due to the many features available that we must support. Here, we will walk you through producing one of our examples, the `BritishShortHairCat`.
1. Define the struct containing all the user fields you want.
- Apply the `#[co_class(...)]` macro to the struct. This will expand the struct into a COM-compatible struct, by adding COM-specific fields.
- You can then use the attribute argument `com_implements(...)` to indicate inheritance of any COM interfaces. The order of interfaces declared is important, as the generated vpointers are going to be in that order.
```rust
use com::co_class;
#[co_class(com_implements(ICat, IDomesticAnimal)]
pub struct BritishShortHairCat {
num_owners: u32,
}
```
2. Implement the necessary traits on the COM struct (in this case, `BritishShortHairCat`).
```rust
impl IDomesticAnimal for BritishShortHairCat {
fn train(&self) -> HRESULT {
println!("Training...");
NOERROR
}
}
impl ICat for BritishShortHairCat {
fn ignore_humans(&self) -> HRESULT {
println!("Ignoring Humans...");
NOERROR
}
}
impl IAnimal for BritishShortHairCat {
fn eat(&self) -> HRESULT {
println!("Eating...");
NOERROR
}
}
```
3. You will have to define a constructor with the below signature. This provides us with a standard constructor to instantiate your COM component.
```rust
fn new() -> Box<BritishShortHairCat>
```
Within this constructor, you need to
- Call the provided `BritishShortHairCat::allocate()` function, passing in your user fields in the order they were declared. **IMPORTANT**
- The `allocate` function in this case has the signature:
```rust
fn allocate(num_owners: u32) -> Box<BritishShortHairCat>
```
```rust
impl BritishShortHairCat {
pub(crate) fn new() -> Box<BritishShortHairCat> {
let num_owners = 20;
BritishShortHairCat::allocate(num_owners)
}
}
```
## Notes
There are many advanced concepts in COM that our library aim to support. Relevant documentation on these advanced features can be found within the [docs] folder.
[docs]: https://github.com/microsoft/com-rs/tree/master/docs
## FAQ
**Is there IDL support?**
As a foundation, we are attempting to create a library that doesn't necessarily rely on having an IDL file. However, it is in the pipeline for future improvements. We will have a command-line tool that will parse the IDL into the required macros.
**Which threading models do this library support?**
As of v0.1, this library is only confident of consuming/producing COM components that live in Single-Threaded Apartments (STA). This Threading Model assumption is used in several places, so producing/consuming these COM components in a Multi-Threaded environment will not work.
**Is there out-of-process COM support?**
Currently, we only support production of in-process COM components. Also, production of a COM component can only be in the DLL format. There will be plans to enable out-of-process COM production as well as producing in the .EXE format.

98
docs/AGGREGATION.md Normal file
Просмотреть файл

@ -0,0 +1,98 @@
# Aggregation
COM allows you to aggregate other COM objects. This means exposing their interfaces as your own, allowing code reuse.
If you plan to use aggregation, then we assume you are somewhat familiar with the inner workings of COM. This explanation assumes the same.
We will walk you through producing a `WindowsFileManager`, which aggregates another COM object, the `LocalFileManager`. Specifically, we choose to aggregate the `ILocalFileManager` interface from `LocalFileManager`.
1. Define an aggregable coclass. Here we use the `#[aggr_co_class(...)]` macro instead of the `co_class` one.
```rust
use com::aggr_co_class;
#[aggr_co_class(com_implements(ILocalFileManager)]
pub struct LocalFileManager {
user_field_one: u32,
user_field_two: u32,
}
impl ILocalFileManager for LocalFileManager {
fn delete_local(&self) -> HRESULT {
println!("Deleting Locally...");
NOERROR
}
}
impl LocalFileManager {
pub(crate) fn new() -> Box<LocalFileManager> {
let user_field_one = 20;
let user_field_two = 40;
LocalFileManager::allocate(user_field_one, user_field_two)
}
}
```
2. Define the class that will aggregate `LocalFileManager`. This can be aggregable or not.
- You are responsible for instantiating your aggregates.
- In order for us to generate the correct QueryInterface implementation, you need to tell us which interfaces **EACH** aggregated object is exposing. To do this, you use a `aggr(...)` attribute argument for **EACH** aggregated object. The order in which these interfaces are defined doesn't matter.
```rust
#[co_class(com_implements(IFileManager), aggr(ILocalFileManager))]
pub struct WindowsFileManager {
user_field_one: u32,
user_field_two: u32,
}
impl IFileManager for WindowsFileManager {
fn delete_all(&self) -> HRESULT {
println!("Deleting all by delegating to Local and Remote File Managers...");
NOERROR
}
}
```
3. Define the class constructor.
Here, we chose to instantiate the aggregate in the constructor. Each aggregated object is initialised as NULL, until the aggregate is instantiated, through the `set_aggregate_*` methods. Hence, you could choose to instantiate aggregates whenever you want.
In order to instantiate the aggregate, you will need to
- Create the aggregated object as an aggregate. This can be done through CoCreateInstance,
- Supply the resultant IUnknown interface pointer to the appropriate `set_aggregate_*` methods. For each base interface exposed, there will be a separate `set_aggregate_*` method defined. Setting aggregate for one base interface will set it for every base interface exposed by the same aggregated object.
In this case, we are exposing only the `ILocalFileManager` as aggregated interfaces. This means a `set_aggregate_ilocal_file_manager` will be generated, which we can use to instantiate the underlying aggregated object.
```rust
impl WindowsFileManager {
pub(crate) fn new() -> Box<WindowsFileManager> {
//Initialise the COM object.
let user_field_one = 20;
let user_field_two = 40;
let mut wfm = WindowsFileManager::allocate(user_field_one, user_field_two);
// Instantiate object to aggregate
// TODO: Should change to use safe ComPtr methods instead.
let mut unknown_file_manager = std::ptr::null_mut::<c_void>();
let hr = unsafe {
CoCreateInstance(
&CLSID_LOCAL_FILE_MANAGER_CLASS as REFCLSID,
&*wfm as *const _ as winapi::um::unknwnbase::LPUNKNOWN,
CLSCTX_INPROC_SERVER,
&IID_IUNKNOWN as REFIID,
&mut unknown_file_manager as *mut LPVOID,
)
};
if failed(hr) {
println!("Failed to instantiate aggregate! Error: {:x}", hr as u32);
panic!();
}
// Instantiate aggregate that exposes ILocalFileManager.
wfm.set_aggregate_ilocal_file_manager(unknown_file_manager as *mut IUnknownVPtr);
wfm
}
}
```