Coder Social home page Coder Social logo

rantanen / intercom Goto Github PK

View Code? Open in Web Editor NEW
63.0 63.0 7.0 2.53 MB

Object based cross-language FFI for Rust

License: MIT License

Rust 62.19% Batchfile 0.11% C++ 30.84% C# 2.94% PowerShell 0.16% CMake 2.89% C 0.06% Shell 0.19% Handlebars 0.61% Vim Script 0.01%

intercom's People

Contributors

fluxie avatar rantanen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

intercom's Issues

Use AST comparison for expansion tests

There are a pile of expansion tests at intercom-attributes/tests. Currently the tests are validated using string comparison.

String comparison works fine for ensuring the result is expected, but unfortunately it requires the code to match down to whitespace.

The tests are meant to ensure the code is syntactically equivalent. The current code formatting is defined by rustc's --pretty option. It would be better to perform the comparison on AST level or after some normalization based on AST. This would allow the target code to be more readable without having to chase the code format style that rustc results in.

There are few alternatives for implementing this:

  1. Generate the sources as they are now, but run them through rustfmt before comparison.
  2. Generate the sources as they are now, but parse them using syn and do the equal checking using PartialEq (==) on the AST.
  3. Some combination of the two.
  4. Extra handling for stripping comments to allow the target source files to include comments that wouldn't be present in the expanded source.

The trade-offs here are between ease of writing code that passes when it should versus ease of figuring out what is wrong when the code fails. The current string comparison using difference crate makes the errors easy to decipher given the diff-formatting. Pure AST based == comparison would result in a rather bad error messages.

Clean up the internal error types

There's a stupid amount of internal error types. Ideally we should have one major error type that we expose from our public APIs (currently intercom-build and intercom-utils). All other types should be well managed, consider !crate failure.

Make CoCreateInstance usable with Intercom

CoCreateInstance isn't that important for us, as the primary Windows interoperability API is winapi-rs. However there are some Windows COM APIs that we would like to use internally and pulling the whole winapi-rs as a dependency while we already implement most of the needed bits ourselves feels a bit silly.

The biggest issues for supporting external COM APIs are:

  • CoCreateInstance support. This is rather trivial to implement for ComItf::new for example.
  • Data types. For now we've mostly been going with the "We support only what we support" approach, which works fine for new APIs that have their primary definition in Intercom itself. However if we want to use existing APIs, we need to support the data types these APIs support. The two big issues on this front are SAFEARRAY (#8) and various string types (#27).

Split C++ tests into raw and wrapper tests

I was looking into enabling exceptions and interface_wrappers tests for Windows and I'm struggling to find a clear way to do this - being a bit afraid of the #import test_lib.dll and #include "test_lib.h" conflicting with each other.

While it would be possible to have all of these tests under the same project/solution/whatever you want to call it, I would prefer the clarity of having every test in cpp-raw come with the same prelude and having the same support classes available to it - instead of opening a random.cpp file and then having to scan includes to see whether that specific file has stuff like CreateInstance available to it or whether it should use ClassFactory::create.

I'm proposing the following:

cpp-raw

  • Minimal "external" requirements. (InitializeRuntime, UninitializeRuntime and CreateInstance).
  • Use of raw interfaces in all of their terrible usability with raw HRESULT values, out parameters, etc.
  • Goal is to ensure the Rust-C++ bridge works on a binary level.
  • Compilation for as many targets as possible: Visual Studio C++, cmake GCC, possibly cmake llvm and cmake Visual Studio in the future.
  • Intercom linkage in various ways: Visual studio #import, Intercom generated headers with dynamic loading, Intercom generated headers with static lib.
  • This is not the "This is how we expect you to use Intercom" scenario. Instead this is the "If this doesn't work, then the nicer things don't work either" scenario.
  • Perhaps platform specific code, such as ensuring GetErrorInfo/SetErrorInfo work on Windows, malloc works on Linux.

cpp-wrapper

  • The full intercom-cpp dependency.
  • Might be limited to "supported" use cases, such as inability to pass BStrs to wchar_t arguments (if BStrs are even supported through intercom-cpp).
  • Compilation primarily through CMake - both for gcc and Visual Studio. We might provide a native VIsual Studio solution, but I don't see a need for it at the moment. We've already proven that native visual studio solution works with cpp-raw. Including a header shouldn't matter.
  • Preferrably no platform specific code. The goal of intercom-cpp is to work on every platform.
    • Small bits like string handling might still be there since those seem to be inherently platform specific and we don't really want to prevent the platform specific use.
    • Stuff like GetErrorInfo/SetErrorInfo should be hidden behind a cross platform API under intercom-cpp which would use those methods for exceptions on Windows while something else would get used on Linux/elsewhere.

