Copyright: Copyright © The MASM32 Project 1998-2005


MASM32 Macro High Level Reference

Productivity Enhancement Techniques

Introduction

The Scope Of MASM32

Short History Of MASM

MASM and other assemblers

Fundamental Architecture

Why is MASM different ?

Basic Architecture Of MASM32

High And Low Level Code

Basic Code Layout

Creating Procedures

Calling Procedures

Using Libraries

Control Flow Techniques

Using the .IF Syntax

Using switch Block Syntax

Using switch$ Syntax

Loop Code Methods

Using .WHILE Syntax

Using .REPEAT Syntax

Macro Categories

Code Calling Macros

Error Reporting

File IO Macros

Memory Allocation

Console Mode Macros

String Macros

File Dialogs

Path And Directory Macros

Text Toolbars

Performance Macros

Memory Line IO Macros

Environment

Macro Library

C Runtime Conversions

Specialised Prototypes

Pseudo Mnemonics


The Scope Of MASM32

The MASM32 Project is a multifaceted project that is directed at experienced programmers so they can learn or write 32 bit MASM code for the Windows Operating System and it addresses a wide range of capacities, some of which are peculiar to MASM.

Mnemonic Programming

At the lowest level there is direct mnemonic coding which is the core of assembler programming. This is followed by reusable procedures in application software that include both mnemonic coding and higher level API programming.

API Programming

Using the Windows API functions is a necessary part of programming 32 bit assembler for the Windows platform. The MASM32 project has been a leading advocate of writing high level code such as API functions like high level code so that code output is both reliable and time effective in development terms.

Library Design

Library design by converting procedures into seperate modules is an important component of MASM32 and it provides its own STATIC library both for its intrinsic use as well as being a model for library construction for custom software.

Language Design Using Macros

Macro design is the next step for assembler programmers using MASM as it involves a good understanding of all of the other capacities that MASM has. The macro system documented in this help file and generally in MASM32 is not only intrinsically useful in its own right but a viable example of how a programmer can shift from being a language user to a language designer and by doing so can become free of the assumptions of other languages and their limitations.


Short History Of MASM

MASM is a very well understood development tool that has been under constant development since 1981 and it is upgraded by Microsoft on a needs basis for hardware changes and operating system version differences. It became a radical tool in about 1990 with the introduction of MASM 6.00 in that it deviated from accepted assembler norms of that period by adding pseudo high level capacity for many common tasks and substantially increased the power and control of its macro capacity so that assembler programmers could construct complex higher level code in creative and original ways.

MASM was nearly lost to public usage in the middle 90s as programming fashion changed with the introduction of Windows 95 but Microsoft kept developing MASM for its own internal needs as a major operating system vendor where critical core components of the Windows operating systems were written in MASM. In this period a lot of folklore developed about what MASM was supposed to NOT be able to do which followed from other assemblers attempting to take over the market position that MASM had held for many years.

A couple of years later as the patches became available to change the last release version of MASM into a true 32 bit executable and MASM also became available through the device development kits for Windows 95 and 98. The licencing of the device development kits was flexible enough to allow MASM to be used for application development in Windows which in turn made the MASM32 Project possible.

A substantial number of people who had used MASM for years were not willing to abandon this type of capacity and actively contributed to the MASM32 Project so that the knowledge they had accumulated over years of commercial experience would be passed on to another generation who would learn and wield a tool of this power again.

MASM continues to be an esoteric tool owned and developed by a major operating system vendor for their own internal use in tuning performance critical areas of operating systems and other related commercial applications. It has never been a tool for the faint of heart and requires a lot of knowledge to use correctly but for those who make the effort and learn a tool of this power, they will have performance at their fingertips that few would understand or believe.

In more recent times as the interest in true low level programming has continued to rise, Microsoft have re-released MASM for their Visual C line of products so that programmers developing code for 32 bit Windows had the capacity to tune performance critical areas of their code.


MASM and other assemblers

MASM has been in constant development since 1981 and has been an important tool in the development of Microsoft operating systems from that period to current versions of 32 bit Windows.

Over a period of nearly 25 years there have been very good assemblers written and many of them have been written by highly skilled programmers or teams of programmers but MASM has simply outlasted them all as it is a necessary tool for operating system development by a major operating system vendor.

With a near 25 year development cycle and a matching corporate development budget MASM has accumulated a very powerful capacity that extends to its enormously powerful parsing capacity, object module compatibility with the major Microsoft development languages, a pre-processor that has never been fully exploited, built in debugging support and it has done all of this while preserving the historical Intel syntax in what is now a superset of the original Intel syntax.

With such a long development cycle, 16 bit code written 15 years ago can still be built using late versions of MASM as the format has remained compatible for longer than this time frame and this for among other reasons is why MASM has such good support as it is used and well understood by a very large number of experienced programmers.

While there is no doubt that there are a number of well written alternative assemblers that are capable of producing high quality binary code for the 32 bit Windows platform, the differences in capacity that follow from a 25 year long corporate budget and development cycle will not be bridged in the remaining life of the 32 bit versions of Windows.

The distinction is that basically the alternative assemblers are well suited for hobbyist programmers who are interested in the actual development of the assembler they are using while MASM is a tool for professional programmers who need to be able to deliver commercial quality code within a reasonable timeframe that is compatible with other code that they may be developing using the Microsoft C compiler.


Why is MASM different ?

As an assembler MASM differs from almost all compilers in that it does not have any built in runtime library at all. MASM comes with the capacity to assembler mnemonics to binary code, load and use external library modules  and a number of built in macros for handling some higher level tasks but it will not produce any runtime code by itself at all.

This difference needs to be understood, especially by a programmer who is learning to write assembler for while programmers learning and writing high level languages can start using the built in runtime capacity to perform the basic tasks, there are no default runtime functions or capacities in MASM whatsoever.

When a programmer starts from scratch using an assembler as powerful as MASM, they are trying to do something that is very difficult as they are trying to learn a language by writing their own runtime library which is almost exclusively an advanced task for programmers who have extensive experience with that language.

It is by no means a disadvantage that MASM does not have a default runtime library as it has the capacity to dial up a runtime library that performs the basic tasks that are commonly associated with a programming language.

The MASM32 Project provides a viable and well tested runtime library that is a combination of both static library procedures and dedicated macros that provide much of the capacity of higher level languages yet without the assumptions of many high level languages.

When you use the MASM32 runtime library, you are using hand written assembler code and hand written macros that are designed to deliver very good performance in all but the most demanding tasks that are better suited for manual coding directly in assembler.

Apart from the intrinsic use of the MASM32 runtime library, the available capacity has another task in mind, by making a large number of functions and capacities available, the programmer learning assembler programming with MASM32 can get a reasonable amount of common tasks up and running reliably and easily enough so that they can start to learn direct assembler coding more quickly.

Rather than being bogged down with the complexities of writing a runtime library for a very powerful assembler like MASM just to perform basic functions, using the runtime library in MASM32 frees the programmer to get their code up and running so they can lay out and design their own pure assembler code for performance critical tasks.

Programmers who try and take on a bare assembler without a runtime library available have a very high failure rate and end up writing no assembler code where the programmer who uses the available capacity to get their code up and running more reliably and more quickly have a far higher success rate and learn to write the more complex direct assembler code within a reasonable time frame.

This situation is something like learning how to swim. If a non-swimmer is dumped out in the deep water, they have a very high chance of drowning before they ever learn how to swim where the non-swimmer who start off from the shore in shallower water will in most instances both survive and learn how to swim.

To enable the MASM32 runtime library, you add the following line at the very start of your application.

    include \masm32\include\masm32rt.inc

It is recommended after you have gained enough experience that you learn how to dial up your own runtime library as it gives you more control and a lot more choices. The following section will show you how to more accurately dial up what you require for your application with basic startup code, include files and library files.

Basic Architecture Of MASM32


Basic Framework Of MASM32
Basic Architecture Of MASM32

The MASM32 Project has developed an integrated system over time to make direct assembler programming accessible in a fast and reliable manner where tasks within its target range can be developed within a reasonable time frame and yield very high performance where needed.

The basic MASM32 architecture is an integrated system of include files, import libraries, macros  and a predefined framework to reliably build and share code. The assembler (ML.EXE) can be used to build files in many different ways but unless there is some form of minimum standardisation, no-one can build another persons code and this was the case years ago before comprehensive include files were made available with the MASM32 project.

The method used to build assembler files with the MASM32 Project makes no claim of being better or faster or any other attribute than any other technique, what it does claim is that it is reliable and very wide spread and this has facilitated a massive range of compatible and sharable code that is available to other MASM32 programmers. What follows is a general description of the layout of a MASM32 assembler source file and why it is done this way.

Leading Code For Any Application

      .486                      ; create 32 bit code

      .model flat, stdcall      ; 32 bit memory model

      option casemap :none      ; case sensitive

This lead code establishes the minimum processor needed to run the application, the memory model required for 32 bit Windows applications, the calling convention for all of the default procedures and sets the assembler to work with case sensitive names as is required with 32 bit Windows.

Hard Coded paths

The MASM32 include files and libraries in the MASM32 project require a hard coded path that is relative to the root directory of the logical drive that MASM32 is installed on. While there are numerous methods of building assembler files that are different and work just as well, the MASM32 Project has used this method to avoid a common problem with programmers who have other programming environments set up on their development computer and that is ending up with the wrong version of LINK or calling the incorrect library or include files. Common problems are an assembler file that normally builds at 2k ending up at 16k because of different file alignment as default with the linker that is run. The hard coded paths solve problems of this type.

Matching Include And Library Files

MASM32 uses its own set of include files and libraries to handle the very large range of system based function that are normally called the Application Programmers Interface or API functions and it is done in a systematic manner. The leading include file in the WINDOWS.INC file that contains a very large number of constants and structures used by Windows functions. It should always be placed first before any other include file.

      include \masm32\include\windows.inc

Libraries and include files in MASM32 are matched to the system DLLs that contain the API functions and are designed to be used in their matching "include" and "includelib" formats so that both the prototype and library function is available.

For each library you require, you also add the matching include file so you get the following code layout.

;     include files

;     ~~~~~~~~~~~~~

      include \masm32\include\windows.inc

      include \masm32\include\masm32.inc

      include \masm32\include\gdi32.inc

      include \masm32\include\user32.inc

      include \masm32\include\kernel32.inc

      include \masm32\include\Comctl32.inc

      include \masm32\include\comdlg32.inc

      include \masm32\include\shell32.inc

      include \masm32\include\oleaut32.inc

      include \masm32\macros\macros.asm

;     libraries

;     ~~~~~~~~~

      includelib \masm32\lib\masm32.lib

      includelib \masm32\lib\gdi32.lib

      includelib \masm32\lib\user32.lib

      includelib \masm32\lib\kernel32.lib

      includelib \masm32\lib\Comctl32.lib

      includelib \masm32\lib\comdlg32.lib

      includelib \masm32\lib\shell32.lib

      includelib \masm32\lib\oleaut32.lib

These include file and libraries handle a very large range of the Windows API functions but there are many other libraries and include files available if you need additional capacity build into the operating system.

The MASM32 Macro File

The above list includes a number of files that are not API function prototypes and libraries. The following line ensures that the dedicated MACRO file for MASM32 is loaded so that the extended functionality is available.

      include \masm32\macros\macros.asm

