Devious Malcontent

 Blog Blog RSS

Windows Hello World in MASM for Windows 1.x (is actually very hard)

Friday 3rd May 2024

I'll cut to the solution, because the hello world part is fairly straightforward, but it turns out that I was neglecting the startup code (APPENTRY.ASM), found in the Microsoft Macro Assembler 6.11 package on win world. - this requires the package to be installed by a dos environment and for the samples to be selected during the installation process, in my case I used DOSBox-x to do the install.

Special thanks to this thread over on the betaarchive.com forums, and this win2x page from bearwindows.zcm.com.au,

I should preface that I originally started writing this article with the intention of using MASM 4.0 among a few other misconceptions.

The *cool thing* that I did in this article, was build an application (an .exe file) (even though it was primitively the most bare bones that a windows application can be), that's capable of running and executing on Windows 1.x, using the latest and greatest modern version of Windows, Windows 10 64-bit edition, and the MASM32 SDK available from masm32.com.

My intention is to port my tile map editor program over to Windows 1.x, and have a version available for every edition of the operating system, but before that we need to get the basics down and make sure that we can at least compile and run an executable for the lowest target system.

For a bit of context, I had previously used MASM 6.11 to recompile the clock application that is part of the sample code, but this app only ever ran on Windows 3.11, so I disregarded it as being code incompatible with Windows 1, that's what led me to try MASM 4.0 as its release timing matches up to the release of the first version of the operating system. - but this was later revealed to be irrelevant.

On a side note, to building the sample clock app for Windows 3.11, I have managed to recompile this with an icon resource (.RC), on Windows 10 after patching the resource compiler, but that's a story for another day.

So, to get started you basically just need to copy APPENTRY.ASM, available in either C:\MASM611\SAMPLES\WINCLOCK\APPENTRY.ASM or C:\MASM611\SAMPLES\WINDLL\ APPENTRY.ASM

Since I installed this via DOSBox-x, my intention is to nuke the folder when I'm done with it, and instead make use of masm32, but before we can compile for Windows 1.0, we need to modify the masm32 SDK, we also need to copy some files out of MASM611, those are C:\MASM611\INCLUDE\WIN.INC to C:\masm32\include, and C:\MASM611\LIB\LIBW.LIB to C:\masm32\lib\LIBW.LIB.

