
Building Win16 GUI Applications in C (2014) - networked
http://www.transmissionzero.co.uk/computing/win16-apps-in-c/
======
userbinator
Some more interesting articles on the MakeProcInstance mechanism:

[https://blogs.msdn.microsoft.com/oldnewthing/20080207-00/?p=...](https://blogs.msdn.microsoft.com/oldnewthing/20080207-00/?p=23533)

[http://www.geary.com/fixds.html](http://www.geary.com/fixds.html)

...and if you think the 16-bit segmented architecture is annoying, you
probably haven't worked with a bank-switched 8-bit system. ;-)

~~~
Stratoscope
Thanks for the mention! Creating FixDS was definitely one of those "aha!"
moments for me. Like every other Windows programmer of that era, I just
assumed that all the rigmarole with EXPORTS and MakeProcInstance() was
necessary. It was simply How Things Are Done.

So seeing that EXPORTS and MakeProcInstance() existed solely for the purpose
of loading the DS register, and finding that in application code the correct
value was already in the SS register and could simply be copied over into DS?
That was quite a surprise. Why were we all going to so much work when it
wasn't necessary at all?

Ever since then, I've had my eye out for places where the conventional wisdom
tells us to write code we don't need.

One that I've seen a lot lately - especially in StackOverflow answers - is an
unnecessarily complicated way of creating closures in JavaScript.

Many JavaScript programmers seem to believe that the way you create a closure
is by writing a function that returns a function. That works, of course, but
unless you really _need_ a function that returns a function, it's a
complication you can do without. After all, a simple function call can create
a closure all by itself.

For example, someone is writing a Google Maps API app that takes an array of
data about different places, loops through the array with a for loop, and
creates a marker for each of those places along with a click event handler for
each marker.

Experienced JavaScript programmers can probably guess what the problem is
going to be. I'll simplify the code a bit here to illustrate:

    
    
      var places = [
          { name: "One", lat: 34.1, lng: -80.7 },
          { name: "Two", lat: 35.2, lng: -81.8 }
      ];
    
      var infowindow = new google.maps.InfoWindow();
    
      for( var i = 0;  i < places.length;  i++ ) {
          var marker = new google.maps.Marker({
              position: new google.maps.LatLng(
                  places[i].lat,
                  places[i].lng
              ),
              map: map
          });
    
          google.maps.event.addListener( marker, 'click', function() {
              infowindow.setContent( places[i].name );
              infowindow.open( map, marker );
          });
      }
    

The problem here, of course, is that when the click callback function is
called asynchronously, the for loop has already run to completion, and the _i_
and _marker_ variables no longer have the correct values for the marker that
was clicked.

Inevitably, someone will now answer this question by suggesting a closure, and
illustrating how you get a closure by using a function that returns a
function:

    
    
      for( var i = 0;  i < places.length;  i++ ) {
          var marker = new google.maps.Marker({
              position: new google.maps.LatLng(
                  places[i].lat,
                  places[i].lng
              ),
              map: map
          });
    
          // Changes are below, using a function that returns a function
          google.maps.event.addListener( marker, 'click', (function( marker, i ) {
              return function() {
                  infowindow.setContent( places[i].name );
                  infowindow.open( map, marker );
              }
          })( marker, i );
      }
    

While that works, it's harder to understand than it needs to be. It's like
using EXPORTS and MakeProcInstance() to load the DS register.

There is a simpler way:

    
    
      for( var i = 0;  i < places.length;  i++ ) {
          addMarker( places[i] );
      }
    
      function addMarker( place ) {
          var marker = new google.maps.Marker({
              position: new google.maps.LatLng(
                  place.lat,
                  place.lng
              ),
              map: map
          });
    
          google.maps.event.addListener( marker, 'click', function() {
              infowindow.setContent( place.name );
              infowindow.open( map, marker );
          });
      }
    

By simply _calling a function_ in the loop, we create the closure we need, and
we get simpler and more readable code too.

Of course you can achieve the same effect by using array.forEach(), because
that calls a function and gives you a closure automatically:

    
    
      places.forEach( function( place ) {
          var marker = new google.maps.Marker({
              position: new google.maps.LatLng(
                  place.lat,
                  place.lng
              ),
              map: map
          });
    
          google.maps.event.addListener( marker, 'click', function() {
              infowindow.setContent( place.name );
              infowindow.open( map, marker );
          });
      });
    

Here's a search that will show numerous StackOverflow answers that promote the
complicated function-returning-a-function pattern:

[https://www.google.com/search?q=stackoverflow+maps+api+marke...](https://www.google.com/search?q=stackoverflow+maps+api+marker+closure+function+returns+function)

~~~
userbinator
Awesome to see _the_ Michael Geary show up on HN! I believe MakeProcInstance
was originally created in order to allow apps with multiple data or stack
segments ('huge' memory model), but as you noted, is certainly not necessary
for the majority of them. In fact, if you were careful or using Asm, even the
64K tiny model would've been sufficient for a lot of applications.

...and I never thought I'd see x86 segment registers and JavaScript closures
mentioned in the same comment.

------
tjalfi
Charles Petzold wrote a new 16 bit app for Windows 1.0 to celebrate the 20th
anniversary of Windows. The source is at
[http://www.charlespetzold.com/etc/Windows1/](http://www.charlespetzold.com/etc/Windows1/)

------
_RPM
That's pretty cool that the executable will run on Windows 10 today. I have
respect for Windows, but that is really impressive.

~~~
digi_owl
While i sit here watching gvfs shit itself randomly because of a minor glib
version bump. This kind of crap is why there is still no "year of the
desktop".

~~~
vbezhenar
WinAPI and glib are different things. Compare WinAPI to libc API and Xlib. And
I'm sure, you can run X application from 1990 on current Linux. I've learned X
programming in 2005, and many resources and mans were dated 1990-1995. They
all worked.

Though OpenSource often tends to move forward without thinking much about
compatibility, that's right. I guess, it's not an interesting topic for
hackers, who work in their free time.

~~~
david-given
It's an interesting exercise to pull up an old (but reasonably complex) Xlib
app and run it on a modern machine; xfig, for example.

They never pause for breath. Like, you cannot see the application think. Ever.
Whenever you ask it to do something, it responds instantly. You click the
mouse, and by the next frame the app has updated and is waiting for your
instructions.

It's astonishing how much nicer this makes them to use.

(And it's interesting that the Xaw classic design is... um... well, it's as
ugly as hell, but it's a kind of functional ugliness which you stop seeing
after a bit. Motif, on the other hand, remains awful. The really interesting
one of the old widget sets is Open Look, which still looks elegant and
polished today:
[http://www.daylight.com/dayhtml/doc/thor/thor.sample3.gif](http://www.daylight.com/dayhtml/doc/thor/thor.sample3.gif))

I wonder if there's any mileage in resurrecting one of these old widget
systems? Update it to work with modern languages and desktop environments,
make sure the accessibility's up to scratch (Motif got this right; don't know
about Xaw and Open Look), make sure that modern workflow menus-and-windows
apps can be written using them...

------
currysausage
Slightly off-topic: if I want to learn developing Win32 applications, what is
be the best place to start and where do I find comprehensive reference
material? I find the simplicity and responsiveness of Notepad2 and similar
applications very impressive.

Should I get Visual Studio 6.0 or is a MinGW workflow [1] the way to go? Is
MSDN still usable as a Win32 reference or is there some old go-to Win32 bible
that I should look for?

[1] [http://www.transmissionzero.co.uk/computing/win32-apps-
with-...](http://www.transmissionzero.co.uk/computing/win32-apps-with-mingw/)

~~~
satysin
Petzold's book that dom0 mentioned is/was the definitive Win32 reference
albeit very dated now. It obviously will not cover anything in Windows 2000
and later(!).

MSDN is still the go to place for all Win32 documentation although it can be
tricky to find exactly what you want as MS prefer to push you towards .NET
related stuff. Even finding plain C++ can be a pain with them pushing you
towards Android C++ development by default for some reason?!

For software you can go with MinGW-W64 or Visual Studio 2015 Community (you
will need to do a custom install and select the VC++ tools as they are not
included by default anymore).

I used theForger's Win32 tutorials back in the day to get started
[http://winprog.org/tutorial/](http://winprog.org/tutorial/)

~~~
userbinator
There's also "win32.hlp" if you want an older, yet offline reference; it's
from the days of 95/NT 4, but surprisingly complete as most of the GUI
functionality was already available back then.

The recent versions of VS are somewhat more difficult for generating single-
file standalone executables which run on OSes older than Vista.

~~~
satysin
Just so others know you will need to install the Windows help system to open
.hlp on Windows 10 (and maybe 8?) as they are deprecated.

If you want to build single exe for Windows XP you need to install the
specific C++ tools for XP option in Visual Studio. Having not had to use it I
do not know if this allows you to build an exe that does not rely on the
latest VC++ redist though?

~~~
mkup
There's a command line compiler option to avoid reliance on VC++ redist: /MT
(in release mode) or /MTd (in debug mode). Default is /MD (in release mode) or
/MDd (in debug mode). With /MT or /MTd Microsoft libc will be linked
statically to your application and VC++ redist DLLs won't be required to run
the application in the end user environment.

Though there's a shortcoming: if your application consists of EXE file(s)
calling into your own DLL(s), then in /MT or /MTd mode you have multiple
instances of libc in the process address space, and so tricks like "malloc()
in dll, free() in exe" or "open() in dll, close() in exe" will produce weird
runtime errors. That won't be a problem if DLL interfaces are strictly C
functions and structs, i.e. without classes, STL, smart pointers, exceptions,
RTTI-related stuff etc.

~~~
userbinator
Static linking can also make the executable a lot bigger, and if the
statically linked library uses new APIs, won't run on older Windows versions
(I know about the special XP support option, but I don't know if it'll let you
create binaries that will run on Win95.)

The other option (which I believe MinGW uses) is to link with msvcrt.dll,
something which MS doesn't officially support and rather strongly discourages
but actually works very well in practice and enables the "one binary for Win95
to Win10" effect.

~~~
mkup
Minimum Windows runtime version depends on libc shipped with MSVC compiler,
for Visual Studio 2008 it's Windows 2000 SP4; for VS2010 it's Windows XP SP3;
for VS2013/VS2015 it's Windows 7 IIRC. There's also /Zl compiler option to
omit libc entirely (e.g. for small programs using only Win32 API).

MSVCRT.DLL used to be default runtime library for code produced by Microsoft
Visual Studio 6 compiler. This DLL is still shipped with Windows, albeit its
sprintf() doesn't support %ll format specification, its time() function
assumes that time_t is 32-bit, and in general it feels very 1998y. It required
some redist on Win95 and NT4 IIRC.

------
lnternet
Heh, I always thought of Win16 as the Windows 3.x API. It never occurred to me
that the "same" API was used for Windows 2.x and even 1.x. Makes sense
though...

Really cool to see that Windows 1.0 application run on Windows 10!

~~~
pavlov
With some minor (but annoying) changes, it was also the OS/2 Presentation
Manager API.

------
keithnz
been there, done that, never going back! :)

it was fun at the time, though information was limited and precious, you ended
up being very dependent on books and magazines to find out about how to get
things done.

~~~
bhdz
It was very funny making Dialogs with a randomly moving "OK" button responding
on WM_MOUSEOVER (or something like it). Make them bigger, toolboxes, always
on-top, and disable the top-right "x" button. Fun times :P

~~~
userbinator
On discovering that buttons and other controls were just different classes of
windows, I wrote a few little games that took advantage of it to spawn moving
buttons, checkboxes, and other widgets. Their sources and binaries are
probably still somewhere, but not currently on this computer. Incidentally, I
believe Minesweeper also does the same with its grid --- they're just buttons.

------
pistle
Ahhh... the days... I remember fumbling through something very similar to this
and the time I spent in MainWndProc once the application became significantly
complex could be awfully frightening for a noob like me.

~~~
bhdz
I "invented" a dispatcher library back then for that. Marking and matching
callbacks with strings and looping through in the proc [ was very proud with
it :-) ]

------
coolgoose
I'm a bit fascinated by the possibilities exposed in the simple demo.

Having right click system menu options that you could extend in what for me is
a simple syntax is awesome :-)

------
est
Does this mean we can write a single binary with GUI which is valid in
ELF/COM/Mach-O ?

~~~
mschuster91
Hmm. ELF and Mach-O should support fat/multiarch binaries IIRC (Mach-O
certainly does, it was used in the days of Apple PowerPC=>Intel transition,
not sure if it's still supported though), and I think it should also be
possible to create COM/COFF binary that can be interpreted and/or run by
Linux/Darwin kernel.

------
bluedino
For added fun, do this on a IBM AT with dual floppy drives. Bah.

------
raverbashing
It feels like cutting a tree with a kitchen knife. Too complex to use directly