@Fluxie Opinions?

Make #[com_library] into a proc macro

The #[com_library] should be a crate level attribute. Currently it is not, because I had trouble making it such due to hygiene issues or some such.

Once we get syn 0.12 in, we should try to turn it into a crate level attribute again.

Fix memory leaks

Decided to check the test output on TravisCI. To my surprise Valgrid reported:

==9307== LEAK SUMMARY:
==9307==    definitely lost: 672 bytes in 12 blocks
==9307==    indirectly lost: 43 bytes in 3 blocks

I was expecting leaks to fail the test run so haven't paid that much attention to memory management. There might be some missing Releases somewhere. Hopefully that's all we are missing - although I wouldn't be surprised if some of the InterfacePtr-ComItf-ComRc conversions end up leaking references.

We should fix those leaks and then try to get Valgrind to fail the test run if it finds leaks so we will find these issues faster the next time.

Maintain span information through attribute expansion

Currently the attribute expansion in intercom-attributes stringifies the tokens and then generates new tokens from the strings. This loses the original span information and causes all error messages to point to the attribute instead of the original source code.

As the attribute expansion doesn't alter the existing items in any way it should be possible to maintain the original tokens in the output. This should in theory maintain the span information and result in better compilation errors.

Support other string types

Current plan

https://github.com/Rantanen/intercom-site/blob/master/content/docs/types/strings.md


Legacy thoughts

Currently String is converted to BStr.

It might be more sensible to reduce the automation in this sense. Perhaps use the following mappings:

  • BStr - BSTR
  • OsString - WCHAR* on Windows, char* on Linux.
  • String - char* everywhere. Essentially being std::string compatible.

Eager implement for AsRef/From/etc. on BStr would be needed for this to be sensible.

On Windows BStr would be allocated with SysAllocString while everything else would use the CoTaskMemAlloc APIs. Not sure what the equivalent on Linux would be, if any. The fallback for both systems would be to use the #6 APIs once we get those.

Implement COM registration

Currently we only support registration free COM. This is mainly because we don't have code to implement the COM registration in registry.

#[com_library] attribute should expand the DllRegisterServer and DllUnregisterServer implementations to write the necessary bits into registry during regsvr32 call.


  • Expand DllRegisterServer and DllUnregisterServer during #[com_library] expansion.
  • The #[com_library] gains only the class names from the attribute list. If we need interface names as well during the DllRegisterServer/DllUnregisterServer then we need the #[com_class] to expand into DllRegisterServer_Xyz or similar functions that DllRegisterServer delegates to.

Support collection data structures

We need some kind of collection support. One option would be implementing COM SAFEARRAY <-> Rust [T] slice.

At least the following things should be possible:

Rust

// Collection parameters and return value.
fn method( &self, values : [u8] ) -> Vec<bool> { ... }

COM clients

var arr = list.ToArray();
foreach( var item in foo.Method( arr ) ) { ... }

Even if we need to go with the SAFEARRAY approach, we don't need to support the full SAFEARRAY to begin with. We could even make the assumption that the SAFEARRAYs can only be used in methods that return ComResult (or similar) and thus the SAFEARRAY conversion methods can yield E_INVALIDARG in bad cases to report invalid use of SAFEARRAYs.

Figure out what to do with ComBox/ComRc/ComItf

Currently we have three "smart pointers": ComBox, ComRc, ComItf

ComBox<T>

Implements IUnknown for the Rust structs. Contains vtables, reference counts and the Rust struct data. Essentially this is the memory layout of the full COM object we have in memory.

ComRc<T>

Reference countable handle to the ComBox

ComItf<T>

COM interface pointer for which we implement the delegating calls from Rust to COM methods.


ComItf and ComBox are clear in their responsibility. ComItf is a pointer to a COM interface through which we can call the methods on the interface as Rust methods. ComBox is the heap allocated memory layout for the COM object that is able to reference count itself and clean up if required.

ComRc however is a bit more tricky. Its primary reason was to hide the ComBox as an implementation detail. However now that we have two different COM object handles that both would need reference counting, the use of ComRc needs to be re-evaluated. One option would be to expose the ComBox as a public API type and instead of having ComRc<Foo> we'd have ComRc<ComBox<Foo>> or ComRc<ComItf<Foo>>. However this results in rather verbose types.

