Hinweis: Dieses Tutorial richtet sich an fortgeschrittene Entwickler. Die hier gezeigten Techniken sind für sehr spezifische Anwendungsfälle gedacht und setzen fundierte Kenntnisse in der Programmierung voraus, insbesondere in den Bereichen .NET, JavaScript und Webtechnologien. Für die meisten GuiXT-Projekte sind diese Kommunikationswege nicht erforderlich. Die Beispiele in diesem Tutorial sind bewusst einfach gehalten, da diese Strukturen in der Praxis schnell erheblich an Komplexität gewinnen können. Es wird daher empfohlen, diese Ansätze nur dann einzusetzen, wenn es keine einfachere Alternative gibt.

In diesem Tutorial werden drei grundlegende Architekturen gezeigt, mit denen GuiXT, JavaScript, .NET und WebAssembly (WASM) miteinander kommunizieren können. Jede Architektur legt fest, welche Komponente die Steuerung übernimmt und welche Verbindungen direkt bzw. indirekt sind. Das folgende Übersichtsdiagramm zeigt alle möglichen Verbindungen auf einen Blick. Eine durchgezogene Linie steht für eine direkte Verbindung, eine gestrichelte für eine optionale und eine gepunktete für eine indirekte Verbindung.

GuiXT kann direkt mit JavaScript (über CallJS) und mit .NET (über CallVB) sprechen. JavaScript kann direkt mit WASM kommunizieren und optional auch mit .NET (z. B. über ein COM-Objekt). WASM hingegen hat keine native direkte Verbindung zu GuiXT und ist immer auf einen Vermittler angewiesen.

Die drei Abschnitte unten zeigen je einen konkreten Kommunikationsweg mit Beispielcode.

GuiXT als Übersetzer

In dieser Architektur fungiert GuiXT als zentraler Vermittler zwischen JavaScript und .NET. JavaScript und .NET kennen sich gegenseitig nicht; alle Daten fließen ausschließlich durch GuiXT-Befehle. Der Ablauf ist vollständig sequenziell und wird immer durch eine Benutzeraktion in GuiXT ausgelöst.

1. Die Bildschirmmaske

Das GuiXT-Script legt einen WebView an, der eine lokale HTML-Seite (TestPage.html) lädt, sowie einen Pushbutton, der bei Klick das InputScript greeting.txt ausführt.

GuiXT
WebView (7.2,15) (24.4,75.1) "websites/TestPage.html" name="testview"
Pushbutton	(6,15)	"Send Hello to .NET" process="greeting.txt"

Der WebView-Befehl öffnet die HTML-Seite im angegebenen Bereich und gibt ihr den internen Namen testview. Über diesen Namen kann GuiXT später gezielt JavaScript-Funktionen in genau diesem WebView aufrufen. Der Pushbutton löst das InputScript aus; erst dann beginnt die Kommunikation.

2. Der Ablauf: greeting.txt

Das InputScript enthält genau drei Zeilen und beschreibt den vollständigen Kommunikationsweg:

GuiXT
CallJS jsval = <testview>.sendText
CallVB netval = Greetings.Greeter.receiveGreeting "&V[jsval]"
CallJS <testview>.receiveText "&V[netval]"

Zeile 1: CallJS jsval = <testview>.sendText
GuiXT ruft die JavaScript-Funktion sendText() im WebView auf und speichert den Rückgabewert in der GuiXT-Variable jsval. In diesem Beispiel gibt die Funktion den String „Hello from JS!" zurück.

Zeile 2: CallVB netval = Greetings.Greeter.receiveGreeting "&V[jsval]"
GuiXT übergibt den gerade erhaltenen Wert (&V[jsval] wird durch den Inhalt der Variable ersetzt) an die .NET-Methode receiveGreeting und speichert die Antwort in netval.

Zeile 3: CallJS <testview>.receiveText "&V[netval]"
GuiXT schickt die .NET-Antwort als Argument an die JavaScript-Funktion receiveText(), die daraufhin den Text im WebView aktualisiert.

3. Die .NET-Klasse

Die .NET-Klasse Greeter enthält eine einzige Methode receiveGreeting, die einen String entgegennimmt und einen String zurückgibt. Sie prüft, ob der Eingabewert exakt „Hello from JS!" lautet, und antwortet entsprechend. Jede andere Eingabe liefert „Invalid Greeting", sodass im Beispiel klar erkennbar ist, ob die Daten korrekt übertragen wurden.

namespace Greet
{
    public class Greeter
    {
        public string receiveGreeting(string text)
        {
            if (text == "Hello from JS!")
            {
                return "Hello from .NET!";
            }
            return "Invalid Greeting";
        }
    }
}

4. Der WebView-Inhalt: TestPage.html

Die HTML-Seite definiert zwei JavaScript-Funktionen, die GuiXT per CallJS aufrufen kann:

HTML
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>TestPage</title>
</head>

<body>
    <script>
        function receiveText(text) {
            if (text === "Hello from .NET!") {
                let node = document.getElementById("hellotext")
                node.innerText = "Received Hello from .NET :)"
            }
        }

        function sendText() {
            return "Hello from JS!"
        }

    </script>
    <p id="hellotext">No hello :(</p>

</body>
</html>

sendText() ist die Datenquelle: Sie gibt einfach den festen String „Hello from JS!" zurück, den GuiXT in Zeile 1 des Scripts abholt. receiveText(text) ist der Empfänger: GuiXT ruft sie in Zeile 3 mit der .NET-Antwort auf. Wenn der Wert korrekt ist, ersetzt die Funktion den Platzhaltertext des <p>-Elements durch „Received Hello from .NET :)".

5. Ergebnis

Nach einem Klick auf den Pushbutton durchläuft GuiXT alle drei Zeilen des InputScripts. Der WebView zeigt anschließend die Bestätigungsmeldung an:

Vorteile
• Einfachster Aufbau: kein zusätzlicher Konfigurationsaufwand, da GuiXT CallJS und CallVB von sich aus unterstützt.
• Klarer, leicht nachvollziehbarer Datenfluss: jeder Schritt ist eine eigene GuiXT-Zeile.
• Gut geeignet für einfache Anfrage-Antwort-Szenarien ohne Zustandsverwaltung.

Nachteile
• GuiXT ist der Flaschenhals: jede Interaktion muss durch ein GuiXT-Script ausgelöst werden.
• Keine direkte Kommunikation zwischen JS und .NET möglich; beide Seiten sind voneinander isoliert.
• Der Ablauf ist strikt synchron; asynchrone Muster (z. B. Events, Callbacks) lassen sich damit nicht abbilden.

Kommunikationsdreieck zwischen GuiXT, JS und .NET

In dieser Architektur können alle drei Komponenten (GuiXT, JavaScript und .NET) direkt miteinander kommunizieren. Der Schlüssel dazu ist ein COM-Objekt, das .NET bereitstellt und dessen Referenz GuiXT einmalig an JavaScript übergibt. Danach kann JavaScript die .NET-Methoden direkt aufrufen, ohne dass GuiXT noch einmal eingreifen muss. Für die umgekehrte Richtung (von .NET zu JavaScript) wird ein Polling-Muster verwendet: .NET schreibt Befehle in eine interne Warteschlange, die JavaScript regelmäßig abfragt.

1. Das COM-Objekt (.NET)

Damit GuiXT ein .NET-Objekt per CallVB erstellen und die Referenz an JavaScript weitergeben kann, muss die Klasse als COM-Objekt sichtbar sein. Das Interface IStringContainer ist mit [ComVisible(true)] und [InterfaceType(ComInterfaceType.InterfaceIsDual)] markiert. InterfaceIsDual bedeutet, dass das Interface sowohl über COM-Dispatch (Late Binding, wie es GuiXT und ältere Skriptsprachen verwenden) als auch über vtable (Early Binding) erreichbar ist. Jede Methode erhält eine feste [DispId], damit die Zuordnung stabil bleibt, auch wenn sich die Reihenfolge der Methoden im Quellcode ändert.

Die Klasse StringContainer implementiert dieses Interface und enthält folgende Methoden:

• Store(s): speichert einen String im Objekt und gibt ihn zurück.
• Read(): gibt den gespeicherten String in Kleinbuchstaben zurück (demonstriert eine echte .NET-Verarbeitung).
• Create(): erzeugt eine neue Instanz und speichert sie statisch in _instance. GuiXT ruft diese Methode auf, um das Objekt zu initialisieren.
• polfunc(): gibt den nächsten Eintrag aus der internen Queue zurück, oder einen leeren String wenn die Queue leer ist. JavaScript ruft diese Methode im Polling-Intervall auf.
• WriteLine(s): fügt einen writeln:<text>-Befehl in die Queue ein. .NET kann damit JavaScript auffordern, Text in das Dokument zu schreiben.

using System.Collections.Generic;
using System.Runtime.InteropServices;

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IStringContainer
{
    [DispId(1)]
    string Store(string s);

    [DispId(2)]
    string Read();

    [DispId(3)]
    IStringContainer Create();

    [DispId(4)]
    string polfunc();
}



[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class StringContainer : IStringContainer
{
    private string mycontent = "";
    private Queue<string> queue = new Queue<string>();
    private static StringContainer _instance;
    public string Store(string s)
    {
        mycontent = s;
        return mycontent;
    }

    public string Read()
    {
        return mycontent.ToLower();
    }

    public IStringContainer Create()
    {
        _instance = new StringContainer();
        return _instance;
    }

    public string polfunc()
    {
        if (queue.Count > 0)
            return queue.Dequeue();
        else
            return "";
    }

    public void Test()
    {
        string command = "console";
        string msg = "Success!";
        _instance.queue.Enqueue(command + ":" + msg);
    }
    public void WriteLine(string s)
    {
        string command = "writeln";
        string msg = s;
        _instance.queue.Enqueue(command + ":" + msg);
    }
}

2. Der WebView-Inhalt: HTML & JavaScript

Die HTML-Seite enthält die JavaScript-Logik für drei Aufgaben: Objekt empfangen, Objekt direkt nutzen und eingehende Nachrichten verarbeiten.

HTML
<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>GuiXT JavaScript functions, package myhtml</title>
</head>
<body>
    <script>

        // .NET object
        var dotnetobject = null;

        function receiveObject(objname) {
            dotnetobject = guixthost.get(objname);
        }

        function saveString(s) {
            dotnetobject.Store(s);
        }
        function displayString() {
            document.body.innerHTML = dotnetobject.Read();
        }

        function dotnetHandler(i) {
            let arr = i.split(":");
            switch (arr[0]) {
                case "writeln": console.log("wrote line");
                                document.body.innerHTML += arr[1]; break;
                case "console": console.log(arr[1]); break;
                default: console.log("Unsupported operation: " + i);
            }
        }

        setInterval(function () {
            if (dotnetobject == null)
                return;
            let i;
            do {
                i = dotnetobject.polfunc();
                if (i !== "")
                    dotnetHandler(i);
            } while (i !== "")

        }, 30);

    </script>
</body>
</html>

receiveObject(objname) wird von GuiXT per CallJS aufgerufen. Der Parameter objname ist der interne Name der COM-Instanz (hier "sc"). guixthost.get(objname) ist eine GuiXT-spezifische Funktion, die im WebView-Kontext verfügbar ist und die gespeicherte .NET-Objektreferenz anhand des Namens zurückgibt. Ab diesem Moment kann JavaScript alle Methoden des .NET-Objekts direkt aufrufen, ohne GuiXT zu involvieren.

saveString(s) und displayString() demonstrieren genau das: sie rufen dotnetobject.Store(s) bzw. dotnetobject.Read() direkt auf. Read() gibt den gespeicherten String in Kleinbuchstaben zurück; das .ToLower() wird dabei im .NET-Prozess ausgeführt, nicht im Browser.

Das setInterval mit 30 ms ist der Polling-Mechanismus für den umgekehrten Weg (.NET zu JS). Solange dotnetobject gesetzt ist, ruft JavaScript in jeder Runde polfunc() auf. Gibt die Methode einen String zurück, verarbeitet dotnetHandler ihn: ein writeln:-Präfix hängt Text ans Dokument an, ein console:-Präfix schreibt in die Browser-Konsole. Die Schleife läuft weiter, bis die Queue leer ist (Rückgabe "").

3. GuiXT-Scripts

Das Layoutscript öffnet den WebView und den Auslöseknopf, genau wie in Abschnitt 1:

GuiXT
WebView (7.2,15) (24.4,75.1) "websites/TestPage.html" name="testview"
Pushbutton	(6,15)	"Send Hello to .NET" process="greeting.txt"

Das InputScript übernimmt die Einrichtung des Kommunikationskanals und zeigt danach, wie GuiXT und JavaScript das .NET-Objekt gemeinsam nutzen:

GuiXT
CallVB sc = JavaScriptCom.stringcontainer.create
//Queues commands
CallVB JavaScriptCom.stringcontainer.WriteLine "1"
CallVB JavaScriptCom.stringcontainer.WriteLine "2"
CallVB JavaScriptCom.stringcontainer.WriteLine "3"

// pass .NET object to JS
CallJS <testview>.receiveObject  "sc"

// set string as content into .NET object
CallJS <testview>.saveString  "Hello from .NET!"

// display string in lowercase, string read from .NET object
CallJS <testview>.displayString

Zeile 1: CallVB sc = JavaScriptCom.stringcontainer.create
GuiXT ruft die COM-Methode Create() auf, die eine neue StringContainer-Instanz anlegt, intern als _instance speichert und zurückgibt. GuiXT registriert das Objekt intern unter dem Namen sc.

Zeilen 3–5: CallVB ... WriteLine "1" etc.
Noch bevor JavaScript das Objekt kennt, fügt GuiXT drei Einträge in die Queue ein. Dies demonstriert, dass .NET Nachrichten vorausschicken kann; JavaScript wird sie beim ersten Polling-Durchlauf vorfinden.

Zeile 8: CallJS <testview>.receiveObject "sc"
GuiXT übergibt den Namen "sc" an JavaScript. Dort holt guixthost.get("sc") die Referenz auf das .NET-Objekt; ab jetzt läuft der Polling-Timer und JavaScript kann das Objekt direkt ansprechen.

Zeilen 11 und 14 demonstrieren den direkten Zugriff von JS auf .NET: saveString schreibt „Hello from .NET!" in das Objekt via Store(), und displayString liest es mit Read() zurück. Der Browser zeigt den Text in Kleinbuchstaben an, weil Read() intern .ToLower() aufruft.

Vorteile
• JavaScript und .NET können direkt miteinander kommunizieren, ohne dass jede Nachricht durch ein GuiXT-Script geleitet werden muss.
• Das Polling-Muster ermöglicht es .NET, jederzeit Nachrichten an JavaScript zu senden und schafft so einen einfachen asynchronen Kanal.
• Flexibler Aufbau: GuiXT übernimmt nur die initiale Einrichtung; danach agieren JS und .NET weitgehend eigenständig.

Nachteile
• Höherer Einrichtungsaufwand: die .NET-Assembly muss als COM-Objekt registriert sein ([ComVisible(true)], COM-Registration).
• Das Polling verursacht dauerhaften Overhead, da der Timer auch dann alle 30 ms läuft, wenn keine Nachrichten anstehen.
• COM ist eine Windows-spezifische Technologie; dieser Ansatz ist nicht auf andere Plattformen übertragbar.
• Die Fehlersuche ist anspruchsvoller, da Daten über mehrere Schichten und einen asynchronen Mechanismus fließen.

JavaScript als Übersetzer

In dieser Architektur läuft .NET vollständig im Browser, und zwar als WebAssembly-Modul. C#-Code wird zu WASM kompiliert; der Browser führt ihn mit nahezu nativer Geschwindigkeit aus, ohne dass ein Server nötig ist. JavaScript übernimmt die Rolle des Vermittlers: es startet die WASM-Runtime, stellt ihr Browser-Funktionen bereit ([JSImport]) und macht C#-Methoden für HTML-Ereignisse zugänglich ([JSExport]). GuiXT muss nichts weiter tun, als die fertige HTML-Seite in einem WebView zu öffnen.

Für .NET WASM wird C# empfohlen. VB.NET wird von der WASM-Runtime zwar grundsätzlich unterstützt, ist jedoch nicht für den produktiven Einsatz vorgesehen und sollte nur dann verwendet werden, wenn es unbedingt erforderlich ist. Alle Beispiele in diesem Abschnitt sind daher ausschließlich in C# gehalten.

1. Voraussetzungen: .NET WASM installieren

Zunächst wird geprüft, ob das .NET SDK in einer aktuellen Version vorhanden ist. Für .NET WASM wird mindestens .NET 7 benötigt; empfohlen ist .NET 10:

Als nächstes wird geprüft, welche Workloads bereits installiert sind. Der Befehl dotnet workload list zeigt alle aktiven Erweiterungen des SDK:

Falls wasm-tools noch nicht in der Liste erscheint, wird es mit folgendem Befehl installiert. Dieser Workload enthält den WASM-Compiler und alle nötigen Build-Targets:

Danach werden die WASM-Projektvorlagen installiert. Das Paket Microsoft.NET.Runtime.WebAssembly.Templates fügt dem dotnet new-Befehl drei neue Vorlagen hinzu; die für dieses Beispiel relevante ist wasmbrowser (WebAssembly Browser App):

2. Projekt erstellen und bauen

Mit dotnet new wasmbrowser -o MyWasmApp wird ein neues Projekt aus der Vorlage erzeugt. Die Vorlage enthält bereits das fertige Stoppuhr-Beispiel mit Program.cs, main.js und index.html:

dotnet build -c Release MyWasmApp kompiliert den C#-Code zu einer WASM-Assembly. Das Ergebnis ist eine .dll-Datei, die der Browser-Runtime übergeben wird. Der Konfigurationsparameter -c Release sorgt für eine optimierte, kleinere Ausgabe:

dotnet publish -c Release MyWasmApp erzeugt den fertigen statischen Ordner unter MyWasmApp\bin\Release\net10.0\publish\wwwroot\. Dieser Ordner enthält index.html, main.js sowie den Unterordner _framework\ mit der WASM-Runtime (dotnet.js, dotnet.wasm und der kompilierten Assembly). Dieser Ordner wird in den GuiXT-Websites-Pfad kopiert:

3. Der C#-Code: Program.cs

Program.cs enthält die gesamte Stoppuhr-Logik in einer einzigen partiellen Klasse StopwatchSample. Der Einstiegspunkt ist die implizite Main-Methode am Dateianfang:

WASM (C#)
using System;
using System.Diagnostics;
using System.Runtime.InteropServices.JavaScript;
using System.Threading.Tasks;

Console.WriteLine("Hello, Browser!");

if (args.Length == 1 && args[0] == "start")
    StopwatchSample.Start();

while (true)
{
    StopwatchSample.Render();
    await Task.Delay(1000);
}

partial class StopwatchSample
{
    private static Stopwatch stopwatch = new();

    public static void Start() => stopwatch.Start();
    public static void Render() => SetInnerText("#time",
                                stopwatch.Elapsed.ToString(@"mm\:ss"));

    [JSImport("dom.setInnerText", "main.js")]
    internal static partial void SetInnerText(string selector,
                                string content);

    [JSExport]
    internal static bool Toggle()
    {
        if (stopwatch.IsRunning)
        {
            stopwatch.Stop();
            return false;
        }
        else
        {
            stopwatch.Start();
            return true;
        }
    }

    [JSExport]
    internal static void Reset()
    {
        if (stopwatch.IsRunning)
            stopwatch.Restart();
        else
            stopwatch.Reset();

        Render();
    }

    [JSExport]
    internal static bool IsRunning() => stopwatch.IsRunning;
}

Der Code startet mit dem Argument "start" (das JavaScript per .withApplicationArguments("start") übergibt) und ruft dann in einer Endlosschleife alle 1000 ms Render() auf, sodass die Anzeige sekündlich aktualisiert wird.

[JSImport("dom.setInnerText", "main.js")] deklariert, dass C# die JavaScript-Funktion dom.setInnerText aufrufen darf, die in main.js registriert wird. Diese Funktion setzt den Text eines DOM-Elements per CSS-Selektor. C# ruft sie einfach als normale statische Methode auf; der WASM-Interop-Layer übersetzt den Aufruf automatisch.

[JSExport] macht C#-Methoden für JavaScript aufrufbar. Toggle() startet oder stoppt die Stoppuhr und gibt zurück, ob sie gerade läuft. JavaScript nutzt diesen Rückgabewert, um den Button-Text zwischen „Pause" und „Start" umzuschalten. Reset() setzt die Stoppuhr zurück und aktualisiert sofort die Anzeige.

4. Der JavaScript-Einstiegspunkt: main.js

main.js ist ein ES-Modul und übernimmt das Bootstrapping der WASM-Runtime sowie die Verdrahtung der Schaltflächen:

JavaScript
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

import { dotnet } from './_framework/dotnet.js'

const { setModuleImports, getAssemblyExports, getConfig, runMain }
                            = await dotnet
    .withApplicationArguments("start")
    .create();

setModuleImports('main.js', {
    dom: {
        setInnerText: (selector, time) =>
                            document.querySelector(selector).innerText = time
    }
});

const config = getConfig();
const exports = await getAssemblyExports(config.mainAssemblyName);

document.getElementById('reset').addEventListener('click', e => {
    exports.StopwatchSample.Reset();
    e.preventDefault();
});

const pauseButton = document.getElementById('pause');
pauseButton.addEventListener('click', e => {
    const isRunning = exports.StopwatchSample.Toggle();
    pauseButton.innerText = isRunning ? 'Pause' : 'Start';
    e.preventDefault();
});

// run the C# Main() method and keep the runtime process running
// and executing further API calls
await runMain();

dotnet.create() lädt und initialisiert die .NET-WASM-Runtime aus dem _framework/-Ordner. .withApplicationArguments("start") entspricht dem Übergeben von args[0] an die C#-Main-Methode.

setModuleImports registriert die JavaScript-Implementierung für alle [JSImport]-Deklarationen in C#. Hier wird dom.setInnerText definiert: ein einfacher Arrow-Function, der document.querySelector(selector).innerText setzt. C# kann diese Funktion danach wie eine native Methode aufrufen.

getAssemblyExports liefert ein JavaScript-Objekt, das alle mit [JSExport] markierten C#-Methoden enthält. Die Schaltflächen-Listener rufen exports.StopwatchSample.Reset() und exports.StopwatchSample.Toggle() direkt auf, als wären es normale JavaScript-Funktionen, obwohl der eigentliche Code in C# läuft.

runMain() startet die C#-Main-Methode und hält die Runtime am Laufen. Da Main eine while(true)-Schleife enthält, läuft der Render-Timer dauerhaft, und weitere [JSExport]-Aufrufe bleiben jederzeit möglich.

5. Die HTML-Seite: index.html

Das HTML ist bewusst minimal gehalten; die Logik steckt vollständig in C# und JavaScript:

HTML
<!DOCTYPE html>
<!--  Licensed to the .NET Foundation under one or more agreements. -->
<!-- The .NET Foundation licenses this file to you under the MIT license. -->
<html>

<head>
  <title>WASMTest</title>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="preload" id="webassembly" />
  <script type="importmap"></script>
  <script type='module' src="main#[.{fingerprint}].js"></script>
</head>

<body>
  <h1>Stopwatch</h1>
  <p>
    Time elapsed in .NET is <span id="time"><i>loading...</i></span>
  </p>
  <p>
    <button id="pause">Pause</button>
    <button id="reset">Reset</button>
  </p>
</body>

</html>

Wichtig ist <script type='module' src="main#[.{fingerprint}].js">: Das type='module' ist zwingend, da main.js ES-Module-Syntax (import) verwendet. Der {fingerprint}-Platzhalter wird beim Publish durch einen Build-generierten Hash ersetzt, der Cache-Busting sicherstellt: Bei jedem Rebuild ändert sich der Dateiname. Der <span id="time"> ist der Ziel-Selektor, den C# per SetInnerText("#time", ...) befüllt.

6. GuiXT bindet die Seite ein

GuiXT muss in dieser Architektur nichts weiter tun, als den wwwroot/-Ordner als WebView zu öffnen. Die gesamte Kommunikation zwischen C# und JavaScript läuft innerhalb des Browsers; GuiXT ist nicht beteiligt:

GuiXT
WebView (7.2,15) (24.4,75.1) "websites/wwwroot/index.html" name="testview"

7. Ergebnis

Nach dem Öffnen des WebViews startet die WASM-Runtime automatisch, lädt die C#-Assembly und beginnt mit der sekündlichen Zeitanzeige. Die Schaltflächen Start/Pause und Reset rufen direkt C#-Methoden auf:

Vorteile
• Die gesamte Logik läuft im Browser, kein serverseitiges .NET ist erforderlich. GuiXT muss lediglich einen WebView öffnen.
• Starke Typsicherheit zwischen C# und JavaScript dank [JSExport] und [JSImport]; keine manuellen String-Konvertierungen.
• Moderne, zukunftsorientierte Architektur: .NET WASM ist plattformunabhängig und wird aktiv weiterentwickelt.
• C# kann direkt auf Browser-APIs zugreifen, ohne dass GuiXT als Mittelsmann agieren muss.
• WASM ist nicht auf .NET beschränkt: Auch andere Sprachen wie Rust, Python, Go oder C++ können zu WASM kompiliert und im Browser ausgeführt werden. Der gleiche Ansatz lässt sich damit auf viele Technologie-Stacks übertragen.

Nachteile
• Größter Einrichtungsaufwand: ein .NET WASM-Projekt muss gebaut und als statische Dateien bereitgestellt werden (_framework/dotnet.js usw.).
• Der erste Seitenaufruf ist langsam, da die WASM-Runtime (mehrere Megabyte) in den Browser geladen werden muss.
• WASM kann nicht direkt mit GuiXT kommunizieren; jede Interaktion mit GuiXT muss über JavaScript als Brücke laufen.
• Debugging ist aufwendiger, da C#-Code im Browser-Kontext läuft und gesonderte Werkzeuge (z. B. Browser-Devtools mit WASM-Support) benötigt.

Kombinierte Architektur: WASM, JS, .NET und GuiXT

Achtung: Diese Architektur ist die komplexeste der hier gezeigten Strukturen und in der Praxis nahezu immer unnötig. Sie wird hier ausschließlich der Vollständigkeit halber gezeigt. Bevor dieser Ansatz in Betracht gezogen wird, sollte sehr sorgfältig geprüft werden, ob einer der drei einfacheren Wege aus den vorangegangenen Abschnitten nicht ausreicht.

Dieses Diagramm kombiniert den Kommunikationsdreieck-Ansatz aus Abschnitt 2 mit dem WASM-Ansatz aus Abschnitt 3. Alle vier Komponenten sind gleichzeitig aktiv: GuiXT orchestriert die SAP-Oberfläche, .NET übernimmt die GuiXT-nahe Logik über das COM-Objekt, JavaScript fungiert als zentraler Knotenpunkt und WASM erledigt rechenintensive oder rein browser-seitige Aufgaben.

Warum zwei getrennte .NET-Runtimes?

Der entscheidende Grund, warum diese Architektur überhaupt existiert, liegt in einem grundlegenden technischen Unterschied: .NET in GuiXT basiert auf dem .NET Framework und kommuniziert über die guinet.dll, die als Brücke zwischen GuiXT-Scripts und .NET-Code dient. .NET WASM hingegen verwendet .NET Core (ab .NET 5 aufwärts), eine vollständig separate Runtime, die nicht im selben Prozess wie das .NET Framework laufen kann. Beide Runtimes sind inkompatibel und können nicht direkt miteinander kommunizieren.

Wenn ein Projekt also sowohl GuiXT-Zugriff über guinet.dll (.NET Framework) als auch Berechnungen oder DOM-Operationen in WASM (.NET Core) benötigt, ist diese Vier-Komponenten-Architektur technisch gesehen die einzige Möglichkeit, beide gleichzeitig zu nutzen. JavaScript ist dabei der einzige Vermittler, der mit allen anderen Komponenten sprechen kann.

Mögliche Aufgabenteilung

• WASM: rechenintensive Aufgaben (z. B. Datenverarbeitung, mathematische Berechnungen), komplexe DOM-Manipulationen im WebView oder Funktionen, die bewusst im Browser bleiben sollen. Zu beachten ist, dass .NET Framework DOM-Manipulationen prinzipiell ebenfalls durchführen kann, etwa über das in Abschnitt 2 gezeigte Polling-Muster; dies erfordert jedoch, dass der JavaScript-Code für jede gewünschte DOM-Operation erst explizit geschrieben und in der Queue registriert werden muss, was bei vielen oder komplexen Operationen deutlich aufwendiger ist als die direkte WASM-Lösung.
• JavaScript: zentrale Drehscheibe; leitet Ergebnisse von WASM weiter an GuiXT (über Rückgabewerte bei CallJS) oder an .NET (über das COM-Objekt via guixthost.get).
• .NET Framework: übernimmt alle Operationen, die guinet.dll voraussetzen, also direkte GuiXT-Interaktion, SAP-Feldmanipulation und ähnliche GuiXT-spezifische Aufgaben.
• GuiXT: steuert die übergeordnete Ablauflogik der SAP-Oberfläche und löst bei Bedarf Kommunikation aus.

Fazit

Diese Struktur ist technisch möglich, aber sie kombiniert die Komplexität aller drei vorangegangenen Abschnitte gleichzeitig. Der Wartungsaufwand, die Fehlersuche über vier verschiedene Runtimes hinweg und die nötige Tiefe des Verständnisses aller beteiligten Technologien machen diesen Ansatz in realen Projekten kaum praktikabel. In fast allen Fällen lässt sich das eigentliche Problem mit einer der einfacheren Architekturen aus den vorangegangenen Abschnitten lösen, ohne dass alle vier Komponenten gleichzeitig benötigt werden.

Empfehlungen: Wann welche Architektur?

Abschnitt 1 (GuiXT als Übersetzer) ist der empfohlene Ausgangspunkt für die meisten Anwendungsfälle. Wenn GuiXT Daten zwischen JavaScript und .NET austauschen soll und der Ablauf klar sequenziell ist, reicht dieser Ansatz vollständig aus. Er ist leicht verständlich, erfordert keinen zusätzlichen Einrichtungsaufwand und lässt sich schnell umsetzen. Für einfache Anfrage-Antwort-Szenarien zwischen WebView und .NET ist er die erste Wahl.

Abschnitt 2 (Kommunikationsdreieck) ist sinnvoll, wenn JavaScript und .NET regelmäßig und unabhängig voneinander kommunizieren müssen, ohne dass jede Interaktion durch ein GuiXT-Script ausgelöst werden soll. Das Polling-Muster ermöglicht es .NET, jederzeit Nachrichten in den WebView zu schicken, was für Szenarien mit kontinuierlichem Datenaustausch oder asynchronen Rückmeldungen aus .NET heraus nützlich ist. Dieser Ansatz erfordert mehr Einrichtungsaufwand als Abschnitt 1, bleibt aber noch gut handhabbar.

Abschnitt 3 (JavaScript als Übersetzer / WASM) ist vor allem dann interessant, wenn rechenintensive Aufgaben oder komplexe Datenverarbeitung rein browser-seitig erledigt werden sollen, ohne einen .NET-Server zu benötigen. Ein weiterer Vorteil ist die Sprachenvielfalt: Da JavaScript selbst viele Funktionen der guinet.dll direkt ansprechen kann, lassen sich über die WASM-Brücke auch andere Programmiersprachen wie Rust, Python oder Go indirekt mit GuiXT verbinden. WASM kompiliert die jeweilige Sprache in ein browserfähiges Modul, JavaScript reicht die Ergebnisse an GuiXT weiter. Dieser Ansatz erfordert gute Kenntnisse der WASM-Toolchain und ist mit einem höheren Einrichtungsaufwand verbunden, kann aber in spezialisierten Szenarien ein wertvolles Werkzeug sein.

Abschnitt 4 (Kombinierte Architektur) sollte nicht eingesetzt werden, außer es besteht eine zwingende, technisch nachgewiesene Notwendigkeit, alle vier Komponenten gleichzeitig zu betreiben. In der Praxis lässt sich nahezu jedes Problem mit einem der ersten drei Ansätze lösen. Die Komplexität, die mit dem gleichzeitigen Betrieb von .NET Framework, .NET Core WASM, JavaScript und GuiXT einhergeht, übersteigt den Nutzen in den allermeisten Fällen bei weitem.