There is also the MASM32 library that contains the runtime functions for both the macros that use library functions and it also makes all of the library available to the programmer using MASM32. The MASM32 library is what is called a STATIC library in that it has the actual code for each library model contained within it where the libraries that contain the API function calls are called IMPORT libraries which contain import data from system DLL files.

MASM can routinely handle STATIC and IMPORT libraries and the matching include files with the above syntax.

You would normally write your local procedure prototypes AFTER the system functions and libraries so that they have access to the API and STATIC library functions that you require for your own code.

Writing The DATA Section(s)

The initialised and uninitialised DATA sections should be added next, the order does not matter. Some people coming from different assembler have had problems with this but it is the normal layout for an assembler file written in MASM.

    .data?        ; uninitialised data section

      align 4

      blankhandle dd (?)

      txtbuffer1 db 128 dup (?)

      txtbuffer2 db 128 dup (?)

    .data         ; initialised data section

      align 4

      preset_number dd 128

      array dd txtbuffer1,txtbuffer2

      AppName db "My Application",0

Start And End Of Code

Following the data section(s) you must tell the assembler where the actual code for your application starts and finishes. This is done with a single label AFTER the data section(s) to indicate where the code starts and after the code is finished, you use a matching statement to tell the assembler that the code has ended. Convention has it that the label is usually named "start" but it can in fact be any label name that is unique within the application. The syntax is as follows.

    start:

    ; All of your code goes between the two statements.

    end start

This is in fact much easier to manage than it may look as MASM supports nested "include" statements so you can put all of the prototypes and libraries in a seperate include file and if it suits your design, you can include pieces of code in external files directly into the code that you are writing. This is a commonly used technique in MASM32 examples and it is done with the different code generators so that the code is a lot easier to read by reducing the amount of clutter to get to the code you are working on.


High And Low Level Code

At its baseline as a bare tool, MASM (ML.EXE) is capable of converting assembler source code written with mnemonics (instructions) into opcodes that are stored in an object module. This is in fact the normal method used with Microsoft languages and it is why Microsoft C/C++ and MASM can be used together because they produce compatible object modules. MASM differs from compilers in that it has no intrinsic runtime library capacity built into it but the advantage of this is that it can use different runtime libraries without being restricted to only one.

Much of the difference between older 16 bit code and modern 32 bit code written in MASM is the sheer volume of high level code that is available through the architecture of the operating system. The availability of something like 12 thousand high level functions through the Windows API (Application Programmers Interface) added this enormous capacity to MASM at no effective cost apart from knowing how to call and use the functions. Without losing any of its low level capacity, the MASM programmer has direct access to the same functions that the operating system uses for most of its basic functionality.

The distinction in code types is still in fact there with assembler code written in MASM, on one hand you have the full range of processor instructions coupled with the operating system architecture of addressing, memory model and the like while on the other hand you have what is effectively a massive very high level library available to perform a very large range of different functions.

There is another factor that also changes the equation. Since about 1990 MASM has been able to do more than just crunch assembler mnemonics. From Verson 6.00 it made a radical departure from assemblers of that time and added the capacity to emulate a number of higher level language capacities with C style runtime comparisons, automated and type checked function calls and a number of loop control methods to make hack style code easier, faster and more reliable to write.

The type of code that such high level emulation produces is reasonably minimalist but contrary to popular opinion in some circles, it has passed the test of time and performs well on late model 32 bit hardware. We do hear from time to time about some new "gee whizz" features of the latest compiler on the block but where MASM has passed the test of time with its high level emulation, much of the current compiler design based on middle 90s RISC theory has not and it shows when you decompile the code.

The other factor rarely mentioned in such discussions is it is normal in MASM to code any speed critical algorithms directly in assembler code where high level users are stuck with high level code unless they can cobble together some inline assembler to get them out of trouble. MASM is in fact able to help in situations where high level code is not fast enough as it creates object modules that are compatible with Microsoft C compilers and it is one of the target tasks of MASM to replace compiler generated code where it is unable to produce the performance that is required.

Many have attempted to make MASM an appendage to another particular language yet MASM does not need any other language to make it work and as long as you have the rest of the tools required like a linker and resource compiler, it has an effective target range of applications where it is almost impossible to compete with in its own terms of size, speed and functionality.

As MASM programmers will from time to time get dragged into "holy war" arguments with supporters of other languages flogging their own set of assumptions about how others should write software in the same manner as they do, it is worth knowing at least a few of the assumptions. While you may end up tired of hearing some script kiddie regurgitating nonsense from a tutor who knew no better, it generally meant that their tutors learnt programming in the early to middle 90s and missed out on assembler programming with the results that they have passed on their ignorance to the next generation.

Assumption : Assembler is only useful for harware access.

Answer : This indicates how far out of date the assumption is, most hardware access like device drivers are written in C or C++. This assumption comes from the days when compilers had problems with accessing DOS 16 bit interrupts which necessitated cobbling together some assembler code to handle the system calls.

Assumption : Assembler is only useful for writing "inner loops".

Answer : It happens to write outer loops just as well and you can add to that nested loops, interdependent loops, crossfire loops, multiple entry and exit loops and many other freestyle variations that will give most compilers nightmares. With compilers that attempt some form of optimisation, it is bad technology to mix manual assembler code with high level code as the optimisers have enough problems of their own without adding to it. Effectively assembler code used with other high level languages should be built into a seperate module and linked into the project.

It is not by accident that much of the compiler literature over the last 10 to 15 years advises against freestyle loop code design that is common in assembler programming. It is generally related to the inability of the compilers to properly handle code of that complexity without making a mess of it. Structure loop coding is like a straight jacket in comparison to the freedom of design that assembler programmers have.

Assumption : Good (Basic/C/Pascal/Fortran/Cobol etc ..) is better than bad assembler.

Answer : Swap "assembler" with any other language mentioned in the list and the statement is still true.

Assumption : Development in assembler is too slow, you should use a low level compiler instead.

Answer 1 : Development with low level compilers is too slow, you should use a high level compiler instead.

Answer 2 : Development with high level compilers is too slow, you should use a scripting language instead.

Answer 3 : Development in a scripting language is too difficult for non-technical users, you should hire an expert like ME to write your code for you. For some outrageously large sum I can produce garbage for you that might do the job.

Answer 4 :  etc .....

Assumption : Assembler is too hard to write.

Answer : For the person saying it, it is probably true. Advise them to limit themselves to something simpler like server side scripting.

Assembler has survived as a viable esoteric tool because of what it can do, not what inexperienced people think it cannot do or should not be able to do. Fashion has it moments but function endures because its serves more people over the long term in ways that are not served by the whims of fashion.


ME

HEY MAN, I am a genuine Bona Fide certified ADA[tm] programmer with experience in VB2, Fortran 77 and direct experience programming Zilog Z80 series processors in HEX. Not only do I have this experience but I am a proven master at MIRC scripting. If I am not too tired after my shift at the local diner from midnight to dawn and have a bit of time before I start my day job pumping gas at the local gas station, I can finish this task for you in the next month or two and it won't cost you any extra above my quotation.


Creating Procedures

The syntax for creating procedure in MASM is reasonably simple and it is the basis for writing reliable code that is type checked by the assembler. It has a two part logic in that you tell the assembler what the procedure format is by writing a prototype.

    item_name PROTO :DWORD,:DWORD

Once you have the prototype written you write the procedure to match that argument count and sizes in the prototype.

    item_name proc arg1:DWORD,arg2:DWORD

The component parts of the above line are,

    1. item_name   The procedure name

    2. proc        Tell the assembler that this is a procedure

    3. arg1        The name used for the parameter

    4. :DWORD      The size of the data passed as a parameter

The name of each parameter and its coresponding data size is what is usually referred to as the argument list. As long as it matches the prototype that you have written, you set the number of items in the argument list to suit the design of the procedure you require.

The following line tells the assembler where the end of the procedure is.

    item_name endp

Between the two line that respectively contain the PROC keyword and the ENDP keyword is where the executable code for the procedure is written.

With this format of procedure MASM creates what is called a stack frame which is very similar to many high level languages and it allows the procedure to be used in much the same way as a high level language. You can safely write recursive code and similar techniques that directly use the stack as memory storage.

At a lower level MASM has a notation to create procedures that do not have a stack frame and this is usually when designing speed critical code where an extra register is required. There is also a minor speed gain in reduced stack overhead but this is only evident in very small procedures where it is viable to simply inline the code which is faster again.


Calling Procedures

The MASM32 Project has been a leading advocate of dealing with high level code in a high level manner and it is one of the factors in why assembler is enjoying new found popularity. MASM has its own built in "invoke" syntax that made emulating high level code far easier and in conjunction with the macro system that MASM supports, there are a range of different methods available to assembler programmers using the standard MASM32 macros.

Using a simple example of a system MessageBox() the progression is as follows.

    invoke MessageBox,hWnd,ADDR Message,ADDR TitleTxt,MB_OK

With this standardised MASM technique, you have high level format complete with type checking and you obtain the return value by reading the EAX register.

MASM macros have the capacity to return a value so that they can be used with a similar notation to a function in a high level langauge in the EAX register. One of the standard MASM32 macros is FUNC() which is used as follows.

    mov retval, FUNC(MessageBox,hWnd,ADDR Message,ADDR TitleTxt,MB_OK)

Effectively the complete function call can be treated as the EAX register and this allows the function call to be directly embedded into assembler notation.

    cmp FUNC(MessageBox,hWnd,ADDR Message,ADDR TitleTxt,MB_OK), IDYES

    je label

Here you have both high level convenience and reliability and low level compact notation and clarity.

It is common practice to be able to insert quoted text into function calls and MASM32 has a number of ways of doing this to acheive clean and compact notation. There are two forms available, one that is used in the STATEMENT position and the other that is used in ther FUNCTION position.

The STATEMENT position.

    fn MessageBox,hWnd,"Hi, I am a Message Box","Greetings",MB_OK

The FUNCTION position.

    mov retval, rv(MessageBox,hWnd,"Message Text for MessageBox","Title Text",MB_OK)

As before with the statement position macro, you obtain the return value in the EAX register where with the function position macro, you treat it as the EAX register and can directly use the function call in assembler notation.

Limitation

The macro parsing engine in MASM does not allow information enclosed in brackets (....) to be split across multiple lines so this will limit some uses of the function position macros. In these situations, you would use the statement position macro instead and manually obtain the return value in EAX. Note that when you write multiline macro code that you must use the line continuation indicator "\" for each split line so the assembler knows that the next line is part of the function call.

Code Efficiency

Each example using the simple MessageBox call all produce the same code. As manual assembler code, it is written as follows.

    push MB_OK

    push OFFSET TitleText

    push OFFSET MessageText

    push hWnd

    call MessageBox

    mov retval, eax


Using Libraries

A library procedure differs from a procedure you have written in your own code only in that it is already assembled into a working binary file and it is common to group many seperate procedures together in a single library. This type of library is usually called a STATIC library as it differs from a library that has EXPORT data for DLLs in that the code is actually contained in the library.

In most instances each static library has a matching include file that contains a prototype for each procedure in the library and all you need to do to be able to use any of the procedures in the library is to use the "include" directive to include the prototypes in the include file and use the "includelib" directive so that the linker can find the library.

      include \masm32\include\masm32.inc

      includelib \masm32\lib\masm32.lib

The standard library for the MASM32 project is an example of a static library that is made available by using these directives and it is a reasonable model for constructing custom libraries that are tuned to the individual programmers requirement.

MASM is of course able to create its own library modules that are then linked into static libraries and if they are written correctly, when you call a procedure from the static library, the only code added to your program is the code that is in the seperate module and any other code that the module depends on. The linker automatically calculates what is called "dependency" of modules and you only ever get the code your application requires.


Using the .IF Syntax

MASM has the capacity to handle both simple and complex block comparison operations in a clear and easy to use syntax that reasonably well emulates higher level code. The .IF block syntax can be safely nested in the normal high level manner and is subject to the normal rules of comparison using the CMP instruction.

Simple

    .if variable == value

      ; do something here

    .endif

This type of code can be extended in two directions, it can handle more complex comparisons on a single line and it can perform multiple comparisons sequentially as is common in high level languages.

Sequential Evaluation

    .if variable == value

      ; do something

    .elseif variable != another_value

      ; do someting else

    .else

      ; otherwise

    .endif

You can repeatedly use the .elseif operator to perform additional sequential comparisons but you may only use one .else operator which must be the last option before the .endif operator.

Using the C comparison runtime operators below, more complex tasks can be performed and in a notation that will be familiar to many programmers.

Complex Evaluation

    .if variable > 50 && variable < 100

      ; number is within range

    .else

      ; number is out of range

    .endif

The code created by the .IF technique is competitive in quality terms to code created by the better end of compilers but note that it is a specific technique that may not be flexible enough for low level code design within an algorithm where register choice and instruction order become critical to the algorithm's performance.

C comparison run-time operators

     Operator      Meaning

     ==            Equal

     !=            Not equal

     >             Greater than

     >=            Greater than or equal to

     <             Less than

     <=            Less than or equal to

     &             Bit test (format: expression & bitnumber)

     !             Logical NOT

     &&            Logical AND

     ||            Logical OR

     CARRY?        Carry bit set

     OVERFLOW?     Overflow bit set

     PARITY?       Parity bit set

     SIGN?         Sign bit set

     ZERO?         Zero bit set


Using Switch Block Syntax

A common task in 32 bit Windows programming is comparing a single variable against a sequence of values and acting on a comparison match. A very common example is the operating system defined method of evaluating messages sent to a WndProc style procedure by the operating system.

This task can be done in a reliable manner using the .IF block syntax that MASM handles in a native manner but there is now a viable alternative written by Greg Falen that encapsulated the .IF block syntax in a very convenient manner to emulate a high level language switch block. This technique can be nested in the normal manner and has proven to be very reliable while making clear and easily readable code. The comparison rules are the same as the CMP instruction.

    switch variable

      case value1             ; there must be at least one (1) case statement

        ; do something here

      case value2             ; optional extra case statement(s)

        ; do something else

      default                 ; optional default processing

        ; do any default processing

    endsw

Below is WndProc code copied directly from a working application.

    switch uMsg

      case WM_INITDIALOG

        invoke SendMessage,hWin,WM_SETICON,1,

                           FUNC(LoadIcon,NULL,IDI_ASTERISK)

        m2m hWnd, hWin

        return 1

      case WM_COMMAND

        switch wParam

          case IDOK

            fn MessageBox,hWnd,"You clicked OK","Hi",

                          MB_OK or MB_ICONINFORMATION

          case IDCANCEL

            jmp quit_dialog

        endsw

      case WM_CLOSE

        quit_dialog:

         invoke EndDialog,hWin,0

    endsw

Some will try and tell you that this form of code is not efficient but the disassembly below demonstrates otherwise. You can manually code sequential choices in a different manner that is slightly fast in some contexts but there is little to gain and you throw away coding speed, reliability and ease of code maintainance.

    mov eax, [ebp+0Ch]

    cmp eax, 110h

    jnz lbl0

    push 7F04h

    push 0

    call fn_00401256

    push eax

    push 1

    push 80h

    push DWORD PTR [ebp+8]

    call fn_00401262

    push DWORD PTR [ebp+8]

    pop [403050h]

    mov eax, 1

    leave

    ret 10h

    jmp lbl4

  lbl0:

    cmp eax, 111h

    jnz lbl2

    mov eax, [ebp+10h]

    cmp eax, 1

    jnz lbl1

    push 40h

    push 403042h

    push 403033h

    push DWORD PTR [403050h]

    call fn_0040125C

    jmp lbl4

  lbl1:

    cmp eax, 2

    jnz lbl4

    jmp lbl3

    jmp lbl4

  lbl2:

    cmp eax, 10h

    jnz lbl4

  lbl3:

    push 0

    push DWORD PTR [ebp+8]

    call fn_00401250

  lbl4:


Using switch$ Syntax

Some higher level languages have techniques for performing sequential comparison of string data and while it is not a built in mechanism in MASM, the macro system is powerful enough to develop this form of high level emulation in conjunction with a static library module and a number of existing macros contained in the MASM32 Project.

Michael Webster has created a macro system that performs sequential comparison of quoted string data against a string address by using a simulated stack macro design by Greg Falen, components of the block .IF syntax, a static library module from the MASM32 library and a number of macros from the MASM32 Project.

Two versions are presented in the MASM32 macro system, the case SENSITIVE version that is documented below and a case INSENSITIVE version that differs only in that the notation has a trailing "i" before the "$" symbol. Note that you should not mix the set of evaluation operators with either macro as each is a seperate system. The syntax rules for each system work identically and the order of else$ and elsei$ must be the same if used.

Case SENSITIVE	Case INSENSITIVE
switch$	switchi$
case$	casei$
else$	elsei$
endsw$	endswi$

The case sensitive version uses a direct character match algorithm for word comparison where the case insensitive version uses a high speed table driven algorithm.

    switch$ string_address

      case$ "quoted_text1"   ; there must be at least 1 case$ statement

        ; perform action here

        ; if word maches

      case$ "quoted_text2"   ; optional additional case$ statement

        ; perform action here

        ; if word maches

      else$                  ; optional default processing

        ; perform default action

    endsw$

Note that the else$ statement if used must be the last option before the endsw$ statement.

Here is a simple working example.

    switch$ lpstr

      case$ "word1"

        fn MessageBox,hWnd,"word1","Title",MB_OK

      case$ "word2"

        fn MessageBox,hWnd,"word2","Title",MB_OK

      case$ "word3"

        fn MessageBox,hWnd,"word3","Title",MB_OK

      case$ "word4"

        fn MessageBox,hWnd,"word4","Title",MB_OK

      else$

        fn MessageBox,hWnd,"default","Title",MB_OK

    endsw$

To demonstrate the actual efficiency of the macro code, this form was used to make the disassembly more readable.

    switch$ lpstr

      case$ "word1"

        nop

      case$ "word2"

        nop

      case$ "word3"

        nop

      case$ "word4"

        nop

      else$

        nop

    endsw$

Below is the disassembly which demonstrates that the generated code is hard to improve on with manual coding of a sequential comparison style of algorithm.

    push 403096h

    push DWORD PTR [ebp+8]

    call fn_00401620

    or eax, eax

    jz lbl0

    nop

    jmp lbl4

  lbl0:

    push 40309Ch

    push DWORD PTR [ebp+8]

    call fn_00401620

    or eax, eax

    jz lbl1

    nop

    jmp lbl4

  lbl1:

    push 4030A2h

    push DWORD PTR [ebp+8]

    call fn_00401620

    or eax, eax

    jz lbl2

    nop

    jmp lbl4

  lbl2:

    push 4030A8h

    push DWORD PTR [ebp+8]

    call fn_00401620

    or eax, eax

    jz lbl3

    nop

    jmp lbl4

  lbl3:

    nop

  lbl4:


Using .WHILE Syntax

.while syntax is a clear code method for less than highly critical loop code where readability and ease of maintainance are the main considerations.

    .while eax > 0

      sub eax, 1

    .endw

The above .while loop produces the following code which shows the logic where the test is performed first by jumping to the end of the loop. This is the opposite of a .REPEAT / .UNTIL  loop  which is dealt with in the following topic. The code jumps to the end of the loop to performs the test and loops back and re-runs the loop if the exit condition is not met.

    jmp lbl1

  lbl0:

    sub eax, 1

  lbl1:

    cmp eax, 0

    ja lbl0

A loop of this type is very easy to write manually but the .while syntax is capable of handling multiple test conditions using the C comparison runtime operators which give it the capacity to write very compact code that is complex in nature while being reasonably easy to modify and maintain.


Using .REPEAT Syntax

.repeat syntax is a clear coding method for less than highly critical loop code where readability and ease of maintainance are the main considerations.

    .repeat

      sub eax, 1

    .until eax < 1

The above .repeat loop produces the following code which shows the logic where the test is performed after the code in the loop has been run This is the opposite of a .WHILE loop  which is dealt with in the preceding topic.

  lbl0:

    sub eax, 1

    cmp eax, 1

    jnb lbl0

A loop of this type is very easy to write manually but the .repeat syntax is capable of handling multiple test conditions using the C comparison runtime operators which give it the capacity to write very compact code that is complex in nature while being reasonably easy to modify and maintain.


Code Calling Macros

This set of macros utilise the built in MASM invoke syntax to retain the reliability of type checking while delivering higher level readability and faster development time with clear coding.

Call a procedure like a high level function

FUNC	Get a return value from a procedure

Quoted string support

fn	High level style procedure call
rv	Get a return value from a procedure

Quoted string with C style string escape support

fnc	High level style procedure call
rvc	Get a return value from a procedure


FUNC

mov rval, FUNC(AnyProcedureCall,args ......)

Description

Call any procedure with a return value.

Parameters

Any procedure call complete with arguments that returns a value.

Return Value

The return value from a procedure call which is normally the EAX register.

Comments

This macro utilises the "invoke" syntax and the EXITM <eax> macro operator to return a value which can be either assigned to a variable with MOV or directly used in the same manner as the EAX register.

    mov retval, FUNC(APIcall,arguments ...)

    test FUNC(YourProc,arguments ...), eax

    cmp FUNC(AnyProc,arguments ...), 1234

    add edx, FUNC(APIcall,arguments ...)


fn

fn SetWindowText,hWnd,"Main Window"

Description

Call any prototyped procedure with quoted string support.

Parameters

Any callable procedure with its argument list

Return Value

If the procedure has a return value, it will be in the EAX register.

Comments

This macro is generally used when a return value is not required. Because it does not use the EXITM operator, its arguments ae not enclosed in brackets so it can be split across multiple lines using the "\" line continuator.


rv

mov retval, rv(MessageBox,hWnd,"Message Text","Title",MB_YESNO)

Description

Call any prototyped procedure that has a return value.

Parameters

Any prototyped procedure complete with its argument list.

Return Value

The return value is in the EAX register.

Comments

When writing code with this macro, the complete macro call can be treated like the EAX register and it can be used directly in normal assembler statements as the EAX register.

test rv(Procedure,Arglist ...), eax

mov revval, rv(Procedure,Arglist ...)


fnc

fnc MessageBox,hWnd,"C style escape\nsequences\n","Example".MB_OK

Description

Call any prototyped procedure with the capacity to use quoted text with C style escape characters.

Parameters

Any prototyped procedure call and its associated argument list.

Return Value

If the procedure has a return value, it will be in the EAX register.

Comments

This macro differs from the "fn" macro only in that it supports additional formatting using C style escape sequences. The reference to this capacity the cfm$ macro. This macro is generally used when a return value is not required. Because it does not use the EXITM operator, its arguments ae not enclosed in brackets so it can be split across multiple lines using the "\" line continuator.


rvc

test rvc(YourProc,"\tC Style\n\tEscape Sequences"), eax

Description

Call any prototyped procedure that has a return value.

