WinRT und UWP mit purem C

Metro Apps, WinRT (Windows Runtime), Immersive siehe IsImmersiveProcess und jetzt also UWP (Universal Windows Platform).
So viele Namen für eine Technologie, mit der Microsoft in die Zukunft gehen wollte.

Sinnvoll oder nicht, darüber kann man streiten. Aber wie kommt man nun da ran?


Mit fertigen Wizards für Apps möchte ich mich nicht aufhalten, schließlich sind nur für MSVC wirksam. Und dotNet Zusammenklickereien sind mir offen gesagt zu trivial, dass ich meine Zeit damit verschwende … das sollen die Anfänger machen.

Bei COM konnte man stets auf das “gute alte” C Interface zurücksteigen und damit jedem, und zwar wirklich jedem Compiler dessen Features eröffnen. CoInitializeEx() machte eine Thread COM-fähig, dann brauchte man noch den Interface-Header mit der Klasse und der richtigen Klassen-GUID und die Funktion CoCreateInstance() erzeugte auch schon eine IUnknown Instanz mit Referenzzähler 1. Jetzt wechselte man noch (falls nötig) mit QueryInterface und der richtigen Interface-ID (IID) zu der Schnittstelle, die man haben wollte, und schon konnte man COM-Methoden aufrufen.

WinRT bzw. die “neue” UWP API baut bekanntlich auf COM auf und erweitert IUnknown zu IInspectable, damit ein Objekt zur Laufzeit fragen kann, welche Interfaces es unterstützt, weil QueryInterface nur Ja oder Nein zu einer Interface Anfrage sagen kann.

Aber WIE lädt man nun WinRT/UWP Klassen in C bzw. purem C++?

WinRT ohne C++/CX?

Microsofts Compiler Erweiterungen sind ja alle recht nett (so wie auch C++/CLI), aber was soll man mit diesen Non-Standards, wenn z.B. den MinGW nutzen möchte?

Alles beginnt mit RoInitialize() um einen Thread auf WinRT vorzubereiten. Zuerst braucht man nun den HSTRING, also einen konstanten (Immutable) Unicodestring, der Klassenpfade beschreiben kann. Die Funktionen WindowsCreateString und WindowsDeleteString lassen einen solchen entstehen.

Die wichtigste Funktion zum laden und verwalten von WinRT/UWP Modulen ist RoGetActivationFactory(). Ihr übergibt man den HSTRING Klassenpfad um eine WinRT Klasse nutzen zu können.

Hier gibt es einen wichtigen Unterschied zu COM, denn es gibt zwei Arten von Methoden:

  1. WinRT Klassen können quasi “statische” Methoden haben, die es COM nicht gab.
    Das Ergebnis von RoGetActivationFactory() ist ein IInspectable, von dem man nun per QueryInterface auf das Static-Interface der Klasse wechselt. Dort hat man dann Zugriff auf die statischen Methoden.
  2. Für WinRT Instanz-Objekte nutzt man QueryInterface um vom zurückgegebenen IInspectable von RoGetActivationFactory() auf das Interface IActivationFactory zu wechseln. Dieses unterstützt nämlich die Methode ActivateInstance() mit der eine neue Instanz des gewünschten WinRT Objektes erzeugt wird.

(Natürlich kann man QueryInterface() auch überspringen, wenn man RoGetActivationFactory() gleich mit der korrekten IID befüttert).

Nun braucht man ganz analog zu COM immer die richtigen Interface-Header und Interface-IIDs um an die benötigten Methoden zu kommen.

Klassensuche

Schlimmer ist es, sich in dem Chaos von Headern zurecht zu finden. Und auch die Namen von Klassen sind in elendslangen Konstanten deklariert.

Angenommen, man braucht Zufallszahlen. Die erhält man aus einer statischen Methode des CryptographicBuffer.

Dazu braucht man:

  • #include <windows.security.cryptography.h>
  • Der Klassenpfad ist in der Konstante RuntimeClass_Windows_Security_Cryptography_CryptographicBuffer und lautet einfach auf: "Windows.Security.Cryptography.CryptographicBuffer"
  • Die Interface-ID findet man in IID___x_ABI_CWindows_CSecurity_CCryptography_CICryptographicBufferStatics
  • Das C-Interface struct-typedef heißt: __x_ABI_CWindows_CSecurity_CCryptography_CICryptographicBufferStatics
  • Und der VTBL-Pointer des Types __x_ABI_CWindows_CSecurity_CCryptography_CICryptographicBufferStaticsVtbl lässt uns dann endlich zur Method GenerateRandomNumber().

… also “schön” sehen dieses langen Namen nicht aus … es handelt sich eben um generierten C Code, der objektorientierte Features wie Namensräume abbilden soll.

Fazit

OK … da sieht man also recht deutlich, warum das Programmieren mit der C++ Erweiterung empfohlen wird. Dort hat man dann zwar auch lange Namensräume für Klassen, wie ABI::Windows::Security::Cryptography::ICryptographicBufferStatics doch die lassen sich leichter per IntelliSense nutzen als die hässlichen generierten C Strukturen. Und wenn man noch using namespace an erlaubten Stellen einsetzt, ist es fast so leicht wie in C#.

However … für Extrembergsteiger sind steile Klippen kein Grund abzuspringen, und so soll man sich auch nicht davon abhalten lassen, mit Nicht-MSVC Compilern in die WinRT Welt vorzudringen.

Es geht nämlich! Quod erat demonstrandum!

PS: Übelste Hardcore-Challenge: Die nötigen RT-Interfaces selbst schreiben und dort “bessere” Namen nutzen. Das hilft manchmal auch sehr.


Wenn sich eine triviale Erkenntnis mit Dummheit in der Interpretation paart, dann gibt es in der Regel Kollateralschäden in der Anwendung.
frei zitiert nach A. Van der Bellen
... also dann paaren wir mal eine komplexe Erkenntnis mit Klugheit in der Interpretation!