I should mention while I was writing this article, I also found another copy of APPENTRY.ASM in the C:\MASM611\LIB folder, (it's all the same file) so that would have saved me some time rather than having to hunt it down and locate it elsewhere.

The last three files we need are: CMACROS.INC, PROLOGUE.INC and VERSION.INC this will make sense when we write the actual core code.

I took the CMACROS.INC and VERSION.INC components out of the Visual C++, Version 1.5 folder, or more directly:

C:\MSVC\SOURCE\STARTUP\CMACROS.INC

C:\MSVC\SOURCE\STARTUP\VERSION.INC

Visual C++, Version 1.5 can be obtained from the WinWord archives here.

PROLOGUE.INC can be found in the MASM611 folder under C:\MASM611\INCLUDE\PROLOGUE.INC

Great now we've got all the components we need, time to set up a build script, I just stole the one from my tile map editor project: https://github.com/DeviousMalcontent/tme/blob/main/src/makeit.bat

But for the sake of completeness, I will post my build script below, assuming the file that we will store our core code in will be called WMB.ASM, short for Windows Message Box.

@echo off
set filepath=%cd%

:next
REM Find MASM32 install path.
IF EXIST "A:\masm32\bin\ml.exe" (set masmpath=A:\masm32)
IF EXIST "B:\masm32\bin\ml.exe" (set masmpath=B:\masm32)
IF EXIST "C:\masm32\bin\ml.exe" (set masmpath=C:\masm32)
IF EXIST "D:\masm32\bin\ml.exe" (set masmpath=D:\masm32)
IF EXIST "E:\masm32\bin\ml.exe" (set masmpath=E:\masm32)
IF EXIST "F:\masm32\bin\ml.exe" (set masmpath=F:\masm32)
IF EXIST "G:\masm32\bin\ml.exe" (set masmpath=G:\masm32)
IF EXIST "H:\masm32\bin\ml.exe" (set masmpath=H:\masm32)
IF EXIST "I:\masm32\bin\ml.exe" (set masmpath=I:\masm32)
IF EXIST "J:\masm32\bin\ml.exe" (set masmpath=J:\masm32)
IF EXIST "K:\masm32\bin\ml.exe" (set masmpath=K:\masm32)
IF EXIST "L:\masm32\bin\ml.exe" (set masmpath=L:\masm32)
IF EXIST "M:\masm32\bin\ml.exe" (set masmpath=M:\masm32)
IF EXIST "N:\masm32\bin\ml.exe" (set masmpath=N:\masm32)
IF EXIST "O:\masm32\bin\ml.exe" (set masmpath=O:\masm32)
IF EXIST "P:\masm32\bin\ml.exe" (set masmpath=P:\masm32)
IF EXIST "Q:\masm32\bin\ml.exe" (set masmpath=Q:\masm32)
IF EXIST "R:\masm32\bin\ml.exe" (set masmpath=R:\masm32)
IF EXIST "S:\masm32\bin\ml.exe" (set masmpath=S:\masm32)
IF EXIST "T:\masm32\bin\ml.exe" (set masmpath=T:\masm32)
IF EXIST "U:\masm32\bin\ml.exe" (set masmpath=U:\masm32)
IF EXIST "V:\masm32\bin\ml.exe" (set masmpath=V:\masm32)
IF EXIST "W:\masm32\bin\ml.exe" (set masmpath=W:\masm32)
IF EXIST "X:\masm32\bin\ml.exe" (set masmpath=X:\masm32)
IF EXIST "Y:\masm32\bin\ml.exe" (set masmpath=Y:\masm32)
IF EXIST "Z:\masm32\bin\ml.exe" (set masmpath=Z:\masm32)
IF "%masmpath%"=="" (
	ECHO Masm32 install path was not found.
	ECHO Looking in each drive letter under :\masm32\bin\ml.exe
	ECHO.
	ECHO You might have to edit this script or download and install masm32 SDK
	ECHO from https://www.masm32.com/
	ECHO.
	PAUSE	
	EXIT	
) ELSE (
	ECHO Masm32 install path was found.
)

REM Build resources if they exist.
if not exist rsrc.rc goto over1
%masmpath%\BIN\Rc.exe /v rsrc.rc
%masmpath%\BIN\Cvtres.exe /machine:ix86 rsrc.res
:over1

REM Clean up.
if exist %1.exe del WMB.exe
if exist %1.obj del WMB.obj

REM assemble the HelloWorld app into an OBJ file.
%masmpath%\BIN\Ml.exe /c /I "%masmpath%\include" "WMB.asm"
%masmpath%\BIN\ml -c -DMODEL=small appentry.asm
%masmpath%\BIN\Link16.exe /A:16 appentry.obj+WMB.obj,wmb.exe,,%masmpath%\lib\LIBW.LIB,WMB.def;

REM link the main OBJ file.
if errorlevel 1 goto errlink
dir WMB.*
goto TheEnd

:errlink
REM display message if there is an error during linking
echo.
echo There has been an error while linking this project.
echo.
goto TheEnd

:errasm
REM display message if there is an error during assembly
echo.
echo There has been an error while assembling this project.
echo.
goto TheEnd

:TheEnd
dir WMB.*
pause

And throw some code in:

.model  small, pascal, nearstack

?WINPROLOGUE = 1
NOKERNEL = 1
NOSOUND = 1
NOCOMM = 1
NODRIVERS = 1
include win.inc

extern __astart:proc

    WinMain     PROTO PASCAL, hInstance:HANDLE,  hPrevInstance:HANDLE,
                       lpszCmdLine:LPSTR, nCmdShow:SWORD
.data
    MsgCaption  db 'MessageBox Caption',0
    MsgBoxText  db 'Hello, World!',0

.code

WinMain         PROC,   hInstance:HANDLE,  hPrevInstance:HANDLE,
                        lpszCmdLine:LPSTR, nCmdShow:SWORD
                invoke MessageBox, NULL,addr MsgBoxText, addr MsgCaption, MB_OK
                ret
WinMain         ENDP

END  __astart

Note: I saved this file as WMB.asm

I almost forgot we will need a definition or .def file:

NAME WinMessageBox
CODE PRELOAD MOVEABLE
DATA PRELOAD MOVEABLE MULTIPLE
HEAPSIZE  128
STACKSIZE 4096

Note: I saved this file as WMB.def

So, I should be now left with these files:

  • APPENTRY.ASM
  • CMACROS.INC
  • makeit.bat
  • PROLOGUE.INC
  • VERSION.INC
  • WMB.asm
  • WMB.def

Assuming you copied and paste everything correctly, it should be a simple matter of running makeit.bat and looking at the result.

But then I get this error

C:\masm32\include\win.inc(2400): error A2085: instruction or register not accepted in current CPU mode, so we need to modify win.inc, open it up and go to line 2400 and comment it out by adding as Semicolon (;) to the start of it, so it should look like this:

WIN.INC in Notepad++


Windows Hello World in MASM for Windows 1.x

Now just rerun makeit.bat and bam! - our script spits out an executable file called appentry.exe, and there's probably a way to specify what file name we want, but I was so relieved to finally get this working after a few days of hardcore debugging. - I was so excited in fact that when I went to test this executable I had to set up fresh Windows 1.0 environment I forgot to set a colour driver, and instead selected monochrome, that's why the image attached is in black and white, as far as I know from what I can find on the Internet no one has done this yet, that is, build an executable with assembler for Windows 1.0. Pretty neat hey!

Edit: I fixed the file name output in the script, so the exe file should be called wmb.exe

For a bit of background

I was recently gifted a 386 laptop, that's still operational and has a plasma display, but it's in dire need of some TLC, the plan is to add a few newer quality of life improvements, like compact flash cards. - I've also got x2 boards that are 8088 clones, but I need some ram for them, still on the hunt for a 286, But that was the spark that lit my desire to do some retro coding,

I've been doing some dev work on some very old versions of Windows (think 1.0) for side projects and I have to say it's an absolute pig of a system! - no debugger, and the slightest defect in your code crashes the entire system, thank God for VM's! any reasonable workflow I can get at the moment is using OTVDM as the debugger, but this also comes with its own set of challenges, sometimes code compiles, runs and works fine in OTVDM but it will randomly crash in Windows 1.0 and 2.0, 3.0 to 3.11 are a lot more stable in comparison, it's actually quite funny how little the Windows API has changed in this time. - you can still build the C lang based projects on modern versions of Windows, and aside from the compiler complaining about security concerns, they work just fine.

In my research, I've also found TASM to be the most stable assembler for the Win 1 and 2 platforms, but I'm not certain what super special sauce they are using that makes their applications work properly, it seems to all come down to the linker, Watcom also had an assembler that I've been meaning to try out...

I wrote this to my news group before I solved the issue with Microsoft Assembler for building apps for Windows 1.0\2.0, 3.1 to 3.11, and as it turns out I was disregarding the Windows Application Startup Routine (APPENTRY.ASM), all that code does is initialize the stack and aligns the first 16 bytes of data in the DATA segment, but I definitely should have made a better effort to document the whole process, as my notes on the subject are pretty sparse, I started compiling bits of it into a YouTube video which is already at least 40 minutes long...

I ended up reading more into how assembler works on windows, from books on the Internet archive and forum posts on betaarchive.org, I eventually had managed to put all the knowledge I gathered together and finally I had a hello world application running on Windows 1.0 that could also be compiled on a modern version of the operating system.

I wish I had spent more time documenting my debugging and troubleshooting process, but by the end I had several different versions of things with several different things I've tried and nothing of value. So, scrapping and starting again seemed like the right approach.

A few other tidbits

I know there are methods in Windows 3.11 to mark applications from Windows 1 and 2 as compatible, but I'm not sure what mechanism does this, I guess I'll cross that bridge when I get there, but from my testing it seems the application we built is fully compatible with Windows 3.11, so I can now build 16-bit apps, so to say.

I have managed to obtain a copy of the code listings form the book Programming Windows (1st edition) by Charles Petzold, my next steps are to see what can and can't be ported across to assembler.

Update 2025:

The author of the book Programming Windows (1st edition), Charles Petzold, now host the original source code for the sample programs under ProgWin1.zip (190K).


Home | Blog Index | RSS