Parameters

Any prototyped procedure complete with its argument list.

Return Value

The return value is in the EAX register.

Comments

This macro differs from the "rv" macro only in that it supports additional formatting using C style escape sequences. The reference to this capacity the cfm$ macro.


Debug Macros

LastError$	Return the error string from GetLastError()


LastError$
LastError$()

Description

The LastError$() macro is used to return the operating system defined error string associated with the last call to the GetLastError() API function.

Parameters

This macro has no parameters and should be used with empty brackets only.

Return Value

The return value is the address of the error string associated with the last call to the GetLastError() API function.

Comments

The return string can be used in either console mode with the "print" macro or in normal GUI mode in any context where a string can be displayed. The buffer used for the error string is overwritten with any subsequent calls to te macro. The buffer is 1024 bytes in length.


File IO Macros

Load and Save Complete File

InputFile	Read entire file into a memory buffer
OutputFile	Write a memory buffer to disk

Low Level File Operations

fcreate	Create a new file and return its handle
fdelete	Delete a disk file
fopen	Open an existing file and return its handle
fclose	Close an open file handle
fread	Read data from an open file
fwrite	Write data to an open file
fprint	Write line of text to an open file
fprintc	Write text to open file with C style formatting
fseek	Set the current position in the open file
fseteof	Set the end of the open file to the current location
fsize	Return the size in bytes of the open file


InputFile

mov hMem, InputFile(lpfile)

Description

InputFile loads a complete disk file into a memory buffer and return the address of that buffer..

Parameters

lpfile  A zero terminated string with the name of the file to open. It can also be quoted text.

Return Value

The return value is the address of the buffer that the file has been loaded into. The length of the disk file is returned in the ECX register.

Comments

You should test for the existence of the file before you try and open it. When you have finished with the data from the file, you should deallocate the memory for the file by using the macro free.

Example

    mov hMem, InputFile("myfile.ext")

    mov flen, ecx

  ; your code

    free hMem


OutputFile

mov rval, OutputFile(lpfile,lpmem,flen)

Description

OutputFile writes the contents of a memory buffer to a disk file.

Parameters

1. lpfile  The address of a zero terminated string that is the name of the file to write. This may also be a quoted text.

2. lpmem  The address of the memory buffer to write to disk.

3. flen  The number of bytes to write to disk.

Return Value

The return value is the number of bytes written to disk or zero if there is an error.

Comments

This macro will overwrite an existing file. As the return value is written to the EAX register, the complete macro can be written directly into assembler code as if it is EAX.

Example

      test OutputFile("newfile.ext",pbuffer,flen), eax

      jnz @F

      ; set error message or condition here

    @@:


fcreate

mov hFile, fcreate(filename)

Description

Create a new file with read / write access.

Parameters

1. filename  A zero terminated string. This can be a quoted text.

Return Value

The return value is the handle of the new file.

Comments

The new file has read and write access. If there is an error, you should test the return value with GetLastError().

Example

    mov hFile, fcreate("mynewfile.ext")


fdelete

cmp fdelete(lpString), 0

Description

Delete a disk file by its name.

Parameters

1. lpString  A zero terminated string that contains the file name to delete from disk. This may also be a quoted literal string.

Return Value

The return vaue is non-zero if the function succeeds, zero if it fails.

Comments

This macro is a wrapper for the Windows API, DeleteFile(). It has been added for consistency with the names of the other file IO macros. It has been written in function format to facilitate testing the return value.

Example

        cmp fdelete("tempfile.ext"), 0

        jne @F

        print "Hmmmmm, this operation failed",13,10

      @@:


fopen

mov hFile, fopen(filename)

Description

Open an existing file for read / write access.

Parameters

1. filename  A zero terminated string. This can be a quoted text.

Return Value

The handle of the file.

Comments

If the function fails, you should test the return value with GetLastError().

Example

    mov hFile, fopen("existingfile.ext")


fclose

fclose hFile

Description

Close an already open file handle.

Parameters

1.  hFile  The open file handle.

Return Value

Function has succeeded if the return value in EAX is not zero.

Comments

Errors should be tested with GetLastError().

Example

    fclose hFile


fread

mov bWritten, fread(hFile,lpMemory,bytecount)

Description

Read data from an open file.

Parameters

1. hFile  The open file handle.

2. buffer  The address of the buffer to write the data to.

3. bcnt  The number of bytes to read.

Return Value

The return value is the number of bytes read from the file.

Comments

The buffer must be large enough to handle the number of bytes read into it. Normally the buffer will be allocated to at least that size or larger.

Example

    mov bWritten, fread(hFile,lpMemory,bytecount)


fwrite

mov bwritten, fwrite(hFile,buffer,bcnt)

Description

Write data to an open file.

Parameters

1. hFile  The open file handle.

2. buffer  The address of the buffer to write the data to.

3. bcnt  The number of bytes to read.

Return Value

The number of bytes written to the file or zero on failure.

Comments

Use GetLastError() if the operation fails.

Example

mov bwritten, fwrite(hFile,lpmemory,bytecount)


fprint

fprint hFile,lptext

Description

Write a line of zero terminated text to a file.

Parameters

1. hFile  The handle of the file to write to.

2. text  The zero terminated text. This may be quoted text.

Return Value

There is no return value.

Comments

This macro always appends a CRLF pair to the end of the line. It is designed for operation that write lines of text sequentially.

Example

    fprint hFile,"This is a line of text"


fprintc

fprintc hFile,"Hi Guys\n"

Description

Write zero terminated text with C style formatting to an open file.

Parameters

1. hFile The handle of the open file

2. lpText The address of a zero terminated string. This may also be quoted text that contains C style escape sequences as provided for in the cfm$ macro.

Return Value

This macro does not return a value.

Comments

This macro allows the programmer to write text to an open file using the text formatting capacity of the cfm$ macro which allows C style formatting of string data. It is useful where you need to sequentially append text to a file without necessarily placing it on a new line.


fseek

mov cloc, fseek(hFile,distance,location)

Description

Set the location of the file pointer.

Parameters

1. hFile  The handle of the open file.

2. distance  The number of bytes to move.

3. location  The reference point to move from.

Return Value

The return value is the current location of the file pointer within the open file.

Comments

The distance argument can be specified as either a positive or a negative number which will be respectively added or subtracted from the position set in the location argument.

The location argument specifies what location in the file to use. There is a choice of 3 locations which are standard Windows equates.

    FILE_BEGIN  The beginning of the file.

    FILE_CURRENT  The current location of the file pointer.

    FILE_END  The end of the file

If for example you wished to set the file pointer at 100 bytes past the beginning of the file, you would use the equate FILE_BEGIN for the location argument and set the distance argument at 100.

Note that this macro will only work on files within the DWORD length. For files larger than 4 gigabytes use the SetFilePointer() API function.

Example

    mov cloc, fseek(hFile,100,FILE_BEGIN)


fseteof

fseteof hFile

Description

Truncate a file at the current file pointer location.

Parameters

1. hFile  The handle of the file to truncate.

Return Value

The macro has no return value.

Comments

The required location can either be set by read / write operations or it can be set using the fseek macro. It is normally used to truncate an existing file.

Example

    fseteof hFile


fsize

mov flen, fsize(hFile)

Description

Returns the size in bytes of an open file.

Parameters

1.  hFile  The handle of the file.

Return Value

The length of the file in bytes.

Comments

The macro is limited to DWORD range. If you need to work on a file larger than 4 gigabytes, use the GetFileSize() API function.


Memory Allocation

MASM32 preprocessor macros now support 3 seperate types of memory allocation, fixed memory using the GlobalAlloc() API function, OLE string memory for dedicated string work and Heap memory using the HeapAlloc() function. While there is a large degree of overlap with memory allocation functions, GlobalAlloc() appears to be faster on large single block allocations, HeapAlloc() shows some speed advantages in repeated small allocations and preallocated OLE string pool memory is tuned at the OS level for string manipulation.

Fixed Memory

alloc	Allocate a fixed block of memory
free	Free a fixed block of memory

String Memory

alloc$	Allocate OLE string memory
free$	Free OLE string memory

Heap Memory

halloc	Allocate Heap Memory
hsize	Return the size of allocate heap memory
hfree	Free Heap Memory

NOTE : There are other more specialised forms of memory allocation available using the Windows API functions.


alloc

mov hMem, alloc(byte_count)

Description

Allocate a block of fixed memory.

Parameters

1. byte_count  The number of bytes of fixed memory to allocate.

Return Value

The starting address of the block of memory.

Comments

Memory allocate with the alloc macro should be freed with the free macro.

Example

mov hMem, alloc(16384)


free

free hMem

Description

Frees memory allocated with the alloc macro.

Parameters

1. hMem  The handle or starting address of the memory allocated with alloc.

Return Value

There is no return value.

Comments

The macro uses the GlobalFree API function and can be used to free any memory allocated using GlobalAlloc().


alloc$

mov hString, alloc$(byte_count)

Description

Allocate a block of OLE string memory.

Parameters

1. byte_count  The count in bytes of the size of the memory to allocate.

Return Value

The return value is the start address of the allocated memory. Properly it is a string handle.

Comments

Memory allocated with the alloc$ macro should be released using the free$ macro.


free$

free$ hString

Description

Frees the memory allocate using the alloc$ macro.

Parameters

1. hString  The handle of the string memory.

Return Value

The macro does not have a return value.

Comments

This macro can be used to free any string memory allocated using the OLE string library.


halloc

mov hMem, halloc(1024)

Description

Allocate heap memory.

Parameters

1. bytecount The count in bytes of memory to allocate.

Return Value

The return value is the address of the allocated memory. Use the LastError$ macro if you have memory allocation failures.

Comments

On recent Windows versions HeapAlloc() appears to be faster than other memory allocation techniques when used with repeated small allocations of memory. The return value should be stored in a variable and when the memory is no longer required, it should be deallocated using the hfree macro within the same scope as it was allocated under.


hsize

mov memsize, hsize(hMem)

Description

Return the size in bytes of the memory allocated using the haloc() macro.

Parameters

1. hMem The value returned by the halloc() macro.

Return Value

The size of the allocated memory block in BYTES or 0FFFFFFFFh if it fails.

Comments

The HeapSize() API used by this macro does not use the GetLastError() API.


hfree

hfree hMem

Description

Free memory allocated by the halloc() macro.

Parameters

1. hMem The starting address returned by the halloc() macro.

Return Value

The macro does not set a return value but any errors can be tested using the LastError$ macro. A non zero result in EAX indicates an error.

Comments

When memory is allocated using a HeapAlloc() function, it is worth testing if the memory is properly deallocated otherwise it runs the risk in high allocation count situations of running out of memory if there is an error.


Console Mode Macros

The console mode macros are designed to make console input and output fast and convenient. Some of them work in very similar way to historical DOS and BASIC commands.

cls	Clear the console screen
ccout	Display text at the console using C style formatting
GetConsoleCaption$	Return the address of the console title bar
exit	Close application
getkey	Wait for key press and return it in EAX
inkey	Wait for user key input
input	Get text from console input
loc	Set the text cursor position
print	Print text to the console
SetConsoleCaption	Set the title bar text of the current console


cls

cls

Description

This is a very simple macro that works the same way as the DOS command.


ccout

ccout "\tC style string formatting\n\tin MASM"

Description

Display text at the console with C style string formatting enabled.

Parameters

1. Quoted text which may contain escape sequences.

Return Value

There is no return value.

Comments

This macro uses the string formatting capacity of the cfm$ macro.


