idl-free C++/WinRT is here.
Idlgen is a library for generating idl files when implementing WinRT components. With this library, you never have to write a single line of MIDL3 again.
Currently, only C++ is supported.
Use your preferred way (cli/GUI) to install nuget package IdlGen.IdlGen.Cpp
, which can be found on nuget gallery.
On top of the nuget package, you can also install IDE extension to streamline the code generation process. The extension provides a convenient "Generate IDL" command in a context menu. If you want to adopt idlgen incrementally in an existing code base, this extension is a must-have.
Note: The above nuget package is required for the extension to work. Please install it in all of your project in which you wish to generate IDL.
- Add a new runtime class (view model or XAML page) from C++/WinRT template. Or, you can start with existing runtime classes.
- Add
pch.h
andidlgen.h
, and other necessary includes in the header of your implementation type. - Make your implementation class inherits
idlgen::author_class
. - Edit the header file. E.g. add a method.
- Build the project. A custom build step that generates idl files would run before
Midl
. - Viola! The idl file of the implementation type has been updated.
Midl
would then update/generate awinmd
with the modification, and C++/WinRT would pick it up when generating projection. - Made a mistake in header, e.g. forgot to add an import? No worries! Just edit your header and rebuild. Idlgen would pick up your changes and proceed to generate an updated idl. It just works™️.
The library would generate the whole runtimm class definition for you. There should be literally zero edit you need to make on the resultant idl file.
The library could automatically generate the following structures in an ordinary idl files:
- Runtime class definition, if an implementation type inherits
author_class
- All (static) methods
- All (static) events
- Import for supported entities in another header file. Supported entities include
- Implementation type. Suppose
A.h
includesB.h
, andB.h
contains the definition of the implementation type ofB
. Any reference toB
inA.h
(projected or implementation), would causeA.idl
to importB.idl
- Authored type. See Struct, Delegate, Enum
- Implementation type. Suppose
Additionally, idlgen could generate the following with the use of some tag types or configured templates:
- Runtime class base
- Properties
- Struct
- Enum
- Delegate
To specify the base type and interface for a runtime class/interface, developers should specify the base types in the corresponding tag template's template parameters. For runtime class, the tag template is idlgen::author_class
. For interface, idlgen::author_interface
.
For example, to inherit from Page
and implement INotifyPropertyChanged
, an implementation type should inherits idlgen::author_class<Windows::UI::Xaml::Controls::Page, Windows::UI::Xaml::Data::INotifyPropertyChanged>
. To add required interface, an authored interface should inherits idlgen::author_interface<BaseInterface>
.
Template types that has a single template parameter and overloads operator()
can be configured as getter or property templates. When a (static) data member is defined with these types, idlgen would recognize these overloaded operator()
as runtime class methods. When configured, these methods would be considered as getter or setter (property).
To specify a template type as getter, add the qualified name of the template to project property IdlGenCppGetterTemplate
.
To specify a template types as property, add the qualified name of the template to project property IdlGenCppPropertyTemplate
.
See property.md for more details. For ordinary properties with T Property()
or void Property(T)
signature, see Structures Requiring Special Author Help
Template types that overloads operator()
with return type and parameters matching event revoker/registrar are recognized as event automatically.
To generate these structures, define the structure in C++ and inherit the correct authoring tag type according to the following table.
To prevent name collision between projected type and its authored types, you can prefix type name with _
to prevent name collision. Idlgen would trim _
out of type names in idl.
Types which inherit the following tag types are called authored types (similar to how implementation of runtime class are called implemenation types).
Note: These tag types must be inherited as immediate base (i.e. not via an intermediate class) due to bootstrapping.
Structure | Tag type | Note | Example | Result |
---|---|---|---|---|
runtime class | idlgen::author_class |
N/A | struct MainPage : MainPageT<MainPage>, idlgen::author_class {} |
runtimeclass MainPage {} |
struct | idlgen::author_struct |
N/A | struct _Point : idlgen::author_struct { int32_t X; int32_t Y; } |
struct Pointer { Int32 X; Int32 Y;} |
delegate | idlgen::author_delegate |
Overload operator() |
struct _Handler : idlgen::author_delegate { void operator()(int32_t a, int32_t b);} |
delegate void Handler(Int32 a, Int32 b) |
enum | idlgen::author_enum |
Must be scoped enum | enum class _State : idlgen::author_enum {A, B} |
enum State {A, B} |
enum flags | idlgen::author_enum_flags |
Must be scoped enum | enum class _State : idlgen::author_enum_flags {A = 0x1, B = 0x2} |
[flags] enum State {A = 0x1, B = 0x2} |
interface | idlgen::author_interface |
N/A | struct _Interface : idlgen::author_interface { void Method(); } |
interface Interface { void Method(); } |
Note: Runtime class's authoring tag type is also included in the table for the sake of completeness. In this sense, an implementation type is also an authored type, but when this document mention authored types, it usually does not mean implementation type.
- Attributes
- Properties defined without configured getter or property templates
- Import file of projected types, non-authored types referenced in the runtime class
The above structures require the help of class author to generate. The library defined a set of custom attributes, which, when declared on a class or methods, would allow the library to generate specified structures.
Please see Idlgen Custom Attributes for more details.
By default, all generated methods/properties are public. To generate protected methods/properties, make them protected in C++. Then, you need to add the following friend declaration to allow projection to access your protected methods:
friend struct winrt::impl::produce<MyClass, IMyClassProtected>;
Alternatively, you can use attribute to specify a declaration with public accessibility should be protected in idl.
By default, all generated methods/properties are not overridable. To generate overridable methods/properties, make them virtual in C++. For getter/property templates (static) data members, you have to use attribute to specify a declaration should be overridable.
The syntax for idlgen's custom attributes is ordinary C++ attributes, with idlgen
being the namespace.
Below is a table for all attributes and their usage.
Attribute | Args | Description | Applicable On | Example Declaration | Resultant idl |
---|---|---|---|---|---|
import |
"value",... |
Add import statement(s) | class | [[idlgen::import("A.idl","B.idl")]] |
import "A.idl";import "B.idl"; |
attribute |
"value",... |
Add an attribute | class | [[idlgen::attribute("webhosthidden", "bindable")]] |
[default_interface][webhosthidden] |
property |
N/A | The method is a property/All applicable methods in a class are properties | class/method | [[idlgen::property]] |
|
method |
N/A | The method is a method/All methods in a class are methods | class/method | [[idlgen::method]] |
|
hide |
N/A | Hide class or methods or data member | class/method/data member | [[idlgen::hide]] |
|
overridable |
N/A | Make a method/property overridable | method | [[idlgen::overridable]] |
|
protected |
N/A | Make a method protected | method | [[idlgen::protected]] |
|
sealed |
N/A | Make a runtimeclass sealed | class | [[idlgen::sealed]] |
Note: By default, the tool would generate [default_interface]
attribute for an empty class (a class that doesn't have any methods other than constructor) so you don't need to add it.
Due to bootstrapping, Idlgen currently has the following limitation:
-
Authored WinRT projected types must be treated as incomplete types in headers. That is, you can only refer to the name of the type as if it is forward declared. This limitation applies to all projected types, including runtime class, enum, etc. In practice, this means method definitions which use projected types must be defined in .cpp files.
- If you are using visual studio, you can right click affected method and choose "Move definition location" to automatically fix it.
-
Authoring tag types must be inherited as immediate base. That is, the following would generate
interface SomeInterface
:
struct _SomeInterface : idlgen::author_interface {};
but the following does NOT:
struct Base : idlgen::author_interface {};
struct _SomeInterface : Base {};
interface Base
would be generated instead.
- When a header listed in
pch.h
is updated,pch.gch
produced by clang (used by idlgen to speed up subsequent IDL generation) would be invalidated. As a workaround, you could "touch" (e.g. insert a newline and then removing it)pch.h
whenever a downstream header is updated to let idlgen re-buildpch.gch
.
What to achieve in IDL | What to do in header |
---|---|
Hide public event handler in XAML | Make the handler private, then add friend struct ClassT<Class> |
Hide public event handler for any other runtime class (See note) | Make the handler private, then add friend ClassT<Class> |
Hide public overriden methods | Use [[idlgen::hide]] on the method |
Make a class' applicable methods properties by default | Use [[idlgen::property]] on the class |
Make methods protected | Declare them in protected: block, then add friend struct winrt::impl::produce<Class,IClassProtected> |
Make wil::single_threaded_rw_property<bool> Prop overridable |
Use [[idlgen::overridable]] on the property |
Note: The friend syntax is different because for XAML ClassT
is a struct, for any other runtime class it is an alias template.
Run msbuild -target:IdlGenCppGenerateIDL -p:IdlGenCppInclude=MyClass.h -p:IdlGenCppExclude="" -p:Platform=x64
.
If you have the extension installed, right click on the header file and click "Generate IDL".
Name | Description |
---|---|
IdlGenCppGenerateIDL |
Master control to configure whether idl files are generated |
IdlGenCppInclude |
Control which and only files to include |
IdlGenCppExclude |
Exclude the files from generating idl |
IdlGenCppDisableUnknownAttributeWarning |
Disable unknown attribute warning |
IdlGenCppPch |
Pch files. Used to speed up generation |
IdlGenCppGetterTemplate |
Template types of data member which should be generated as getter |
IdlGenCppPropertyTemplate |
Template types of data member which should be generated as property |
IdlGenCppAdditionalDefines |
Additional prepropcessor definitions. Used to define e.g. NOMINMAX |
Idlgen ouputs build logs to Output -> Build window. Look at what is failing and see if you need a rebuild, or encountered any of the following issues.
Idlgen needs to parse WinRT projection header files and other headers as if it's compiling your header, so make sure you have included all necessary headers. It's suggested that you add pch.h
to all header files to ease include effort.
Fix your header and rebuild, and idlgen would re-generate the idl. It should just work™️. If it doesn't, please file a bug.
How it works: Idlgen runs a bootstrap pass before IDL generation so malformed idl doesn't cause problems. If for some reason bootstrapping doesn't work, you can revert your edit to header (assuming the old header + idl was compilable), build once, and edit again. Please also file a bug.
In a large code base, it is difficult to edit all existing header files to make them generate correct idl at once. It is suggested that large code base strictly follows the edit-one-header-at-a-time workflow to avoid generating a bunch of invalid idl files which stop the project from building.
Follow these steps to configure the project for an edit-one-header-at-a-time workflow:
- Set
IdlGenCppGenerateIDL
to true. - Set
IdlGenCppExclude
to**/*.h
to exclude all headers by default. - Follow the instruction in Generate IDL for Only One Header to generate only one header file at at time. It's highly recommended that you use the VS extension which has the convenient "Generate IDL" context menu command.
Since idl files are meant to be included in the source, there is no need to generate these idl files in CI (Continuous Integration). The library provide an option to control generating idl files (IdlGenCppGenerateIDL
for C++). Make sure you set this property to false
in CI.
Just follow the advice in Incremental Adoption in Existing Codebase.
- This library conceptually depend* on only C++/WinRT. All types/names/concepts/whatever not defined by C++/WinRT but required for a feature should be configurable.
- Use tag types in the language (currently, C++ only) whenever possible. This aligns with the design of cppwinrt, and has type safety in certain sceneario (e.g. for specifying bsse type). Only use attribute when it has to (e.g. hide/property).
*Conceptual dependence (or logical dependence) means depending on the API or even implementation details of a library when implementing the logic of genrating idl. For example, this library depends on the existence winrt::event_token
(i.e. the type is hard coded) in C++/WinRT to generate event, but does not depend on wil
to generate property. wil::*
are expected to be configured as getter/property templates in project properties instead. The implementation of this library, however, is free to take a dependency on wil (e.g. installing the nuget).
Idl generation from C++ actually involves the classic chicken and egg problem. To generate idl, a header file needs to be compiled. To compile the header file, C++/WinRT projection need to exist. To generate C++/WinRT projection, winmd and thus idl files are required.
Idlgen solves this problem by inserting a "bootstrap" build step before generating idl. During bootstrapping, idlgen would generate dummy idls and forces C++/WinRT to generate projection. Said projection would thus free idlgen from issues such as definition removal.
Contributions are welcome! If you found a bug, please file a bug report.
For more details, such as build instruction, please see Contribution.md.