August 4th, 2021

What is a static Windows Runtime class, how should I express it, and when should I use it?

The Windows Runtime has this thing called “static classes”. What are they?

A static Windows Runtime class is a class that has no instances. An example of this is the FileIO class:

static runtimeclass FileIO
{
    static IAsyncOperation<String> ReadTextAsync(IStorageFile file);
    static IAsyncAction WriteText(IStorageFile file, String contents);

    static IAsyncOperation<IVector<String>> ReadLinesAsync(IStorageFile file);
    static IAsyncAction WriteLines(IStorageFile file, IIterable<String> lines);

    static IAsyncOperation<IBuffer> ReadBufferAsync(IStorageFile file);
    static IAsyncAction WriteBuffer(IStorageFile file, IBuffer buffer);

    /* etc */
}

There is no FileIO object. You can’t say

// C#
var fileIO = new FileIO(); // nope

// C++/WinRT
FileIO fileIO; // nope
auto fileIO = FileIO(); // nope

// C++/CX
auto fileIO = ref new FileIO(); // nope

// JavaScript
let fileIO = new FileIO(); // nope

None of these work because FileIO is not an object. It’s just a way to gather related functions under a common umbrella, similar to a namespace.

The term static class comes from the C# concept of the same name.

The way to express a static class is to put the word static in front of the class declaration, like we did above with FileIO:

static runtimeclass FileIO
{
    ...
}

If you leave out the static keyword (which is easily overlooked), then what you have is a class with no nonstatic members, also known as an empty class.

Empty classes are also a thing. They represent objects that you can’t do anything with except pass to other methods. They are often used to capture some information into an opaque object which can then be passed around.

[default_interface]
runtimeclass WidgetLocation
{
}

runtimeclass Widget
{
    WidgetLocation Location;
}

// C#
// Move widget1 to the same location as widget2.
widget1.Location = widget2.Location;

The only thing you can do with a Widget­Location is pass it to something that wants a Widget­Location. You can’t inspect the Widget­Location to learn anything about it directly. You have to ask somebody else to interpret it for you.

The FileIO class is not one of these empty classes. It’s not like you get an opaque FileIO object that represents some internal state. There simply isn’t any such thing as a FileIO object at all.

And for that case, what you have is a static class.

Bonus chatter: The MIDL3 compiler leads you into a pit of failure here. There are five patterns for runtime classes:

  No static members Has static members
No instances N/A static runtimeclass C
{
 static void S();
}
Has instances, empty [default_interface]
runtimeclass C
{

}

[default_interface]
runtimeclass C
{
 static void S();
}
Has instances, nonempty runtimeclass C
{
 void M();

}

runtimeclass C
{
 void M();
 static void S();
}

The top left corner is N/A because if a class has no static members and no instances, then there’s nothing there.

If there are no instances, then you say that you are a static runtimeclass. That takes care of the first row.

If you are an empty class, then you need to say [default_interface] to tell the MIDL3 compiler that you want it to generate an empty interface to represent the empty instances.

If you are a nonempty class, then you don’t need to say [default_interface] (although it’s harmless to do so) because the MIDL3 compiler is already forced to generate an interface to represent the instance methods.

Notice that this case

runtimeclass C
{
 static void S();
}

is not even on the list of legal declarations!

The MIDL3 compiler lets you use this erroneous declaration. It assumes that you meant “Has instances, empty” and secretly synthesizes an empty [default_interface] for you. It’s only when you try to do anything that requires this synthesized [default_interface], that the MIDL3 compiler then yells at you, “Hey, you forgot to say [default_interface].”

The trap is that if you intended the class to be a static class, but simply forgot to apply the static keyword, then you will never do anything that requires the synthesized [default_interface], and you never get any error message. The secretly synthesized empty interface is still generated, but it is completely useless since there is no way to obtain any objects that implement it.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

13 comments

