Slack für Windows 3.1: Ein Client für Retro-Systeme im Jahr 2019

Slack Client für Windows 3.1 in Aktion

Im Oktober 2019 veranstaltete mein damaliges Unternehmen SP Digital einen internen Hackathon. Mein Kollege Subhransu und ich arbeiteten an einer ungewöhnlichen Idee: Wir wollten eine brandneue Windows 3.1 App entwickeln. Windows 3.1, ein Betriebssystem, das vor fast 30 Jahren veröffentlicht wurde, schien die perfekte Spielwiese für ein solches Retro-Projekt. Die von uns gewählte Anwendung war ein Slack-Client – schließlich existieren Slack-Clients für die meisten Plattformen, aber ein spezieller Slack Für Windows 3.1 fehlte definitiv.

Einige Monate später, im Dezember 2019, nahm ich zum zweiten Mal am Super Silly Hackathon teil, gemeinsam mit der Retrocomputing-Enthusiastin Hui Jing. Mit den Erkenntnissen aus unserem ersten Projekt entwickelten wir ein kleines Spiel für Windows 3.1, was uns enorm half. Dieser Artikel, Teil 1 unserer Reihe, beschreibt detailliert die gewonnenen Erkenntnisse und den Prozess, wie eine neue, aber „alte“ App mithilfe moderner Tools und des Wissens über alte Technologien erstellt werden kann. Vielleicht bietet uns dies auch heute noch wertvolle Lektionen. Der Blogbeitrag ist einige Monate verspätet, da ich Zeit benötigte, den Code zu bereinigen und die Dokumentation zu schreiben. Vergessen Sie nicht, auch Teil 2 unseres Spielprojekts mit Hui Jing zu lesen!

Die Retro-App in Aktion

Natürlich werfen wir zunächst einen Blick auf die Anwendung selbst. Sie werden sehen, wie ein moderner Slack für Windows 3.1x aussieht.

Slack Client für Windows 3.1 in AktionSlack Client für Windows 3.1 in Aktion

Hier sehen Sie Bilder und ein Video der App in Aktion. Links ist mein Thinkpad 390e zu sehen, auf dem Windows 3.1 läuft. Rechts ist mein Mac mit dem modernen Slack-Client zum Vergleich abgebildet.

Vergleich: Slack Client auf Windows 3.1 und moderner Slack Client auf MacVergleich: Slack Client auf Windows 3.1 und moderner Slack Client auf Mac

Falls Sie zu ungeduldig sind, um weiterzulesen, und direkt in den Code eintauchen möchten, finden Sie das Repository auf GitHub unter https://github.com/yeokm1/w31slack.

Warum ausgerechnet Windows 3.1?

Oder, genauer gesagt, warum Windows für Workgroups (WFW) 3.11? Es gibt einen besonderen Grund dafür: WFW 3.11, veröffentlicht im Jahr 1993, war das erste Windows-Betriebssystem für Endverbraucher, das über eine netzwerkinstallierbare TCP/IP-Unterstützung verfügte. Wenn man also eine App unter Verwendung der Standard-Windows-APIs schreiben möchte, ist WFW 3.11 das älteste System, auf das man zurückgreifen kann, ohne einen noch exponentiell höheren Aufwand betreiben zu müssen. Dies macht es zu einem idealen Kandidaten für einen experimentellen Slack für Windows auf einer historischen Plattform.

Dennoch, wie Sie sehen werden, ist der Aufwand, eine solche App zu schreiben, auch für sich genommen eine Herkulesaufgabe. Windows 3.1 verbraucht größtenteils 16-Bit-Anwendungen, was das Ende dieser Ära war, bevor Windows 95 auf den Markt kam und die Entwicklung von Software grundlegend veränderte.

Entwicklungsumgebung für Retro-Software

Der wichtigste Aspekt bei der Entwicklung einer Anwendung ist die Compiler-Toolchain. Alles muss sich um sie drehen, und dieses Projekt bildet da keine Ausnahme. Ein solcher „Ausflug“ in die Vergangenheit erfordert eine präzise Abstimmung von Hard- und Software, die für moderne Entwickler ungewohnt ist.

Entwicklungsumgebung: Visual C++ 1.52 Compiler für Windows 3.1Entwicklungsumgebung: Visual C++ 1.52 Compiler für Windows 3.1

Dafür nutzten wir Visual C++ 1.52. Dies ist der letzte VC++-Compiler von Microsoft, der auf WFW 3.11 abzielen konnte. VC++ 1.52 ist selbst ein 16-Bit-Programm, kann also nur auf 32-Bit-Windows-Systemen ausgeführt werden. Theoretisch sollte es sogar auf Windows 10 (32-Bit) laufen, aber eine solche native Einrichtung stand mir nicht zur Verfügung.

Daher musste ich die Option einer virtuellen Maschine (VM) mit Windows 2000 in Betracht ziehen, unter Berücksichtigung folgender Punkte:

  1. Ältestes Windows-Betriebssystem mit voller Virtualbox-Unterstützung für Treiber, Netzwerk und bidirektionale Zwischenablage.
  2. Keine Produktaktivierung, im Gegensatz zu seinem Nachfolger Windows XP, was den Einrichtungsprozess vereinfachte.
  3. Geringster Ressourcenverbrauch und kleinster Festplatten-Fußabdruck.
  4. Kompatibel sowohl mit Windows 10/Mac als auch mit der SMB-Dateifreigabe von Windows 3.1.

Das Testen eines 16-Bit-Programms auf einer 32-Bit-OS-VM ist kein sehr realistisches Szenario, da VMs oft zu „perfekt“ sind. Wir mussten unsere App während der Entwicklung gelegentlich auf der tatsächlich alten Hardware mit WFW 3.11 testen, um reale Bedingungen zu simulieren und sicherzustellen, dass unser Slack für Windows 3.1 auch dort zuverlässig funktioniert.

Außerdem konnten wir unseren Code nicht direkt auf der Windows 2000 VM schreiben, da diese nur begrenzte Softwareunterstützung für moderne IDEs wie Visual Studio Code bot, ganz zu schweigen von Sicherheitsproblemen.

Windows 10 und Mac können nicht direkt mit dem alten SMB-Protokoll von WFW 3.11 kommunizieren. Für ähnliche Herausforderungen bei der Zusammenarbeit von verschiedenen Systemen oder zur Fernwartung, könnten Ihnen unsere Empfehlungen zu Alternativen zu TeamViewer nützlich sein, auch wenn sie für modernere Umgebungen konzipiert sind.

Hardware-Setup

Schema des Hardware-Entwicklungs-Setups für die Windows 3.1 AppSchema des Hardware-Entwicklungs-Setups für die Windows 3.1 App

Die primäre Entwicklung erfolgte auf dem Mac mit dem Visual Studio Code Editor, wobei die Dateien auf dem Mac-Dateisystem lagen. VC++ 1.52 griff über ein zugeordnetes Laufwerk auf einer SMB-Freigabe auf die Quelldateien zu und führte die Kompilierung durch.

Zwischentest des Slack Clients auf einer Windows 2000 VMZwischentest des Slack Clients auf einer Windows 2000 VM

Ich konnte Zwischen-Tests auf Win 2K durchführen, da es 16-Bit-Anwendungen ausführen kann.

Windows für Workgroups 3.11 greift über SMB auf eine Netzwerkfreigabe zuWindows für Workgroups 3.11 greift über SMB auf eine Netzwerkfreigabe zu

Mit einer Win 2K VM, die für ein Bridge-Netzwerk konfiguriert war und somit dasselbe Subnetz nutzte, konnte mein natives WFW 3.11 System auf den SMB-Freigabeordner der Win 2K VM zugreifen, wie oben dargestellt. Das war tatsächlich das erste Mal, dass ich das Netzwerklaufwerk-Symbol im Dateimanager sah.

Die Größe der Binärdatei beträgt 23 KiB. Das ist um viele Größenordnungen kleiner als die moderne Slack-App im App Store – ein deutlicher Hinweis auf die Effizienz, die unter den damaligen Einschränkungen notwendig war, um überhaupt einen Slack für Windows 3.1 zu realisieren.

Die Herausforderungen der Programmierung

Das Programmieren einer alten App bringt einige sehr einzigartige Herausforderungen mit sich, die man bei modernen Toolchains und Programmiersprachen nicht kennt. Ich musste mich durch viele Probleme kämpfen, von denen ich die wichtigsten im Folgenden detailliert beschreibe.

API-Dokumentation: Eine Reise in die Vergangenheit

API-Dokumentation und Anwendungsbeispiele sind bei der Softwareentwicklung von großer Bedeutung. Typischerweise verlassen sich Programmierer stark auf Google und Stack Overflow. Doch Windows 3.1 erschien in den Anfängen des Internets, und Google kann nicht helfen, wenn relevante Websites schlichtweg nicht existieren. Im Grunde genommen konnten Windows 3.1, seine API-Dokumentation und die damit verbundenen Probleme nicht einfach gegoogelt werden – man musste sich auf traditionellere Methoden wie Bücher verlassen! Wenn es um die Einhaltung präziser Vorgaben geht, sei es bei der Programmierung oder der Textgestaltung, können Richtlinien zur Groß- und Kleinschreibung in Google Docs aufzeigen, wie wichtig klare Regeln sind – auch wenn die Quellenlage hier eine andere war.

Referenzliteratur

Referenzbuch: Programming Windows 3.1 von Charles PetzoldReferenzbuch: Programming Windows 3.1 von Charles Petzold

Ich kaufte das Buch „Programming Windows 3.1“ von Charles Petzold bei Amazon.

„Wenn Sie für Windows (3.1) programmieren wollen, kaufen Sie dieses Buch. Es wird sich innerhalb weniger Stunden bezahlt machen.“ Dem stimme ich voll und ganz zu.

Zum Zeitpunkt der ursprünglichen Erstellung dieses Beitrags konnte ich keine Softcopy dieses Buches finden, was bedeutete, dass ich bei der Recherche auf die altmodische Art und Weise vorgehen musste: das Buch kaufen und dann das Inhaltsverzeichnis und den Index durchgehen.

Im Jahr 2020 wies ein Kommentator (siehe unten) darauf hin, dass eine Softcopy dieses Buches nun auf dieser archive.org-Seite verfügbar ist.

Ich habe auch die Beispielcodes der Diskette in dieses GitHub-Repository hochgeladen.

Header-Datei-Inspektion

Manuelle Überprüfung von Header-Dateien für Windows 3.1 APIsManuelle Überprüfung von Header-Dateien für Windows 3.1 APIs

Manchmal musste ich bestimmte APIs verwenden, die im Buch möglicherweise nicht dokumentiert waren, zum Beispiel Netzwerk-APIs. In diesem Fall musste ich die Header-Dateien im „include“-Verzeichnis des Compilers manuell inspizieren, um herauszufinden, welche Parameter für die Funktionen erforderlich waren.

Inverse Deduktion aus modernen Windows-APIs

Das Nachschlagen der Funktionsparameter war nicht immer ausreichend, da ich wissen musste, was die Funktion tatsächlich tut. Hier erwies sich Microsofts legendäre Abwärtskompatibilität als nützlich. Viele moderne Windows-APIs ähneln den traditionellen APIs sehr. Microsoft bietet sogar eine Anleitung zum Erstellen einer App auf die traditionelle Weise.

Die Windows-API (auch bekannt als Win32-API, Windows Desktop API und Windows Classic API) ist ein C-Sprach-basiertes Framework zum Erstellen von Windows-Anwendungen. Es existiert seit den 1980er Jahren und wird seit Jahrzehnten zur Erstellung von Windows-Anwendungen verwendet. Fortschrittlichere und einfacher zu programmierende Frameworks wurden auf der Windows-API aufgebaut. Zum Beispiel MFC, ATL, die .NET-Frameworks. Selbst der modernste Windows Runtime Code für UWP- und Store-Apps, der in C++/WinRT geschrieben wurde, verwendet die Windows-API darunter.

Selbst wenn die modernen APIs unterschiedlich waren, konnte mir die Dokumentation genügend Informationen geben, um abzuleiten, was die älteren APIs tun.

C89-Sprachstandard: Die Hürden der Vergangenheit

VC++ 1.52 wurde in den frühen 1990er Jahren veröffentlicht und unterstützte offensichtlich nur den C-Standard dieser Ära, nämlich C89. Obwohl ich C in meiner Arbeit recht häufig verwende, hatte C89 einige Eigenheiten, die mein Leben schwerer machten, besonders bei der Entwicklung eines anspruchsvollen Projekts wie eines Slack für Windows 3.1.

Variablendeklaration am Anfang des Funktionsumfangs

Grundsätzlich bedeutet dies, dass ich eine Variable nicht mitten in einer Funktion deklarieren konnte. Um eine Vorstellung davon zu bekommen, wie mühsam dies ist, werfen Sie einen Blick auf dieses Codebeispiel.

Codebeispiel für C89 Variablendeklaration am FunktionsanfangCodebeispiel für C89 Variablendeklaration am Funktionsanfang

Dieses Beispielcode befasst sich mit der Ereignisschleife beim Empfangen von Callbacks von Windows. Alle Variablen, die in dieser Funktion jemals verwendet werden, mussten oben deklariert werden, nicht erst dann, wenn ich sie benötigte. Das führte zu viel Hin- und Herschalten im Code.

Ein Nutzer auf Hackernews, userbinator, merkte dazu an: „Beachten Sie, dass C89 nur die Deklaration von Variablen mitten in einem Scope verhindert; man kann einfach einen inneren Scope mit neuen Variablendeklarationen an dessen Anfang erstellen.“ Dies ist zwar eine gültige Technik, die aber die Lesbarkeit und Struktur des Codes für ein komplexeres Projekt wie unseren Slack für Windows 3.1 weiterhin erschwert.

Keine sicheren Funktionen

In der Schule wurde mir beigebracht, „sicherere“ Funktionen wie snprintf anstelle von sprintf zu verwenden. Die neueren Funktionen ermöglichen es dem Programmierer, die Anzahl der zu verwendenden Bytes anzugeben, wodurch gefährliche Pufferüberläufe verhindert werden. Diese Funktionen kamen erst in C99 heraus, waren also für mich keine Option. Ich musste bei der Definition meiner Puffergrößen sehr vorsichtig sein, um die Stabilität unseres Slack für Windows 3.1 zu gewährleisten.

Mangel an Bibliotheken

Die alte C89-Unterstützung schloss viele C-Bibliotheken aus, die ich online finden konnte. C ist schon eine schwierige Sprache; ohne Bibliotheksunterstützung bedeutet dies, viel Arbeit von Hand erledigen zu müssen. Dies betraf alles, von grundlegenden Datenstrukturen bis hin zu komplexeren Algorithmen, die in modernen Entwicklungsumgebungen selbstverständlich sind.

Speicherverwaltung: Das 16-Bit-Labyrinth

Dies war eine extrem harte Nuss, die ohne Petzolds Buch für mich unmöglich zu knacken gewesen wäre. Die Stack-Größe eines 16-Bit-Programms beträgt typischerweise 4-6 KiB, wobei der lokale Heap bis zu 64 KiB umfassen kann. Das ist kleiner als die Größe der HTTP-Antwort plus des von Slack zurückgegebenen JSONs! Eine enorme Herausforderung für einen funktionstüchtigen Slack für Windows 3.1.

Im Kontext von IBM PC-kompatiblen und Wintel-Plattformen ist eine 16-Bit-Anwendung jede Software, die für MS-DOS, OS/2 1.x oder frühe Versionen von Microsoft Windows geschrieben wurde und ursprünglich auf den 16-Bit Intel 8088 und Intel 80286 Mikroprozessoren lief. Solche Anwendungen verwendeten eine 20-Bit- oder 24-Bit-Segment- oder Selektor-Offset-Adressdarstellung, um den Bereich der adressierbaren Speicherorte über das hinaus zu erweitern, was mit nur 16-Bit-Adressen möglich war.

Programme, die mehr als 2^16 Bytes (65.536 Bytes) an Anweisungen und Daten enthielten, benötigten daher spezielle Anweisungen, um zwischen ihren 64-Kilobyte-Segmenten zu wechseln, was die Komplexität der Programmierung von 16-Bit-Anwendungen erhöhte.

Quelle: https://en.wikipedia.org/wiki/16-bit#16-bit_application

Wikipedia hat hier absolut Recht! Die folgenden 2 Seiten haben mich buchstäblich vor stundenlangem Googeln bewahrt, vorausgesetzt, Google hätte überhaupt eine Antwort geliefert!

Buchseite über Speicherzuweisung für 16-Bit-Anwendungen in Windows 3.1Buchseite über Speicherzuweisung für 16-Bit-Anwendungen in Windows 3.1

Details zu den Global Heap Memory APIs in Windows 3.1Details zu den Global Heap Memory APIs in Windows 3.1

Um einen „großen“ Speicherblock von den im Buch beschriebenen APIs zuzuweisen, mussten wir ihn aus dem Global Heap allokieren. Dies habe ich wie folgt getan:

#include <windows.h>
//Up to 64K is possible
#define MAX_GLOBAL_MEM_ALLOCATION 32000

LPSTR lpGlobalMemory;
DWORD allocatedMemorySize;

lpGlobalMemory = GlobalAllocPtr(GMEM_MOVEABLE, MAX_GLOBAL_MEM_ALLOCATION);
allocatedMemorySize = GlobalSize(GlobalPtrHandle(lpGlobalMemory));

printf("We got allocated %u bytes of memoryn", allocatedMemorySize);

GlobalFreePtr(lpGlobalMemory);

LPSTR ist ein „FAR“ 32-Bit-Zeiger, der einen größeren Speicherbereich ermöglicht. Der konventionelle „char“-Zeigertyp war in dieser Ära nur 16-Bit. Wir teilen WFW 3.11 mit, wie viel wir zuweisen möchten. Nach der Speicherzuweisung ist es wichtig zu überprüfen, wie viel tatsächlich zugewiesen wurde. Und nach Beendigung der Speichernutzung muss dieser wieder freigegeben werden.

Netzwerkkommunikation: Hürden und Umwege

Die Netzwerkkommunikation war ein weiterer komplexer Bereich, der besondere Lösungen erforderte, um unseren Slack für Windows 3.1 überhaupt funktionsfähig zu machen.

HTTPS-Probleme

Sichere REST-API-Kommunikation über HTTPS ist heute die Norm im Umgang mit Webdiensten. In jener Ära war dies jedoch nicht der Fall.

WFW 3.11 verwendet die Windows Sockets API (Winsock) Version 1.1. Die Secure Socket Extensions für Winsock kamen erst in Version 2 heraus. Der konventionelle Weg wäre die Verwendung von OpenSSL, aber das ist so komplex, dass es keine realistische Chance gab, dieses gesamte Projekt auf C89 unter WFW 3.11 zu portieren.

Diagramm: HTTP-zu-HTTPS-Proxy-Lösung für Slack Client auf Windows 3.1Diagramm: HTTP-zu-HTTPS-Proxy-Lösung für Slack Client auf Windows 3.1

Nach langem Zögern und Widerwillen entschied ich mich für einen „Trick“ und schrieb meinen eigenen http-to-https-proxy in Golang. Dieser agierte als transparenter Vermittler, der das Host-Feld der HTTP-Anfragen inspizierte und die rohen Socket-Bytes als HTTPS an Slacks Server weiterleitete. Das Ergebnis wurde dann Byte für Byte an die App zurückgesendet.

Puristen mögen diese Lösung vielleicht nicht, aber dies war das Beste, was ich mit meinen Fähigkeiten in einer angemessenen Zeit erreichen konnte, um einen rudimentären Slack für Windows 3.1 zu realisieren.

Leser haben mir vorgeschlagen, dass ein bestehendes Projekt namens stunnel bereits existiert, sodass dieser selbstgeschriebene Proxy nicht notwendig sei. Ich habe stunnel ausprobiert, aber es scheint nur zu funktionieren, wenn es auf demselben System läuft wie die App, die den Tunnel benötigt, und es würde offensichtlich auf einem Windows 3.1 System nicht funktionieren.

Es gibt möglicherweise einige SSL-Bibliotheken für eingebettete Systeme, die einen Versuch wert wären, wie BearSSL, mbed TLS und WolfSSL. Weitere Gedanken zu diesem Thema finden Sie am Ende dieses Blogbeitrags.

Socket-Programmierung

Das letzte Mal, dass ich Low-Level-Socket-Programmierung betrieben habe, war an der Universität. Heutzutage wird mit modernen Programmiersprachen und APIs alles für Sie erledigt. Aber solche Bibliotheken existierten für C89, soweit ich mich erinnerte, nicht. Also musste ich die gesamte POST-Anfrage von Hand konstruieren und die Socket-APIs direkt aufrufen, um die Kommunikation unseres Slack für Windows 3.1 mit den Servern zu ermöglichen.

#include <winsock.h>
#pragma comment(lib,"winsock.lib")
#define POST_MESSAGE_FORMAT "POST /api/chat.postMessage?channel=%s&as_user=true&text=%s HTTP/1.1rnHost: slack.comrnAuthorization: Bearer %srnAccept: */*rnConnection: closernrn"

WSADATA wsaData;
struct sockaddr_in serveraddr;

WORD wVersionRequested = MAKEWORD(1, 1);
nErrorStatus = WSAStartup(wVersionRequested, &wsaData);

sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr(ipaddress);
serveraddr.sin_port = htons((unsigned short) port);
error = connect(socketID, (struct sockaddr *) &serveraddr, sizeof(serveraddr));

sprintf(buff, POST_MESSAGE_FORMAT, "win31", "hello", "token");
bytesSent = send(socketID, request, requestLength, 0);
bytesReceivedThisInstant = recv(socketID, (char *) &buff, 1, 0);

closesocket(socketID);
WSACleanup();

Der obige Code startet die Winsock-API, erstellt den TCP-Socket, verbindet sich damit, konstruiert die Anfrage, sendet die Anfrage, empfängt die Antwort, schließt den Socket und beendet die Winsock-API.

Die meisten Anfragen verwenden heute standardmäßig die Option „Content-Encoding: deflate, gzip“, um Komprimierung zur Reduzierung des Bandbreitenbedarfs zu ermöglichen. Hier hatte ich keine Möglichkeit, sie zu dekomprimieren, daher wurde dies weggelassen, um den Server zu zwingen, die Antwort im reinen Textformat zu liefern.

Ich glaube, tief in allen Netzwerkbibliotheken wird es so gemacht. Nur hatte ich jetzt nicht den Vorteil dieser Abstraktion, was die Entwicklung des Slack für Windows 3.1 um einiges komplizierter machte.

JSON-Parsing

Die Ausgabe der meisten REST-APIs erfolgt im JSON-Format. Auch hier waren keine nativen JSON-Dekodierungs-APIs in VC++ 1.52 verfügbar. Nach langer Suche fand ich einen C89-kompatiblen JSON-Parser namens JSMN. Dieser hatte jedoch einige Eigenheiten, die die Verwendung anspruchsvoller machten.

Typische JSON-Parser liefern eine Dictionary-Ausgabe, bei der Sie die gespeicherten Werte durch Angabe eines Schlüssels abrufen können.

value = jsonDictionary['key']

Aus Gründen der Speicherplatzersparnis gibt JSMN jedoch kein Dictionary aus. Es liefert ein Array von Token-Strukturen, die auf die Token-Grenzen im ursprünglichen JSON-String zeigen. Nehmen wir ein Beispiel aus meinem Code:

#include "jsmn.h"

jsmn_parser parser;
jsmntok_t tokens[MAX_TOKENS];
jsmntok_t currentToken;
char currentTokenString[MAX_TOKEN_KEYWORD_SIZE];
LPSTR startOfJson;
DWORD lengthOfJson;

jsmn_init(&parser);
jsmn_parse(&parser, jsonString, lengthOfJson, tokens, MAX_TOKENS);

for(index = 0; index < MAX_TOKENS; index++){
    currentToken = tokens[index];
    tokenStartPosition = currentToken.start;
    tokenSize = currentToken.end - tokenStartPosition;

    if(tokenSize <= 0){
        continue;
    }
    if(tokenSize > MAX_TOKEN_KEYWORD_SIZE){
        continue;
    }

    memset(currentTokenString, 0, MAX_TOKEN_KEYWORD_SIZE);
    fmemcpy(currentTokenString, startOfJson + tokenStartPosition, tokenSize);

    printf("Current token %sn", currentTokenString);

}

Was hier geschieht, ist, dass ich alle von JSMN bereitgestellten Tokens durchlaufe und versuche, sie auszudrucken. JSMN liefert die Start- und Endpositionen des übergeordneten Arrays, daher muss ich diese Werte verwenden, um den Speicher in ein separates Char-Array für Druckzwecke zu kopieren. Sicherlich nicht so trivial.

_fmemcpy ist das FAR-Pointer-Äquivalent für memcpy, spezifisch nur für die Windows-Plattform. Ich habe den Quellcode von JSMN geringfügig modifiziert, um größere Arrays basierend auf dem Datentyp LPSTR zu ermöglichen.

JSON-Parsing-Optimierung

Aufgrund der sehr geringen Stack-Größe war die maximale Anzahl der gleichzeitig zu parsierenden Tokens extrem begrenzt. Dies verursachte Probleme, da dies bedeutete, dass ich nur sehr wenige Schlüssel und Werte aus dem String parsen konnte. Um dies zu lösen, führte ich ein inkrementelles Parsing durch, wie unten gezeigt.

Inkrementelles JSON-Parsing zur Speicheroptimierung in Windows 3.1Inkrementelles JSON-Parsing zur Speicheroptimierung in Windows 3.1

Für diese Konversationsverlaufs-API habe ich den JSON-Teil für Teil bis zum Ende geparst, um zu vermeiden, den gesamten gigantischen JSON auf einmal zu parsen. Dies war eine kritische Optimierung, um die Funktionsfähigkeit unseres Slack für Windows 3.1 unter den gegebenen Speicherbeschränkungen zu gewährleisten.

Testen: Qualitätskontrolle unter Retro-Bedingungen

Test-Driven Development (TDD) wird in der modernen Softwareentwicklung häufig eingesetzt, war aber in jener Ära nicht so verbreitet. Das Testen war meiner Meinung nach auch für dieses “Science Project” äußerst wichtig, da ich sicherstellen wollte, dass mein Code in seinen einzelnen Einheiten funktioniert, bevor er zu einem kompletten Slack für Windows 3.1 zusammengesetzt wurde.

VC++ 1.52 schien keine integrierten Testfunktionen zu haben. Ich konnte kein C89-kompatibles C-Test-Framework finden, das mit VC++ 1.52 funktionieren würde, also habe ich ein einfaches selbst entwickelt.

Test-Setup für die Entwicklung von Windows 3.1 AnwendungenTest-Setup für die Entwicklung von Windows 3.1 Anwendungen

Ich erstellte ein Duplikat-Projekt namens Test.mak, das TestMain.C kompilierte. Dieses enthielt die einzelnen Testfunktionen test.h innerhalb jedes Pakets. Beim Ausführen der Tests führte test.h die einzelnen Tests in seiner Datei aus, die wiederum die Funktionen im Paket testeten.

Testergebnisse im Visual C++ DebuggerTestergebnisse im Visual C++ Debugger

Die Testergebnisse wurden dann in der VC++-Debuggerausgabe angezeigt.

Mock-Server für Netzwerktests

Aufgrund meiner mangelnden Vertrautheit mit den Winsock-APIs schrieb ich tatsächlich Mock-Server in Golang, um HTTP-Socket-Anfragen, die während des Testprozesses gestellt wurden, wiederzugeben oder zu beantworten. Konsultieren Sie das Repository, um mehr darüber zu erfahren! Dies ermöglichte es mir, die Netzwerkkommunikation isoliert zu testen, was für die Entwicklung eines stabilen Slack für Windows 3.1 von entscheidender Bedeutung war.

Ressourcennutzung: Alt gegen Neu im Vergleich

Aufgrund der großen Nachfrage gab es Fragen zur Ressourcennutzung der Windows 3.1 App + VM + Proxy im Vergleich zur offiziellen Mac Slack App. Also führte ich einen Testlauf durch, und hier ist das Ergebnis!

Mac-Speicherverbrauch

Speicherverbrauch Vergleich: Win 3.1 Slack App vs. moderne Mac Slack AppSpeicherverbrauch Vergleich: Win 3.1 Slack App vs. moderne Mac Slack App

Sie können auf die Datei klicken, um genauer hinzusehen. Die WFW 3.11 VM ist so konfiguriert, dass sie 8 MB RAM mit 16 MB Video-RAM verwendet. Die Mac Slack App ist nur bei einem Workspace angemeldet.

Basierend auf dem Top-Ergebnis:

  • Virtualbox VM: 132 MiB
  • http-to-https-proxy: 9 MiB
  • Slack: 72,2 MiB
  • Slack Helper GPU: 176,5 MiB
  • Slack Helper Renderer: 147,4 MiB

Insgesamt verbraucht Slack 396,1 MiB, während die WFW3.11 VM + Proxy 141 MiB benötigt. Allein beim Speicherverbrauch kann meine App tatsächlich gewinnen!

Offensichtlich ist dies kein direkter Vergleich, da die Mac Slack App voll funktionsfähig ist und die VM einen erheblichen Overhead hat. Dennoch zeigt es, wie ressourceneffizient eine Anwendung wie unser Slack für Windows 3.1 unter den damaligen Einschränkungen sein konnte. Im Kontext moderner Software wie PowerPoint für Linux sind solche Vergleiche faszinierend, da sie die Fortschritte und Kompromisse in der Softwareentwicklung über die Jahrzehnte hinweg aufzeigen.

Mac-Festplattennutzung

Festplattennutzung Vergleich: Win 3.1 VM mit App vs. moderne Mac Slack AppFestplattennutzung Vergleich: Win 3.1 VM mit App vs. moderne Mac Slack App

Die VM ist derzeit dynamisch dimensioniert, aber man kann sehen, dass sie nur 100 MiB verwendet, im Vergleich zu den 165,5 MiB der offiziellen Mac Slack App.

Also wieder ein Sieg für die Retro-App!

Fazit: Lehren aus der Vergangenheit der Softwareentwicklung

Dieses Projekt wäre ohne die Beispielanleitung von Transmission Zero Building Win16 GUI Applications in C und die Zusammenarbeit mit meinem Kollegen Subh während des Firmen-Hackathons nicht möglich gewesen. Es war eine faszinierende Reise zurück zu den Wurzeln der Softwareentwicklung, um einen funktionierenden Slack für Windows 3.1 zu schaffen.

Ohne den Vorteil moderner Bibliotheken und Sprachen musste ich mich um viele Low-Level-Details kümmern: Socket-Programmierung, HTTP, JSON-Parsing und UI-Design im Code – alles unter engen Speicherbeschränkungen. Das extrem instabile und absturzanfällige WFW 3.1 half dabei nicht. Dennoch war es eine fantastische Lektion, um zu verstehen, wie die Dinge unter der Haube funktionieren.

Ich musste die Dinge auf die altmodische Art und Weise erledigen, indem ich Bücher und Header-Dateien las, da es an Online-Dokumentation mangelte. Ich kann die Notlage der Programmierer vergangener Jahre nachempfinden, die ohne den Vorteil von Online-Suchmaschinen codieren mussten.

Wenn Sie bis zu diesem Punkt gelesen haben, bin ich sicher, dass Sie zu denen gehören, die daran interessiert sind, wie eine alte App mithilfe moderner Tools und des Wissens über alte Technologien erstellt werden kann. Mit diesem Blogbeitrag hoffe ich, dass Sie Spaß daran hatten, den extrem mühsamen Entwicklungsprozess für die neueste Windows 3.1 App kennenzulernen, die heute auf der Welt veröffentlicht wird.

Wenn Ihnen das gefallen hat, vergessen Sie nicht, Hui Jings Blogbeitrag über die Erstellung einer einfachen Windows 3.1 Spiel-App anzusehen. Ihr Blogbeitrag konzentriert sich stärker auf den UI-Aspekt, was eine großartige Ergänzung zu diesem Beitrag ist, da ich ihn hier nicht ausführlich behandelt habe.

Wenn Sie die Live-Demo meiner App und meinen Vortrag zu diesem Thema sehen möchten, schauen Sie hier vorbei!

Klarstellung zur Verwendung eines Proxys: Keine einfache Lösung

Ich erhielt viel negatives Feedback bezüglich der Verwendung eines HTTP-zu-HTTPS-Proxys. Lassen Sie mich die Gründe dafür klarstellen:

  1. Diese App ist als Proof-of-Concept einer neuen 16-Bit-App gedacht, die nur zum Spaß erstellt wurde. Nicht für den Produktionseinsatz. Es gibt keinen wirklichen Vorteil, wenn ich zusätzlichen Aufwand betreiben würde, um TLS zum Laufen zu bringen, wenn in der Praxis niemand diese App verwenden wird, nicht einmal ich selbst. Es ging darum, einen rudimentären Slack für Windows 3.1 zu zeigen.
  2. Ich habe bereits viele Monate unter der Woche und an Wochenenden damit verbracht, den Hackathon-Code zu bereinigen, Fehler zu beheben und die Unit-Tests zu schreiben. Ich denke, es ist Zeit, aufzuhören und die Dinge zu dokumentieren. Ich habe einen Vollzeitjob, und dies ist lediglich ein Hobbyprojekt.
  3. Mikrocontroller und andere speicherarme Plattformen werden als Beispiele dafür genannt: „Wenn die es können, warum können Sie es nicht für Win 3.1 tun?“. Die Leute vergessen, dass das 64K-segmentierte Speichermodell ungewöhnlich ist und der Aufwand, diese Bibliotheken zum Laufen zu bringen, möglicherweise nicht so einfach ist. Die C89-Sprachunterstützung könnte erhebliche Schwierigkeiten bereiten. Der winzige Stack und lokale Heap von 16-Bit-Win 3.1-Apps erhöhen den Herausforderungsfaktor definitiv. Und diese Portierungsbemühungen werden wahrscheinlich von Vollzeitprogrammierern durchgeführt.
  4. Mit genügend Zeit bin ich sicher, dass TLS-Funktionen zu dieser Win 3.1-App hinzugefügt werden könnten. Aber der dafür erforderliche Zeitaufwand würde wahrscheinlich die Entwicklung der Hauptfunktionalität der App selbst übersteigen.
  5. Ich habe noch kein Beispiel einer erfolgreichen TLS 1.2/1.3-fähigen 16-Bit-Win 3.1-App gesehen. Wenn es eine gibt, würde ich gerne von dem Entwickler der App erfahren, wie dies erreicht wurde.

Viele haben Mbed TLS empfohlen, hier ist das Wort des Mbed TLS Teams:

„Mbed TLS unterstützt keine 16-Bit-Architektur… Ich vermute jedoch, dass Sie auf andere Probleme im Zusammenhang mit 16-Bit-Plattformen in anderen Modulen stoßen werden.“ Quelle 1: https://tls.mbed.org/discussions/crypto-and-ssl/help-with-ecc-and-bignum Quelle 2: https://forums.mbed.com/t/mbed-tls-size-t/3103

Ich hoffe, Sie verstehen die Gründe, warum ich aufgab und einen Proxy verwendete. Lassen Sie es mich noch einmal wiederholen: „Puristen mögen diese Lösung vielleicht nicht, aber dies ist das Beste, was ich mit meinen Fähigkeiten in einer angemessenen Zeit erreichen konnte.“