Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Generate rust code from IDL/TLB/COM dll? #216

Open
bugproof opened this issue Sep 11, 2021 · 20 comments
Open

Generate rust code from IDL/TLB/COM dll? #216

bugproof opened this issue Sep 11, 2021 · 20 comments

Comments

@bugproof
Copy link

bugproof commented Sep 11, 2021

Is there any way to interact with COM API without writing any code manually? In .NET I can just reference COM library and I can use it without writing any code. IIRC there is also a .dll generator for COM libraries made in C++ - Tlbimp.exe. Is such scenario supported with com-rs or do I always need to manually translate everything to Rust (com::interfaces! macro) before I'm able to call any method?

I think it's related but it's not TBL but TLB... #114

@bugproof bugproof changed the title Generate rust code from IDL? Generate rust code from IDL or COM dll? Sep 11, 2021
@bugproof bugproof changed the title Generate rust code from IDL or COM dll? Generate rust code from IDL/TLB/COM dll? Sep 11, 2021
@rylev
Copy link
Contributor

rylev commented Sep 20, 2021

We're currently doing some work to figure out the future of this library and its role in the Windows Rust ecosystem along with windows-rs. This is something we would most likely eventually support, but it's unlikely to be something we could add anytime soon (due to lack of development and review bandwidth). Unfortunately, for now you'll have to write the interfaces manually.

@kennykerr
Copy link
Collaborator

The Windows crate is beginning to provide this capability. You can provide a .winmd (windows metadata) file that describes a component and the Windows crate will generate the necessary bindings. The Windows crate ships with .winmd files for the Windows API, but eventually you will also be able to declare new APIs directly in Rust. For existing components, there is a tool that will produce a .winmd file by parsing a C/C++ header file. Here for example is a test component that the Windows crate uses to test both Win32/COM and WinRT APIs:

https://github.com/microsoft/windows-rs/tree/master/tests/component

@bugproof
Copy link
Author

We're currently doing some work to figure out the future of this library and its role in the Windows Rust ecosystem along with windows-rs.

So it will be merged with windows-rs eventually or it will become unmaintained?

The Windows crate is beginning to provide this capability. You can provide a .winmd (windows metadata) file that describes a component and the Windows crate will generate the necessary bindings. The Windows crate ships with .winmd files for the Windows API, but eventually you will also be able to declare new APIs directly in Rust. For existing components, there is a tool that will produce a .winmd file by parsing a C/C++ header file. Here for example is a test component that the Windows crate uses to test both Win32/COM and WinRT APIs:

https://github.com/microsoft/windows-rs/tree/master/tests/component

I think generating from TLB would provide more value as not all COM libraries come with C/C++ headers. There are also COM libraries that were written in C#.

@kennykerr
Copy link
Collaborator

I think generating from TLB would provide more value as not all COM libraries come with C/C++ headers. There are also COM libraries that were written in C#.

The .winmd file is the canonical format we use for API metadata. If you can produce a conversion tool that generates a .winmd file from a .tlb that should work just fine, but that's not something we're planning to support. The .winmd format is also relatively easy to produce from C#.

@bugproof
Copy link
Author

bugproof commented Sep 22, 2021

I think generating from TLB would provide more value as not all COM libraries come with C/C++ headers. There are also COM libraries that were written in C#.

The .winmd file is the canonical format we use for API metadata. If you can produce a conversion tool that generates a .winmd file from a .tlb that should work just fine, but that's not something we're planning to support. The .winmd format is also relatively easy to produce from C#.

That's where the trouble begins. I mostly use C# when I need to interact with some COM library. I just add <COMReference> to my .csproj or click Add reference in VS or Rider and things magically work. It will use tlbimp and generate interop .NET DLL. It's extremely straightforward. Now if I wanted to do the same in Rust I don't know where to start. I don't know much about .winmd format and how can I produce .winmd format from existing .NET or native .DLL and how much it takes to setup a workspace that is able to generate Rust code from it because it's not documented anywhere or at least it's not obvious enough.

The github page for the generator also mentions it's exclusively to generate metadata for Windows API and not for any other third party COM libraries registered on your system.

What would be needed is something like tlbimp that generates Rust definitions instead. Or still rely on tlbimp to generate .NET DLL and generate Rust definitions or .winmd from that interop DLL

EDIT: just tried to use winmdexp on generated COM interop assembly for .NET but it didn't work. Many errors. Classic ActiveX/COM libs differ a bit from WinRT

https://github.com/Arnavion/winapi-tlb-bindgen

@mzdk100
Copy link

mzdk100 commented Jan 29, 2024

I would also like to be able to generate .rs code directly from the .idl file.

@kennykerr
Copy link
Collaborator

I'm still looking into this for https://github.com/microsoft/windows-rs

@MarijnS95
Copy link
Contributor

For what it's worth, huge shout-out already to win32metadata + windows-rs for already making it so approachable to turn some C(++) COM headers into sensible and usable Rust :)

Isn't it already possible to go from IDL -> .h -> .winmd -> Rust?