We need to figure out if there is a way to alias both "reference to a ComBox<Foo>" and "reference to a ComItf<Foo>" into ComRc<Foo>? If this isn't possible, then we need to figure out whether we need ComBox as a public type at all or whether we could always use ComItf in its place? Essentially merging ComRc and ComItf.

We also need to figure out how to get the underlying private data struct from ComItf. This could be through a private interface that allows us to do something similar to ComItf::get_data::<RustStruct>( com_ptr )

Support both dynamic and static linking for C++

Static linking would be nice in general, but the real reason I want it is supporting kcov on circleci.

Currently circleci has trouble measuring coverage results from our C++ integration tests. I suspect this is due to kcov not catching the dynamically loaded library: SimonKagstrom/kcov#230

An "easy" workaround for us would be to run the unit tests with static linkage for coverage. That would also allow testing dynamic linking on normal tests and static linking during coverage runs. (Of course the dynamic loader wouldn't get coverage results, but at least everything else would).

Figure out how to inject the type library into the dll

We could use midl to compile the generated idl into a type library.

Injecting the tlb into the final dll would at least a couple of neat possibilities:

  • Referencing the DLL in C# directly without having to use tlbimp to generate the interop assembly manually first.
  • The ability to implement IDispatch using DispInvoke and DispGetIDsOfNames.

Create `build.rs` utilities for generating IDL/manifest/etc.

The current IDL/manifest story is the use of intercom-utils binary to generate these and then use them in a stand alone fashion.

As we want to embed the IDL (in the form of typelib) and manifest in the final library binary, we would need to generate these two files in build.rs during compilation. This means turning the current intercom-utils into a library crate that can be invoked from build.rs.

We will still want to have the command line tool for things like generating cross language bindings (such as the C++ headers) that are not needed during build. We'll probably keep the idl/manifest generation there as well as there might be use cases for these and we'll have the code for that anyway.

(Unless we change manifest generation to use mt.exe on Windows and base it on the idl. We don't need the manifest on other platforms anyway as far as I can tell.)


  • Hack together a build.rs for the test_lib that builds the IDL, turns it into a typelib with midl.exe and embeds this as a resource into the final dll.
  • Extract most of the build.rs into a new intercom-build crate.
  • Figure out what to do with the current intercom-utils crate.
    • We'll probably want to get rid of this crate as it is. We can have intercom build both a lib and a binary instead.

Support IDispatch

We could derive the support for IDispatch interface when implementing COM interfaces. This would allow using Intercom components from dynamic scripting languages.

Unfortunately this requires a lot of other things, such as support for VARIANTs, etc. Not something that will be arriving immediately.

Syntax errors shouldn't crash the compilation

Intercom attempts to parse the source code in various places. Both before the actual build in build.rs and during the build through the attributes.

If we fail to parse the source code, currently we panic, which prevents the compilation.

This also prevents any proper error messages.

It would be better to emit a warning/error and ignore the rest of the bits. We'll trust in rustc failing the compilation on its own.

Allow automatic TLBID/IID/CLSID specifier

The various #[com_...] attributes require GUIDs as parameters. These are used to define the various IID_..., CLSID_..., etc. global GUIDs.

Currently the user needs to specify them here but they aren't seemingly used anywhere else.

It would be more convenient if the user was able to specify the GUIDs with something like;

#[com_class( AUTO_GUID, IComInterface )]
pub struct Foo { ... }

When the com_class is expanded (intercom-attributes/src/lib.rs, expand_com_class), instead of reading the GUID from the attribute as is, the GUID is generated in a deterministic way based on the current crate and the item name.

ie. something along the lines of:

AUTO_GUID => Guid::random_seed( env!("CARGO_PKG_NAME") + current_item_name )

Tasks:

  • Implement deterministic GUID generation. This should be in the intercom::Guid.
  • Accept AUTO_GUID when parsing the GUID attribute parameter.
  • Ensure the attributes (intercom-attributes) and the IDL generation (intercom-utils) result in the same GUIDs.

All of this should happen at runtime during attribute expansion or IDL generation. There should be no major changes in the generated code.

Allow com_class with no CLSID

Similar to #1, we should allow specifying [com_class] with no CLSID. This would result in a full CoClass, etc. but withotu CLSID the class cannot be constructed through the class factory.

The type can still be used as a return type from functions, etc.

ComResult should be Result<_, ComError>

We should surface the ComError better in Rust. This means making it available as the Err-value in ComResult.

Though for performance reasons we will need to either...

  • Make retrieving the error details (message, etc.) retrieve the IErrorInfo on demand, or...
  • Add ComHResult<..> type that contains only the HRESULT.

The former would result in simpler intercom type system. However if the on-demand stuff happens much later the error message might have been overwritten already. The latter would allow us to resolve the ComError for ComResults immediately and also allow ComError to be send/sync given it would be immutable. (On-demand resolution would prevent send since the error message needs to be resolved in the thread where it happened).

Put the struct data into a RefCell within the ComClass

Currently the COM calls are skipping borrow checkers and there are no real safeties against calling multiple &mut self methods in parallel.

We should try to achieve the following:

  • If all of the implemented interfaces/etc. are defined as &self methods, the underlying type will be ImmutableComClass which has the type without a RefCell. This allows more performant calls (no borrow checking). The user can use such types for immutable data or for performance critical mutable data at which point they are responsible for handling the mutable data with their own RefCells.
  • If any of the implemented interfaces/etc. use &mut self, the underlying type will be MutableComClass which has the type within a RefCell. This enables runtime borrow checking.

This might need us to expand the #[com_class] attribute with something like #[com_class_mut] or #[com_class(IReadOnlyInterface, mut IWriteInterface)]

[Discussion] Interoperability with winrt-rust crate?

I'm the main author of the winrt-rust crate, which is somewhat closely related to COM (because WinRT is based on COM). There are some interesting relationships with this crate, for example I'm currently also offering a very simple BSTR wrapper (although that's basically never needed in WinRT and probably should not be part of the winrt-rust crate).

With winrt-rust it's currently not possible to write your own components, only consume the WinRT API (using wrapper code generated by a C# tool). Perhaps the necessary glue code for writing custom components could be (partially) shared by intercom and winrt-rust?

Unfortunately I don't really know if there are any actual use cases of interoperability between classic COM and WinRT (because I've never used classic COM), but I think it's worth discussing anyway ;)

Take item publicity into account when generating globals

#[com_interface(...)]
pub trait T { }

#[com_class(...)]
struct S;

Should result in:

  • pub IID_T as T is pub
  • priv CLSID_S as S is priv

Otherwise we'd need to make everything pub as the (currently) pub functions, vtables, etc. would end up exposing the private items, which Rust will complain about.

Strongly typed raw COM pointers

Instead of using RawComPtr everywhere, we should probably have things like RawIUnknownPtr or just IUnknownPtr which wraps a RawComPtr.

This would allow impling traits directly on these pointers. Currently we have ComItf for this - but strong pointer types would reduce (or remov?) the need for that.

Need a way to handle type systems in `ComItf`/`ComRc`

Currently the pointer wrappers: ComItf and ComRc have Rust traits as generic parameters but on the low level they carry COM pointers.

This worked while we had one-to-one mapping between Rust traits and COM interfaces. However now that we have multiple type systems our ComItf might need to point to one of the two COM interfaces. This becomes problematic especially when handling ComItf/ComRc as return values/parameters.

Logically when a ComRc is returned from a Rust method, the the caller would expect a COM interface pointer to the Automation interface of the returned object if the method they received it from uses Automation type system - on the other hand a C++ caller using the Raw type system would expect to receive a Raw type system interface pointer.

This means that the ComItf we return from Rust needs to be a fat type which stores the object as is and is able to resolve either the automation type system or raw type system when we are converting the Rust return value into raw COM return value in the tyhandlers.

Hide Windows API behind a feature

Currently we have #[cfg(windows)] and #[cfg(not(windows))] in few places. This makes it impossible to exercise the non-Windows code when compiling on Windows.

It would be better if these were configured using features instead - this would allow building Intercom on Windows without the dependencies on Windows API and thus achieving the same compilation results as Linux got. Making it less likely to break Linux builds by accident.

Stronger concept of com interface

Currently things like the IID is available only as a convention of having the IID_ prefix.

We could turn this into a trait instead where the IID of an interface was available through something similar to IInterface::iid_of() or ( IInterface as IIDOf )::iid_of() if the former doesn't work.

This would allow us to do all kinds of fancy things, such as providing a strongly typed version of query_interface. Something similar to:

let new_interface = old_interface.query_interface::<INew>()

// Given
fn query_interface<T : IIDOf>( &self ) -> T {
    self.query_interface( T::iid_of() )
}

Define and implement proper C++ exception hierarchy

We could model our exception as follows:

std::logic_error
->intercom::logic_error

  • -> intercom::no_such_interface
  • -> ...

std::runtime_exception
-> intercom::runtime_exception

  • -> ...

This was the next task I was planning to do after getting the initial wrapper implemented. I saw that you already changed some of the exceptions to runtime_exception. However, I think that at least "No such interface" exception needs to be derived from std::logic_error.

For supporting dynamic_cast-like construct I would prefer a try-method: try_query_interface, for example. Especially in MS-COM calling Queryinterface and checking for possible E_NOINTERFACE is a valid method for checking if a COM object implements certain functionality which makes this feature pretty much mandatory.

Consensus on what cross platform support for Intercom means

  • Must the binary interface change?
    • Call convention must change between stdcall and C because of compiler differences and stdcall being required on Windows.
  • Should the binary interface change?
    • Other than the call convention, no other interface changes.
  • What to do with error values?
    • Opened a separate issue #33
  • Can/should we still call it COM?
    • It's still following Component Object Model binary interface standard. I'm fine downplaying its importance, but I don't think it's technically wrong. Even Microsoft is referring to their COM implementation as "Microsoft COM".
  • Can/should we still call this Intercom?
    • For now, yes. But specifically "Intercom", not "InterCom" - this is to downplay the COM part.

My vision

If the binary interface doesn't need to change because of compiler differences, then it shouldn't change. At least there shouldn't be any functional changes in how the parameters and return values are handled. The traits-to-vtables translation logic is the most complex part of Intercom. It would be better if we didn't add more complexity into it by adding more ways to handle how Result<T,E>s and such are converted from traits to extern-compatible method signatures.

For similar reasons we would keep the basic error values the same. Current S_OK, E_NOINTERFACE, E_POINTER, E_INVALIDARG and E_FAIL would stay. Possibly E_NOTIMPL as well. These are error values that we need for the base interfaces, such as DllGetClassObject, CreateInstance and QueryInterface. Also numeric error codes as return values is a concept that is common for various platforms.

Currently we define some other error codes in addition to these. These include codes like E_ACCESSDENIED but also STG_E_FILENOTFOUND, etc. These we might want to make platform specific - maybe even remove the current definitions and rely on the raw OS errors as exposed by Rust. These are needed for converting errors anyway - not something that we rely on internally.

Finally, when it comes to naming, I would still keep referring to the communication protocol as COM. As I mentioned in #18, there is prior art for COM on other platforms in:

However this would be strictly limited to "COM protocol" or "COM interface" instead of COM platform/API/SDK/framework, which might imply runtime services which we do not provide. Also any terminology related to OLE, OLE Automation, Automation, ActievX, etc. should be avoided unless specifically describing compatibility solutions targeting these technologies.

If we accept COM to describe the C++-virtual-table-compatible-binary-protocol, then I don't see why we couldn't keep using Intercom as the crate name. It's ambiguous enough that even without COM I could make a case for it!

  • Inter-(platform/language/operation) COM
  • Inter-(platform/language) Communication
  • Intentionally ambiguous Rust-C++ Object Model \:D/

Handle tuples as result success type

Tuples should map to multiple [out] values. No [retval] will be present in this case.

fn foo( &self ) -> Result<bool, E>;
// No tuple; Normal retval
// > HRESULT Foo( [out, retval] bool* __out );

fn foo( &self ) -> Result<(), E>;
// Unit tuple; No return values of any kind
// > HRESULT Foo();

fn foo( &self ) -> Result<( bool, u8, f32 ), E>;
// Tuple; Multiple out values
// > HRESULT Foo( [out] bool* __out1, [out] uint8_t* __out2, [out] float* __out3 );

Figure out what to do with VARIANTs in raw type system

Currently not supported. Should we support these?

Support would require multiple raw VARIANT structs, which differ on the string types - and possibly the array types in the future.

Currently all interface pointers within VARIANTs are of Automation type system, but this is somewhat easy to change.

One proposal is to have

VARIANT_raw and VARIANT_automation structs. VARIANT_automation is the same as Microsoft's VARIANT struct. VARIANT_raw would replace BSTRs with char*s, etc. Rust would need to define both of these raw structs. The friendly intercom::Variant would then implement conversion into and from both of these.

The low level parameter support would come just by having the tyhandler::VariantParam defining the underlying com_ty as the respective raw struct. We'll use into/from and com_into/com_from for conversions anyway so that should magically just work once the com_ty handles the type system.

The big downside here is that we'd need to come up with two different names for these in intercom-cpp. The global VARIANT is already reserved in Windows programming so that kind of needs to stay. On the other hand Intercom-cpp might want to use intercom::VARIANT as the raw version, which is more likely to be used with intercom C++ code anyway.

Or should we have intercom::VARIANT_raw and intercom::VARIANT_automation as the two concrete types and then some flags to define which typedef intercom::VARIANT into one of those?

Support IErrorInfo

Currently we properly support only ComResult<T> or Result<T, HRESULT> as the return types from Rust functions.

We should also include support for Result<T, E> through IErrorInfo. We still need HRESULT as the return value from the method so we will require HRESULT : From<E> for the error types. Also given IErrorInfo is able to return error description, we should require E: Fmt or at the very least E: Debug.

The technical details will probably include "error info slots" on ComBox for each implemented interface - or at least those interfaces that include Result<T,E> types.

Given we will need HRESULT: From<E>, we probably also need to change HRESULT from pub type HRESULT = i32 type alias into a proper pub struct HRESULT( i32 ) type to prevent the automatic i32 <-> HRESULT aliasing.


Expected tasks:

  • Include error info slots on the ComBox.
    • These should behave similar to our current vtable_list. We probably need to define ErrorInfoSlots or similar dependent type in the CoClass trait that gets defined in the #[com_class] expansion.
    • Given #[com_class] expansion doesn't get to examine the actual methods, we'll define a slot per interface even if the interface doesn't include Result<T,E> return values.
    • The slot should have space at least for a Option<Box<Fmt>> or similar structure. Whatever is the trait that we use to acquire the description if needed.
  • Edit the COM methods generated during #[com_impl] expansion to reset the error info when the COM method is called and store the error info if the call to the Rust method was a failure.
    • This might need a new ReturnHandler in the returnhandlers.
    • The return handlers need to be able to write the error result into the error slot. The cleanest way to do this is probably by taking a let &mut error_slot = ... reference to the interface specific error slot in the #[com_impl] method expansion and then considering the error_slot a global value in the return handlers. Otherwise we'd need to pass the interface name to the ReturnHandler so that they know which slot to write the possible error.
  • #[com_class] needs to include the IErrorInfo support for query_interface. This should be done in similar way to how we do IUnknown implementation. Try to keep as much of the IErrorInfo implementation static as possible so we could implement it in intercom crate instead of having to rely on code generation to define it.
  • Plus any IDL changes that we might need.

If IErrorInfo is properly implemented, we should get proper error messages in C#. This should be included in unit tests.

Support ComRc return values

Currently ComResult<ComRc<T>> fails as ComRc would need a default value for type handling.

Instead of using Default::default we should define EmptyReturnValue or similar trait. We can have a blanket impl that delegates to Default::default if that is available. For other types this would allow us to implement a somewhat unsafe 'default value' for return values.

For example ComRc might instantiate ComRc that contains a null pointer. This invalidates the ComRc contract (of never being null) so implementing such Default value would be extremely unsafe - on the other hand if it is a very special EmptyReturnValue trait that is used only for (somewhat sane) uninitialized values it makes more sense.

The value() fn of that trait could also be unsafe to further mark it as being outside the usual invariants.

Attempt to get rid of #[feature(fundamental)]

Currently we need #[feature(fundamental)] to be able to impl Deref for ComItf<IComInterface> in the user crate as neither Deref or ComItf are user types.

I'm not even sure why we really need this impl as ideally we'd want to move that impl into Intercom crate so that the type system has some sort of an idea that any ComItf<T> can act as if it was a trait object &T.

I believe the following architecture might achieve that last part without needing fundamental:

// Library crate:

type RawComPtr = *mut std::os::raw::c_void;

// The basic ComItf container.
struct ComItf<T : ?Sized> {
    ptr : RawComPtr,
    phantom : std::marker::PhantomData<T>,
}

// Trait for COM interface traits.
// Handles turning a ComItf<T> into a T reference.
trait ComDelegate {
    fn wrap( ptr : &ComItf<Self> ) -> &Self;
}

// Allow Deref into trait for the ComItf.
// Works as long as the trait implements ComDelegate, which is used for wrapping.
impl<T: ComDelegate + ?Sized> std::ops::Deref for ComItf<T> {
    
    type Target = T;
    fn deref( &self ) -> &Self::Target {
        T::wrap( &self )
    }
}

// User crate:

// Normal trait.
trait IFoo {
    fn bar( &self, a : u32 ) -> u32;
}

// Impl ComDelegate (intercom type) for user trait.
// No need for #[feature(fundamental)]
impl ComDelegate for IFoo {
    fn wrap( ptr : &ComItf<IFoo> ) -> &(dyn IFoo + 'static) {
        ptr
    }
}

// Impl IFoo (user trait) for ComItf<IFoo>.
// No need for #[feature(fundamental)]
impl IFoo for ComItf<IFoo> {
    fn bar( &self, a : u32 ) -> u32 {
        ( self.ptr as usize + a as usize ) as u32
    }
}

fn main() {

    let ptr : RawComPtr = 1234 as RawComPtr;
    let itf = ComItf::<IFoo> { ptr, phantom: std::marker::PhantomData };
    println!( "{}", itf.bar( 10 ) );
}

Implement string allocator

Currently our BSTR implementation uses SysAllocString, etc. methods from the Windows API. This is the de facto way to allocate/deallocate BSTRs on Windows.

For cross platform purposes we need an alternative way. One option would be to statically implement a IStringAllocator interface with a known GUID. This allows the COM client to acquire a IStringAllocator interface that provides FreeString(BSTR) method. This method can then allocate/deallocate the BSTR properly.

The only important consideration with the implementation is that the allocation and deallocation is done in a compatible way with each other. Thus if we allocate the BSTR from Rust heap, the deallocate must free the BSTR into Rust heap. If we use SysAllocString then the freeing must be done with SysFreeString.


Tasks:

  • Define the interface as intercom::IStringAllocator or similar trait. The implementation can be static intercom::StringAllocator as well. We might not even need the trait but instead just use the implicit impl instead.
  • Modify the #[com_library] code gen to include the StringAllocator in the ClassFactory constructor.
  • Implement string allocation with SysAllocString in Windows and something else (such as Rust heap/Box) on other platforms.

Implement cross platform API selection

Related to #31, #18

Current plan

No features, such as winapi. Features suffer from the inheritance problem with crates. If crate B wants to use Intercom to invoke COM component, written with Intercom, without winapi and B is a dependency of A that specifies winapi feature for Intercom then B will end up using winapi as well.

Instead we will have two separate mod, with (draft) names:

  • intercom::platform::native
  • intercom::platform::generic
Effect ...::native + Windows ...::native + Linux/OSX/etc. ...::generic + Any OS
extern "stdcall" "C" "C"
BSTR alloc SysAllocString* Rust heap Rust heap
BSTR dealloc SysFreeString or #6 #6 #6
IErrorInfo GetLastError ??? <- Same ???
ComRc::create() CoCreateInstance Not available Not affected by mod

This allows the Intercom user to specify per interface whether that interface should use the platform conventions or "cross-platform" conventions, which is essentially whatever we need to use on non-Windows platforms anyway as they don't have the COM infrastructure built in.

create() and CoCreateInstance

We will still have differences like ComRc::create() that will be only available on Windows. This won't be affected by the chosen intercom::compat module as ComRc is defined under the root intercom module. We could define the create() as a ComRcExt trait similar to OsStringExt, but I don't see any reason for this for now.

What makes the create()-case different from extern is that create() is purely new implementation. It doesn't change anything, like extern "C" does for extern "stdcall". As such, cross platform projects can just opt to not use it. Compiling it in on Windows doesn't prevent these projects from working on Linux if they never end up calling it.

For now we'll just #[cfg(windows)] impl the create() on ComRc and be done with it.

Implement support for context parameters such as TypeSystem

There should be no technical reason to have 1:1 mapping between Rust and COM parameters. We are already coming up with COM OUT values based on Rust return types.

We could further skip some of the Rust method parameters on the COM level and fill these in by Intercom.

The primary (and for now only) use case for this would be the type system argument. Being able to tell what type system is being used to invoke a function has immediate use case in writing unit tests for Intercom itself. But it would also be required if someone wants to write methods by managing interface pointers, etc. themselves.

The big open question here is what kind of marker we'll want for such parameters. Below are some options in terms of the intercom::TypeSystem enum.

/// Assume certain types are always contextual types.
fn foo( &self, ts : intercom::TypeSystem );

/// Require an attribute on the parameter.
fn foo( &self, #[intercom::context] ts : intercom::TypeSystem );

/// Use a generic type instead.
fn foo( &self, ts : intercom::CallContext<intercom::TypeSystem> );

Personally I'm in favor of the first one. Intercom needs to be aware of these types anyway and as such we can be certain that these types are not COM-compatible (no #[repr(C)]).

The one dirty aspect with this is that when Rust wants to call such COM methods, there's no clean way to communicate to the caller that these things are not needed...

Although things like ts should probably affect the preference of which TS to use in case the ComItf we are using to call these methods happens to have both Automation and Raw interfaces available. Though is it an error if the asked interface is not available? There's no real TypeSystem::Any variant and we don't want one for output purposes.

Detect HRESULT/Non-HRESULT functions and adapt error handling in the generated layer

There are various internal error conditions that we would love to signal forward. These include things such as:

  • Two *mut pointers pointing to same data and need to be converted to &mut, which violates Rust reference rules.
  • Rust implementation panic!ed.
  • String data needs UTF-8 conversion but isn't UTF-8.
  • etc.

If the method is one that returns HRESULT (or specifically Result<...>), Intercom can ride on top of this and convert these error conditions into HRESULTs automatically. However if the method is not one that returns HRESULT, there's no other option than to panic! (well... the other option would be to ignore the errors, but that's even worse).

For this reason Intercom would need to recognize the method type and behave accordingly.


Maybe we could generate something similar to:

fn hresult(...) -> HRESULT {
    type ErrType = HRESULT;  // <- Depends on method return value.
    if( some error happened ) {
        return ErrType::From( E_FAIL as an example );
    }
    ...
}

fn not_hresult(...) -> bool {
    type ErrType = IntoPanic;  // <- Depends on method return value.
    if( some error happened ) {
        return ErrType::From( E_FAIL as an example );
    }
    ...
}

This would minimize the amount of changes the return value would cause for the function. It would need to be taken into account in one place only. IntoPanic would implement:

impl<T> From<T> for IntoPanic {
    fn from(t:T) -> IntoPanic {
        panic!(..);
    }
}`

Ensure Intercom can represent any COM interface without ty/return handling

First issue that relates somewhat to winapi-rs interoperability \o/

Intercom has always aimed for making COM code look rustful. This involves automatically converting between COM strings and Rust strings, mapping Result types to [out]/[out, retval] parameters and HRESULTs, etc.

This works great when you get to decide the interface. \o/

However... there are cases, where it would be nice to use Intercom with existing interfaces, which need to be defined as they are. This could involve things like having [out] parameters in the middle of the parameter list, etc.

The problem with these bits is that they do not play well with the Intercom tyhandling/returnhandling. [out] params for example, are currently defined with Result return types, which automatically insert these at the end of the parameter list.

On the other hand, from Rust point of view (ignoring IDL bits), there is no real difference between a u32 return type and a *mut u32 parameter. As a result it should be possible to define the interfaces by hand using the raw pointer parameters, etc. and skipping the return handling by using HRESULT as the return value instead of a Result<...> of some kind.

Use crate name as `[com_library]` name

Currently #[com_library] attribute requires the type library name as the first parameter. It is strongly recommended that this library name matches the crate name - so strongly that it makes no sense for those two to differ.

It would be better to drop that parameter completely and just use the crate itself as the library name.

There is a slight issue with this though. We need a way to find out the name of the crate using the attributes. The usual way to find the crate name programmatically is to use the env variables with the env! macro, which inspects these at compile time.

When it comes to attribute expansions, if we used the env! macro, we'd end up with the intercom-attributes crate name. However as the crate compilation is runtime for attribute expansions, we should be able to use the env variables as is through the std::env module.

This same issue applies to #1.


Tasks:

  • Read the crate name during com_library attribute expansion.
  • Convert the name from library_name to LibraryName for COM capitalization purposes. This conversion is already done in intercom-utils for IDL generation. The conversion should be moved to intercom-common/utils.rs so we can share the code.
  • Ensure intercom-utils handles the new com_library attribute.

GCC/LLVM support

In theory the COM libraries created using intercom should be cross-platform compatible. There's no major Windows dependencies involved here.

The big issue is the lack of midl for GCC/LLVM. As such the intercom-utils would need a new cpp-classes or similar command that processes the Rust sources similar to what idl does, but instead of producing an idl file, the command produces C++ sources similar to the various _i.c and _i.h sources that midl produces.

These sources need to implement C++ classes that result in compatible vtable definitions with the COM vtables. Ideally they should use the same "stdcall" calling convention that we already use in the intercom functions for Windows COM. However we do have the option of using "stdcall" on Windows and something else that behaves better on Linux as long as that "something else" is binary compatible between GCC/LLVM/Rustc.


Tasks:

  • Implement the C++ header generation in intercom-utils.
  • Enable the cpp unit tests for Linux builds on Travis. This involves wrapping the CoInitialize/CoUninitialize in some sort of IntercomInit/Uninit method that is able to call the CoInitialize/etc. on Windows and loads the type library using dlopen or similar on Linux. The CoCreateInstance needs to be abstracted so that on Linux we acquire the ClassFactory and construct the instance manually.
  • Enable unit testing on Travis with both GCC and LLVM.

Define cross-platform error handling model for detailed errors

On Windows we have GetErrorInfo/SetErrorInfo - however we want a cross platform way of doing this so we can support the ComError (IErrorInfo) - based error messages on other platforms as well.

One option is to implement this as a "static service class", similar to the string allocator of #6.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.