SBS WALK32 Win32 Assembly Language Kit Version 1.00 Donated to the Public Domain Sven B. Schreiber sbs@psbs.franken.de 100557.177@compuserve.com 03-17-1996 Before You Start This programming toolkit is designed for the experienced ASM programmer who not only wants to casually write some assembly code, but instead aims at building large projects in all assembly language. If you would classify yourself into this category, WALK32 is for you. Please note that WALK32 is not a stand-alone development environment. You will still need some additional products to be able to use it. Luckily, you won't need very much additional software. Just two pieces are required: - A programming editor of your choice. (However, I highly recommend using Alan Phillips' "Programmer's File Editor" PFE. Check out SimTel's FTP site, URL ftp://ftp.coast.net/SimTel/win95/editor/pfe0602i.zip. Alan's e-mail address is A.Phillips@lancaster.ac.uk.) - Microsoft's Macro Assembler MASM 6.11d. Altough WALK32 will also work with earlier versions down to 6.00, you should still upgrade, since Microsoft has actually removed some annoying bugs. Other assemblers are currently not supported, but I'm looking forward to get e-mail from anyone who would like to adapt WALK32 to other products, especially those from Borland and Eric Isaacson. (The only restriction is that you'll have to do it for free, because WALK32 should remain in the Public Domain forever.) As soon as you have managed to get the above components, you're set to step into the world of Win32 assembly language programming. Disclaimer This software is provided "as is" and any express or implied warranties, including, but not limited to, the implied warranties of merchantibility and fitness for a particular purpose are disclaimed Dedicating WALK32 to you is my way of saying "Thank You" for your wonderful books and technical articles in various journals. Without this input, WALK32 had never come into being. Please don't stop letting us share your knowlegde about the inner workings of operating systems. And let's hope there will always be enough people who are buying your books, so you can keep up the good work. Thank you again. 1. Introduction Although Windows NT has been around for some time, programming for the Win32 environment has not been a big issue so far. Things have changed considerably, when Windows 95 came into being. While Windows 95 itself is not a particularly well-designed 32-bit operating system, it has at least triggered the interest of programmers toward Win32 programming. This is one of the good sides of this otherwise quite handicapped operating system. Many programmers with a strong belief in small and fast hand-optimized assembly code were quite frustrated when Windows 3.xx began to take over the PC market previously held by DOS. Assembly programming support for Windows has always been quite minimal. Windows is an operating system tightly bound to the C programming language, and hence it deliberately supports C programming tools. Most C programmers who write a WinMain() function never care to spend any thought on how their program enters WinMain() in the first place. They don't know anything about startup code, function prologues and epilogues, stack frames, and parameter passing conventions. The compiler hides these confusing details from them. In perfect harmony, the compiler and operating system work together hand in hand. ASM programmers, on the other hand, do have to worry about these issues, because they are actually working with the bits and bytes these nifty little tricks are composed of. If a C programmer writes SetWindowText (hWnd, "Hello World"), she/he doesn't have to care by which mechanisms Windows will actually receive the items hWnd and "Hello World". An ASM programmer works well below the surface of C statements like this. For her/him, both parameters are passed quite differently to Windows: While the value of the 16-bit hWnd is pushed onto the parameter stack as is, the string parameter "Hello World" needs to be passed as a 16:16 bit pointer. Thus, from an ASM programmer's point of view, the above C statement is roughly equivalent to the following sequence of low level ASM instructions: hWnd WORD ? sHelloWorld BYTE "Hello World!",0 ... push hWnd push ds push offset sHelloWorld call far ptr SetWindowText ... Of course, expert Windows ASM programming does not stay at this low level view of the operating system. Experienced programmers use macros and other high-level assembly language features to make the code clearer and easier to read and write. Doing so is also one of the aims of the WALK32 programming kit. However, the real nature of the Windows API will always shine through, and ASM programmers have to cope with that. Windows 3.xx put a considerable burden on the shoulders of ASM programmers in that it used a 64K segmented memory architecture. Yes, it's true that former Z80 programmers did a terrific job in squeezing brilliant program code into a single 64K memory block the program had to share with the operating system. But given more memory, this code could easily have been even more brilliant. In 16-bit Windows 3.xx, you typically worked with tiled 64K segments to simulate large contiguous memory blocks. If you were an experienced programmer targeting 80386+ machines, who had no fear of manipulating memory descriptor structures, you could also write some kind of mixed 16/32-bit code. But since the basic data type of Windows 3.xx was the 16-bit word, this was not something to be particularly enjoyed. Now we are finally moving to 32-bit Windows. You might have a mind of your own on all the fuzz that is made on 32-bit programming. But, in my opinion, one thing is certainly true: 32-bit programming is much easier and less error-prone to the ASM programmer than writing code for the old 16-bit segmented model. Hence, Win32 is actually a much better platform for ASM programming than its predecessor, even if some "Visual X freaks" might want you to believe the opposite. Don't care - they don't know what they're talking about. 2. Why Another Programming Toolkit? In some sense, WALK32 is YAPT - yet another programming toolkit. So why did I care to write and distribute it? The answer is simple: ASM support for Win32 is not much better than in the older Win16 days. I don't think that this will change in the future. Alas, I suspect that it will become even worse, if I'm looking at all the hype about those new wonderous languages, like Delphi or Java. In fact, even the last domain of Windows ASM programming - device drivers - is abandoned more and more in favour of high-level languages. WALK32 is, in essence, a programming toolkit I wrote for my own purposes. My main interest was to create an environment which is easy to handle, and which gives me full control of the binary code being generated. Of course, to achieve this, I had to provide one of the cornerstones of programming development myself: The Linker. This tool performs the last step in the process of converting an ASM source file to executable machine code. Most programmers are not selling source code. They are selling executables. Hence, controlling the linking step in the development process means controlling the product being sold. Knowing the internals of linking means knowing what's going on in an executable file. Having the linker source code readily available means being able to model the final product at its lowest layer. Of course, WALK32 provides the full source code of its linker to you. The same is true for all other utilities packed into WALK32. Feel free to customize those programs to your heart's desire. This is Public Domain Software. The main purpose of WALK32 is not to confront you with a strange little piece of Windows assembly source code and tell you: "See? It works!" Instead, I want to give you all you need to build really great Windows applications. Hence, the "generic" sample application of WALK32 called "MiniApp" is actually much more than the minimum code required for a GUI application. It contains ready-to-use code to manage menus, dialogs, dialog color palettes, and window messages. Starting with MiniApp, you can immediately start to write your own application-specific code without the hassle of building the necessary framework before. 3. WALK32 Components WALK32 consists of the following main components: - A full-featured PE (Portable Executable) file linker called W32Link. - Main include file, containing Win32 constant, type, and structure definitions. - Another include file, containing the application and DLL startup source code. - Segment and PE section management macros. - Macros related to Unicode support. - Several demo applications and DLL's. - A collection of programming utilities for various purposes. 3.1. W32Link Contrary to the linker shipped with Microsoft's Win32 development tools, the W32Link PE file linker does not take COFF formatted object files. Instead, it relies on the good old OMF (Relocatable Object Module Format) standard released by the TIS (Tool Interface Standards) Committee [1]. OMF is a well-established standard which is supported by many current assemblers. The old OMF specification of the DOS days has been updated to support 32-bit object code some time ago. WALK32 relies heavily on those 32-bit extensions. The main purpose of a PE file linker is to read OMF records from an object file, process the data inside them, and finally write an executable file according to Microsoft's PE/COFF specification [2]. The PE file structure is quite different from the NE (New Executable) format of Win16. A PE file consists of a sequence of sections, and every section contains chunks of related code or data, each serving a special purpose. While it's true that the contents of some sections are not quite easy to understand at first glance, the PE format is much easier to handle than the Win16 NE format. Besides some important constraints, the PE file format is composed of a quite liberal set of rules. This means that the linker is faced with several degrees of freedom when trying to build a valid PE file. Every PE file linker has its own idiosyncrasies and buries them in the depths of the PE file structure. Matt Pietrek provides you with a close look at them in his recent Windows 95 book, Windows 95 System Programming Secrets [3]. W32Link goes at great lengths to optimize the PE files it produces. For more details on what's in them, see the chapter on the W32Link internals. W32Link uses a very simple mechanism to support the import of functions and variables from DLL's. The "import libraries" of W32Link are plain ANSI files, formatted like any of the Windows profiles (a.k.a. ".INI files"). You can edit the libraries manually, using your favourite text editor. However, it is much more convenient to use the PEexport utility coming with WALK32, which outputs a ready-to-use import library section directly from any DLL image file specified on its command line. If you start W32Link without any parameters, you'll get an info screen telling you how the command line has to be formatted: F:\asm\Win32>W32Link ________________________________________________________________ W32Link.ASM Win32 PE File Linker V1.00 03-16-1996 Sven B. Schreiber sbs@psbs.franken.de This is Public Domain Software ________________________________________________________________ Usage: W32Link [Options] Options: /d = Detailed OMF Report ON /s = Silent Mode ON /t = Dump Win32 Symbol Table F:\asm\Win32>_ The command line options supported by W32Link control the amount of information displayed while linking the object file. If you don't specify any options, W32Link displays a single header line per OMF record it processes: F:\asm\Win32>W32Link Hello ________________________________________________________________ W32Link.ASM Win32 PE File Linker V1.00 03-16-1996 Sven B. Schreiber sbs@psbs.franken.de This is Public Domain Software ________________________________________________________________ Icon File: "D:\ASM\WIN32\W32LINK.ico" Input File: "D:\ASM\WIN32\Hello.obj" 00000000: [80] THEADR Translator Header 0000000D: [96] LNAMES List of Names 00000022: [98] SEGDEF Segment Definition 0000002C: [8C] EXTDEF External Names Definition 000000BF: [A0] LEDATA Logical Enumerated Data 00000219: [9D] FIXUPP 32-Bit Fixup Record 000002BC: [A2] LIDATA Logical Iterated Data 000002C9: [A0] LEDATA Logical Enumerated Data 00000314: [A0] LEDATA Logical Enumerated Data 0000032B: [8B] MODEND 32-Bit Module End W32Link: Normal end. F:\asm\Win32>_ Large object files containing lots of OMF records can produce large header listings, so you might want to suppress the headers using the /s switch: F:\asm\Win32>W32Link /s Hello ________________________________________________________________ W32Link.ASM Win32 PE File Linker V1.00 03-16-1996 Sven B. Schreiber sbs@psbs.franken.de This is Public Domain Software ________________________________________________________________ Icon File: "D:\ASM\WIN32\W32LINK.ico" Input File: "D:\ASM\WIN32\Hello.obj" W32Link: Normal end. F:\asm\Win32>_ The /d switch has exactly the opposite effect: It shows almost all OMF details the linker collects from the object file: F:\asm\Win32>W32Link /d Hello ________________________________________________________________ W32Link.ASM Win32 PE File Linker V1.00 03-16-1996 Sven B. Schreiber sbs@psbs.franken.de This is Public Domain Software ________________________________________________________________ Icon File: "D:\ASM\WIN32\W32LINK.ico" Input File: "D:\ASM\WIN32\Hello.obj" 00000000: [80] THEADR Translator Header Source File Name: "Hello.asm" 0000000D: [96] LNAMES List of Names 00000022: [98] SEGDEF Segment Definition Symbol file "F:\ASM\WIN32\W32LINK.NT" will be loaded Output File "F:\ASM\WIN32\Hello.exe" will be created _main SEGMENT BYTE PRIVATE USE32 '1$3$21582' Segment size: 0000032B Bytes 0000002C: [8C] EXTDEF External Names Definition [0138] "GetVersionExA" (KERNEL32.dll) [0114] "GetStartupInfoA" (KERNEL32.dll) [009F] "GetCommandLineA" (KERNEL32.dll) [00D1] "GetEnvironmentStringsA" (KERNEL32.dll) [00EB] "GetModuleHandleA" (KERNEL32.dll) [00E9] "GetModuleFileNameA" (KERNEL32.dll) [0062] "ExitProcess" (KERNEL32.dll) [0116] "GetStdHandle" (KERNEL32.dll) [024F] "WriteFile" (KERNEL32.dll) 000000BF: [A0] LEDATA Logical Enumerated Data Segment "_main", Offset 00000000, Length 00000153 Code: Memory 00001000/000000F0, File 00000400/00000200, ".text" Data: Memory 00002000/0000021B, File 00000600/00000200, ".data" 00000219: [9D] FIXUPP 32-Bit Fixup Record -> 00000022: Address 0040202F -> 0000002B: Address 0040202F -> 00000031: External ID 0001 "GetVersionExA" -> 00000037: Address 004020EF -> 00000040: Address 004020C3 -> 00000046: External ID 0002 "GetStartupInfoA" -> 0000004C: Address 004020EF -> 0000005E: Address 004020F3 -> 00000065: External ID 0003 "GetCommandLineA" -> 0000006C: External ID 0004 "GetEnvironmentStringsA" -> 00000075: External ID 0005 "GetModuleHandleA" -> 00000080: Address 00402107 -> 00000087: External ID 0006 "GetModuleFileNameA" -> 00000093: External ID 0007 "ExitProcess" -> 000000A2: External ID 0008 "GetStdHandle" -> 000000A7: Address 0040220B -> 000000B4: External ID 0008 "GetStdHandle" -> 000000B9: Address 0040220F -> 000000C6: External ID 0008 "GetStdHandle" -> 000000CB: Address 00402213 -> 000000D5: Address 0040201B -> 000000FC: Address 00402217 -> 00000104: Address 0040220F -> 0000010A: External ID 0009 "WriteFile" 000002BC: [A2] LIDATA Logical Iterated Data Segment "_main", Offset 00000153, Length 00000080 000002C9: [A0] LEDATA Logical Enumerated Data Segment "_main", Offset 000001D3, Length 00000044 00000314: [A0] LEDATA Logical Enumerated Data Segment "_main", Offset 0000031B, Length 00000010 0000032B: [8B] MODEND 32-Bit Module End Module entry point: 00001000 Memory Usage: 84% - Win32 Symbol Table 1% - Application Symbol Table 5% - Object Names Table 1% - External Names Table 0% - Public Names Table 2% - Module Names Table 1% - Fixup Table W32Link: Normal end. F:\asm\Win32>_ If you had additionally specified the /t switch, the output log would have grown to an immense length, showing you a complete sorted list of all API symbols found in the import libraries requested by the program to be linked. I'm sure you'll agree with me that it's no use wasting space by including an example here. You can try this switch yourself when you like to. When W32Link begins to process an object file, it searches in this file's directory for an icon file of the same name, but ending in .ico. If it finds one, which is also a valid Windows icon file, the first icon included in it will be copied into the executable file and become an icon resource. This resource can be accessed by the application or DLL using the LoadIcon() API call. If W32Link doesn't find this file, it will search for a default icon file named W32Link.ico in the program directory of W32Link. If it doesn't find it, too, or if its contents are invalid, W32Link uses an icon built into its own executable file. By default, this icon is the same as the one in W32Link.ico. But nobody can prevent you from replacing the contents of W32Link.ico by any other icon if it doesn't please you. WALK32 includes a WALK32.bat file containing all necessary program calls to assemble and link a specified source file. You invoke it by typing WALK32 Hello. It will assemble and link Hello.asm, producing Hello.exe. Here's what the batch does: @echo off if "%1" == "" goto label1 set WALK32=%1 :label1 if "%WALK32%" == "" goto label2 ml /I. /Zm /c /Cp /Ta %WALK32%.asm if errorlevel 1 goto label3 W32Link /d %WALK32% >%WALK32%.log if errorlevel 1 goto label3 if exist %WALK32%.exe rename %WALK32%.exe %WALK32%.exe if exist %WALK32%.dll rename %WALK32%.dll %WALK32%.dll del %WALK32%.obj goto label3 :label2 echo. echo Please specify a file name! echo. :label3 This might possibly look somewhat complex, but in essence, the batch does very simple things. The lines doing the main work are: ml /I. /Zm /c /Cp /Ta %WALK32%.asm W32Link /d %WALK32% >%WALK32%.log Here, %WALK32% is a reference to an environment variable designed to hold the file name of the last build. This saves you from typing the same name over and over while working on a lengthy project. After specifying the source file's name for the first time, it suffices to simply invoke WALK32 without parameters later to rebuild the same application. The W32Link command in WALK32.bat involves the /d switch, so you'll get a detailed listing. This command also redirects standard output to a .log file. Thus, you won't actually see all OMF details unless you browse this log file. The output on the screen will look as if you had specified the /s switch, i.e. you'll get the W32Link banner, the paths of the icon and input files, and a terminating status report telling you if the executable has been built successfully. These lines are output to the standard error pseudo-file, hence they are not redirected to the log file. All other batch commands are simply for your convenience. For instance, there are two rename commands which have no purpose but to preserve the case of the file name you've typed on the command line. Because W32Link is a DOS program, it doesn't know anything about long file names or case differences in file names. It always stores the file name in upper case only. The rename commands take care of making the file name of the executable look exactly like you typed it. WALK32.bat assumes that the MASM program files are somewhere in the DOS search path. You need just two files from your MASM package: ml.exe, and ml.err. If they are stored somewhere else, you can of course place a fully qualified path into WALK32.bat immediately before the ml command invoking the assembler. 3.2. Include Files The Win32 API defines a huge pile of constants, types, and structures used in calls to the Win32 functions. WALK32 provides a main include file which predefines them for you. All you have to do is to insert an "include" pseudo instruction into your source file. The main include file is called W32Main.inc. It is possible that this file is currently incomplete. I've tried to add all definitions which occur in most programs. Should you encounter any missing constants, types, or structures, I would be glad to receive a note from you, so the next release will include them. The second WALK32 include file is called W32Start.inc and is of at least equal importance. It contains the application's or DLL's startup code, as well as some code to test if the operating system platform supports Unicode if requested. Unicode support is described in more detail in a later chapter. The startup code is not strictly necessary, but very convenient. It does some routine initialization tasks and - by the way - builds the WinMain() stack frame known from many programming books. That's right - Windows doesn't provide any WinMain() setup itself when your application is started! The program's entry point is simply called without any parameters passed to it. All those nice things like the instance handle or the command line have to be set up by the application itself. The code in W32Start.inc does this for you, so you don't have to bother. 3.3. Section Management A lot of stuff in the W32Main.inc include file is dedicated to PE file section management. As you might know, Win32 processes (i.e. applications and the DLL's used by them) run in a flat 32-bit address space of 4GB size. The term "flat" means that every location inside this address space can be reached by a simple 32-bit near pointer. Sometimes, this memory access mode also called linear addressing. In a sense, this resembles the old "tiny" memory model, used by .COM executables under DOS. The only difference is, that the tiny model uses 16-bit pointers only, so the address space is limited to 64KB only. Although the 4GB address space of a Win32 process is linear, the code and data of a Win32 executable is still arranged in sections. This has nothing in common with segments used on Win16 platforms. Sections are only needed to separate the address region occupied by the process' code and data into subregions with different access rights. For instance, a code section will be marked executable and read-only, while a program data section is usually non-executable and allows read/write access. Although your program will receive different selectors in the CS and DS registers to reflect those access properties, both selectors will have the same base address. This means that any CS relative pointer will reference the same memory location when used relative to DS. Wow, this surely is convenient! But let's get back to W32Main.inc. This multi-purpose include file defines some really complex macros which generate object code that will be interpreted by the assembler and linker in a special way. From the programmer's point of view, they only mark the beginning and end of the code and data sections of the source code. To the assembler, they present instructions needed to set up the single Win32 flat segment. They also include some data in the object code needed by the linker to build the PE file sections later. You'll learn more about this topic later, in the chapter on the linker's internals. 3.4. Unicode Support Unicode is a great Win32 feature. Windows NT uses the Unicode character set internally, wherever characters or strings of characters are processed or stored. This means that Windows NT works with 16-bit characters, not the 8-bit ones used by DOS and 16-bit Windows. This is great news, because it promises to finally make those damned character code pages obsolete, which have been haunting programmers and users for decades. The bad story about Unicode is that poor man's Win32 system - Windows 95 - does not support Unicode in any way. Unicode is simply a no-no if you want your application to run under Windows NT and Windows 95. You'll have to stick with good old ANSI and good old code page problems like you used to under Windows 3.11. WALK32 allows you to write programs using Unicode in a very transparent way. That is, you may choose to assemble your program either for ANSI or for Unicode without changing any line of code. All you'll have to do is to flip an assembly flag. Unicode support of WALK32 is done by a set of macros defined in - you guessed it - W32Main.inc, as well as by some intelligence built into the linker. You will be surprised how easy it is to write Unicode applications! Unicode programming is explained in more detail in a later chapter. 3.5. Demos And Tools Of course, the best programming kit is worth nothing if it doesn't provide any practical examples of how it's used. WALK32 gives you a set of sample files at hand which you can use as a basis for the development of your own apps. The samples cover several important aspects of every day Win32 programming. Finally, WALK32 comes with some special programming utilities which hopefully will aid you to save time and effort when writing software for the Win32 environment. Of course, full ASM source code is included for all utilities. 4. Writing Win32 Programs This is the chapter you've desperately been waiting for. It will show you step by step how to write your own Win32 applications using the WALK32 framework. We'll start with console applications, which are quite simple to write, before we'll finally moving into the GUI business. 4.1. Win32 Console Applications Those of you who were developing applications for the DOS market until recently will be delighted to see that it is much easier to write Win32 console mode applications. These application are as straightforward as their DOS counterparts, but they can use a much richer API, and they run in a 32-bit flat address space. Thus, thinking about how to pack your data into 64k segments and switching segment registers back and forth belongs to the past. Other than Win32 GUI applications, console applications don't need any callbacks, window procedures, and message handlers. You will realize this, while we're examining the WALK32 sample program Hello.asm in detail now. This program is another incarnation of the world-famous "Hello World" program dominating the first chapter of most programming books. 4.1.1. The WALK32 Program Header The first lines of any program written for WALK32 define a series of assembly/linker switches which determine the type of program to be generated: CONSOLE equ 1 ;0 = gui, 1 = console UNICODE equ 0 ;0 = ansi, 1 = unicode DLL equ 0 ;0 = application, 1 = dll WIN95 equ 0 ;0 = windows nt, 1 = windows 95 The CONSOLE switch tells the linker to generate a PE file header which marks the file as containing a console application. (If you want to know how the linker can "see" switches defined in the source code, please consult the chapter "W32Link Internals" following later in this text.) The UNICODE flag switches some macros and data structures defined in W32Main.inc to accept Unicode characters and strings. Other than the ANSI type of characters used in Win16 land, Unicode characters are 16-bit each, hence you can forget about all that code page crap that bothered us before. The bad news is that Windows 95 doesn't support Unicode, so if you want to target Windows NT and Windows 95, you should always set the UNICODE flag to 0. The DLL switch does exactly what you would think of it: If your program should become a Dynamic Link Library (DLL) after linking, set this flag to 1. This will select special DLL startup code from the W32Start.inc file and force the linker to mark the executable as a DLL file. And finally, there's the WIN95 switch. Now, this is really one of the Win32 aspects I hate most. Why do Microsoft define such a splendid API specification like Win32, only to cripple it some time later by publishing a silly product like Windows 95 which doesn't implement all that Win32 stuff correctly? I don't want to delve into the depths of this strange decision now, since you can read all about it on CompuServe or Usenet. Only keep in mind that, if your main target is Windows 95, you should set this flag. Failing to do so is not a Windows 95 compatibility killer like setting the UNICODE flag. Currently, the WIN95 switch changes the behavior of the Forward macro defined in W32Main.inc only. (See the sample program FWD.asm for a typical case where the Forward macro might be used.) Later WALK32 releases might add some further references to this switch. After defining the assembly/linker switches, the include file W32Main.inc should be loaded, because the stuff defined in it will be needed immediately afterwards: include W32Main.inc ;Win32 main header file The next line is quite important, since it specifies which W32Link import libraries you want to use: IMPORT "NT" ;import library list WALK32's import mechanism differs considerably from the usual method of MS Visual C++ (MSVC++) or similar products. Microsoft uses COFF formatted import libraries which are merged with your application's object file by the linker. Moreover, you will find several macro definitions in the MSVC++ header files which convert any API references involving characters to their appropriate ANSI or Unicode variants. In WALK32, the linker uses a simple ANSI text file to resolve any API references. These text files will be referred to as "W32Link Import Libraries" throughout this text. Their file names are always of type W32Link.*, where the extension specifies the contents of the library. It's these extensions which are specified on the IMPORT parameter line above. For example, the TestDll.asm sample program uses the W32Link import libraries W32Link.NT and W32Link.XXX. Hence, you will find an IMPORT definition in this file, looking like: IMPORT "NT", "XXX" ;import library list There's not a fixed limit of how many libraries may be specifed, However, the OMF record format used to code the object file has intrinsic limitations for record and string lengths. Also, the linker maintains a single 64k segment for all imports only, so you cannot specify an arbitrary number of items. But usually you will stay well below those limits. You may wonder why W32Link maintains two separate import libraries for Windows NT and Windows 95. Well, for one reason, Windows NT exports several API's which are not available under Windows 95. To some extent, this is also true the other way round. If you take a close look at the files W32Link.NT and W32Link.95, you will also note that the numbers specified for the API names are quite different. These "hint" and "ordinal numbers" help the PE file loader to locate the various API entry points. Although the loader is clever enough to do this work even with incorrectly stated hints, you should always use the W32Link import library matching your preferred target operating system, because your executables will load faster if the hints are correct. (If you want to learn more about PE executables, exports, and imports, I strongly recommend reading Matt Pietrek's masterpiece Windows 95 System Programming Secrets [3]). Following the import definition, you find a line that says: DEFAULT_ICON equ 101 ;application icon id You will probably never want to touch this line, and I easily could have put it into the W32Main.inc file. But since this is an application specific constant, I've left it in the source file. This value defines the resource ID of the application's main icon. This icon will appear in any Program Manager group you put your application in. You can also associate this icon to any windows your application creates, so this icon will appear whenever one of these windows is minimized. The value 101 is somewhat magic. I've found no specification telling which values are valid resource IDs. But since Microsoft don't use resource IDs below 101 in their sample applications, it seemed obvious to me to adopt this value here. Should you ever wish to change this value, don't forget to change it in the linker's source code as well. If your application needs any private constants or data types, you should place them immediately after the DEFAULT_ICON definition line. 4.1.2. The WALK32 Program Frame WALK32 takes much effort to save you from writing any stupid segment or section definitions. Because the PE file format is so simple and clearly structured, there's not very much variance in those definitions across programs. So why not yank them off the application code and put them into a common file like W32Main.inc? Exactly this is the idea behind the BeginImage, EndImage, BeginCode, EndCode, BeginData, EndData, EndIData, and EndUData macros. The typical framework for code and data of a Win32 program looks like this: BeginImage _main, text, data ; BeginCode ; Code defined here goes into the .text section. EndCode ; BeginData ; Data defined here goes into the .data section. EndIData ; Data defined here will not be included in the PE image file. EndUData That's all! The quirky segment and section mumbo-jumbo is completely handled by some diligent macros. Only the BeginImage macro leaves you with some degrees of freedom. It takes three parameters. The first of them will become the internal name of the flat 32-bit segment. This name is quite unimportant - it's just there to give the linker something to refer to. The other two parameters name the PE code and data sections. BeginImage will truncate them to seven characters and put a period in front of them. Thus, the PE file in our example will have two sections named .text and .data, respectively. Those are the default names for PE code and initialized data sections. If you study Microsoft's PE/COFF specification [2] or Matt Pietrek's Windows 95 book [3], you will learn that most Win32 executables contain a .bss section where any uninitialized data will be placed. Matt Pietrek also tells you that Borland's "TLINK32 doesn't emit a .bss section. Instead, it extends the virtual size of the DATA section to account for uninitialized data." ([3], p. 580) This is exactly what W32Link does. There's absolutely no requirement to have a .bss section in a PE file. It only eats up space unnecessarily. Most programs have data which needs not be initialized. You should always put data of this kind between the EndIData and EndUData lines, since this will keep your executable as small as possible. Should you ever write a program without any uninitialized data, you can replace the adjacent EndIData and EndUData lines by a single EndData line. Please note that the BeginImage macro adds some extra data to the beginning of the segment it defines. This is a pair of section descriptors needed by the linker to cut the single flat segment into separate PE file sections. This extra data is 32 bytes long and is - of course - stripped by the linker after evaluating it. (You'll find more on this topic in the chapter on "W32Link Internals", following soon.) 4.1.3. The WALK32 Program Body After the Win32 loader has mapped your application's executable file to memory, it will transfer control to the entry point specified in the PE file header. You'll probably wonder where this entry point might be, since none of the segment/section control macros seems to specify one. In fact, the EndImage macro defines it tacitly, and sets it to _Win32Startup. This symbol is defined in W32Start.inc. This means, that the Win32 loader branches to the WALK32 startup code after doing its work. Because the linker wouldn't like to be left with an unresolved reference to _Win32Startup, you should include Win32Startup some place after the BeginCode definition. Although the exact position of the startup code inside the code section is not critical, it's good programming practice to place it at the very beginning: BeginCode include W32Start.inc ;Win32 startup code The remaining space between these two lines and the EndCode definition is at your disposal. However, you must write a function called WinMain(), because the startup code calls it after doing some initializations. (If you are writing a DLL, i.e. the DLL switch in the header is set to 1, this function must be named LibMain() instead of WinMain().) A typical WinMain() function looks like this: WinMain: WinMainPrologue ... ;some application specific code mov eax,0 ;load return code WinMainEpilogue Again, macros from W32Main.inc are involved here to save you from writing dumb code sequences nobody cares for. WinMainPrologue saves a couple of registers and builds a stack frame for WinMain(), and WinMainEpilogue destroys this stack frame and restores the registers saved before. (In DLL source files, use the LibMainPrologue and LibMainEpilogue macros instead.) By the way, W32Main.inc defines two more pairs of prologue/epilogue macros for window procedures and threads, named CallbackPrologue, CallbackEpilog ue, ThreadPrologue, and ThreadEpilogue. They are quite similar and differ only to the extent that that the epilogues account for different sizes of the stack frame to be destroyed. When your WinMain() function is entered, the WALK32 startup code has prepared the stack to contain four parameters your program is likely to need. Yes, that's right: It's not the Win32 loader which prepares the WinMain() parameter stack. It's the startup code. On the other hand, the LibMain() DLL initialization function parameters are supplied by the loader. Sometimes, the Win32 world appears to be a bit weird. (See _Win32Startup in W32Start.inc for an ugly piece of code making heavy use of conditional assembly.) As you might know, the Win32 API is optimized to facilitate interfacing by high level programming languages. Contrary to ASM code, which usually passes parameters in CPU registers, high level languages are passing data on the stack. This means, that any piece of your code called by the Win32 system will have to examine the stack to retrieve any parameters passed to it. The designers of the 8086 CPU implemented a special register called BP (Base Pointer) to make stack addressing easy. In the 32-bit descendants of the 8086 CPU, starting from the 80386, a 32-bit "Extended Base Pointer" EBP is provided. Examining the definition of the WinMainPrologue macro reveals, that the parameter stack frame is constructed in the following way: push ebp mov ebp,esp push ebx push esi push edi This prologue is the same for WinMain(), LibMain(), callback, and thread functions. First, the current value of EBP - which is usually the address of the stack frame of the calling routine - is saved on the stack. Then EBP is set to ESP. From now on, any parameters on the stack can be addressed through ASM instructions involving operands of type [ebp+x], where x is a positive constant. Now let's trace the contents of the stack after EBP has been set. At [ebp], we will find the previous value of EBP. At [ebp+4], the return address of the calling routine is stored. Hence, [ebp+8] is the effective address of the first function parameter, [ebp+12] holds the next one, and so on. (Luckily, there's no distiction between NEAR and FAR pointers in the Win32 world anymore, so all function parameters will usually be of the DWORD type.) Before you can safely access the parameters on your function's stack, you'll have to know how they are ordered there. C language compilers use a special parameter passing convention, which differs quite a bit from the PASCAL convention. When a C function is called, parameters are pushed from right to left on the stack, and the calling routine has to remove them from the stack after the called function returns. PASCAL programs push parameters from left to right, and the called function has to remove them before returning. That's quite a difference. Although Windows 3.xx was developed using the C language, its designers voted for the PASCAL calling convention for most of the API functions. For Win32, Microsoft chose another variant, which is a mixture of the C and PASCAL conventions: For parameter passing, the C method (i.e. right to left) is appropriate, while using the PASCAL method for parameter cleanup (i.e. letting the called function do the work). I don't know why Microsoft opted for this unusual and somewhat confusing way. Maybe they wanted the best of both worlds. The C parameter passing scheme makes it much easier to write functions which accept a variable number of parameters, while the PASCAL cleanup style saves some space because cleanup doesn't have to be done each time a function is called. Anyway, by now it should be clear that the first (or leftmost) parameter of a function is the last one to be pushed on the stack, and hence appears at [ebp+8]. Generally, all parameters are found at the effective addresses [ebp+8+((n-1)*4)], where n is the ordinal number of the parameter, ranging from 1 upwards. Again, WALK32 saves you from writing silly stuff like [ebp+x] by defining symbolic names for all common parameters in W32Main.inc. For instance, the WinMain() parameters are defined as follows: hInstance textequ <[ebp+08]> ;instance handle pEnvironment textequ <[ebp+12]> ;environment data pCmdLine textequ <[ebp+16]> ;command line dCmdShow textequ <[ebp+20]> ;window display Thus, you can write things like, for instance: mov eax,hInstance ;make hInstance global mov hInst,eax Here, hInst is a global variable defined in the data section, while hInstance simply evaluates to [ebp+08]. This code snippet loads the instance handle passed to WinMain() from the stack and saves it in the application's data section, making it accessible to any callback functions defined subsequently. (See the quite elaborate MiniApp.asm sample for practical details.) Please note that WALK32 does not define an hPrevInstance parameter, thus deviating from the samples in many Win32 programming books. In my opinion, it's pure nonsense to pass this parameter to WinMain(), since it's always NULL in the Win32 environment. This is due to the logical separation of Win32 process address spaces. If you are spawning several instances of your application, every instances gets its own virtual address space of 4GB size. Because these instances cannot "see" each other, it's no use to pass previous-instance handles to them. Those handles are invalid outside the address spaces they refer to. To fill the gap caused by dropping hPrevInstance, I chose to pass a pointer to the process' environment memory block. Like under bad old DOS, this block consists of a sequence of null-terminated strings, and the last string is followed by another null byte. But unlike in a DOS environment block, the strings are not followed by an 0001h WORD and the full path of the load file name. Another WinMain() deviation of WALK32 concerns the contents of the command line pointed to by the pCmdLine parameter. The Win32 API always supplies the full command line including the application's name. The Microsoft startup code of MSVC++ strips the application's name from the command line and passes the remaining tail to WinMain(). Not so the WALK32 startup code: It preserves the command line in its original form. But let's get back to the Hello.asm program sample now. By now, we know how WinMain() is embedded into the PE code section. The only task left to us is to fill WinMain() with some useful code. As this program doesn't have to do much more than to splash a simple "Hello World" message to the console, we don't have to write very much code. You will be surprised that writing to the console works exactly like under DOS. First, you have to get hold of a handle to the console: Win32 GetStdHandle,\ ;get standard input handle STD_OUTPUT_HANDLE mov hStdOutput,eax The Win32 macro is another goodie from the W32Main.inc include file. Instead of having to write an odd-looking sequence of push instructions and finally call the desired API, use the Win32 macro to produce a much more readable piece of code. This macro also marks the API symbol as external and arranges the parameters supplied to it according to the C parameter passing convention, so you don't have to bother about it. Simply write the parameters in the order they appear in the Win32 specification, and that's it. The GetStdHandle() API call retrieves a standard handle, which can be used subsequently in conjunction with the Win32 file access functions. As Hello.asm shows, you can pass STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, or STD_ERROR_HANDLE to GetStdHandle(). Now, doesn't this remind you of something? Yes, of course, they perfectly correspond to the stdin, stdout, and stderr handles known from the prehistoric days of DOS programming. If GetStdHandle() returns anything other than INVALID_HANDLE_VALUE, you can use the returned value immediately in any subsequent WriteFile() calls. Hello.asm uses a helper function instead of calling WriteFile() directly: mov esi,offset sHelloWorld ;display text call OutputString Let's take a closer look at OutputString() now: ; ;============================================================================== ; ; > esi - string ; ; < esi - next address ; ;----------------------------------------------------------------- ------------ ; OutputString: ; if UNICODE push esi Win32 WideCharToMultiByte,\ ;convert unicode -> ansi CP_ACP,\ 0,\ esi,\ -1,\ NULL,\ 0,\ NULL,\ NULL mov ecx,eax push ecx Win32 LocalAlloc,\ LMEM_FIXED,\ ecx pop ecx pop esi cmp eax,NULL jz OutputString3 push eax push esi Win32 WideCharToMultiByte,\ ;convert unicode -> ansi CP_ACP,\ 0,\ esi,\ -1,\ eax,\ ecx,\ NULL,\ NULL pop esi shl eax,1 ;compute next address add esi,eax pop eax push esi push eax mov esi,eax ;use ansi string endif ; mov ebx,esi ;seek string terminator OutputString1: inc ebx cmp byte ptr [ebx-1],0 jnz OutputString1 push ebx dec ebx ;compute string length sub ebx,esi jz OutputString2 Win32 WriteFile,\ ;display string hStdOutput,\ esi,\ ebx,\ ,\ NULL OutputString2: pop esi ;load next address ; if UNICODE pop eax Win32 LocalFree,\ eax pop esi ;load next address OutputString3: endif ; ret Well, isn't this quite a bit code just to output a single text line on the console screen? Right. But probably you want to write programs doing a bit more stuff later. So I decided to provide a universal console output function which you may want to use in your more interesting projects. Please note how OutputString() refers to the UNICODE switch. If UNICODE happens to be set to 0, this whole bunch of code collapses to: ; ;============================================================================== ; ; > esi - string ; ; < esi - next address ; ;----------------------------------------------------------------- ------------ ; OutputString: mov ebx,esi ;seek string terminator OutputString1: inc ebx cmp byte ptr [ebx-1],0 jnz OutputString1 push ebx dec ebx ;compute string length sub ebx,esi jz OutputString2 Win32 WriteFile,\ ;display string hStdOutput,\ esi,\ ebx,\ ,\ NULL OutputString2: pop esi ;load next address ret That's much easier to understand, isn't it? First, the string supplied in ESI is scanned for its null terminator. This is necessary, because WriteFile() is not designed to write strings, but arbitrary data blocks, and will insist in knowing how many data bytes you want it to write. If the string is empty, OutputString() will simply skip the WriteFile() call, since there's nothing to do. All the remaining code we stripped off by setting UNICODE to 0 is required to support Unicode in a transparent way. Ideally, you will want to write you application in a way that makes it easy to build your program either for the ANSI or Unicode world. Setting the UNICODE switch to 0 or 1 should be the only action to take. In most cases, Unicode handling is easy because the linker will automatically link to the appropriate APIs, depending on the state of the UNICODE flag. WriteFile() is one of the few exceptions. It accepts unformatted data only, so it doesn't know about ANSI and Unicode strings. If you want to write Unicode data to a file or device, you have to do the conversion yourself. To ease character conversion, Win32 supplies the WideCharToMultiByte() and MultiByteToWideChar() APIs. The term "WideChar" refers to Unicode characters, which always take 16 bits instead of eight. "MultiByte" can be any character set, even comprising characters of variable length. ANSI is a (trivial) special case of a MultiByte character set. To tell Windows that you want to use ANSI, you have to supply CP_ACP as the code page ID in any MultiByte API calls. The Unicode variant of the OutputString() function first calls WideCharToMultiBy te() to determine how many bytes will be required to store the ANSI version of the original Unicode string. Then it allocates local memory for the string, calls WideCharToMultiByte() again to put the converted string into the buffer, calls WriteFile() to write it to the screen, and finally frees the local buffer. This code is not a particularly beautiful sight, but that's how it goes in a mixed ANSI/Unicode world. Before shifting our interest to GUI application, let's take a final look at the Hello.asm source code. Since OutputString() obviously accepts Unicode strings, the "Hello World" screen message must be defined in a Unicode-aware way. Here's how it's done: sHelloWorld: STRING Weird, isn't it? ASM programmers are used to define strings using the DB or BYTE instructions, like, for instance: sHelloWorld BYTE CR, LF, "Hello World !!!", CR, LF, 0 This is perfectly OK as long as you are never planning to port your application to Unicode. If Unicode is involved, the DB or BYTE instructions aren't appropriate anymore, because every character is a 16-bit quantity. Moreover, you need to evaluate the UNICODE switch if you want to select the character set transparently. W32Main.inc saves you from this hassle by defining the STRING macro. Unicode will be discussed in much detail in a later chapter. The only thing I want to say about this macro here is, that you should always keep in mind that it's really a macro, not a data type, although it looks like one. Please compare the variants of the "Hello World" string definition above. It's only a tiny, but important detail: While the definition involving the BYTE instruction doesn't require a colon immediately after the sHelloWorld variable name, the STRING variant does. That's not very pretty, but MASM requires it to be so. And so be it. 4.2. Win32 GUI Applications Contrary to Win32 console applications, which run in a simple text mode console window, its GUI counterparts take advantage of Windows' "Graphical User Interface" (GUI). It takes much more effort to write a simple GUI application, because Windows requires you to perform a non-trivial amount of initialization before your app can start doing something useful. WALK32 provides you with a GUI application frame called MiniApp.asm you can use as a basis for your own projects. The WALK32 program header looks much like the one you've seen in the Hello.asm sample. Only the state of the CONSOLE switch differs, i.e. must be set to 0: CONSOLE equ 0 ;0 = gui, 1 = console Skipping to the CONSTANTS section, you find several definitions not present in the previous console mode sample program: ; ;================================================================================= ; ; CONSTANTS ; ;================================================================================= ; WMU_INIT equ WM_USER+0101h ;initialization message ; ;-------------------------------------------------------------------------------- ; IDM_FILE equ 100 ;file menu IDM_FILE_EXIT equ IDM_FILE+00 ;file / exit ; IDM_HELP equ 200 ;help menu IDM_HELP_HOWTO equ IDM_HELP+00 ;help / how to use help IDM_HELP_ABOUT equ IDM_HELP+01 ;help / about ; ;-------------------------------------------------------------------------------- ; IDW_STATUSBAR equ 1000 ;status bar ; ;-------------------------------------------------------------------------------- ; IDC_HELP_ABOUT equ 2000 ;help / about box IDC_HELP_ABOUT_INFO equ IDC_HELP_ABOUT+00 ;help / about / info ; ;-------------------------------------------------------------------------------- ; MAX_DIALOGS equ 30 ;dialog directory size All of these constants refer to items typically required by GUI applications, like menus, dialog items, and messages. I don't want to write down a complete Win32 GUI programming course here, because this would easily become a two-inch handbook. So I'll restrict myself to the various helper routines found in MiniApp.asm, which are designed to do the routine work for you, so you can concentrate on the specific problems of your programming project. 4.2.1. Initializing The Application Early in its WinMain() function, MiniApp.asm makes a call to a function called Initialize(), which does all the crazy preparatory work needed to set up a Win32 GUI application. This includes: - Determining the full path of an initialization file, which can be used to load default settings on startup, and storing it in a variable called sDataFile; - Determining the full path of a help file, which can be opened from the application's help menu, and storing it in a variable called sHelpFile; - Creating the application's main menu bar; - Creating the application's main window and attaching the menu bar to it; - Creating a status bar at the bottom of the main window. In case the initialization terminates without error, WinMain() posts a WMU_INIT message to its main window, and drops into the well-known message loop described in every programming book. WMU_INIT is a user defined message and is set to WM_USER+0101h in the program's CONSTANTS header. This message causes the program to perform last-minute initializations which have to be delayed up to the point when everything is set up to finally enter the program's main message loop. Please note that Microsoft recommends to never use any user-defined messages below WM_USER+0101h to avoid conflicts with system messages which might be located in this very region. For example, the common controls use messages starting from WM_USER+1 upwards. 4.2.2. Dispatching Messages One of the most hard working functions in MiniApp.asm is the D. In no event shall the author Sven B. Schreiber be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this software, even if advised of the possibility of such damage. Dispatcher() function. This is a very short, but convenient function, which helps you to maintain a clear structure in your programs. One of the ugliest things in Windows programming are the endless "switch/case" monsters found in many C language programs. These code beast do nothing but select message handling code, depending on the type of message read from the message queue. In ASM programming, the "switch/case" construct can be emulated by a sequence of CMP/JNZ blocks with the message handling code in between. But I tell you: Don't you do that! This technique makes your code as unreadable as could be. The best way to escape code monuments like that is to use tables and pack the message handlers into individual, separate subroutines. The Dispatcher() function helps you to let this miracle happen. Dispatcher() takes a message code and a dispatch table as parameters, selects the appropriate handler from the table, and simply returns the return code of this handler. Note that the Dispatcher() is fully reentrant, hence you can call it a second time to dispatch, say, dialog item notifications while being in the midst of a previous Dispatcher() call trying to deliver a WM_COMMAND message. The first dispatch level is the main window procedure MainWndProc(): MainWndProc: CallbackPrologue mov eax,dMsg ;dispatch messages mov ebx,MainWndTable call Dispatcher CallbackEpilogue Here we finally meet the CallbackPrologue and CallbackEpilogue macros mentioned in an earlier section, which manage the parameter stack frame needed for callback functions. In any calls to the Dispatcher(), the table supplied in register EBX plays the most important role: MainWndTable: ; DWORD WMU_INIT, MainWndInit DWORD WM_DESTROY, MainWndDestroy DWORD WM_INITMENUPOPUP, MainWndInitMenuPopup DWORD WM_SIZE, MainWndSize DWORD WM_PAINT, MainWndPaint DWORD WM_COMMAND, MainWndCommand ; DWORD -1, DefaultHandler This table declares which window message should trigger what action. For instance, if a WM_COMMAND message is retrieved from the message queue, the MainWndCommand() function will be called. If the message ID doesn't match any of the listed IDs, the Dispatcher() will call the function referred to in the last row of the table, i.e. DefaultHandler(), which is defined as: DefaultHandler: Win32 DefWindowProc,\ ;default message processing hWnd,\ dMsg,\ dParam1,\ dParam2 ret This is, of course, the default Windows message handler which knows what's best to do with any message your window procedure refuses to accept. 4.2.3. Defining Menu Actions Let's get back to the WM_COMMAND case mentioned above. Here's what the MainWndCommand() function does: MainWndCommand: mov eax,dParam1 ;dispatch commands and eax,0FFFFh mov ebx,MainCmdTable jmp Dispatcher Doesn't that look familiar? Here we've got our second Dispatcher() level. On this level, several types of WM_COMMAND messages are dispatched. If we look at the MainCmdTable dispatch table, we know immediately what this is all about: MainCmdTable: ; DWORD IDM_FILE_EXIT, MainCmdFileExit ; DWORD IDM_HELP_HOWTO, MainCmdHelpHowTo DWORD IDM_HELP_ABOUT, MainCmdHelpAbout ; DWORD -1, DefaultHandler Since we are looking for IDM_* values supplied in the lower half of the dParam1 parameter accompanying the WM_COMMAND message, this must be a menu command dispatcher. Here we've got the handlers for the File | Exit, Help | How to Use Help, and Help | About MiniApp menu items. If you've already seen any Win16 or Win32 programs written in C, you might have noted that it's kind of an unwritten law that menus be placed in a separate resource file. These files usually have a .rc file extension. WALK32 deviates from this practice and simply places menus into the application's data section, like any other data structures. WALK32 uses menu templates to define menus. Here's how the main menu of the MiniApp.asm sample application is set up: tMainMenu equ $ ; hMainMenu HMENU NULL ;main menu handle ; hFileMenu HMENU NULL ;file menu handle DWORD MF_ENABLED STRING <&File/0/1> ; DWORD IDM_FILE_EXIT DWORD MF_ENABLED + MF_UNCHECKED STRING ; hHelpMenu HMENU NULL ;help menu handle DWORD MF_ENABLED STRING <&Help/0/1> ; DWORD IDM_HELP_HOWTO DWORD MF_ENABLED + MF_UNCHECKED STRING <&How to Use Help/0/1> ; DWORD 0 ;separator DWORD 0 STRING ; DWORD IDM_HELP_ABOUT DWORD MF_ENABLED + MF_UNCHECKED STRING <&About MiniApp/0/0> ; DWORD -1 ;end I think it's not very hard to understand how this menu template is structured. The first entry is labelled hMainMenu and will be set to the menu handle after the template has been processed by WALK32's CreateMainMenu() function. Following this handle are two blocks, which correspond to the File and Help items on the menu bar. Each one of them is again composed of sub-blocks defining the menu bar texts as well as the entries in their pop-up lists. The first sub-block tells CreateMainMenu() which text to display on the menu bar. Any subsequent sub-blocks specify pop-up items. CreateMainMenu() knows when to start a new menu bar item by examining the flag bytes following each of the string definitions. If it encounters something like <&How to Use Help/0/1>, the trailing /1 indicates that another sub-block follows. The last sub-block is marked by a trailing /0. If you want to insert a separator line into the pop-up list, just create an empty sub-block without any text. Finally, a value of -1 signals that the end of the menu template is reached. Besides specifying the text strings for the menu bar and pop-up lists, every sub-block also transports ID and status data. Menu IDs are needed to attach actions to menu items. When the user selects an item from a pop-up list, Windows will send a WM_COMMAND message to the window owning the menu and put the menu ID into the lower half of the dParam1 message parameter. Menu items can be disabled, grayed, or checked. If you don't want a menu item to be selected under some circumstances, you can set its status to MF_DISABLED or MF_GRAYED. For a menu item which behaves like a toggle switch, it's quite useful to show a visual cue of its current state. Windows can place a checkmark symbol in front of a pop-up list item if the function toggled by this item is currently "ON". Menu checkmarks are controlled by the MF_CHECKED and MF_UNCHECKED status codes. WALK32 supports automatic menu item status control. MiniApp.asm implements this feature by handling the WM_INITMENUPOPUP message. This handler uses the WALK32 function FindMainMenuPopup() to determine which menu bar item has been clicked, and UpdateMainMenuPopup() to update the status of the pop-up list. Both functions examine the menu template passed to them in the EBX register, so all you have to do is set the status fields in the template structure appropriately, and WALK32 does the rest for you. 4.2.4. Opening Dialog Windows Examining the MainCmdHelpAbout() handler reveals another interesting WALK32 feature: MainCmdHelpAbout: mov eax,hWnd ;display about box mov ebx,TemplateHelpAbout mov edx,ModalDlgProc call ModalDialog jmp ReturnNULL Here, the main point of interest is the ModalDialog() function. It does all the work needed to open a modal dialog window. It takes a memory resident dialog template and passes it to the Win32 DialogBoxIndirectParam() API. It also centers the dialog window on the screen if you want it to. As you probably know, a modal dialog automatically disables its parent window. This is quite useful if you don't want to allow the user to switch away from this dialog and use other functions of your program. However, sometimes it might be desired to do just that. A typical case is a status window, which displays some information while the user may proceed working with your application. This is what modeless dialogs are made for. Besides ModalDialog(), WALK32 also provides a ModelessDialog() function to create modeless dialogs. It works quite similar to its counterpart. The main difference is that ModalDialog() does not return until the dialog it spawned is closed, while ModelessDialog() returns immediately. There are some more subtle differences: A modal dialog is closed using the EndDialog() API, while a modeless dialog wants to see DestroyWindow() instead. Before destroying the modeless dialog window, you should also set the input focus to the dialog's parent windows, because otherwise another application might become active after the dialog disappears. All programming books tell you, that you must insert a call to IsDialogMessage() into your main message loop when you have opened a modeless dialog window to allow this window to receive keyboard messages. WALK32 provides a dialog manager taking care of that for you. See function DispatchDialogMessage() in MiniApp.asm, if you're interested in the implementation of this feature. The WALK32 dialog manager's various functions are described in more detail in a later section about color palette management. 4.2.5. Using Dialog Templates The same things said about menu resources before also hold for dialogs. They are classified as resources, and hence are placed in a .rc resource file in most C programs. Again, WALK32 deviates from this practice and puts dialogs, like menus, into the application's data section. Dialog resources are always composed of a DLGTEMPLATE structure, followed by several DLGITEMTEMPLATE "slots". The former defines the size and style of the dialog window as well as the window's title text. It also specifies how many DLGITEMTEMPLATE structures are about to follow. There are two important catches: First, Windows 95 - other than Windows NT - requires dialog templates to be aligned on a DWORD boundary; Second, dialog resources always use Unicode characters, no matter if the rest of your code is written for ANSI or Unicode. To escape the DWORD alignment problem, it's best to place any dialog templates at the very beginning of the data section. This is the best guarantee for DWORD alignment. The second problem isn't really a problem, since W32Main.inc defines two macros called DLGTEMPLATE and DLGITEMTEMPLATE which take care of all Win32 resource peculiarities. You may ask why DLGTEMPLATE and DLGITEMTEMPLATE are macros instead of structures. Good question. In Microsoft's Win32 Programmer's Reference, they are indeed defined as structures. The answer is that their definitions are so damn complex that it's better to use the power of macro programming to handle them correctly. So let's see what the About Box dialog template from MiniApp.asm looks like: TemplateHelpAbout equ $ ; DIALOGATTRIBUTES {PaletteHelpAbout, TextBoxHelpAbout} ; DLGTEMPLATE DS_MODAL+WS_MODAL, 8,\ -1, -1, 200, 118,\ ; DLGITEMTEMPLATE SS_WHITEFRAME+WS_CONTROL,\ 21, 79, 18, 18,\ -1, STATIC,\ <> ; DLGITEMTEMPLATE SS_ICON+WS_CONTROL,\ 22, 80, 16, 16,\ -1, STATIC,\ -1, DEFAULT_ICON ; DLGITEMTEMPLATE SS_WHITEFRAME+WS_CONTROL,\ 161, 79, 18, 18,\ -1, STATIC,\ <> ; DLGITEMTEMPLATE SS_ICON+WS_CONTROL,\ 162, 80, 16, 16,\ -1, STATIC,\ -1, DEFAULT_ICON ; DLGITEMTEMPLATE BS_DEFPUSHBUTTON+WS_CONTROL+WS_GROUP,\ 60, 70, 80, 36,\ IDOK, BUTTON,\ <&OK> ; DLGITEMTEMPLATE SS_CENTER+WS_CONTROL,\ 12, 12, 176, 48,\ IDC_HELP_ABOUT_INFO, STATIC,\ ; DLGITEMTEMPLATE SS_BLACKFRAME+WS_CONTROL,\ 12, 12, 176, 48,\ -1, STATIC,\ <> ; DLGITEMTEMPLATE SS_BLACKFRAME+WS_CONTROL,\ 15, 15, 170, 42,\ -1, STATIC,\ <> ; TextBoxHelpAbout DWORD IDC_HELP_ABOUT_INFO DWORD NULL For now, let's forget about the DIALOGATTRIBUTES structure at the top of this definition. We'll getback to it soon. As noted before, a template always starts with a DLGTEMPLATE structure. The first parameter of this structure specifies the dialog and window styles. For a middle of-the-road modal dialog window, it's OK to specify DS_MODAL+WS_MODAL. Following the style flags is the DLGITEMTEMPLATE count. The next four parameters specify the origin and size of the window (horizontal position, vertical position, horizontal size, and vertical size). If any of the origin members is set to -1, the window will be centered along the corresponding axis. The last DLGTEMPLATE parameter defines the caption text of the dialog window. Since the DLGITEMTEMPLATE count is set to eight in this example, eight DLGITEMTEMPLATE structures must follow now. Every DLGITEMTEMPLATE definies a static text field, button, edit control, listbox, combobox, icon, or whatever the dialog should be composed of. Again, the first parameter indicates the style of the dialog item in question. It usually is a combination of WS_CONTROL and a specific style ID, like SS_CENTER for centered static text. Next are the origin and size of the dialog item. Please note that the origin is not stated absolute, but relative to the client area of the dialog window. The next two parameters identify the dialog item in terms of its dialog and class IDs. Dialog IDs are user-defined constants which help the dialog window procedure to associate any notifications retrieved from the message queue to dialog items. If the dialog item is "anonymous", use -1 as a placeholder. The class ID can be any of the predefined control classes, like BUTTON, EDIT, STATIC, LISTBOX, SCROLLBAR, or COMBOBOX. The last DLGITEMTEMPLATE parameter is an optional text constant assigned to the dialog item. For buttons, this is the label appearing on the button's face. For static text fields, it's the text to show up inside the field, of course. For some items it doesn't make sense to assign text to them. For example, a DLGITEMTEMPLATE for a static black frame can't do anything useful with this text, so an empty string <> is specified here. Icons in dialog windows are somewhat exceptional. Since the application's icon is the only resource that WALK32 throws into the PE file resource section, it is accessed using a resource ID. As you might recall from the discussion of the WALK32 program header, this ID is set to 101 by the linker, and the symbolic constant DEFAULT_ICON is used to refer to it. Whenever a DLGITEMTEMPLATE contains a reference to an item in the PE resource section, the last line of the definition specifies the resource ID, preceded by -1. The -1 prefix tells Windows that this parameter is not a string, but a number. Hence the icon DLGITEMTEMPLATE of our example shows -1, DEFAULT_ICON as its last parameters. To understand what the last part of the dialog template labelled TextBoxHelpAbout is for, we have to go back to the top of the template definition. Now it's time to explain what's the use of the initial DIALOGATTRIBUTES structure found there. This is not a Win32 special, but a WALK32 feature. 4.2.6. Managing Dialog Color Palettes One of the most important wishes of every programmer is to have full control of the dialog's colors. Although it's hard to believe, this is not a trivial task. WALK32 addresses this problem by adding color palette management to every GUI application. MiniApp.asm shows how it's done. MiniApp.asm maintains a dialog directory, which can take up to 30 dialog entries. The number of entries can be customized by changing the MAX_DIALOGS constant in the program header. The main purposes of the dialog directory are to keep track of the colors used by any open dialog window and to manage messages for modeless dialog windows. The dialog directory management is done by a set of six functions, named ClearDialogDirectory(), AllocDialogRecord(), GetDialogRecord(), FreeDialogRecord(), SetDialogRecordData(), and GetDialogRecordData(). The first of them, ClearDialogDirectory(), is called in response to the WMU_INIT message posted by WinMain() during program initialization. AllocDialogRecord() and FreeDialogRecord() are called whenever a dialog is created or terminated, respectively. Thus, you will always find a call to AllocDialogRecord() in the WM_INITDIALOG handler, and a call to FreeDialogRecord() in the handler, when the dialog window is about to be destroyed. In every call to AllocDialogRecord(), a dialog mode flag is passed in register EDX, which is FALSE for modal dialogs, and TRUE for modeless dialogs. This flag is tested in DispatchDialogMessage() to decide if any messages for modeless dialog windows are in the message queue. DispatchDialogMessage() automatically calls the Win32 IsDialogMessage() API if it finds a pending message for any of the modeless dialog windows currently open. The fourth of the dialog management functions, GetDialogRecord(), is called whenever Windows wants to know which color it should use to draw the dialog box or components thereof. On this behalf, Windows sends WM_CTLCOLOR* messages to the dialog window procedure. The handlers of these messages always start with a call to GetDialogRecord() to retrieve the colors associated to the item to be drawn. Included in every dialog record is a 32-bit field reserved for any private data of the dialog which owns the record. To write to and read from this field, the SetDialogRecordData() and GetDialogRecordData() functions are provided. When AllocDialogRecord() assigns a record to a dialog, it sets the data field's contents to NULL. The dialog may use this field to store arbitrary dialog-specific data for whatever purpose temporarily. If 32 bits are not enough, this field may hold a memory handle to a much larger parameter block as well. To tell Windows which color palette should be used for the dialog, every dialog template starts with a DIALOGATTRIBUTES structure, which contains a pointer to a color palette as its first member. Color palettes are defined using the DIALOGPALETTE structure, which specifies the foreground and background colors for dialog text, static text, text boxes, edit controls, and list boxes. The second DIALOGATTRIBUTES member may be somewhat confusing. It points to an array located at the end of the dialog template. In our example, this array is called TextBoxHelpAbout. Obviously, it defines a list of text box Ids. But what the heck is a text box? You will not find any clues on this topic in the Win32 SDK documentation. No wonder this is a special feature implemented in WALK32. In short, a text box is a special case of a static text field. Static text will usually have the same background color as the dialog window itself, so the text appears to be painted directly on the dialog's client area. But sometimes you might want to put static text into a box using a different background color. WALK32 defines this to be a "text box". To allow the palette management functions to distinguish them from ordinary static text, the dialog IDs of them need to be listed in the array pointed to by the second member of the DIALOGATTRIBUTES structure. This array can contain an arbitrary number of dialog IDs and is terminated by a NULL entry. 4.2.7. Using 3D Effects One of the nice features of Windows 95 is that it gives your dialogs a neat 3D look by default. Although the Windows NT upgrade from 3.50 to 3.51 added a lot of Windows 95 gimmicks, the 3D dialog styles obviously didn't make it into the new version. To give your dialog windows a good looking outfit anyway, you'll have to use the CTL3D32 DLL. Using this DLL is not a big deal. It just takes two API calls to initialize, and another one to clean up. Again, MiniApp.asm shows you the trick. The right place to initialize CTL3D32 is the initialization function Initialize(), called by WinMain(): Initialize: Win32 Ctl3dRegister,\ ;request 3d controls hInst Win32 Ctl3dAutoSubclass,\ hInst That's all! From now on, dialogs will automatically receive a beautiful 3D look. Cleaning up things is even easier. After WinMain() leaves its message loop, it simply calls: Win32 Ctl3dUnregister,\ ;clean up 3d library hInst The sad story about CTL3D32 is that Windows 95 doesn't come with this DLL, nor can it use the Windows NT version of it. So if you want to give your application a 3D look under both operating systems, you're stuck somehow. If you rely on the default behaviour of Windows 95, your application will look poor under Windows NT. If you are using Windows NT's CTL3D32 mechanism, your application will not start under Windows 95 because of a missing DLL. The solution to this dilemma is to use a dummy DLL under Windows 95, which implements the Ctl3dRegister(), Ctl3dAutoSubclass(), and Ctl3dUnregister() APIs, but does nothing useful with them. The WALK32 package contains a sample source file called CTL3D32.asm, showing you how to achieve this goal. I've renamed the executable file of this sample program from CTL3D32.dll to CTL3D32.XXX, to prevent you from accidentally loading it while you are testing the other sample programs. For example, if you place the dummy CTL3D32.dll into the same directory as the MiniApp.exe program and start the latter under Windows NT, you will see the about box lose all of its 3D effects. If you are shipping a Win32 application using CTL3D32 to your customers, your setup program should attempt to detect the operating system and place the dummy DLL into the \WIN95\SYSTEM directory in case it identifies Windows 95. When using CTL3D32, keep in mind that some 3D effects of CTL3D32 are not emulated by Windows 95. For instance, the wonderful SS_BLACKFRAME, SS_GRAYFRAME, and SS_WHITEFRAME effects are not supported by Windows 95. To get a 3D effect similar to the CTL3D32 SS_BLACKFRAME, you can use a disabled edit control, like MiniApp.asm does in its about box template. 5. Writing Win32 DLLs DLLs are a special type of Win32 executables. Usually, they consist of a collection of service functions, which can be called by any application as if they were its own subroutines. Subroutines which are needed for several different programs are good candidates to be placed into a DLL, so the same code sequences do not have to be included in every application. However, if you are in doubt whether to create a DLL or not, always vote for NO. There are several good reasons to avoid DLLs, as Lou Grinzo points out in his book Zen of Windows 95 Programming ([4], p. 155ff). Although a DLL consists of passive code waiting to be called from outside, you can include active code which runs completely autonomous. Win32 multithreading makes it possible. Like in 16-bit Windows, every DLL has a main entry point for initialization and clean-up. In WALK32, this entry point is named LibMain() (see W32Start.inc for implementation details). Nothing prevents you from creating another thread during initialization, which will run in the background inside the address space of the application which loaded the DLL. This can be quite handy if your DLL needs to do some regular housekeeping. But always remember to suspend this thread when it's got nothing to do. Use the Sleep(), WaitForSingleObject(), or WaitForMultipleObject() APIs for that purpose. Never let the thread run in a polling loop, since this is the best multitasking killer you can get. If a DLL background thread has to run in a loop for some obscure reason, you should always lower the threads priority using the SetThreadPriority() API. Try the lowest priority - THREAD_PRIORITY_IDLE - first. You will be surprised how much CPU time your thread will get anyway. 5.1. DLL Initialization Win32 DLLs have a single entry point through which they can receive notifications from the system. There are four system events which cause the entry point to be called: - The DLL attaches to a process; - The DLL detaches from a process; - The DLL attaches to a thread; - The DLL detaches from a thread. In Win32 land, process attachment/detachment happens exactly once, when the DLL is loaded and later freed. Because every process gets its private address space with all DLLs it requests mapped in, each virtual in-memory copy of a DLL belongs exclusively to the process in which address space it resides. However, thread attachments/detachments can occur many times, e.g. whenever a thread of this process calls LoadLibrary() or FreeLibrary(), respectively. In Win32 parlance, the entry point is usually called DllMain(). WALK32 uses the historic name LibMain() instead. Should you ever want to change this default, simply edit the startup include file W32Start.inc. The DLL entry point is called with three parameters: hInstDLL indicates the DLL's instance handle and tells you to which memory location in the parent process' address space the DLL has been mapped; dReason is a notification code and identifies one of the four system events introduced above; and dParamDLL is an additional parameter and is currently not used. When dReason is set to DLL_PROCESS_ATTACH, indicating that the DLL has just been loaded, LibMain() is expected to return a flag to signal success or failure. If it returns FALSE, the process is aborted immediately, and the loader displays an error message. Of course, you can again use the almighty Dispatcher() function from the MiniApp.asm sample application to dispatch the four system events to their individual handlers: ; ;============================================================================== ; ; DLL ENTRY POINT ; ;============================================================================== ; ; > [ebp+08] - hInstDLL ; [ebp+12] - dReason ; [ebp+16] - dParamDLL ; ; < eax - return code ; ;----------------------------------------------------------------------------- ; LibMain: mov eax,dReason ;dispatch init call mov ebx,LibMainTable jmp Dispatcher ; ;----------------------------------------------------------------------------- ; ; DLL INITIALIZATION ; ;----------------------------------------------------------------------------- ; LibMainTable: ; DWORD DLL_PROCESS_ATTACH, DllProcessAttach DWORD DLL_PROCESS_DETACH, DllProcessDetach DWORD DLL_THREAD_ATTACH, DllThreadAttach DWORD DLL_THREAD_DETACH, DllThreadDetach ; DWORD -1, DllUnknownReason Now it's your responsibility to write some useful code for the DllProcessAttach(), DllProcessDetach(), DllThreadAttach(), DllThreadDetach(), and DllUnknownReason() handlers. 5.2. Exporting DLL Functions The export of functions is the main business of a DLL. Normally, a DLL consists of a set of self-contained pieces of code, which can be used by the process which loaded the DLL. In most cases, the process calls this code using a symbolic name, much like it would call one if its own internal functions. To make this possible, the DLL must "publish" the symbols it supports, as well as the corresonding function entry points. This is called "export". To export a DLL function, you just have to use MASM's public directive. Thus, the offset of the function will be packed into a PUBDEF record in the object file, and the linker will pick it up and add it to the PE file's export section. Here's a slightly edited excerpt from the MiniDll.asm sample file: ; ;============================================================================== ; ; EXPORTED FUNCTIONS ; ;============================================================================== ; public GetDllPath ; ;============================================================================== ; ; DLL SERVICES ; ;============================================================================== ; GetDllPath: mov eax,offset sLoadFile ;return load file path ret Alternatively, you could have used the Export macro, which combines the definition of the label given as parameter and its public declaration in a single statement. Please note that the Export macro actually exports an intermediate symbol consisting of the specified name plus a leading @ character. The @ character will in turn be stripped of again by the linker. Using the Export macro, the example above would change to: ; ;============================================================================== ; ; DLL SERVICES ; ;============================================================================== ; Export GetDllPath ; mov eax,offset sLoadFile ;return load file path ret The MiniDll.asm sample exports just a single function, GetDllPath(). It doesn't perform very interesting work, but only returns a pointer to the sLoadFile variable, where the startup code has put the full path of the DLL's executable. MiniDll.asm comes with a tiny companion program derived from the Hello.asm sample discussed some way above. This TestDll.asm program does nothing but call the DLL's GetDllPath() function and display it on the console. If you've installed both programs in, say, D:\asm\Win32, running TestDll.asm should show the message: The MiniDll path is "D:\asm\Win32\MiniDll.dll" To allow you to assemble and link TestDll.asm yourself, I've included an import library named W32Link.XXX. This library is referenced in the IMPORT declaration of TestDll.asm, near the beginning of the source code: IMPORT "NT", "XXX" ;import library list The contens of W32Link.XXX are not very exciting: [MiniDll.dll] GetDllPath=0,1 Not very much, but enough to show you how the export of DLL functions works. 5.3. Forwarding DLL Functions Export forwarding is a powerful means to do all sorts of strange things. Windows NT uses forwarding internally to let the NTDLL.dll module do a lot of work for KERNEL32.dll. When a DLL is said to forward a function, this means that it contains an export entry for this function in its executable file, but doesn't service any calls to it itself. Instead, it passes this call to another DLL and returns to the calling process whatever the other DLL returned to it. In Windows NT, forwarding is supported directly by the Windows DLL loader. Whenever the loader finds a exported symbol whose associated entry point lies inside the PE file's export section, it interprets the entry point as a string pointer, telling the loader which DLL will service this call. Again, brain-damaged Windows 95 is too dumb to support this great feature too. But with a little wizardry, you can simulate forwarding even under Windows 95. W32Main.inc defines a special macro which makes forwarding really easy. It is cleverly named Forward. The sample program FWD.asm shows you how to use forwarding to solve a common problem of many programmers. It's a quite frequently asked question how one can synchronize a DIY program with a third party application. FWD.asm allows you to start and terminate a user-defined program in tandem with another program for which you haven't got the source code. The solution to this problem is "patching". Every Win32 application includes an import section in its PE executable, telling the loader which functions from which DLLs are needed. In the course of loading the executable, the loader will resolve all references to these imported functions. The secret of FWD.asm is to take over the role of one of the DLLs specified in an application's PE import section. FWD.asm is designed to put itself in front of SHELL32.dll. I've selected this one because it is used by many popular Windows applications, so chances are good that cou can test this sample in a true real-world situation. To mimick the behaviour of SHELL32.dll, FWD.asm exports all functions of this DLL which are supported by both Windows NT and Windows 95: ; ;============================================================================== ; ; FORWARDED FUNCTIONS ; ;============================================================================== ; Forward SHELL32, CheckEscapesA Forward SHELL32, CheckEscapesW Forward SHELL32, CommandLineToArgvW Forward SHELL32, Control_FillCache_RunDLL Forward SHELL32, Control_RunDLL Forward SHELL32, DllGetClassObject ... Forward SHELL32, ShellExecuteA Forward SHELL32, ShellExecuteEx Forward SHELL32, ShellExecuteExA Forward SHELL32, ShellExecuteW Forward SHELL32, Shell_NotifyIcon Forward SHELL32, Shell_NotifyIconA It is important to remeber that the Forward macro is sensible to the status of the WIN95 assembly switch from the program's header. If WIN95 is set to 0, the linker will put true forwarder links into the PE file's export section. To support the linker in doing so, the Forward macro concatenates its arguments, placing an @ character in between and marking the resulting symbol as public. The linker recognizes this special symbol, replaces the @ character by a period, puts this string into the PE export section and creates an export record pointing to this string. If WIN95 is 1, indicating that we are expecting a toy operating system to load this DLL, the macro evaluates to a jmp instruction, branching to the DLL servicing this call. Please note that in this case, Forward simply ignores its first operand. Instead, the linker determines the name of the requested DLL by searching the function name in the specified import libraries. You could specify any bogus name here or even leave this parameter blank. But, to maintain compatibility to Windows NT, I strongly recommend to specify the correct DLL name here. Now, since FWD.dll can take over any calls originally directed to SHELL32.dll, your next task is to patch the executable file of the target application by simply overwriting the SHELL32.dll reference in the import table by FWD.dll using a binary file editor. To locate this reference, you can use the LOOK utility distributed with WALK32 or any GREP-like search tool. If done, copy the files FWD.dll, FWD.ini, and MiniApp.exe to the application's directory. From now on, every time you launch the patched application, MiniApp.exe should come up at the same time, and close down when the application terminates. Crazy, isn't it? 6. Unicode Support WALK32 enables you to write applications with full Unicode support. It's a simple matter of assigning 0 or 1 to an assembly flag to switch from ANSI to Unicode. As I've explained in an earlier chapter, this flag is located in your program's header and is named UNICODE. Setting UNICODE to 1 has several implications. First, a couple of definitions in W32Main.inc involving characters are told to use WORDs for characters instead of BYTEs. Second, the startup code in W32Start.inc is changed to include an operating system platform check to test if the target system supports Unicode. Third, a flag is set in the object file to instruct the linker to use the Unicode variants for all APIs which come in both ANSI and Unicode flavours. For each and every API involving characters or strings, Win32 actually provides two names, which differ only in their last character. The API designed to process ANSI characters ends in "A", while the Unicode counterpart ends in "W". Consider a simple and popular function like MessageBox(). If you'd search the linker's import library W32Link.NT for this entry, you wouldn't find it. But you will find MessageBoxA() and MessageBoxW(). Of course, it wouldn't be very clever to code a direct call to one of these functions, although WALK32 allows you to. But doing so would make it almost impossible to switch from one character set to the other. Instead, you should simply call MessageBox() without any postfix character attached, although this function doesn't really exist. The linker is clever enough to match this name against the ANSI and Unicode variants, according to the status of the UNICODE flag. It's interesting to note that for some functions, a third flavour without any "A" or "W" extension is exported too. It's not entirely clear to me what they are made for. One could suspect that they're possibly able to detect what type of string is passed to them. But nope. Testing one of them revealed that it supported ANSI only, so it was not particularly useful. I guess they are there for internal purposes. When the linker has to choose between a function with postfix and one without, it always votes for the more specific one, i.e. the function including the postfix. This strategy has proved to be reliable so far. Besides the problem of selecting the right API from the status of the UNICODE switch, there's another much more troublesome problem: How do you define Unicode strings in your source file? As mentioned earlier, it's no good to use the BYTE instruction for strings except if you want to target non-Unicode platforms exclusively. Somehow, you'll have to use an instruction which is dependent on the UNICODE switch. Therefore, W32Main.inc defines the STRING macro, whose only purpose is to aid you in writing Unicode strings. Here's the declaration of the screen message from the Hello.asm sample source file: sHelloWorld: STRING The STRING macro is one of the ugliest parts of WALK32. I really don't like it. Although it does a great job in transparently defining strings in either ANSI or Unicode format, it suffers quite heavily from all sorts of escapes. As you see in the sample line above, the text to be included in the string is defined as an MASM text, hence it appears in angular brackets. Since MASM doesn't allow all sort of characters inside thes brackets and also defines some special macro control characters, I had to come up with an escape mechanism to allow the specifiation of any valid character as part of a string. As you might have guessed from our example, the escape character is the regular slash "/". I'd rather used the backslash instead, as C programmers do, but unfortunately, this character is MASM's line continuation character and can produce all kinds of strange effects if appearing inside a text definition. So the regular slash character mad it. The following table shows which characters are illegal in string definitions, and which escape sequence applies to them: Character Escape Sequence / // ! /: ` /- " /= \ /| % /# & /+ < /( > /) Besides these, there are some more escapes which were not defined by necessity, but for convenience, to allow almost C-like notation: Item Escape Sequence New Line /n Tabulator /t End of String /0 Binary 1 /1 Thus, if UNICODE is 0, the string definition evaluates to: sHelloWorld: BYTE 0Dh, 0Ah, "Hello World !!!", 0Dh, 0Ah, 0 If anyone has an idea of how to make ANSI/Unicode string definitions more intuitive, please tell me. As a supplement to the STRING macro, W32Main.inc also provides "pure" ANSI and Unicode string macros, named ASTRING and WSTRING, respectively. Their syntax is exactly the same as for the STRING macro. In fact, this macro is defined in terms of the other two: STRING macro _STRING_ ; if UNICODE WSTRING <_STRING_> else ASTRING <_STRING_> endif ; endm Defining static strings is not the only problem that you might meet while writing Unicode compatible code. Sometimes, you'll have to parse strings returned by the operating systems ot things like that. For these cases, W32Main.inc defines a special character data type named CHAR. This data type is a BYTE if the UNICODE flag is set to 0, and a WORD otherwise. To avoid conditional assembly throughout the source code, the number of bytes needed to represent a character is defined as constant CHAR_. When you are scanning strings, you should never use the inc and dec instructions to get to ne next character, like you certainly used to do under DOS and Windows 3.xx. Instead, add or subtract CHAR_ to/from the string pointer. To compare a character referenced by an index register against a constant value, you can also use the CHAR ptr operand prefix, which evaluates to BYTE ptr for ANSI and WORD ptr for Unicode. But what if you want to load a register from a memory location? How do you know whether you should use an eight- or 16-bit destination register? To address this problem, I've included a pile of pseudo-instruction macros in W32Main.inc, which are explained in the following table: Pseudo-Instruction Operation lodczx Load register EAX from memory location lodc Load register AL or AX from memory location , depending on the state of the Unicode switch stoc Store register AL or AX to memory location , depending on the state of the Unicode switch xchc Exchange register AL or AX with memory location , depending on the state of the Unicode switch cmpc Compare register AL or AX against memory location , depending on the state of the Unicode switch stoc Store register AL or AX to memory location , depending on the state of the Unicode switch addc Add memory location to register AL or AX, depending on the state of the Unicode switch subc Subtract memory location from register AL or AX, depending on the state of the Unicode switch incc Increment the BYTE or WORD memory location , depending on the state of the Unicode switch decc Decrement the BYTE or WORD memory location , depending on the state of the Unicode switch cc2bc Convert character count to byte count bc2cc Convert byte count to character count Get the picture? In short, most of these instructions use AL or AX to represent a character. You do not need to know which one is used, nor should you include any assumptions about it in your code. If you need to know an explicit register name, use the lodczx instruction, which zero-extends both ANSI and Unicode characters to 32 bits and copies them to the EAX register. The incc and decc instructions are quite useful if you are scanning strings using an index register. If ESI holds a pointer to a string, incc esi will always get you the next character in the string, whether you are working with ANSI or Unicode. decc esi does the same in reverse. The cc2bc and bc2cc instructions are convenient if you are working with Unicode APIs which receive character count parameters, while your program works with byte counts, as is usually the case with memory buffers allocated dynamically. cc2bc and bc2cc help you to switch easily from one representation to the other. On an ANSI platform, these macros simply evaluate to nothing, because in this special case, characters are represented by bytes, and hence the character count always equals the byte count. In a Unicode world, cc2bc becomes shl ,1, while bc2cc evalautes to shr ,1. 7. W32Link Internals The final chapter of this documentation is devoted to the linker of WALK32, which plays an important role in this programming package. W32Link is not a general purpose linker like Microsoft LINK. It is highly specialized and customized to work optimally with OMF files produced by Microsoft's MASM 6.xx assembler. This chapter informs you about the inner working of W32Link. If you are someone who just loves to look beneath the surface of things, this chapter is for you. You may also be interested in reading it if you wish to port WALK32 to another assembler platform. 7.1. Supported OMF Record Types W32Link supports only a limited number of the great wealth of OMF records defined in the long history of this object format. Many OMF record types have been introduced by vendors to support some features of their assembler, compiler, or linker products. Lots of them are typically used in the context of high level language compilers only. Since W32Link doesn't even attempt to support these products, it doesn't have to be able to handle these records. Record types supported by W32Link are: Record TypeID Description ------ ----------------------------------------------------------------------- THEADR Translator Header. This record contains the name of the original ASM source file. LHEADR Library Module Header. W32Link regards LHEADR and THEADR as synonymous. LNAMES List of Names. This records defines symbolic names which are referenced by records following later. LLNAMES Local Logical Names Definition. W32Link regards LNAMES and LLNAMES as synonymous. SEGDEF Segment Definition. This record supplies information about a segment defined in the source file. W32Link supports a single segment only. If it finds another segment definition, an error is reported. W32Link requires the segment to have the attributes BYTE PRIVATE USE32. EXTDEF External Names Definition. This record is a list of symbols imported from other modules. An object file can contain several records of this type. LEXTDEF Local External Names Definition. W32Link regards EXTDEF and LEXTDEF as synonymous. PUBDEF Public Names Definition. This record is a list of symbols exported from this module. An object file can contain several records of this type. Usually, only DLL executables include them. LPUBDEF Local Public Names Definition. W32Link regards PUBDEF and LPUBDEF as synonymous. LEDATA Logical Enumerated Data. The assembler places raw binary data into this type of record. An object file can contain several records of this type. LIDATA Logical Iterated Data. This type of record receives binary data which is defined using iterative or recursive methods, like MASM's dup operator. An object file can contain several records of this type. FIXUPP Fixup Record. This is one of the most complex OMF records. It instructs the linker where in a previous LEDATA or LIDATA record it should fix any references to relocatable objects. W32Link doesn't supports fixup locations other than 32-bit offsets. It also doesn't support fixup threads and fixups in LIDATA records. MODEND Module End. Besides marking the end of the object file, this record also defines the entry point of the module, as defined in the END directive in the source file. Besides these supported record type, W32Link accepts records of type COMENT, LINNUM, VERNUM, and VENDEXT, but ignores their contents. If W32Link outputs !!! RECORD IGNORED, you should carefully check if your executable file is OK. It's usually no problem to ignore these records, but, who knows... All remaining OMF record types - and there are quite a few left - are no-no for W32Link. If the object file contains one of them, W32Link will imediately abort the process of linking. However, it will save the work it has done so far, but the resulting file will be useless except for the purpose of curious investigation. 7.2. Section Descriptors W32Link introduces a somewhat idiosyncratic approach to the definition of sements and sections. To allow the highest degree of addressing flexibility, the WALK32 macros are designed to define a single 32-bit segment only. From the assembler's point of view, there are no sections altogether, only a single huge segment where all code and data goes into. So how can W32Link construct individual sections from this monolith? The answer is easy: It expects the segment to start with a 32-byte section descriptor block, which contains all information necessary to divide the segment into sections. The descriptors are inserted automatically by the WALK32 BeginImage macro. The structure of a section descriptor is straightforward: SECTION_DATA struct scFileBytes DWORD 0 ;number of file bytes scMemoryBytes DWORD 0 ;number of memory bytes scSectionName BYTE 8 dup (0) ;section name SECTION_DATA ends The first DWORD contains the number of bytes the section will need in the PE image file. This is the size of the initialized portion of the section. The second DWORD specifies the number of bytes the section occupies in memory after loading. This is the total section size, including the uninitialized portion, if any. These values are filled in by the BeginCode, EndCode, BeginData, EndData, EndIData, and EndUData macros. The remaining eight bytes indicate the name of the section as it will appear in the corresponding PE section header. If the name's length is less than eight characters, it must be null-terminated. This structure member is filled in by the BeginImage macro, depending on its parameters. W32Link needs two section descriptors of this kind, one for the code section, one for the data section. It cannot process any OMF data records or references to them, unless it has found both of them in its input stream. This is why they have to be the first binary data items in the module's segment. W32Link also insists on receiving both descriptors in a single LEDATA record. If, for some reason, the size of the very first LEDATA record in an object file is less than 32 bytes, W32Link will refuse to process this file. But since assemblers tend to pack as much data as possible into a data record, this should never be a problem. 7.2. Sections Emitted By W32Link The Microsoft PE/COFF specification [2] leaves a considerable amount of freedom to the linker. Only the general outline of a PE executable file is mandatory. It's up to the linker to name and arrange ist sections. Despite of this, W32Link tries to mimick the preferences of the Microsoft VC++ 2.2 kit as close as possible. It deviates only in the following two regards: - W32Link never emits a .bss section for uninitialized data. Instead, it reserves space for this data in the .data section, but doesn't include it in the image file. To account for this difference in size, it sets the size parameters in the .data section header appropriately. This approach is reported by Matt Pietrek [3] to be used by Borland's TLINK32, too. - W32Link doesn't emit separate .idata and .edata sections. Instead, it creates a .link section and writes the import data to the beginning and the export data to the end of this section. The idea to implement it this way struck me when I found out that several Windows NT 3.51 DLLs don't have a .edata section and export their functions from various other sections, like .text, or .rdata. These tricks do not affect the behaviour of the program, but they do save some space in the executable. Every section introduces some alignment overhead, because the size of a section is usually a multiple of 512 Bytes inside the file, and a multiple of 4096 in memory. (These constants are defined in W32Link.asm as FILE_PAGE, and MEMORY_PAGE, respectively.) Eliminating two sections saves about half a KB of disk space on the average. Not much, but worth considering. Any executable file produced by W32Link - whether application or DLL - always contains five sections: - .text is the code section; - .data is the data section; - .link contains import and export data; - .rsrc holds the application's default icon; - .reloc lists fixups needed if the image is not loaded to its preferred address. The preferred load address is set to 0040,0000h for executables, and 1000,0000h for DLLs. If you want to change these default setting, you'll have to edit W32Link.asm, setting the constants APP_BASE and DLL_BASE to the desired values. 7.3. Import Libraries W32Link maintains special import libraries, whose structure is totally different from the libraries coming with Microsoft development products. Import libraries are usually object files themselves, and it's the linker's duty to merge the application's object file with the import objects it requires. W32Link takes a completely different approach. The import libraries are simple ANSI text files, looking like ordinary Windows profiles (a.k.a. ".INI files"). This makes it easy to modify these files manually. But usually you won't edit them in this way. WALK32 includes the DOS utility PEexport to ease the process of generating import libraries. PEexport expects the name of a PE DLL file on the command line. If the specified file is a valid PE executable, PEexport will proceed parsing the file's export table, and writing its contents to the screen. If you redirect standard output to a file, you will get an import library for the DLL under examination. Of course, you can concatenate several PEexport files to yield a large general-purpose import library, using the ">>" redirection symbol. That's also the way WALK32's W32Link.NT and W32Link.95 files have been generated: C:\winnt35\system32>PEexport ADVAPI32.dll >F:\asm\Win32\W32Link.NT ________________________________________________________________ PEexport.ASM PE Export Section Dumper V1.01 01-29-1996 Sven B. Schreiber sbs@psbs.franken.de This is Public Domain Software ________________________________________________________________ Export section is '.text' PEexport: Normal end. C:\winnt35\system32>PEexport COMCTL32.dll >>F:\asm\Win32\W32Link.NT ________________________________________________________________ PEexport.ASM PE Export Section Dumper V1.01 01-29-1996 Sven B. Schreiber sbs@psbs.franken.de This is Public Domain Software ________________________________________________________________ Export section is '.rdata' PEexport: Normal end. C:\winnt35\system32>PEexport comdlg32.dll >>F:\asm\Win32\W32Link.NT ... Import libraries contain several sections, each referring to an individual DLL. The section header, enclosed in square brackets, defines the name of the DLL. Following the header is a sequence lines consisting of key/value pairs, where the "=" character separates the key from its value. Every pair defines one of the DLL's exported functions. The key (left from the "=" character) names the function, and the value (right from the "=" character) lists the hint and ordinal number of this function. Here's a short excerpt from W32Link.NT: [ADVAPI32.dll] AbortSystemShutdownA=0,1 AbortSystemShutdownW=1,2 AccessCheck=2,3 AccessCheckAndAuditAlarmA=3,4 AccessCheckAndAuditAlarmW=4,5 ... This section obviously lists the APIs of the ADVAPI32.dll. API function AccessCheck(), for instance, is identified by a hint of 2 and an ordinal number of 3. The relation of hints to ordinal numbers is easy: A hint is always zero based, while an ordinal number can be biased arbitrarily. But one thing is certain: For every DLL, there exists an integer N for which the equation ordinal number = hint + N holds for all functions of this DLL. This integer is called the bias of the DLL's ordinal numbers and is defined somewhere in the export section of the DLL. If an application imports functions from a DLL, the Windows loader has to look for the entry point of this function in the DLL's export section. The hint is an offset into the export table and is used by the loader as its first guess. If the requested function cannot be found at this offset, the loader searches the whole export table for a matching entry. That's why WALK32 comes with separate import libraries for Windows NT and Windows 95. The files do not only differ in some function names which are supported by one platform, but not by the other - they also show gigantic differences in the hints and ordinal numbers for functions which are supported by both of them. W32Link doesn't use the ordinal numbers in the import libraries. Only the hints are needed to optimize the import section of an executable. The ordinal numbers are included for future programming utilities which might want to know about them. 7.4. What About Resources? In discussions on CompuServe forums, I've been asked how WALK32 handles resources. Well, WALK32 has no resource compiler nor does any of the sample programs anything like a .rc file. Besides stuffing an icon resource into the target file's .rsrc section, W32Link is not interested at all in resources. Unless you are working on huge projects with multiple language versions, I don't see what advantages a resource file might have. Everything you put into a .rc file can be put into the .asm file as well. That's how WALK32 handles resources. 7.5. W32Link Remote Control While reading the sections about the assembly switches like CONSOLE, UNICODE, and DLL, and how they affect the type of executable being generated, you may have wondered how W32Link is able to read the status of these switches. After assembly, there are now switches anymore, because assembly switches are nothing but constant definitions, and constant references are resolved by the assembler, so they don't show up in the object file. If setting the DLL switch to 1 causes W32Link to mark the resulting executable as a DLL file, how did it get this information? The solution to this enigma is not very straightforward. The OMF object format does not define a standard way to communicate source file settings to the linker. There are some highly specialized OMF record types which have no other purpose than this, but they are very product specific and cannot be used to transport arbitrary information. But there is a very small loop-hole in the segment definition record, which can at least pass an arbitrary string to the linker: It's the segment class name. Since W32Link expects a single huge segment only, it has no use for segment classes, which are used to control the grouping of segments. Hence the class name of this single segment is open for other, more useful purposes. W32Link uses it together with the WALK32 BeginImage macro from the W32Main.inc file to pass data from the source file to the linker. Thus, the source file has some kind of "remote control" for the linker. This makes programming quite flexible, because you can set some linker settings in the source file instead of using confusing linker command line option switches. The class name of a segment is stored as part of an OMF SEGDEF record. If you are using the /d command line switch of W32Link to get a detailed linker log, you can see the internals of the SEGDEF record. For example, assembling and linking the TestDll.asm sample file, you get: 0000002A: [98] SEGDEF Segment Definition Symbol file "F:\ASM\WIN32\W32LINK.NT" will be loaded Symbol file "F:\ASM\WIN32\W32LINK.XXX" will be loaded Output File "F:\ASM\WIN32\TestDll.exe" will be created _main SEGMENT BYTE PRIVATE USE32 '1$3$21582$5789784' Segment size: 00000347 Bytes Obviously, this record triggers several actions, like selecting import libraries, and defining the name of the executable file to be created. All of this information and more is carried by the class name, which you see at the and of the fifth line, and which is 1$3$21582$5789784 in this example. The segment class name consist of a sequence of decimal numbers, separated by a dollar sign, and ranging from 0 up to 4,294,967,295. The first number can be either 0 or 1, and it tells W32Link whether it should use its built-in settings or let any settings defined in the source file override them instead. The WALK32 macros are set to always override W32Link's own settings, so you will always see a value of 1 here. The second number defines the executable's attributes. It is a concatenation of bit fields, having the following purposes: Bits 3..0 Subsystem ID (3 = Console, 2 = GUI) Bit 4 Unicode flag Bit 5 DLL flag Bits 31..6 Reserved (Set to 0) In our example, this parameter is 3, so we've got a console mode application (not a DLL), using ANSI characters. Peeking into the source file's header verifies this conclusions perfectly: CONSOLE equ 1 ;0 = gui, 1 = console UNICODE equ 0 ;0 = ansi, 1 = unicode DLL equ 0 ;0 = application, 1 = dll There are several other subsystem IDs defined in Microsoft's PE/COFF specification [2], but WALK32 doesn't support them since they don't have any practical importance for Windows NT or Windows 95 applications and DLLs. In the segment class name, two numbers are still remaining: 21582, and 5789784. Let's see what they look like in hexadecimal format: 21582 = 0000,554Eh 5789784 = 0058,5858h If you store these numbers in memory and let a debugger show you the ANSI representations of them, you will suddenly understand: They are actually strings! 0000,554Eh represents "NT", and 0058,5858h represents "XXX". It's the extensions of the import libraries, being encoded here! These two numbers result in emitting W32Link the following two lines to the log: Symbol file "F:\ASM\WIN32\W32LINK.NT" will be loaded Symbol file "F:\ASM\WIN32\W32LINK.XXX" will be loaded The next line from the log was reading: Output File "F:\ASM\WIN32\TestDll.exe" will be created How does W32Link know how the target file has to be named? Well that's simple: The portion of the path before the extension is copied from the object file path, and the extension is .exe if the DLL bit field is 0, and .dll if it's 1. If you want to change these default extensions, you have to edit the APP_EXTENSION and DLL_EXTENSION text constants in W32Link.asm. 8. References [1] Tool Interface Standards (TIS) Committee: TIS Portable Format Specification, Version 1.1. OMF: Relocatable Object Module Format. Available from Intel's FTP server: ftp://ftp.intel.com/pub/IAL/TIS/pf11h.zip [2] Microsoft Corporation (1994): Microsoft Portable Executable and Common Object File Format Specification 4.1. Microsoft Developer Network, Development Library CD, January 1996. [3] Matt Pietrek (1996): Windows 95 System Programming Secrets. Foster City (CA): IDG Books Worldwide, Inc. [4] Lou Grinzo (1996): Zen of Windows 95 Programming. Scottsdale (AZ): Coriolis Group Books. 03-17-1996 Sven B. Schreiber Since WALK32 is declared as "Public Domain Software", you are allowed to distribute it without any restrictions on any storage or communications media. But keep in mind that this does not entitle you to receive any fees for the software. It's OK to charge your customers for the costs of the distribution media, but the WALK32 software itself always has to be free of charge. Trademarks All brand names and product names used in this documentation are trademarks, registered trademarks, or trade.