Write C++, F5, and see your C++/WinRT app running. Git-ignore idl in your codebase.
idlgen is a C++20 library that helps you get rid of idl and implementation type while authoring C++/WinRT types, with the aim of bringing the developer experience on par with C++/CX.
- Writing authored type which are ordinary C++ types with some standard-compliant ergonomic DSL to express WinRT concept
- Consume WinRT APIs and WinRT component libraries as usual
- Incremental compilation
- C++20 (the library uses concept and string-as-NTTP)
- Use your preferred way (cli/GUI) to install nuget package
IdlGen.IdlGen.Cpp, which can be found on nuget gallery. - Add
#undef GetCurrentTimeafter your#include <unknwn.h>and#include <windows.h>
- Add a header file.
#include <winrt/author/base.h> - Add a namespace in the form
winrt::XX::author. You can nestXXintoXX::YY::ZZ. - Declare a struct inside the namespace following the rules below. You can declare multiple types in the same header file. Write function definition in cpp files as usual.
- Make sure you include all necessary include in your authored type header. Idlgen does NOT use pch due to gcc's abysmal pch performance.
- Viola! Hit F5 and you should see your project building. You can inspect generated idl and implementation type files in the Generated Files folder. For an authored type header with the name
Foo.h, they are namedFoo.idl,Foo.impl.h, andFoo.impl.cpp.
For a "I want to do X, what do I write" kind of cheatsheet, see cheat-sheet.md.
- C++/WinRT terminology
Idlgen allows you to write authored types. You never write idl nor implementation type directly. These are generated by idlgen.
To aid generation, you have to follow some rules:
- WinRT concepts that can be 100% expressed in C++ is expressed using C++. E.g. declaring an enum.
- You must declare your type in
authornamespace. Only typse inauthornamespace are authored types (idlgen would trimauthorandwinrtin the final idl) - Your authored type must live inside header and must not contain any function definition. Function definition goes to cpp files.
- Authored type can only reference projected types, or, in some cases, other authored types (see below).
- Projected types from upstream dependencies can be referenced directly.
- Projected types of authored types must be forward declared.
- Only in the following scenarios authored types are allowed to be referenced by an authored type: a. Inherit an authored interface b. Using authored value types (struct/enum) directly. Using value types with reference, or in array is NOT using them directly.
For all other cases, authored type should forward declare relevant projected types, regardless of whether it's a struct, enum, delgate, and so on.
That's it. That's all the rules.
Note: To speed up generation, idlgen does little to no check on whether what you write is correct. Most errors will only surface as idl error.
#include <winrt/author/base.h>
#include <winrt/Microsoft.UI.Xaml.Data.0.h>
#include <winrt/Microsoft.UI.Xaml.Controls.0.h>
namespace winrt::MyNamespace::author
{
struct IMyInterface : winrt::author::winrt_interface
{
};
struct MyType : winrt::author::runtimeclass<
winrt::Microsoft::UI::Xaml::Controls::Page,
winrt::Microsoft::UI::Xaml::Data::INotifyPropertyChanged
>, IMyInterface
{
int Property(winrt::author::getter = {});
winrt::author::setter Property(int value);
winrt::event_token PropertyChanged(Microsoft.UI.Xaml.Data.PropertyChangedEventHandled const& handler);
void PropertyChanged(winrt::event_token token);
void AuthoredTypeSpecificMethod(winrt::author::ignore = {});
};
}
The above snippet declare a runtime class MyType and a WinRT interface IMyInterface in namespace MyNamespace. MyType extends Microsoft.UI.Xaml.Controls.Page. MyType has a property Property, and an event PropertyChanged. It also has a method that is only meant to be called by other authored types AuthoredTypeSpecificMethod() (note the winrt::author::ignore parameter is defaulted).
Idlgen uses marker types to identify whether your authored types are runtimeclass, or struct, or enum. Other times, idlgen needs to know extra information about a WinRT entity (whehter a method denotes a getter, etc). Below are the rule-of-thumb of how to use marker types:
- To declare an WinRT entity, inherit a marker type
- To express WinRT-only concepts, place a marker type in a method parameter
For more details, see the table below:
| Fully qualified name | Usage | Where to use | Note |
|---|---|---|---|
winrt::author::runtimeclass |
Declare a runtime class, specify base class and projected interface | base | Empty template argument is allowed |
winrt::auther::winrt_interface |
Declare an interface | base | To require an authored interface, simply inherit it |
winrt::author::winrt_struct |
Declare a struct | base | |
winrt::author::delegate |
Declare a delegate | base | Use constructor to specify parameters (return type is always void) |
winrt::author::attribute |
Declare an attribute | base | There is no way to apply an attribute declared in other idl yet |
winrt::author::getter |
Declare this function is a getter | function parameter | Always specify a default value via = {} |
winrt::author::setter |
Declare this function is a setter | return type of function | |
winrt::author::unsealed |
Declare this class is unsealed | base | |
winrt::author::static_class |
Declare this class is static class | base | |
winrt::author::partial |
Declare this class is partial | base | |
winrt::author::ignore |
Declare this entity is ignored in idl | function parameter | Always specify a default value via = {} |
winrt::author::apply_attr |
Apply attr to an entity | base | 1st template argument is the attribute. 2nd+ are attribute parameters |
winrt::author::attr_string |
Specify a string attirubte parameter | apply_attr 's template arguments |
|
winrt::author::attr_type |
Specify a type attribute parameter | apply_attr 's template arguments |
|
winrt::author::contentproperty and friends |
Built-in attributes in idl | apply_attr 's 1st template argument |
Only public, protected* members are reflected. Private members are never reflected. If you have member functions that should be pubic but not included in idl, add wirnt::author::ignore = {} in the parameter.
*Protected members are only declared as protected in idl if the runtime class is unsealed. Otherwise, it's ignore in idl.
- If you reference another authored type declared in another header file, make sure you include that header file.
- To declare enum or
[flags]enum, simply declare enum classes withintandunsignedas the underlying types respectively. - When value types are passed by values in functions, it's not possible to forward declare them. Therefore, authored value types (struct/enum) as stated in rule (7) can be passed as-is. If you only need to pass authored value type reference/array of value type around, you should forward declare their projected types and pass around their projected types instead.
Contributions are welcome! If you found a bug, please file a bug report.
For more details, such as build instruction, please see contribution.md.
Idlgen is powered by C++26 static reflection and constexpr code generation. Before compiling midl, a program written in C++26 would reference all of your authored types and reflect them. The output of the reflection is some idl and implementation type files, which are then picked up by custom build tasks and include them as Midl, ClInclude, and ClCompile targets.
- Authored type must define all of their methods in .cpp files, due to bootstrapping.
- To speed up generation, idlgen does little to no check on whether what you write is correct. Most errors will only surface as idl error.
- Since static reflection is currently only supported in gcc, the library actually ships a complete gcc toolchain, which adds significantly to nuget size.
- Compile time cannot be improved by pch.h. The only way to improve compile time is the usual C++/WinRT tricks: forward declaring type, include only the
.0.hheader, and so on. This and the above limitation can be removed once MVSC supports static reflection and all required constexpr machinary.
XAML Compiler can NOT work with header file that doesn't have ordinary .h extension. Do NOT in order to setup IdlGenCppInclude=*.author.h change extension to e.g..author.h. If you want to use wildcard in IdlGenCppInclude, you can put all your authored type in a specific folder, e.g. \author, and specify IdlGenCppInclude=author\*.h.
Specify MyClass.h in VS -> right click project -> Properties -> Idlgen -> Include. Or, in cli: msbuild -target:IdlGenCppGenerateIDL -p:IdlGenCppInclude=MyClass.h -p:IdlGenCppExclude="" -p:Platform=x64.
| 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 |
IdlGenCppPch |
Pch files. Used to speed up generation. Only 1 pch file is supported |
Idlgen ouputs build logs to Output -> Build window. Look at what is failing and see if you need a rebuild. Remove pch.h.gch, clean the resolution, and rebuild usually fixes it. Please file a bug report if you have minimal reproducible steps, thank you!
Make sure your .h file has DependenUpon set to your .xaml file. Otherwise XAML compiler would NOT include .g.hpp in XamlTypeInfo.g.cpp
- Know that your existing idl can coexist with idlgen, by simply not touching the relevant .idl and .h files.
- Make use of
IdlGenCppIncludeandIdlGenCppIncludeto selectively include new authored type files - Migrate old idl + .h files to author types one by one. Remove existing idl if it has been migrated.
- Note that your author types can NOT reference any other implementation type as per the rules. Therefore, it's best if you convert your leaf types to author types first.
Files generated by idlgen are NOT supposed to be checked in to version control. Do NOT git add files in the Generated Files folder.
Generating idl 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, some C++/WinRT projection need to exist. To generate C++/WinRT projection, winmd and thus idl files are required.
Other languages (C#, cppfront) solve this by multi-phase compilation. Essentially, a symbol can be defined after its use. These symbols include symbols in type annoation, method names, and so on.
C++ does not have this luxury. The best C++ have is forward declared entity. And that's about it. We can forward declare the existence of a type, but we cannot forward declare what methods it have. The result is the current design of idlgen: we can forward-delcare projected type, but we cannot reference them in our function body in header files as any projected type's method call would cause a hard undefined symbol error during static reflection.
Careful readers would notice there is a loophole in this no-function-definition-in-authored-type rule. That is, as long as the function body does NOT reference anything that cannot be forward-declared for a projected authored type, you can write your function body in header file just fine. It's just that this is not really teachable. So, the more advanced version of this rule is actually: "Your authored type must live inside header and must not reference any forward-declared projected type's method". Note that authored type's method is also prohibited as implied by rule (4).
TLDR: Idlgen solves this problem by setting up rules about what authored types can reference. See usage.