GetConsoleCaption$

mov pTitle, GetConsoleCaption$()

Description

Return the address of the text from the title bar of the console.

Parameters

This macro has no parameters. It should use empty brackets () to satisfy the macro parser in MASM.

Return Value

The return value is the address of a 260 byte buffer that holds the zero terminated text from the title bar of the console.

Comments

The 260 byte buffer is reused with a subsequent call to the macro so if the string data needs to be preserved it should be copied to a memory buffer. This can be done with the "szCopy" procedure in the MASM32 library or the "cst" macro in the MASM32 macro file.

Example :

    mov pCaption, GetConsoleCaption$()

    invoke szCopy,pCaption,pBuffer

      or

    cst pBuffer, pCaption


inkey

inkey

inkey NULL

inkey "Press a key to continue ..."

Description

Suspend a console application and wait until the user presses a key.

Parameters

Optional parameters.

1. No arg will display the default message at the current cursor location.

2. NULL displays the cursor with no text.

3. A user defined zero terminated string. This can be quoted text.

Return Value

The scancode for the key pressed is returned in EAX

Comments

The cursor is displayed at the current cursor position which is at the right end of any text that is used as a prompt. The macro will exit on any normal key but does not respond to control keys or F keys.

The default display text if no parameter is used is "Press any key to continue ..."

You can manually code this capacity with the following.

    print "Press a key to perform the next action"

    call wait_key

    print chr$(13,10)


print

print lptext

print lptext,13,10

print "Quoted Text"

print "Quoted Text",13,10

Description

The print macro displays text at the current text cursor location on the console.

Parameters

1. lptext  The address of a zero terminated string. This can also be quoted text.

2. Optional extra quoted text and / or byte notation.

Return Value

There is no return value.

Comments

The print macro is very flexible in its capacity. It will display either a zero terminated string that is specified as an address as its 1st argument or it will display the contents of quoted text. The optional 2nd argument can contain any combination of quoted text and byte specifiers in the same manner as data specified in the initialised data section.

Example

    mov DWORD PTR number, 1234

    print "The result is ",62,32

    print str$(number)," bytes",13,10

Console output => The result is > 1234 bytes


SetConsoleCaption

SetConsoleCaption MACRO title_text:VARARG

Description

Set the text on the title bar of the current console.

Parameters

1. title_text A zero terminated string containing the rquired txt to display. This may be quoted text.

Return Value

The macro does not return a value.

Comments

This macro can be used in conjunction with GetConsoleCaption$  to save and restore the previous title bar text


input

mov lpstring, input()

mov lpstring, input("Enter number here ",62," ")

Description

The input macro receives text input from the console and returns the address of that text. The text cursor or the prompt and text cursor is set at the current location on the console.

Parameters

1. Optional quoted text and character notation.

Return Value

The return value is the address of the text typed at the console.

Comments

By convention, STDIN appends a carriage return / line feed pair of characters to the end of the text typed at the console. The input macro calls a seperate function that removes the CRLF pair so that the input text can be directly converted to numeric data without any problems. The current version has a 128 character limit.


loc

loc xc, yc

Description

The loc macro sets the location of the text cursor on the console.

Parameters

1.  xc  The X co-ordinate.

2.  yc  The Y co-ordinate.

Return Value

There is no return value.

Comments

The X and Y co-ordinates are both zero based.


exit

exit

Description

A simple macro to terminate a program. This macro is not exclusively a console macro and can be used to terminate any program.


getkey

getkey

Description

Wait for a key press and return the value in EAX.

Parameters

There are no parameters for this macro.

Return Value

The return value is written in the low byte of the EAX register. EAX is zero extended so that it can be tested either as a DWORD or as a BYTE.

Comments

This macro can be used in the construction of simple menus which depend on which key a user presses. Create a "switch" block that tests the value of EAX or AL and branch to whatever code you require from that switch block.


String Macros

MASM32 already has a well tested library that has a reasonable number of procedures for manipulating string data. These can be used with similar code design to C style programming and the basic technology is the same, zero terminated strings that work from the string data address.

The macro system in MASM is powerful enough to reuse these C style procedures and present them in a format that has much of the speed and convenience of development that basic string functions have if the fundamental differences are understood. It is important to understand what the differences are so please read the overview of how these string macros work.

String Macro Overview

Allocating String Buffers

Note that the string macros are used in the function position which means they can be assigned to variables or used within more complex macros if the differences are properly understood.

Normal string macros

add$	Concantenate two strings
append$	Append a zero terminated string and return next position
cat$	Concantenate multiple strings
chr$	Return the address of combined quoted strings and bytes
cmd$	Return the address of the command line
ptr$	Cast a buffer to a pointer
len	Return the length of a zero terminated string
istring	Find a substring in a source string
lcase$	Convert string to lower case
ucase$	Convert string to upper case
left$	Read characters from left side of string
right$	Read characters from right side of string
ltrim$	Trim the left side of a string of spaces
rtrim$	Trim the right side of a string of spaces
trim$	Trim both ends of a string of spaces
remove$	Remove sub string from source string
rev$	Reverse a string
cfm$	Format a quoted string with C style escapes

String to integer conversions

hval	Convert hex string to 32 bit integer
sval	Convert signed string to 32 bit integer
uval	Convert unsigned string to 32 bit integer

Integer to string conversions

ustr$	Convert unsigned 32 bit integer to a zero terminated string
sstr$	Convert signed 32 bit integer to a zero terminated string
uhex$	Convert unsigned 32 bit integer to a zero terminated hex string

Also check the macros that use the C Runtime Library as they support a wider range of data types.


String Macro Overview

Basic as a language has one of the easiest techniques available for manipulating string data but it does so by adding a particular style of overhead where strings are repeatedly created and copied during the normal style of string operation. Effectively a basic program needs to have the code for a string engine in it and this is a different technology to the zero terminated string techniques used in both C and the MASM32 library procedures.

The string macros in MASM32 differ to this extent that they DO NOT create new strings but modify the existing ones so that when an operation is performed on a zero terminated string, the ORIGINAL data has been changed.

With basic, if you write a simple line of code,

    b$ = LCASE$(a$)

You have created a new string b$ and the original a$ has not been modified.

With the following example in MASM code, variables like a$, b$ are simple DWORD variables declared on the stack with the LOCAL keyword.

    LOCAL a$  :DWORD

    LOCAL b$  :DWORD

With MASM32, if you write the line of code,

    mov b$, lcase$(a$)

The string data at the address a$ is modified and the address is returned to the variable b$.

The capacity to nest function style macros is very useful and it allows code like,

    mov a$, ucase$(ltrim$(a$))

This technique and similar more complex examples is possible because the return value from each macro is used as a parameter for the next lower macro. At a single sequence of nesting this technique works fine but problems can arise if there are multiple sequences of nesting as the second one may overwrite the value for the first if the value was returned in the EAX register. this can show in simple code that tried to use the same sequence of string functions twice.

    invoke function,nested1(a$),nested2(b$),number

If there is a risk of this problem, it is easy enough to solved by assigning the two nested macros to a variable and then using the variable in the following procedure call.

    mov var1, nested(a$)

    mov var2, nested(b$)

    invoke function,var1,var2,number

Managing String Buffers

String buffer size must be managed in the same manner as normal zero terminated strings. Where basic automatically allocates the buffer for each string, with zero terminated strings the programmer must ensure that the buffer size is large enough to receive the finished result. With multiple string operations, it is often viable to set two or more work buffers and keep reusing them. In some instances you need to reset the string length to zero by writing an ascii zero byte at the beginning of the string.

If for example you allocated a buffer of 1024 bytes and have data written to it so it needs to be cleared after it has been used so it can be classed as an empty string again which some macros require, all you do is cast the buffer back to a pointer again using the ptr$() macro and the buffer can be reused again.

    LOCAL buffer[1024]:BYTE     ; allocate 1024 bytes on the stack

    mov lpstring, ptr$(buffer)  ; cast the buffer to a pointer

    ; perform the required actions using lpstring.

    mov lpstring, ptr$(buffer)  ; clear the buffer again

CODE EFFICIENCY

By retaining the technology of zero terminated strings, this macro system deliver code quality that is hard to improve on manually yet it has much of the development speed and ease of writing basic code once you understand how it works.

    mov pstr, right$(ucase$(left$(lcase$(pstr),9)),6)

This nested set of macros disassembles to the following assembler code.

DISASSEMBLY

004014FC FFB514FFFFFF           push    dword ptr [ebp-0ECh]

00401502 E8C9030000             call    fn_004018D0

00401507 6A09                   push    9

00401509 50                     push    eax

0040150A 50                     push    eax

0040150B E840030000             call    fn_00401850

00401510 50                     push    eax

00401511 E86A040000             call    fn_00401980

00401516 6A06                   push    6

00401518 50                     push    eax

00401519 50                     push    eax

0040151A E8E1030000             call    fn_00401900

0040151F 898510FFFFFF           mov     [ebp-0F0h],eax


Allocating String Buffers

Some of the string macros require a buffer that is larger than the source string. In simple situations you can use memory allocated on the stack with the syntax,

    LOCAL pstr  :DWORD

    LOCAL buffer[260]:BYTE

This method is easy to use and you do not have to deallocate the buffer as it is done on procedure exit. To make the buffer easier to use, it is useful to cast the address to a pointer variable which is done as follows.

    mov pstr, ptr$(buffer)

When you need larger buffers it is viable to allocate dynamic memory which can be done with either of the following two memory allocation macros.

    mov pstr, alloc(16384)

or

    mov pstr, alloc$(16384)

The value returned by these memory allocation macro is already the pointer to the memory. Make sure that when you have finished using the allocated memory that you free the memory before you end the procedure.

If you have used alloc, free it with,

    free pstr

If you have used alloc$, free it with,

    free$ pstr

Where it is possible, load the source string directly into the buffer from the method that you obtain the string. If this is not possible, you can copy the data into the buffer or alternatively, set the start of the buffer to a zero byte and append the source you require to the zero length string in the bufer.


add$

mov pstr, add$(lpBuffer,lpString)

Description

Concantenate two strings.

Parameters

1. lpBuffer  The address of the source string.

2.  lpString  The address of the string to append to the source.

Return Value

The return value is the address of the source string.

Comments

The buffer containing the source string must be large enough to hold the additional string data appened to it.


append$

mov location, append$(string,buffer,location)

Description

Append a string to another string and return the location of the next character after the appended string.

Parameters

1. string  The address of the source string to append the following string to.

2. buffer  The string to append to the source string.

3. location  The address within the source string to write the second string to.

Return Value

The return value is the next character past the end of the appended string data so it can be used as the location parameter for a subsequent call to append$.

Comments

The source string must be placed in a buffer that is large enough to hold all of the string data appended to it. The advantage of this technique is that by returning the end location the following call does not need to rescan the length of the string to find the starting position to write to.


cat$

mov lpbuffer, cat$(lpBuffer,[variable number of strings])

Description

Concantenate multiple string into a single buffer.

Parameters

1. lpBuffer The address of the source buffer to append other strings to.

2. A variable number of string addresses to append to the source buffer.

Return Value

The return value is the address of the source buffer that can be assigned back to itself or to another pointer (string handle).

Comments

This is a very flexible and useful macro that can handle multiple strings in different formats. If the source buffer does not already have string data in it, the first byte MUST be set to a zero byte so it is seen as a zero length string. The source string buffer must be large enough to receive all of the string data appended to it.

Example

    mov lpbuffer, cat$(lpbuffer,"The result is ",ustr$(count)," bytes in length")


chr$

mov pstr, chr$("quoted text",13,10)

Description

Write mixed text and byte data to the .DATA section and return the starting address of that data.

Parameters

A variable number of items of quoted text and byte data.

Return Value

The return value is the starting address of the data written to the .DATA section.

Comments

This macro writes directly to the .DATA section and it can use either quoted text or byte data in the normal format for the .DATA section. It can be used in places where the internal MASM reserve characters interfere with quoted text or byte data being used.

fn MessageBox,hWnd,chr$("one",13,10,"two",13,10"three",13,10),lpTitle,MB_OK


cmd$

mov pCmd, cmd$(argument_number)

Description

Return the address of the nominated command line argument.

Parameters

1. argument_number  The nominated command line argument number.

Return Value

The return value is the address of the nominated command line argument.

Comments

the argument_number parameter is a 1 based index that specificed which command line argument to return. The following values are returned in the ECX register to indicate success or failure of the macro call.

      1 = successful operation

      2 = no argument exists at specified arg number

      3 = non matching quotation marks

      4 = empty quotation marks


ptr$

mov lpstring, ptr$(buffer)

Description

Cast the address of a local variable to a pointer variable.

Parameters

1. buffer  A buffer allocated on the stack.

Return Value

The address of the buffer.

Comments

This macro loads the address of a stack variable into a register and sets the first two (2) bytes to zero. Assigning the return value to a variable makes the value into a pointer which is generally easier to use that repeatedly using LEA to obtain the address.


len

mov flen, len(lpString)

Description

Scan the length of a zero terminated string and return its length.

Parameters

1. lpString  The string address to get the length for.

Return Value

The return value is the length of the string not counting the zero terminator.


istring

mov pSubStr, istring(spos, source, sub_string)

Description

Search for a substring in a source string.

Parameters

1. spos  The start position in the source string to search from.

2. source  The source string to search.

3. sub_string  The sub string to search for.

Return Value

The return value is the 1 based character index for the substring or ZERO if the substring is not found.

Comments

The spos parameter is a 1 based index so the initial search position should be set to 1 or higher.


lcase$

mov lpString, lcase$(lpString)

Description

Convert a zero terminated string to lower case characters.

Parameters

1. lpString  The address of the zero terminated string to convert.

Return Value

The return value is the address of the converted string.

Comments

The original string data is changed by this macro.


ucase$

mov lpString, ucase$(lpString)

Description

Convert a zero terminated string to upper case characters.

Parameters

1. lpString  The address of the zero terminated string to convert.

Return Value

The return value is the address of the converted string.

Comments

The original string data is changed by this macro.


left$

mov lpString, left$(lpString,slen)

Description

Read a set number of characters from the left side of a string and overwrite the original buffer with the results.

Parameters

1. lpString  The address of the string to read from.

2. slen  The number of charactes to read from the string.

Return Value

The return value is the address of the source string.

Comments

The original string data is overwritten by the result of this macro.


right$

mov lpString, right$(lpString,slen)

Description

Read a set number of characters from the right side of a string and overwrite the original buffer with the results.

Parameters

1. lpString  The address of the string to read from.

2. slen  The number of charactes to read from the string.

Return Value

The return value is the address of the source string.

Comments

The original string data is overwritten by the result of this macro.


ltrim$

 mov lpString, ltrim$(lpString)

Description

Remove spaces and tabs from the left side of a string.

Parameters

1. lpString  The address of the string to trim.

Return Value

The return value is the address of the source string.

Comments

The result overwrites the original string.


rtrim$

mov lpString, rtrim$(lpString)

Description

Remove spaces and tabs from the right side of a string.

Parameters

1. lpString  The address of the string to trim.

Return Value

The return value is the address of the source string.

Comments

The result overwrites the original string.


trim$

mov lpString, trim$(lpString)

Description

Remove tabs and spaces from both ends of a string

Parameters

1. lpString  The address of the source string.

Return Value

The return value is the address of the source string.

Comments

The result overwrites the original string.


remove$

mov lpSource, remove$(lpSource,lpSubString)

Description

Remove any instance of a substring from a source string.

Parameters

1. lpSource  The address of the source string.

2. lpSubString  The address of the sub string.

Return Value

The return value is the address of the source string.

Comments

The result overwrites the original string.


rev$

mov lpString, rev$(lpString)

Description

Reverse a zero terminated string.

Parameters

1. lpString The address of the string to reverse.

Return Value

The return value is the address of the reversed string.

Comments

The result overwrites the original string.


hval

mov value, hval(lpString)

Description

Convert a hexadecimal string to a 32 bit integer.

Parameters

1. lpString  The address of the hexadecimal format zero terminated string.

Return Value

The return value is the 32 bit integer value.

Comments

The hexadecimal string should be in correct HEX notation and should not have any leading or trailing notation.

0xED230AB7 should have the 0x stripped from it.

0ED230AB7h should have the trailing h stripped from it.


sval

mov value, sval(lpString)

Description

Convert a signed string value to a 32 bit integer.

Parameters

1. lpString  The address of the signed numerical string value

Return Value

The return value is the 32 bit integer value.

Comments

The macro will recognise either leading sign notation for the negative values or no leading notation for a positive value.


uval

mov value, uval(lpString)

Description

Convert an unsigned decimal value to a 32 bit integer.

Parameters

1. lpString  The address of the string to convert.

Return Value

The return value is the 32 bit integer value.

Comments

A signed value should not be assed to this macro as the results will be unreliable, use sval for signed values.


ustr$

mov lpResult, ustr$(uInteger)

Description

Convert an unsigned 32 bit integer to a zero terminated string.

Parameters

1. uInteger  An unsigned 32 bit integer.

Return Value

The return value is the address of the zero terminated string that holds the result as an OFFSET.

Comments

Each macro call in the assembler source code provides a 20 byte long buffer in the data section to hold the results of the conversion.


sstr$

mov lpResult, sstr$(Integer)

Description

Convert an signed 32 bit integer to a zero terminated string.

Parameters

1. Integer  An unsigned 32 bit integer.

Return Value

The return value is the address of the zero terminated string that holds the result as an OFFSET.

Comments

Each macro call in the assembler source code provides a 20 byte long buffer in the data section to hold the results of the conversion.


uhex$

mov lpHex, uhex$(integer)

Description

Convert an unsigned 32 bit integer to a hexadecimal zero terminated string.

Parameters

1. integer  An unsigned 32 bit integer.

Return Value

The return value is the address of the zero terminated string that holds the result as an OFFSET.

Comments

Each macro call in the assembler source code provides a 20 byte long buffer in the data section to hold the results of the conversion.


File Dialogs

OpenFileDlg	Display an Open File dialog and return the selected file name.
SaveFileDlg	Display an Save File dialog and return the selected file name.


OpenFileDlg

mov fname, OpenFileDlg(hWin,hInstance,lpTitle,lpPattern)

Description

Display an Open File common dialog box.

Parameters

1. hWin  The parent window handle.

2. hInstance  The instance handle of the applciation.

3. lpTitle  The address of a zero terminates string.

4. lpPattern  The address of a zero terminated string that specifies the file pattern(s) to use.

Return Value

The return value is the address of a 260 byte buffer that holds the selected file name or if the 1st byte in the buffer is zero, the use cancelled the selection.

Comments

The format of the file pattern requires name and pattern pairs zero seperated and double zero terminated. To create the zero terminated string, you can set the information in the .DATA section as follows.

.data

    pattern db "Text Files",0,"*txt",0,"Ini Files",0,"*ini",0,0

.code

You can also use a pseudo mnemonic from the MASM32 macro file as follows.

    sas pattern, "All files",0,"*.*",0

Example

    mov fname, OpenFileDlg(hWnd,hInstance,"Open a New File",ADDR pattern)

    cmp BYTE PTR [eax], 0               ; test if 1st byte is zero

    jne @F                              ; jump forward if its not

    ; write cancel processing code here

  @@:


SaveFileDlg

mov fname, SaveFileDlg(hWin,hInstance,lpTitle,lpPattern)

Description

Display a Save File common dialog box.

Parameters

1. hWin  The parent window handle.

2. hInstance  The instance handle of the applciation.

3. lpTitle  The address of a zero terminates string.

4. lpPattern  The address of a zero terminated string that specifies the file pattern(s) to use.

Return Value

The return value is the address of a 260 byte buffer that holds the selected file name or if the 1st byte in the buffer is zero, the use cancelled the selection.

Comments

The format of the file pattern requires name and pattern pairs zero seperated and double zero terminated. To create the zero terminated string, you can set the information in the .DATA section as follows.

.data

    pattern db "Text Files",0,"*txt",0,"Ini Files",0,"*ini",0,0

.code

You can also use a pseudo mnemonic from the MASM32 macro file as follows.

    sas pattern, "All files",0,"*.*",0

Example

    mov fname, SaveFileDlg(hWnd,hInstance,"Save File As ....",ADDR pattern)

    cmp BYTE PTR [eax], 0               ; test if 1st byte is zero

    jne @F                              ; jump forward if its not

    ; write cancel processing code here

  @@:


Path And Directory Macros

Path macros

pth$	Return path of current application
CurDir$	Return the path of the current directory
SysDir$	Return the path of the system directory
WinDir$	Return the path of the Windows directory

Directory macros

chdir	Change Directory
mkdir	Make Directory
rmdir	Remove Directory
rndir	Rename Directory


pth$

mov cpth, pth$()

Description

Return the address of the current application path.

Parameters

This macro has no parameters.

Return Value

The address of a 260 byte buffer that contains the address of the current application.

Comments

The empty brackets are required by MASM's internal parser.


CurDir$

mov cdir, CurDir$()

Description

Return the address of the current directory for the calling application.

Parameters

This macro has no parameters.

Return Value

The address of a 260 byte buffer that contains the address of the current directory.

Comments

The empty brackets are required by MASM's internal parser.


SysDir$

mov sdir, SysDir$()

Description

Return the address of the Windows system directory.

Parameters

This macro has no parameters.

Return Value

The address of a 260 byte buffer that contains the address of the Windows system directory.

Comments

The empty brackets are required by MASM's internal parser.


WinDir$

mov wdir, WinDir$()

Description

Return the address of the Windows directory.

Parameters

This macro has no parameters.

Return Value

The address of a 260 byte buffer that contains the address of the Windows directory.

Comments

The empty brackets are required by MASM's internal parser.


chdir

chdir lpstring

Description

Change from the current directory to another directory.

Parameters

1. lpstring  The address of a zero terminated string containing the path of the directory to change to. This may also be a quoted text.

Return Value

This macro does not have a return value.

Comments

Use GetLastError() if you have any errors with the macro call.


mkdir

mkdir lpstring

Description

Create a new directory.

Parameters

1. lpstring  The address of a zero terminated string that contains the name and path of the new directory. This may also be a quoted text.

Return Value

This macro does not have a return value.

Comments

Use GetLastError() if you have any errors with the macro call.


rmdir

rmdir lpstring

Description

Remove an existing directory.

Parameters

1. lpstring  The address of a zero terminated string that contains the name and path of the directory to remove. This may also be a quoted text.

Return Value

This macro does not have a return value.

Comments

Use GetLastError() if you have any errors with the macro call. This macro will fail if the directory has either files or subdirectories in it.


rndir

rndir oldname, newname

Description

Rename an existing directory.

Parameters

1. oldname The address of the old name.

2. newname The address of the new name.

Return Value

This macro does not have a return value.

Comments

Use GetLastError() if you have any errors with the macro call.


Text Toolbars

Description

The text toolbar macros are designed to be used as a complete system for producing the minimalist interface demands of a flat toolbar with text only buttons. They are designed to be placed in a procedure by themselves with no other code as the macros handle both local variables and the return value code.

There are two variations of the toolbar code, one WITH and one WITHOUT a seperating line at the top.

TB_BEGIN hParent     ; without top seperator

TB_BEGIND hParent    ; with top seperator

The hParent argument for both is the handle of the parent window to place the toolbar on. This is normally the parameter passed to the procedure that the toolbar macro code must be placed in.

There are two options to add items to the toolbar, a text item and a seperator between text items.

Text Item

TxtItem ItemID, ButtonID,Quoted_Text

Parameters

1. ItemID  A zero based text item ID that must be sequential for each additional text item added to the toolbar. 0 1 2 3 4 5 6 etc ...

2. ButtonID  The WM_COMMAND identifier for command processing.

3. Quoted_Text  The text to display as the text toolbar item.

Text Item Seperator

    TxtSeperator

The TxtSeperator macro has no parameters.

End Macro

    TB_END

The TB_END macro has no parameters. It must be the last line in the procedure.

Example

text_bar proc hParent:DWORD

    TB_BEGIN hParent

    TxtItem  0,  50, "New"

    TxtItem  1,  51, "Open"

    TxtItem  2,  52, "Save"

    TxtSeperator

    TxtItem  3,  53, "Cut"

    TxtItem  4,  54, "Copy"

    TxtItem  5,  55, "Paste"

    TxtSeperator

    TxtItem  6,  56, "Exit"

    TB_END

text_bar endp

Comments

This macro system has been tested successfully on both Windows created with CreateWindowEx() and with dialog windows. To ensure that you have enabled the correct common control library, the following code is required in the application using the flat text toolbar.

    LOCAL icce:INITCOMMONCONTROLSEX

    mov icce.dwSize, SIZEOF INITCOMMONCONTROLSEX

    mov icce.dwICC,  ICC_BAR_CLASSES

    invoke InitCommonControlsEx,ADDR icce


Performance Macros

The macros in this category

memalign	Align memory to a power of 2
chtype$	High speed character recognition


memalign

    memalign reg, number

Description

Align memory to a user defined power of 2.

Parameters

1. reg  Either a 32 bit register or a 32 bit memory operand that contains the current location in allocated memory.

2. number  The user defined power of 2 alignment value.

Return Value

There is no return value.

Comments

The macro is normally used on allocated memory where alignment is required for performance or specification reasons. Alignment must be by a power of two (2).

2, 4, 8, 16, 32, 64 etc ....

This is the actual macro in the macros.asm file.

    memalign MACRO reg, number

      add reg, number - 1

      and reg, -number

    ENDM


chtype$

Description

High speed alternative to procedures for character class recognition.

Parameters

1. character  Either a BYTE register or the address of a BYTE as a memory operand.

Return Value

The return value is a 32 bit pointer to a byte in the character table that this macro uses.

It is returned in this form, BYTE PTR [eax+chtyptbl] and the contents of the BYTE will be one of the following character classes.

  1 = numbers

  2 = upper case characters

  3 = lower case characters

  4 = arithmetic operators

  5 = punctuation & crlf

  6 = space or tab

Comments

The application of a macro of this type is for a project that must evaluate characters in very large quantities where speed is a factor in the performance of the application.

When this macro is used, it adds a 256 byte character table to the executable so that characters can be determined in a single read from the character table. Typical uses for the macro are in the following examples.

    movzx ecx, chtype$([ebp+4])

    cmp chtype$([esp+4]), 2

    cmp chtype$(ah), dl

The speed advantage of this technique is because it can determine the character class with a very low instruction count.

    movzx eax, BYTE PTR character

The macro uses the following form to return a value that is placed in a normal assembler notation form.

    EXITM <BYTE PTR [eax+chtyptbl]>


Memory Line IO Macros

Macros for reading and writing lines of text directly to and from memory.

linein$	Read line of text into a user defined buffer
lineout$	Write line of text from a user defined buffer
tstline$	Test if line of text is empty
Count Lines	A procedure to count the lines in a text file


linein$

    mov spos, linein$(source,buffer,spos)

Description

linein$ reads a line of text from a source memory buffer to a user defined memory buffer and updates the spos variable to the next "read" position in the source.

Parameters

1. source  The address of the memory to read the line of text from.

2. buffer  The user defined buffer to write the line of text to.

3. spos  A variable that contains the required "read" offset in the source.

Return Value

The return value is the next "read" location in the source memory. The line of text is written to the user defined memory buffer. The line length not including zero or 13 is returned in the ECX register.

Comments

In most instances the spos variable is set to zero before the read operation(s) begins so that the reading of lines of text begin at the beginning of the text. The source is typically a file loaded into memory but it can be any text source that is carriage return / line feed delimited.

Allocation of the user defined buffer must take into consideration the absolute longest line of text and ensure the allocated buffer is larger than the longest line. On 32 bit Windows using a buffer that is far larger than the expected longest line is viable because of the available memory to the system so allocating 4k or even 16k will not effect performance in most instances.

    mov buffer, alloc$(16384)

    ; perform your text operation here

    free$ buffer

Where you can safely ensure that the lines of text are limited to a normal length, it is easier to use the stack as a LOCAL buffer.

    LOCAL buffer  :DWORD

    LOCAL txt[128]:BYTE

    mov buffer, ptr$(txt)

This macro and the algorithm that it uses from the MASM32 library is not designed to handle binary data and the results of trying to read binary data are undefined. It should only be used on text data that is delimited by ascii 13,10 . A zero terminator for the source works OK as well.

The spos variable is properly an OFFSET indicator from the start of the source memory and the underlying algorithm automatically updates the variable so that the next read location does not have to be scanned to determine where to read from. The spos variable is always updated after the macro returns so by subtracting one (1) from the spos value you have the number of bytes read from the source buffer.

Example

    LOCAL rpos:DWORD    ; read position variable

    LOCAL wpos:DWORD    ; write position variable

    mov rpos, 0         ; set variables to zero

    mov wpos, 0

    mov rpos, linein$(lpsource,lpbuffer,rpos)

    cmp tstline$(lpbuffer), 0

    je bypass

  ; process the line of text

  ; --------------------------------------------

  ; output the line of text with a CRLF appended

  ; --------------------------------------------

    mov wpos, lineout$(lpbuffer,lpdest,wpos,0)

  bypass:


lineout$

    mov spos, lineout$(source,buffer,spos,op_crlf)

Description

Write a line of text from a user defined source to a memory buffer with the choice of appending a CRLF to the end of the line.

Parameters

1. source  A user defined line of zero terminated text.

2. buffer  The destination buffer to write the source to.

3. spos  A variable that contains the required "write" offset in the destination.

4. op_crlf  A variable that defined if a carriage return / line feed pair are appended to the end of the line.

Zero     = appended CRLF.

Non zero = no CRLF appended.

Return Value

The return value is the next "write" location in the destination buffer. The line of text from the source is written to the destination buffer. The ECX register returns the number of bytes written to the destination buffer.

Comments

The destination buffer must be large enough to receive the sum total of text written to it. In circumstances where this is not possible, you can either develop a memory re-allocation strategy or open a file and write each line of text to the file until the data is finished. Check the File IO Macros to use this option if it suits your code design.

The spos variable is properly an OFFSET indicator from the start of the destination memory and the underlying algorithm automatically updates the variable so that the next write location does not have to be scanned to determine where to write to. The spos variable is always updated after the macro returns so by subtracting one (1) from the spos value you have the number of bytes written to the destination buffer.


tstline$

    tstline$ lpstr

Description

Test if a line of text has usable content.

Parameters

1. lpstr  The address of the line of text to test.

Return Value

Return value is either the first usable text character zero extended to 32 bit or zero if the line has no useful content.

Comments

tstline$ scans the line of text specified in lpstr bypassing any leading spaces or tabs to obtain the first usable character. The macro is designed to test if a line of text is either empty or only contains spaces or tabs. Returning the first character in AL allows the testing of a comment or similar so that the line can be either included or avoided while reading the source. The return value of a usable character is zero extended to 32 bit to allow for 32 bit evaluation if required.

Example

    test tstline$(lpstring), eax

    jz no_useful_content

This direct assembler notation is possible because the macro returns a value in EAX and can be used anywhere where EAX can be used.


Count Lines

This is a procedure that may be added later to the MASM32 library. It counts the number of ascii 10 characters to determine the number of lines in a text file. As the last line of text may be zero terminated and would not add to the count of lines, it is advisable to add 1 to the final count so you can set up loop code with this result as a counter.

The procedure can be called with normal MASM syntax,

    invoke lfcnt,lpText

    mov lcnt, eax

    add lcnt, 1

    ; or

    mov lcnt, rv(lfcnt,lptext)

    add lcnt, 1

The procedure is prototyped in the normal manner,

lfcnt PROTO :DWORD

The code below can be copied and pasted into the application.

; --------------------------------------------------------------

align 4

lfcnt proc txt:DWORD

  ; ---------------------------------------------

  ; get line count of text by counting line feeds

  ; ---------------------------------------------

    xor eax, eax                            ; set counter to zero

    mov ecx, txt

    sub ecx, 1

    jmp @F

  pre:

    add eax, 1                              ; add 1 to return value if LF found

  @@:

    add ecx, 1

    cmp BYTE PTR [ecx], 0                   ; test for zero terminator

    je ccout

    cmp BYTE PTR [ecx], 10                  ; test line feed

    je pre

    jmp @B

  ccout:

    ret

lfcnt endp

; --------------------------------------------------------------


Environment

Set or get an environment string. The functionality for both macros comes from the MSVCRT runtime DLL.

setenv	Set an environment string
env$	Get an environment string


setenv

setenv szString

Description

Set a zero terminated string as an environment variable.

Parameters

1. szString Either a zero terminated string or quoted text.

Return Value

The macro does not return a value.

Comments

The zero terminated string needs to be in the form "var=string"

Example : setenv "link=\masm32\bin\"

Example : setenv lpString


env$

mov lpenv, env$(szString)

Description

Return the address of an environment string if it exists.

Parameters

1. Either a zero terminated string or quoted text

Return Value

If the string exists the address of the string is returned otherwise zero is returned.

Comments

The return value should be tested for zero to ensure that an environment string has been returned.

Example :

    mov penv, env$("link")

    .if penv != 0

      print penv,13,10

    .endif


Macro Library

This category of macros are used as components in creating other more powerful macros.

reparg	Replace quoted strings with their DATA offsets.
tstarg	Test for quoted text and return an error if it is.
argcount	Return the count of arguments passed to a macro.
getarg	Return an argument from a list by its index.
op_type	Return a macro argument operand type.
cfm$	Format a string using C style escapes


reparg

reparg(macro_variable)	return offset as invoke ADDR.

repargv(macro_variable)	return offset in a register.

repargof(macro_variable)	return direct offset.

Description

Replace quoted text in a macro with a data section offset or return the original value if its is NOT quoted text.

Parameters

1. macro_variable An argument passed to a macro.

Return Value

reparg returns an INVOKE format ADDR variable.

repargv returns the offset in the EAX register.

repargof returns the direct offset.

Comments

This group of macros make it possible to use quoted text directly in a procedure call. If you were writing a simple macro that had a system MessageBox in it, you can use reparg as follows to handle quoted text.

    YourMessageBox MACRO hndl,text1,text2,styl

      invoke MessageBox,hndl,reparg(text1),reparg(text2),styl

      EXITM <eax>

    ENDM

You would call the macro this way.

mov hRetval, YourMessageBox(hWnd,"My Message","Msg Box Title",MB_OK)

The macro places the quoted text in the data section and substitutes the offset in the data section for the quoted text.


tstarg

tstarg(macro_argument)

Description

Display an error and stop the assembler if a quoted text is passed to it.

Parameters

1. macro_argument The argument passed to the macro.

Return Value

If the argument is not quoted text, it returns the argument passed to it.

Comments

This macro is a form of error detection so that quoted text is not passed to another macro if it should not be used in that argument position. Typical applications are a macro that specifies the address of a buffer where quoted text will not work.


argcount

argcount(arguments)

Description

Return the number of arguments in an argument list.

Parameters

1. arguments  The list of arguments to count

Return Value

The return value is the number of arguments in the argument list.

Comments

The argument list is usually passed to a macro that uses this macro as a single name followed by VARARG.

    YourMacro MACRO function:REQ,arglist:VARARG

      ;; macro statements

      var = argcount(arglist)

      ;; macro statements

    ENDM


getarg

    getarg(index,arguments)

Description

Return the content of an argument in an argument list specified by its index.

Parameters

1. index  The argument number to return

2. arguments  The argument list to return it from

Return Value

The return value is either the text of the argument or the text "-1" to indicate that the index number is larger than the argument count.

Comments

You can test the return value for the text "-1" with the IFIDN macro operator. This macro is among other things very useful for reading a list of arguments in reverse order to their position in the argument list.


op_type

    op_type(macro_argument)

Description

Determine what operand type has been passed to the macro.

Parameters

    1. macro_argument The argument passed to the macro.

Return Value

    0 = UNKNOWN operand type

    1 = LOCAL or GLOBAL label

    2 = GLOBAL variable

    3 = LOCAL variable

    4 = IMMEDIATE operand or CONSTANT

    5 = REGISTER

    6 = LOCAL procedure in module

    7 = EXTERNAL procedure or API call

Comments

This macro is useful when you are designing code within a macro that can take various operand forms. By knowing the operand type, you can construct the code to work correctly with that operand type.


cfm$

Description

Format a quoted string using C style escape sequences.

Parameters

A quoted string that can have escape sequences contained within it.

Return Value

The return value is the address of the formated string data as an offset in the .DATA section.

Comments

This macro is not designed to conform to the C "printf" string formatting specification, it is designed to give this form of formatting convenience to MASM programmers who may have a C background. It can be used either directly in a procedure call in normal code or be used in a dedicated macro that requires this style of formatting.

The direct formatting escapes are as follows, the numbers enclosed in brackets are the ASCII characters inserted into the quoted text.

\n = newline (13,10)

\t = tab (9)

\\ = "\" backslash (92)

\q = quote (34)

The following escapes are MASM specific to bypass the restrictions that MASM places on certain characters being contained within a quoted string.

\l = "<" left angle bracket. (60)

\r = ">" right angle bracket. (62)

\x = "!" exclaimation mark. (33)

\a = "(" left bracket. (40)

\b = ")" right bracket. (41)

Example

print cfm$("\tThis is a test\n\tof the \qfmt$\q macro\n")

Result

        This is a test

        of the "fmt$" macro


C Runtime Conversions

The following macros were developed by Greg Lyon using the functionality of the MSVCRT C runtime DLL. To use these macros you must use the include file and library for the msvcrt runtime DLL that is supplied with MASM32.

The following macros take a specified data size operand and return the address of a string that holds the results. The macro name prefixes indicate the data type that is converted. A leading "u" means an unsigned operand, a leading "s" means a signed operand. The leading "x" indicates a hexedecimal output format. The macros are used in the FUNCTION position to facilitate attributing return values or using direct mnemonics with the macro. Note that the string addresses are returned as an OFFSET in the data section.

Number to string macros.

ubyte$()	Unsigned BYTE input
sbyte$()	Signed BYTE input
xbyte$()	Hex notation output BYTE
uword$()	Unsigned WORD input
sword$()	Signed WORD input
xword$()	Hex notation output WORD
udword$()	Unsigned DWORD input
sdword$()	Signed DWORD input
xdword$()	Hex notation output DWORD
uqword$()	Unsigned QWORD input
sqword$()	Signed QWORD input
xqword$()	Hex notation output QWORD
real4$()	32 bit FLOAT input
real8$()	64 bit FLOAT input
real10$()	80 bit FLOAT input

The following macros are the inverse of the above in that they convert ascii string format numeric characters to a range of different sized numeric data types. The prefixes are "a" for ascii and "h" for hex. The suffixes indicate whether the result is signed or unsigned for integer instructions. "u" for unsigned, "s" for signed and "r" with the three data sizes for floating point conversions. Note that the result is returned as an OFFSET in the data section.

String to number macros.

a2ub()	Ascii to unsigned BYTE
a2sb()	Asci to signed BYTE
h2ub()	Hex to unsigned BYTE
a2uw()	Ascii to unsigned WORD
a2sw()	Ascii to signed  WORD
h2uw()	Hex to unsigned  WORD
a2ud()	Ascii to unsigned DWORD
a2sd()	Ascii to signed DWORD
h2ud()	Hex to unsigned DWORD
a2uq()	Ascii to unsigned QWORD
a2sq()	Ascii to signed QWORD
h2uq()	Hex to unsigned QWORD
a2r4()	Ascii to 32 bit FLOAT
a2r8()	Ascii to 64 bit FLOAT
a2r10()	Ascii to 80 bit FLOAT


Specialised Prototypes

The following macros are for creating prototypes for procedures that have a known starting address and a known argument list. This can be from using the API call GetProcAddress() or from obtaining the start address of a procedure from a virtual table or an address passed to a callback procedure.

SPROTO	Create a STDCALL prototype
CPROTO	Create a C prototype


SPROTO

SPROTO MACRO func_addr:REQ,arglist:VARARG

Description

Create a STDCALL prototype for a procedure that has a known starting address and a known argument list.

Parameters

1. func_addr The name to be used for the address of the procedure that is being prototyped.

2. arglist A normal prototype list

Return Value

The return value is assigned to an equate of your own choice that must be a unique name within the application. See the example below for details on how to use it.

Comments

The following code shows how the two prototyping macros are used.

main proc

    LOCAL user  :DWORD

    LOCAL msvc  :DWORD

    LOCAL pbuf  :DWORD

    LOCAL buffer[64]:BYTE

    mov pbuf, ptr$(buffer)

  ; ---------------------------

  ; prototype the two functions

  ; ---------------------------

    .const

      mbox  SPROTO(adr1,:DWORD,:DWORD,:DWORD,:DWORD)

      _l2a  CPROTO(adr2,:DWORD,:DWORD,:DWORD)

    .code

  ; -------------

  ; load the DLLs

  ; -------------

    mov user, rv(LoadLibrary,"user32.dll")

    mov msvc, rv(LoadLibrary,"msvcrt.dll")

  ; -----------------------------------

  ; load the procedure address into the

  ; variable specified in the prototype

  ; -----------------------------------

    mov adr1, rv(GetProcAddress,user,"MessageBoxA")

    mov adr2, rv(GetProcAddress,msvc,"_ltoa")

  ; -----------------------

  ; call the two procedures

  ; -----------------------

    invoke _l2a,1234,pbuf,10

    fn mbox,0,pbuf,"title",MB_OK

  ; -----------------

  ; free the two DLLs

  ; -----------------

    invoke FreeLibrary,user

    invoke FreeLibrary,msvc

    ret

main endp


CPROTO

CPROTO MACRO func_addr:REQ,arglist:VARARG

Description

Create a C calling convention prototype for a procedure that has a known starting address and a known argument list.

Parameters

1. func_addr The name to be used for the address of the procedure that is being prototyped.

2. arglist A normal prototype list

Return Value

The return value is assigned to an equate of your own choice that must be a unique name within the application. See the example below for details on how to use it.

Comments

The following code shows how the two prototyping macros are used.

main proc

    LOCAL user  :DWORD

    LOCAL msvc  :DWORD

    LOCAL pbuf  :DWORD

    LOCAL buffer[64]:BYTE

    mov pbuf, ptr$(buffer)

  ; ---------------------------

  ; prototype the two functions

  ; ---------------------------

    .const

      mbox  SPROTO(adr1,:DWORD,:DWORD,:DWORD,:DWORD)

      _l2a  CPROTO(adr2,:DWORD,:DWORD,:DWORD)

    .code

  ; -------------

  ; load the DLLs

  ; -------------

    mov user, rv(LoadLibrary,"user32.dll")

    mov msvc, rv(LoadLibrary,"msvcrt.dll")

  ; -----------------------------------

  ; load the procedure address into the

  ; variable specified in the prototype

  ; -----------------------------------

    mov adr1, rv(GetProcAddress,user,"MessageBoxA")

    mov adr2, rv(GetProcAddress,msvc,"_ltoa")

  ; -----------------------

  ; call the two procedures

  ; -----------------------

    invoke _l2a,1234,pbuf,10

    fn mbox,0,pbuf,"title",MB_OK

  ; -----------------

  ; free the two DLLs

  ; -----------------

    invoke FreeLibrary,user

    invoke FreeLibrary,msvc

    ret

main endp


Pseudo Mnemonics

The following macros are designed to be used like assembler mnemonics but they contain higher level capacity.

m2m	Memory to memory copy of a variable
mrm	Memory to memory copy using EAX
sas	Assign a string to a LOCAL variable
cst	Copy one zero terminated string to another


m2m

m2m var2, var1

Description

Copy a DWORD sized memory operand to another DWORD sized memory operand.

Parameters

1. The destination operand.

2. The source operand.

Note the reverse ordering as is common with mnemonics.

Comments

X86 hardware does not support direct memory to memory copy and the capacity must be writen using existing mnemonics. This version uses the stack to push the source operand onto the stack and pop it back off the stack as the destination operand.


mrm

mrm mem2, mem1

Description

Copy one DWORD memory operand to another DWORD memory operand.

Parameters

1. The destination operand.

2. The source operand.

Note the reverse ordering as is common with mnemonics.

Comments

X86 hardware does not support direct memory to memory copy and the capacity must be writen using existing mnemonics. This version uses the EAX register which is faster than using te stack but it uses a register.


sas

sas a$, "This is an assigned string"

Description

Assign a string to a local variable.

Parameters

1. A DWORD sized local variable.

2. The quoted text to assign to the local variable.

Comments

This is the same technique used in higher level languages to ensure a string only has LOCAL scope. String data is normally stored in the initialised data section of the executable file and by assigning it to a LOCAL variable, the string is only accessible within the procedure that it is allocated in. Once the string is assigned to the local variable it is used directly as the address of the string without using the ADDR or OFFSET operators.

Example:

    LOCAL a$:DWORD    ; allocate a LOCAL variable

    ........

    sas a$,"My Quoted Text"

    ........

    fn MessageBox,hWnd,a$,"Hi Guys",MB_OK


cst

    cst buffer,szText

Description

Copy a zero terminated string to a memory buffer.

Parameters

1. buffer The destination memory buffer.

2. szText A zero terminated string. This may also be quoted text.

Comments

The macro tests the first argument, the destination memory buffer and will halt and display an error message if it is a quoted text.