@kennykerr
Copy link
Collaborator

With some effort. I'm hoping to streamline the process to only require the Rust toolchain.

@mzdk100
Copy link

mzdk100 commented Jan 29, 2024

I tried: https://github.com/microsoft/windows-rs/blob/master/crates/tests/component/src/component.idl
It is true that rs can be generated from idl, but it has an intermediate process .idl>.winmd>.rs.
I am using a third-party idl which is very old and cannot be successfully converted to .winmd format, so this solution will not work.

@Wawha
Copy link

Wawha commented Mar 21, 2024

Does someone has a simple example/code to generate a .winmd (or Rust code ideally) from an idl or tlb file?

I try to look at Rafael Rivera post, but for the moment I didn't success to generate an .winmd file (the process is very complicated...).
I'm looking for single commande line, or a sample github project, but I did not find anything until now.

@kennykerr
Copy link
Collaborator

@riverar
Copy link

riverar commented Mar 21, 2024

@Wawha Hey there! I'd be interested in any feedback you have on the article. (I wrote it.)

Here's a sample/skeleton project that generates Rust bindings from Microsoft IDL (https://github.com/riverar/washington-rs). Specifically, the sample demonstrates taking IDL, generating metadata with WinmdGenerator, and using windows_bindgen to generate bindings for use in a Rust crate.

@Wawha
Copy link

Wawha commented Mar 26, 2024

@kennykerr thank you. Unfortunately, I was not able to generate Rust code with the samples "components", because my COM idl was to complicated.

But using the project washington-rs from @riverar, I finally succeed to do it!
I missed that project at first look, because I do not see reference to IDL or COM object in the project description/readme.
Thank you both for your (quick) answer!

@NuSkooler
Copy link

@riverar Hi! I have two .idl files: base.idl with types, and service.idl that includes base.idl and includes a interface with some methods.

I tried following your article + "washington" repo, but I'm running into a issue where only the types in base.idl are present in the generated Rust bindings. The generated .CS and C headers have the full contents, however.

My generate.proj looks like this:

<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.Windows.WinmdGenerator/0.62.23-preview">
    <PropertyGroup Label="Globals">
       <OutputWinmd>../.windows/winmd/My.Project.winmd</OutputWinmd>
       <WinmdVersion>255.255.255.255</WinmdVersion>
       <Idl>./idl</Idl>
       <AdditionalIncludes>$(CompiledHeadersDir);$(Idl)</AdditionalIncludes>
   </PropertyGroup>
   <ItemGroup>
        <Idls Include="$(Idl)/service.idl;$(Idl)/base.idl" />
        <Headers Include="$(CompiledHeadersDir)/service.h;$(CompiledHeadersDir)/base.h" />
        <Partition Include="main.cpp">
            <TraverseFiles>@(Headers)</TraverseFiles>
            <Namespace>My.Project</Namespace>
            <ExcludeFromCrossarch>true</ExcludeFromCrossarch>
            <ShowEmitWinmdOutputDetails>true</ShowEmitWinmdOutputDetails>
        </Partition>
    </ItemGroup>
</Project>

service.idl looks a bit like this:

[
	uuid(e2c414aa-7c07-45a9-b09f-0b1a26ad1e55),
	version(1.0)
]
interface MyService {
	HRESULT MyMethod(
		handle_t hbind,
		[out] PCONTEXT* phContext
		);
};

Any ideas?

@riverar

This comment was marked as outdated.

@riverar
Copy link

riverar commented Sep 6, 2024

@NuSkooler In this case, because of missing inherited IUnknown, WinmdGenerator is treating the methods as flat C functions. And because there is no function-to-library mapping, it ultimately gets dropped.

-interface MyService {
+interface MyService: IUnknown {
	HRESULT MyMethod(
		handle_t hbind,
		[out] PCONTEXT* phContext
		);
};

@NuSkooler
Copy link

@riverar Thanks for jumping on this so quick!

I'm not a COM expert by any means, so I might be misunderstanding something here: The MyService interface in question is a RPC service. In C, this is exposed with RpcServerRegisterIf2 and connected to via RpcBindingFromStringBinding from a remote process. Does this make any difference?

When I add IUnknown to the service interface, many errors about types are generated.

@riverar

This comment was marked as outdated.

@riverar
Copy link

riverar commented Sep 7, 2024

@NuSkooler Here's an RPC example:

base.idl:

import "wtypesbase.idl";

[
	uuid(6e3125bb-9df7-4c8c-976c-0b5626bc8194),
	version(1.0)
]
interface IBase {
	HRESULT BaseMethod();
};

libmappings.rsp (put next to the .proj file):

--with-librarypath
BaseMethod=base_client.dll

winmd output:

// ...
[DllImport("base_client.dll", ExactSpelling = true, PreserveSig = false)]
public unsafe static extern HRESULT BaseMethod([In][Out] void* IDL_handle);

As shown above, you still have the responsibility of compiling the MIDL-generated client stubs (e.g. base_c.c) into a DLL you can call into. I chose base_client.dll as an example.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants