
Building a new Windows 3.1 app in 2019: A Slack Client - yeokm1
http://yeokhengmeng.com/2019/12/building-a-new-win-3-1-app-in-2019-part-1-slack-client/
======
userbinator
_Notice the binary size of the app is only 64KiB._

In the demoscene this would be a disqualification as it's 676 bytes over the
limit, but in this case I'll overlook it because of the sheer awesomeness of
what you've done (and I'm sure trimming off 676 bytes wouldn't be too
difficult ;-)

More seriously, this is an _excellent_ proof-of-concept that a Slack client
does not need to use hundreds of MB of RAM and consume most of a modern CPU
core to provide its basic functionality. I'm a long-time Win32 programmer who
started in the tail-end of the Win16 days and one of the things I've wanted to
write if I had the time and need was a Win32 native Slack client, to show that
it can be done with far less resources, but you've gone even further with
Win16. Win32 has native TLS support (via SChannel library, not well-documented
but examples exist) and you'd be able to even use it practically (32-bit
applications will run on all current versions of Windows, both 32 and 64-bit.)

Note that C89 only prevents declarations of variables in the middle of a
_scope_ ; you can simply create an inner scope with new variable declarations
at _its_ start. Like so...

    
    
        case WM_PAINT: {
          PAINTSTRUCT ps;
          HDC hdc = BeginPaint(hwnd, &ps);
          ...
        }
    

Another small tip: if you make it a dialog-based application, you won't have
to bother with WM_PAINT and drawing text yourself --- you can just make the
"statusText" and "settingsText" edit controls, and use SetWindowText (or
SetDlgItemText) to set their content. They will paint themselves automatically
just like the listboxes that you used for the chat contents and userlist. At
least you'll save some of those 676 bytes that way...

~~~
rvanmil
> More seriously, this is an excellent proof-of-concept that a Slack client
> does not need to use hundreds of MB of RAM and consume most of a modern CPU
> core to provide its basic functionality.

If you’re so sure that this is possible, then where is the Electron competitor
which allows us to build cross platform applications like this, with the same
levels of productivity and a consistent and decent user interface? No one
needs to be convinced that a native Slack client for each platform would be
much better, I wish they would build those clients. The problem is most
companies can’t reasonably justify the costs of building multiple native
clients when a single cross platform one is good enough.

~~~
bonzini
Qt and to some extent GTK+ come close. GTK+ can be programmed in Vala which is
a fine language that transpiles to C and supports idioms such as asynchronous
programming.

~~~
marton78
That's cross platform for Windows, Mac and Linux, but what about Web, Android
and iOS?

~~~
shakna
Ignoring that you probably _don't_ want the same interface across all of
those...

Qt, as well as Windows, macOS and Linux actually can do Android [0] and iOS
[1], and Web is halfway there [2].

[0] [https://doc.qt.io/qt-5/android.html](https://doc.qt.io/qt-5/android.html)

[1] [https://doc.qt.io/qt-5/ios.html](https://doc.qt.io/qt-5/ios.html)

[2]
[https://wiki.qt.io/Qt_for_WebAssembly](https://wiki.qt.io/Qt_for_WebAssembly)

~~~
danShumway
I just want to step in quick to advocate. _Please_ do not use Qt for the web.
Their web export tools aren't just unfinished, they're actively hostile to web
paradigms. The finished product doesn't render to the DOM, it's completely
screenreader inaccessible, it overrides browser preferences. In a lot of ways,
Qt on the web is even worse than Flash was.

You know all of the arguments against Electron that people trot out about how
it's not real native, and it doesn't follow the right UX conventions or
paradigms? Qt on the web is exactly like Electron on native, except _way
worse_ and without any sign that it's going to improve, because fundamentally
Qt is structured around the idea that it should be able to spit out a binary
blob of pixels instead of hooking into web-native primitives like the DOM.

Look into something that's showing more promise, like .NET and Razor. The Rust
community is also doing some good work here, although I don't know that they
have any UI frameworks that are mature enough to be reliable in an enterprise
setting.

These aren't true cross-platform efforts yet, since they're still forcing you
to think about HTML. But if I was going to bet on any project ending up with a
real cross-platform solution that felt good on the web, some kind of export
option from an Open Source Windows Forms or similar is probably what I would
bet on. Either Microsoft or (possibly) the Rust community are the two parties
that I think are most likely to end up producing a true native UI toolkit that
can actually export to the web in a usable, acceptable way.

------
yeokm1
I built a Win 3.1 app during a company hackathon just for fun. Here, I detail
learnings and process for how a new old app can be created with the aid of
modern tools and hindsight of old technologies. And perhaps what lessons can
it offer us today.

Without the benefit of modern libraries and languages, I had to read up and
take care of many low level details, socket programming, HTTP, JSON parsing,
UI design in code all under tight memory constraints. Nevertheless, it was a
terrific lesson in understanding how things work under the hood.

I had to do things the old-fashioned way reading books and header files due to
the dearth of online documentation. I can empathise with the plight of the
programmers of yesteryears who had to code without the benefit of online
search engines.

With this blog post, I hope you'll find it interesting to learn about
developing a modern-ancient app for Win 3.1.

[https://github.com/yeokm1/w31slack](https://github.com/yeokm1/w31slack)
[https://github.com/yeokm1/http-to-https-
proxy](https://github.com/yeokm1/http-to-https-proxy)

~~~
eb0la
I expected to be compiled in some kind of VM, not on an _actual_ computer with
Windows 3.11 :-).

I feel "old-style" documentation is much better than we have now. My most
productive _python_ only work times were when I had only the python .hlp file
and a _physical_ paper book.

What are your impressions?

~~~
kstenerud
I've been programming since 1983. The internet opened up publishing to the
masses, which means that non-professional people could also publish, resulting
in much more documentation than ever before.

But now the amateur documentation writers are competing with the professional
ones; the quality of the professional documentation is still as high as ever,
but it can be drowned out by the amateur quality documentation.

All in all, finding the information you need is MUCH quicker via a search
engine, but the signal-to-noise ratio is lower.

~~~
userbinator
The quality of Microsoft's own documentation has also fallen considerably; the
latest nail in the coffin is that docs.microsoft.com thing that started to
replace MSDN several years ago, that created tons of ridiculous fuckups in the
"migration process". While doing that they also "open sourced" their
documentation on GitHub, which to me sounds more like they're just trying to
rely on free labour from the "community" to fix the mess.

One of the more memorable WTFs I've seen is this, which is still wrong as of
this post:

[https://docs.microsoft.com/en-
us/windows/win32/api/wininet/n...](https://docs.microsoft.com/en-
us/windows/win32/api/wininet/nf-wininet-internetopenurla)

Compare to the old version of the page on MSDN:

[http://web.archive.org/web/20080828050103/http://msdn.micros...](http://web.archive.org/web/20080828050103/http://msdn.microsoft.com/en-
us/library/aa385098\(VS.85\).aspx)

See what's missing? The newer version is not the correct one... in "migrating"
the document to the new site, for a reason that completely defines all
rational explanation, _the return type of the function declaration became
void_. There are plenty of pages on the new site with this serious error, and
they've remained unfixed for well over a year. People report such problems on
their GitHub, and they get fixed --- individually --- as they're reported, but
it still boggles the mind how such a blatant and widespread error could go
through (and the old, correct, content deleted flippantly) without someone in
power shouting "STOP!":

[https://github.com/MicrosoftDocs/windows-driver-docs-
ddi/iss...](https://github.com/MicrosoftDocs/windows-driver-docs-
ddi/issues/336)

~~~
bluedino
MSDN was a gold standard in the 90's. Now you have tragedies like
social.technet.com

~~~
fortran77
The low quality of the technet "answers" are mind-boggling. Why doesn't
Microsoft pull the plug on this?

Also, what's the purpose of the people who will answer nearly any question
with a very low-quality (but wordy) answer? What are they getting from this
effort?

~~~
nottorp
There must be some reputation game there.

You see it on SO too, people answering basic questions 2 seconds after they're
postedd with long pastes from documentation that are sorta related to the
original question.

------
ink_13
Reading this article and others by the same author on his travails of getting
Windows 3.11 to install and then bridging it to modern(-ish) tech was like
being transported back to the 90s when I was first learning about computers.
Seeing those screenshots of the unmistakable Win 3.1-style dialogs was a real
nostalgia hit. What a treat.

10 points (well, an upvote, I guess) for anyone who knows why the Windows DOS-
mode installer says "Press F3 to exit"

~~~
Iv
I seem to recall that F1 and F2 had some sort of hard wired functions (that
were printed on my keyboard) but I don't remember which they were...

~~~
dfox
There was never any that much hardwired functions for F1 and F2 on PC (apart
for the convention that F1 is almost always help).

On the other hand there is a related issue: on DEC (and some other) terminals
with LK201-style keyboards (which in turn inspired the "modern" PC/AT keyboard
layout with F1-F12) the keys in positions of F1-F5 had fixed functions. This
is the reason why there are about four different escape sequences across
vt220-compatible terminal emulators for F5. There simply was not any DEC
terminal that had F5 on its keyboard (F1-F4 are almost always mapped to
PF1-PF4).

------
xeeeeeeeeeeenu
Related: winevdm makes running 16-bit apps on 64-bit Windows possible:
[https://github.com/otya128/winevdm](https://github.com/otya128/winevdm)

~~~
yeokm1
Oh I did not know about this! I just based my understanding from the official
Microsoft documentation. Thanks!

------
xg15
> _The stack size of a 16-bit program is typically 4-6 KiB with a similar size
> for the heap. This is smaller than the size of the HTTP reply + JSON
> returned by Slack!_

Maybe this would be a good opportunity to stop for a moment and reflect about
whether or not the industry is really moving in the right direction.

~~~
winrid
I'm not sure slack actually uses these apis in their own app? They use
websockets for a lot of the communication right? In which case the server just
pushes small messages.

------
ufmace
After the first few paragraphs, I was most eager to see how he managed to
handle modern TLS on Win3.1. Last time I wrangled with vintage stuff near that
timeframe, that was the toughest part.

Alas, he "cheated" for it with a proxy app. Can't say I blame him, since it's
probably about as much work over again to get modern TLS 1.2 working on such
an old Windows.

~~~
userbinator
IMHO the fact that it's hard to get TLS 1.2 (or indeed any form of "pure"
cryptography) working at all on an obscure platform says a lot about the state
of commonly available crypto libraries --- as those who are against regulating
it like to say, it's "just math"; and math that any computer should
theoretically be able to do. OpenSSL is one of the more portable ones, yet I'm
not sure how it would be able to handle the 64K segmented model. It would need
at least 32KB (maximum TLS record size, multiplied by both directions) and a
little bit more per TLS connection. That said, a 2048-bit RSA key is "only"
256 bytes, and ECDH ones are smaller, so I think a TLS 1.2 (or even 1.3)
implementation on the platform is definitely in the area of "feasible, but not
trivial". A lot of IoT stuff has similar constraints (minus the segmented
addressing).

------
butz
Considering that Windows 3.1 takes less space than Electron binary, one can
actually run this program in VM and get better performance and less RAM usage
than official app.

~~~
zzo38computer
I have DOSBOX, but Windows 3.1 runs very slowly on DOSBOX.

~~~
anthk
I was running win 3.1 under DOSBox fastly since forever (athlon days). You may
have some CPU settings wrong.

------
devit
If the target was just "Windows 3.1", he should have built a 32-bit app using
the Win32s API
[[https://en.wikipedia.org/wiki/Win32s](https://en.wikipedia.org/wiki/Win32s)]

It's also probably possible to use a current compiler, maybe clang or gcc, to
compile such code (officially only up to VC++ 4.2 is supported), although it
might require some hacks.

~~~
egorfine
Win32s applications can be executed on a limited subset of hardware supported
by Windows 3.x.

~~~
dfox
Which is moot point given that the Microsoft-supplied TCP/IP stack is
essentially also Win32s application and thus only runs on the same subset.

------
EliRivers
Petzold is Gold. I have a copy of the Fifth Edition that I bought new and I
expect it to remain useful for some time yet.

~~~
Multicomp
Fwiw programming windows by Petzold through the 5th edition covers native
Windows development through Windows XP.

The sixth edition on words begins to use the. Net CLR and other non-native
toolchains as Microsoft changed them. So, those are less valuable to me.

If you want to do native Windows development, that is probably the single best
book you could buy.

------
asveikau
This presents c89 as the most antiquated thing in the world, but VS didn't
support mixed declarations and code in a .c file until 2015. There is still a
lot of code out there that avoids this to be able to work with MS's compilers.

~~~
barrkel
Could have done it with C++, would have worked for calling WinAPI too.

~~~
asveikau
If there are gripes about c89 I don't want to think about how different c++
support would be relative to the late 90s or early to mid 2000s, let alone
now.

------
sneak
> _Purists may not like this solution but this is the best I can do with my
> abilities in a reasonable amount of time._

I am going to be using this line a lot in the future.

------
amluto
You can probably run this app using WINE on any modern Linux 64-bit system. Or
maybe even Mac OS X Catalina.

(The Linux x86 maintainers are semi-seriously considering deprecating the
horrible machinations needed for 16-bit on 32-bit to work, but 16-bit on
64-bit Will be supported for the foreseeable future.)

------
Ididntdothis
Windows 3.1 and 16 bit would be a little too painful for me. Windows 95 was
quite nice with 32 bit and preemptive multitasking though. I kind of miss this
or Windows 2000.

~~~
vardump
NT4 was _very_ nice, although no USB.

Didn't crash and was smooth as butter.

~~~
1996
I find this history so super interesting.

Have you kept a nt4? Do you still use it? What do you think is the closest
equivalent: win200, reactos??

~~~
vardump
No, I haven't used it since early 2000. Windows 2000 was the successor, also a
good release. I guess both are pretty close to what NT4 was.

------
frabert
As a side note, since TFA mentions you can't run 16b apps on 64b Windows, I'd
like to mention that otvdm/winevdm actually allows you to do it by running
Win16 apps on Win32 using Wine!

~~~
yeokm1
Thanks for raising this to me!

------
JordanFarmer
Nice, Would have been a lot using with Visual Basic 4, but I guess easy wasn't
the point.

~~~
tluyben2
Same with Delphi. We did decades of applications going from Turbo Pascal in
the 80s to Delphi in the 90s and 00s and 10s without hardly any changes (this
was possible because I wrote a tooling to convert the DOS Pascal graphics code
to Delphi Windows automatically so we ran 1 version of code that compiled on
both) right up until the company was sold a couple of years ago.

------
robmccoll
You could avoid the copy out of the JSON string by using a field width
specifier in your printf formatting string. Those should be available in ANSI
C / C89/90.

    
    
       currentToken = tokens[index];
       tokenSize = currentToken.end - currentToken.start;
     
       if(tokenSize <= 0){ continue; }
     
       printf("Current token %.*s\n", tokenSize, startOfJson + currentToken.start);

~~~
yeokm1
The printf is just for example code.

In actual fact, I'll have to copy the data to a char array to be sent to
display in the listbox.

~~~
robmccoll
Fair enough. I read this quite literally and thought "not exactly":

> hence I need to use those values to copy out from memory to a separate char
> array for printing purposes. Certainly not so trivial.

------
kleiba
_I had to do things the_ old-fashioned _way reading books and header files
[...]_

O tempora o mores!

I'm getting old...

------
fortran77
Another option would have been Turbo Pascal for Windows 3.1

[https://en.wikipedia.org/wiki/Turbo_Pascal#Turbo_Pascal_for_...](https://en.wikipedia.org/wiki/Turbo_Pascal#Turbo_Pascal_for_Windows)

------
bensherman228
Cool tutorial, I read an article about ideas for mobile applications
([https://mwdn.com/mobile-app-ideas-2020-how-to-create-
somethi...](https://mwdn.com/mobile-app-ideas-2020-how-to-create-something-
new/)) and now I want to create my own, there is one idea about how to create
another application from one application, I read your article and was
inspired. thanks :)

------
coryrc
> Therefore, if one wants to write an app using Windows OS standard APIs, WFW
> 3.11 is the oldest one can go back without putting in even more exponential
> effort.

Hey, what's wrong with Trumpet Winsock? :-)

~~~
cr0sh
My thought too - but then I recalled - vaguely - the "setup" procedure for it
- so "even more exponential effort" sounded a bit right...

------
andraganescu
This is so amazing, I love all you people who take the time to do these super
mega awesome tinker/fun projects and then share them with to the world!

------
nitwit005
Disappointed not to see the now deprecated WSAAsyncSelect function in use. It
was their extension to make sockets play nicely with the UI event loop.

They needed some way that you could be waiting for either window messages or
socket data. Otherwise your window would stop responding to input or repaint
itself if some socket operation blocked.

------
ct520
Reading how he cheated with proxy. That was what I wanted to see. :( Had a
client of a enterprise style company integrate into one of our products. Dude
was the definition of cowboy coder so we often looked at logs. All I can say
is wow - them logs for negotiating a https connection in BASIC..

~~~
1996
please tell us more!

------
agumonkey
When I found a win95 box with tp7 on it, my first thought was to write
'modern' things on it. HTTPS, Haskell... I starter writing a lisp. Didn't
finish sadly. Because i love the idea of bridging 'old' with the good parts of
'new'

~~~
pavlov
Though Lisp is much older than Windows 95 of course :)

~~~
agumonkey
lisp is forever more modern, you know that

------
thrower123
Everyone really should be made to code up a few Win32 apps, at least, before
they are allowed to make an Electron app, just so they can get a real sense of
what the trade-offs actually are, instead of relying on hearsay and FUD.

------
theandrewbailey
> Also, we can’t write our code directly on the Windows 2000 VM as it has
> limited software support for modern IDEs like Visual Studio Code not to
> mention security issues.

I'm guessing that Visual C++ 1.52 has no code editing capabilities like Visual
Studio?

~~~
yeokm1
VC++ 1.52 has an internal text editor but nowhere near the capabilities of
Visual Studio Code or any modern text editors.

I also use Git and sourcetree and they won't run on Win 2K.

------
deddo
This is amazing, very well done. I think every programmer should from time to
time build something for a very old platform and appreciate the progress
achieved so far.

~~~
Endy
By which you mean that they'll recognize everything that's been stolen from
the users, right? Win 3.1 & DOS was probably about the last time I really felt
in control of my PC.

------
Jaruzel
> _Windows 10 [...] cannot directly talk to the ancient SMB protocol used by
> WFW 3.11._

Not true, you can downgrade the SMB version requirement in the Windows 10
registry.

~~~
peteri
But then you'll have to spend the rest of your life dodging Ned Pyle of
Microsoft.

------
ben174
It’s unfortunate slack is this difficult to integrate with. Jabber or IRC
clients could be spun up with zero pain.

~~~
Tepix
Show us :-)

I'm sure writing mIrc was not "zero pain" back in the day. And Jabber needs a
XML parser which is more complex than a JSON parser.

~~~
anthk
There are tiny XML parsers out there.

Also, an IRC client is a joke, it can be compiled and built even on BSD 4.3,
released in late 80's.

The suckless guys created some IRC clients in few lines, and the sj client is
not that difficult.

Heck, you could write an IRC client with Bash and even with Netcat/ed.

------
int_19h
I wonder how much easier this would have been in Borland C++ or Borland Pascal
with OWL.

Or even Delphi! Delphi 1 targeted Win16.

------
mkj
Aaw cheating with https. I wonder if an embeddable library like BearSSL would
be hard to get working.

~~~
yeokm1
Something I can try next time! I didn't know about BearSSL before

~~~
anthk
Or FreePascal/Lazarus with OpenSSL. If you build a Win16 statically linked
binary it may work.

[https://wiki.freepascal.org/Win16](https://wiki.freepascal.org/Win16)

------
breadandcrumbel
So a properly native Slack client exists for Windows 3.1 before modern
platforms? Whaat the hell?

~~~
Shorel
[https://news.ycombinator.com/item?id=19617699](https://news.ycombinator.com/item?id=19617699)

------
mhd
I'm a bit disappointed that the code doesn't appear to use Whitesmith style...

------
jamil7
Do the public slack APIs offer enough to built a third party client?

~~~
yeokm1
I felt it was enough for me. :)

[https://api.slack.com/](https://api.slack.com/)

~~~
jamil7
Thanks! given the amount of complaining about slack on HN I thought someone
would have produced a native open source client by now. I might look into it.

------
wnoise
snprintf() may not be C89, but fgets() sure is.

~~~
yeokm1
Noted. Will update my blog post!

------
breadandcrumbel
I'm pretty surprised to see slack client exists for Windows 3.1 before modern
platforms

------
lspears
Bravo

------
haecceity
This is a lot of work to avoid using that WYSIWYG editor.

~~~
thenewnewguy
There's an option to disable the WYSIWYG editor now :)

Unfortunately, it still doesn't work as good as before: text like `Object`s
(no space between the ending ` and next text) doesn't work anymore, and
according to Slack's support this is 'working as intended'.

------
yeokm1
Unsure why it goes to twitter. Here is the blog post.
[http://yeokhengmeng.com/2019/12/building-a-new-
win-3-1-app-i...](http://yeokhengmeng.com/2019/12/building-a-new-win-3-1-app-
in-2019-part-1-slack-client/)

~~~
dang
Changed now from
[https://twitter.com/gravislizard/status/927593460642615296](https://twitter.com/gravislizard/status/927593460642615296).

Edit: oh, I see what happened now. We made a mistake, intending to set the URL
for
[https://news.ycombinator.com/item?id=21831931](https://news.ycombinator.com/item?id=21831931).
Sorry!

------
jojo9978
OMG, this is amazing tutorials bro