Discussion is closed. Login to edit/delete existing comments.

  • Paulo Pinto

    Leaving a reply here below, as I cannot reply to @Me Gusta directly.

    @Me Gusta

    There is nothing to be confused from my side, I have been complaining to the respective teams due to the lack of respect for their customers since C++/WinRT has been introduced as C++/CX replacement, without any kind of Visual Studio tooling.

    If anything it is sad so many developers suffer from bulb paradox regarding C++ tooling and prefer to develop software for Windows...

    Read more
  • GL

    I asked myself how it would be possible for any other class to inspect an empty class , since is not supposed to know the implementation of even if they're written by the same person, since all interaction with has to go through the ABI (at least I get the impression that this is the case). Until I realized one could and does not have to delcare all COM interfaces...

    Read more
  • Paulo Pinto

    I still don’t get why we have to put up with IDL, with its arcane idiocracies, and the lack of appropriate tooling in “modern” Windows development as if we are back in 2000.

    • Felix Kasza

      I don’t see what’s so difficult about IDL. I’ve been using MSRPC since 1994, and once one matches one’s thinking to the purpose and the tools at hand, it is as easy to work with as any other non-trivial environment. “Old” does not imply “bad”, and the absence of a clicky-clicky GUI is not a lack of “appropriate” tooling.

      • Paulo Pinto · Edited

        - Lack of syntax highligting;
        - Lack of code completion;
        - Manually merging generated C++ code into already modified translation units
        - No support for generating C++20 modules (although this one is debatable)
        - Requires additional boilerplate for XAML integration
        - As of MIDL 3.0, some strange newly born east const love

        Understandbly any developer that lives on Notepad and cmd, might not appreciate the productivity of clicky-clicky GUI.

        Those of us that tasted C++ Builder and...

        Read more
      • Felix Kasza · Edited

        I agree with all your points, I just happen to disagree with your estimation of their impact. Code completion in a declarative language with a very limited vocabulary, for instance, seems less of a necessity to me than, say, taking the whole mess around /CX and WinRT and WIL and whatnot out the back and shooting it. Just check out the weirdness Raymond explains in the following article and tell me that this looks like...

        Read more
      • Me Gusta

        @Paulo Pinto
        To be honest, I think you may have gotten things confused here.
        C++/CX was the original, it came along with Visual Studio 2012 and Windows 8. But by 2016/2017 when C++/WinRT was added to the Windows SDK and the official Microsoft C++/WinRT tooling was made available to Visual Studio, UWP development was already unpopular.
        But the lack of popularity for C++ targeting UWP isn't just tooling, I honestly don't care about manually writing...

        Read more
      • Paulo Pinto

        UWP being on the way out due to lack of proper tooling was exactly my point.

        I bet the WinUI 3.0 and Window 11 UI adoption among C++ community will be just as bad.

      • Raymond ChenMicrosoft employee Author

        Not sure what the complaint is. If you use C++/CX, C++/WinRT, C#, or JavaScript, you just treat it like a normal static method.

        FileIO::WriteTextAsync(file, contents); // C++/CX
        FileIO::WriteTextAsync(file, contents); // C++/WinRT
        FileIO.WriteTextAsync(file, contents); // C#
        FileIO.writeTextAsync(file, contents); // JS
    • switchdesktopwithfade@hotmail.com

      Because C++ is controlled by a committee of lawyers who worship boilerplate and a cultural juggernaut has grown out of that. If you want actual innovation you have to live in the managed code world. C/C++ is a bad language even for OSes and drivers because it resists code locality at all costs.

      • Paulo Pinto

        There were the C++/CX extensions, similar to C++ Builder and Qt, but internal politics at play in the Windows team apparently prefer to impose IDL without Visual Studio tooling on the rest of us.

        It would not matter, if this was like Windows Runtime C++ Template Library, mostly use only at Microsoft internally, but no, they had to impose its clunky tooling on the rest of the world.

    • Brian MacKay

      Oh, MIDL and IDL go back to well before 2000. The second edition of Kraig Brockschmidt’s “Inside OLE” came out in 1995. I remember getting that and suddenly having the book made working with IDL much simpler (and helped me understand what I was doing).

      • Paulo Pinto · Edited

        I know, I was using it in 1998 for porting an Objective-C project into Windows/C++, which we in the end decided it was too much trouble and used plain MFC.

        Guess what, IDL tooling for C++/WinRT is just as bad in 2021.

        I just threw a random date there, to mean the days before .NET when Visual C++ 6.0 was around.