Hacker News new | past | comments | ask | show | jobs | submit login
Building Win16 GUI Applications in C (2014) (transmissionzero.co.uk)
122 points by networked on Nov 13, 2016 | hide | past | favorite | 41 comments



Some more interesting articles on the MakeProcInstance mechanism:

https://blogs.msdn.microsoft.com/oldnewthing/20080207-00/?p=...

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. ;-)


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...


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.


You get a closure whenever you evaluate a function statement. When a statement containing the 'function' keyword is evaluated, you get a closure implicitly instantiated for that new function instance.

Invoking an already existing function instance doesn't make a new closure - it keeps its existing one. Evaluating a statement containing the 'function' keyword does make a new closure.

A closure links to the enclosing function's scope, all the way back to the root/global scope (the window object on browsers).

Memory leaks can occur when closure scopes link infinitely, so it's handy to use the function-returns-function pattern (but not the way you described it) to 'reset' the closure links to something near the root scope.

JS module systems like CommonJS have done well to encourage very tidy closure scope chains even in extremely complex javascript programs.


https://en.wikipedia.org/wiki/Immediately-invoked_function_e...

first "bad" example will work just fine if you replace "var i/marker" with "let i/marker"


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/


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


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".


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.


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)

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...


I bet you could compile an old Xlib (or Xt/Motif) application on Linux, but good luck running a binary from way back when -- the libc/glibc and a.out/ELF switch should put breaks on that.

On the other hand, if your actual premise is open source, there's simply less incentive of avoiding breaking binary compatibility. I bet there were a few angry words and stomped feet in the last decades at Microsoft because of having to maintain that.

Not that glib/gtk are poster childs for even source-level compatibility, not even within one major version. There's a reason why several projects are switching to Qt.

(I'm doing my Cato bit here: I still think that they should've done an Xt toolkit instead of inventing the wheel yet again. There certainly was some experience there, given that GIMP 0.x was written for Motif.)


You'd certainly need to go through a _lot_ of hoops to make it run - a number of X extensions have come and gone, and even assuming you're not consuming some additional library and are just writing X calls...

...Linux 0.01 didn't come out until 1991, so you'd be hard-pressed to find a compatible binary from 1990. ;)


Yeah sorry. But when i see MS having such a commitment to backwards compatilbity, and then the supposed "vanguard" of desktop Linux can't even be bothered to keep things working within their domain (while trying to dictate how everyone needs to build their distros to be "user friendly" and attractive to developers no less) i just get riled up.


Say what you will about Microsoft, but one feature they really take seriously is backward compatibility, wherever feasible.


It's super impressive! But it won't run on 64 bit Windows.

I recently wrote a simple 32 bit Windows app that runs on 3.11 (with the 32 bit subsystem installed) through Windows 10. On the more recent Windows versions it supports theming and it's per-monitor high DPI aware. That is to say, it looks and works like a modern app.

To do this I had to build it as a freestanding binary with MingGW and do some version probing and dynamic DLL lookups but altogether it wasn't too hard.


Did you consider Github or similar for this? Sounds superb and plays exactly into the strength of MS Windows - compatibility and high performance when using Win32 API.


I abandoned the project as soon as my curiosity was satisfied. If I ever revisit it I’ll upload it to somewhere and submit here because indeed it’s worth sharing.


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-...


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/


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.


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?


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.


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.


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.


Programming Windows, 5th Edition (1998) is still practically completely valid, although it does of course not mention anything that came after it.


I'm learning bits and pieces via https://handmadehero.org/


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!


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


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.


Crazy times. A null pointer reference used to cause a full computer reboot. You had to read your printf logs to find where approximately the program stopped.

I spent a sizeable amount of my scholarship money on Microsoft Systems Journals, Charles Petzolds "Programming Windows" and "Undocumented Windows". The latter would teach you tricks like saving the Window position in an INI file so you could make it reappear in the same place where it was closed...


Books, magazines, abd CompuServe! PC Magazine had Win16 experts answering questions on CompuServe.


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


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.


Worse was when those resources were not available. You were pretty much SOL.


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.


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 :-) ]


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 :-)


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


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.


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


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




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: