EuroAssembler Index Manual Download Source Macros


Sitemap Links Forum Tests Projects

winabi.htm
Macros
GetArg
GetArgCount
PutArg
PutEnv
StdInput
StdOutput
TerminateProgram
WinABI

MS Windows x64 Application Binary Interface macros.

This library contains macros for some basic OS interactions: retrieving environment and command-line arguments, standard I/O, program termination, invocation of 64bit MS Windows Application Binary Interface as specified in [WinABI64].

Programming interface macros represent elegant way of interaction with operating system using only one single statement. Otherwise we would have to push or load appropriate registers with argument values in the right order, store caller-save registers, manually arrange stack alignment, call the imported function and finally restore the stack.

Most macro names in this 64bit library winabi.htm are identical with macros from 32bit library winapi.htm and other API libraries. If you really need to include both libraries in one common source file, definitions of those macros should be forgotten before inclusion of the other library with %DROPMACRO GetArg,GetArgCount,GetEnv,StdInput,StdOutput,TerminateProgram or with %DROPMACRO * in order to prevent warning W2512 Overwriting macro "!1S" previously defined at !2@.

winabi HEAD
       INCLUDEHEAD1 winansi.htm  ; Make sure that %WinANSI is available before WinABI invocation.
↑ WinABI   Function, Argument1, Argument2,,,Unicode=%^UNICODE, Lib=, Fastmode=No

Macroinstruction WinABI invokes Function exported from MS Windows 64bit by [WindowsAPI]   in the FastCall calling convention compatible with [WinABI64].

Ambiguous functions, which have both ANSI and WIDE variant, may be specified with or without explicit suffix A or W.
Function may also be provided as a GPR with address of the function (pointer to its thunk in [.idata]).

The macro is similar to FastCall's Invoke with two differences:

  1. If the Function name occurs on the %WinANSI list, it will be suffixed with A or W, depending on current %Unicode value.
  2. The Function name will be declared as imported from library Lib=.

WinABI functions require Direction Flag be zero on input and they do not change its value.
64bit Windows functions do not keep the original contents of flags, RCX, RDX, R8..R11,XMM0..XMM5, but when this macro WinABI is invoked with keyword Fastmode=No (default), it preserves all XMML and GP registers except for RAX and XMM0.

According to FastCall convention, arguments are pushed backwards on stack as QWORDs, starting from the last to the fifth. The first four parameters are loaded to RCX, RDX, R8, R9 and not pushed on stack. Nevertheless, room for the first four arguments is always reserved on stack (so called shadow space) even when the invoked function has less than four arguments.
When the Function expects argument in floating-point format instead of integer, pointer or immediate, the first four arguments are loaded to XMM0, XMM1, XMM2, XMM3 rather than to GPR.
Stack pointer will be OWORD aligned before the CALL instruction is performed.

Invoked function does not remove arguments from stack, it terminates with a simple near RET. Epilogue of macro WinABI restores RSP to its original value.

Input
Function is the name of invoked WinAPI procedure imported from system DLL
Function may also be provided in the form of GPR containing the entry address of the function (pointer to its thunk in [.idata] section). Import from DLL by ordinal number is not supported.
Arg* can be a SIMD register or anything pushable:It is programmer's responsibility to provide exactly that many arguments as is specified in WinAPI documentation.
Any argument may be suffixed with type specificator #SS or #SD (case insensitive), which signalizes that it represents floating-point value in Scalar Single or Scalar Double precision format, and that it should be therefore passed to the Function in XMM register instead of GPR.
Suffix is not necessary with XMM register (argument passed in XMM is always assumed to contain floating-point number).
Suffix is not necessary when Fastmode=No, because the robust version transfers the first four arguments simultaneously in SIMD and GP registers anyway, no matter if they are float or integer.
Suffix is not necessary in 5th and higher arguments in both modes (they are passed via machine stack regardless of their type).
Unicode=%^UNICODE is an extended Boolean switch which specifies if ANSI or WIDE variant of Function should be called. By default it copies the option set by EUROASM UNICODE=.
Lib= is the name of dynamic linked library (quoted or unquoted, always without path) which exports the invoked Function. This keyword may be empty or omitted Fastmode=No is an extended Boolean switch. When it's false or omitted (default), the WinABI macro preserves all registers except for RAX and XMM0 which return the result of Function, similary to other robust macros from EuroAssembler libraries.
Although Microsoft ABI specifies fastcall convention with floating-point parameters %1..%4 delivered in XMM0, XMM1, XMM2, XMM3, and with parameters %1..%4 of all other types delivered in RCX, RDX, R8, R9, arguments of WinABI in robust mode (Fastmode=No) may be supplied in those registers too in arbitrary order, for instance WinABI MessageBox,RDX,R8,R8,MB_OK.
Fastmode=Yes is the macro expansion mode where Invoke | WinABI does not preserve caller-save registers and it loads the first four arguments directly to XMM0, XMM1, XMM2, XMM3 when they are floating-point values, and to RCX, RDX, R8, R9 in all other cases, so the invokation is faster. Registers RCX, RDX, R8, R9, XMM0..XMM3 cannot be used as macro arguments, because they are being overwritten in prologue. Or they should be only used in the exact ABI-specified order, e.g. WinABI Function, RCX, XMM1, R8, XMM3. Similary, registers RCX, RDX, R8, R9 cannot be used instead of Function name in fast mode. Use other GPR, e.g.
IMPORT WriteConsoleW,Lib=kernel32.dll
LEA RDI, [WriteConsoleW]
WinABI Fastmode=Yes, RDI, RBX, Message, SIZE# Message, WrittenChars, 0
Output
RAX, XMM0, RFlags are set as returned from the invoked Function.
RBX,RBP,RSP,RSI,RDI,R12..R15,XMM6..XMM15 are preserved (callee-save registers).
Other XMM and GPR are preserved only if Fastmode=No.
Depends on
WinANSI
Documentation
Windows functions are documented in [WindowsSDK] and in [WindowsAPI].
Microsoft FastCall convention is described in [WinABI64] and [MSx64Conv].
Example
Expansion of WinABI invokation of the function CreateFile with seven arguments in both modes: WinABI CreateFileA, FileName, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0 | | ; Fast Version. |00000000: | WinABI CreateFileA, FileName, GENERIC_READ, FILE_SHARE_READ, 0, \ |00000000: | OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0, Fastmode=Yes |00000000:54 + PUSH RSP ; Store original stack pointer value (equilibrum). |00000001:40F6C408 + TEST SPL,1000b ; Test RSP OWORD alignment at run-time. |00000005:7506 + JNZ .WinABI1: |00000007:54 + PUSH RSP ; Store and update 2nd copy of original RSP (equilibrum). |00000008:4883042408 + ADDQ [RSP],8 ; Those two instructions aren't executed if RSP was properly aligned. |0000000D: + .WinABI1: |0000000D:6A00 + PUSHQ 0 ; Push 7th argument. |0000000F:6880000000 + PUSHQ FILE_ATTRIBUTE_NORMAL ; Push 6th argument. |00000014:6A03 + PUSHQ OPEN_EXISTING ; Push 5th argument. |00000016:41B900000000 + MOV R9,0 ; Load 4th argument. |0000001C:41B801000000 + MOV R8,FILE_SHARE_READ ; Load 3rd argument. |00000022:BA00000080 + MOV RDX,GENERIC_READ ; Load 2nd argument. |00000027:488D0D(00000000) + LEA RCX,[FileName] ; Load 1st argument. |0000002E:4883EC20 + SUB RSP,4*8 ; Make room for shadow space in fast mode. RSP is OWORD-aligned. |00000032:E8(00000000) + CALL CreateFileA ; Call the imported function. |00000037:488D642438 + LEA RSP,[RSP+7*8] ; Discard transferred arguments, keep RFlags. |0000003C:5C + POP RSP ; Restore RSP to equilibrum from 1st or 2nd copy. |0000003D: | ; WinABI in fast mode occupies 61 bytes of code. |00000000: | ; Robust version. |00000000: | WinABI CreateFileA, FileName, GENERIC_READ, FILE_SHARE_READ, 0, \ |00000000: | OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0, Fastmode=No |00000000:6A00 + PUSHQ 0 ; Push 7th argument. |00000002:6880000000 + PUSHQ FILE_ATTRIBUTE_NORMAL ; Push 6th argument |00000007:6A03 + PUSHQ OPEN_EXISTING ; Push 5th argument |00000009:6A00 + PUSHQ 0 ; Push 4th argument |0000000B:6A01 + PUSHQ FILE_SHARE_READ ; Push 3rd argument |0000000D:6800000080 + PUSHQ GENERIC_READ ; Push 2nd argument |00000012:50 + PUSH RAX ; Alloc 1st argument on stack. |00000013:488D05(00000000) + LEA RAX,[FileName] ; Load 1st argument. |0000001A:48870424 + XCHG RAX,[RSP] ; Transfer the pointer without clobbering RAX. |0000001E:6A07 + PUSHQ 7 ; Push the number of arguments. |00000020:488D05(00000000) + LEA RAX,[CreateFileA] ; Load the function address (pointer to its thunk in [.idata]). |00000027:E805000000 + CALL WinABI@RT ; Call the runtime with function in RAX. |0000002C:488D642440 + LEA RSP,[RSP+8*8] ; Restore stack to equilibrum, preserving RFlags. |00000031: | ; WinABI in robust mode occupies 49 bytes of code (plus 190 bytes of runtime code, once per program).
Remark
Macro WinABI can be expanded in one of two modes:

Selection of the mode depends on optimisation criterion:

Examples of a few typical OS invocations and their emitted sizes in bytes:
Example of Windows function invocationNumber
of args
WinABI
Fastmode=Yes
WinABI
Fastmode=No
GetCursor02819
SetArcDirection, [hDC], AD_CLOCKWISE24336
MessageBox, NULL, Text, Caption, MB_OK45347
AngleArc, [hDC], 100, 120, R9, [StartAngle]#SS, XMM14#SS67465
CreateFile, FileName, GENERIC_READ, FILE_SHARE_READ, 0, \
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0
76149

Robust variant of WinABI is often shorter, but it emits 190 additional bytes of runtime procedure (only once in a program), so it is profitable when we have more than cca twenty Windows invocations in the program. Robust variant may also spare some push/pops because it doesn't change any scratch registers.

When you want to switch on the Fastmode for all WinABI invokations, you don't have to append ,Fastmode=Yes to every invokation of WinABI or Invoke , if you set preprocessing variable %Fastmode in the beginning of your program: %Fastmode %SETB On.
Stack alignment
Fastcall convention requires that stack-pointer RSP should be OWORD (16 bytes) aligned before the function is called. In fast mode this is provided by pushing RSP once or twice in macro prologue.
Robust mode postpones the actual function call to its runtime procedure Invoke@RT, so the RSP alignment takes place there.
Stack layout of WinABI and Invoke when Fastmode=Yes
WinABI Function, Arg1,Arg2,Arg3,Arg4,Arg5,Arg6,Fastmode=Yes ┌────────┐ │OrigRSP │ <──Only present if RSP was OWORD unaligned. ├────────┤ │OrigRSP │ ╞════════╡ <══RSP is OWORD aligned here. │ Arg6 │ ├────────┤ │ Arg5 │ ╞════════╡ ┐ │ undef │ │ ├────────┤ │ │ undef │ ├─Shadow space. ╞════════╡ │ │ undef │ │ ├────────┤ │ │ undef │ │ ╞════════╡ ┘ │ return │ └────────┘
Stack layout of WinABI and Invoke when Fastmode=No
WinABI Function, Arg1,Arg2,Arg3,Arg4,Arg5,Arg6,Fastmode=No ┌────────┐ ┐ │ Arg6 │ │ ├────────┤ │ │ Arg5 │ │ ├────────┤ │ │ Arg4 │ │ ├────────┤ │ │ Arg3 │ ├─Frame of Invoke@RT. ├────────┤ │ │ Arg2 │ │ ├────────┤ │ │ Arg1 │ │ ├────────┤ │ │NrOfArg │ │ NrOfArg=6 in this example. ├────────┤ ┘ │RET(@RT)│ ├────────┤ ┐ │ RSI │ │ ├────────┤ │ │ RDI │ │ ├────────┤ │ │ R12 │ │ ├────────┤ │ │ RCX │ │ ├────────┤ │ │ RDX │ │ ├────────┤ │ │ R8 │ │ ├────────┤ │ │ R9 │ │ ├────────┤ │ │ R10 │ │ ├────────┤ │ │ R11 │ ├─Local stack ├────────┤ │ of Invoke@RT. │ XMM1L │ │ ├────────┤ │ │ XMM2L │ │ ├────────┤ │ │ XMM3L │ │ ├────────┤ │ │ XMM4L │ │ ├────────┤ │ │ XMM5L │ │ ├────────┤ │ ┐ │(align) │ │ ├──────Only present if RSP is OWORD-unaligned. ╞════════╡ ┘ ┘ ─┐ │ Arg6 │ │ ├────────┤ │ │ Arg5 │ │ ╞════════╡ ┐ │ │ Arg4 │ │ │ ├────────┤ │ ├─Frame of Function. │ Arg3 │ │ │ ╞════════╡ ├Shadow │ │ Arg2 │ │ space │ ├────────┤ │ │ │ Arg1 │ │ │ ╞════════╡ ┘ ─┘ <══ RSP is OWORD-aligned here. │RET(API)│ └────────┘
WinABI %MACRO Function, Arg1, Arg2,,, Fastmode=%Fastmode, Unicode=%^UNICODE, Lib=
%Fast   %SETB %Fastmode
%Robust %SETB ! %Fast
Fa      %IF %Fast                       ; Align stack in fast mode only.
          PUSH RSP                      ; Store original stack pointer value (equilibrum).
          TEST SPL,1000b                ; Test stack OWORD alignment at run-time.
FaEv      %IF %# & 1b || %# <= 5        ;>If the number of Function arguments is 0,1,2,3,4,6,8,10,,(even),
            JZ .WinABI%.:               ;   store 2nd copy of equilibrum when RSP is OWORD-unaligned.
          %ELSE FaEv                    ; If the number of arguments is 5,7,9,11,,, (odd),
            JNZ .WinABI%.:              ;   store 2nd copy of equilibrum when RSP is OWORD-aligned.
          %ENDIF FaEv
          PUSH RSP                      ; Store and update 2nd copy of original RSP (equilibrum).
          ADDQ [RSP],8                  ; Those two instructions aren't executed if RSP was properly aligned.
.WinABI%.:
        %ENDIF Fa
%GPR    %SET  RCX,RDX,R8,R9             ; Enumerate registers for transfer of integer|pointer values.
%SIMD   %SET  XMM0,XMM1,XMM2,XMM3       ; Enumerate registers for transfer of floating-point values.
%ArgNr  %SETA %#                        ; Number of macro ordinals, i.e. number of Function arguments + 1.
Arg     %WHILE %ArgNr > 1
%Arg     %SET %*{%ArgNr}                ; Transfer all Function arguments, start with the last one.
%ArgNr   %SETA %ArgNr-1                 ; %ArgNr is now the ordinal Nr of Function argument (,,3,2,1).
%suffix  %SET Q                         ; %suffix of MOV will be Q, SS or SD (MOVQ, MOVSS or MOVSD).
         %IF '%Arg[%&-2..%&-1]'=='#S'   ; If suffix #SS or #SD is present in argument notation,
%suffix    %SET %Arg[%&-1..%&]          ;  let %suffix be SS or SD
%Arg       %SET %Arg[1..%&-3]           ;  and remove it from the argument.
         %ENDIF                         ; %Arg may be GPR,SIMD,imm@abs,ptr@rel,[mem@abs],[mem@rel].
Rb       %IF %ArgNr>4 || %Robust        ; Transfer %Arg via stack.
RbSc       %IF TYPE#(SEGMENT#(%Arg))='N'; %Arg is not relocatable (scalar).
RbScRg       %IF TYPE#(%Arg)='R'        ; It can be GPR,SIMD,imm@abs,[mem@abs].
RbScRgXm       %IF REGTYPE#(%Arg)='X'   ; %Arg is a GP or SIMD register.
                 SUB RSP,8
                 MOV%suffix [RSP],%Arg  ; %Arg is a SIMD register.
               %ELSE RbScRgXm
                 PUSHQ %Arg             ; %Arg is a GP register.
               %ENDIF RbScRgXm
             %ELSE RbScRg               ; %Arg it not a register.
               PUSHQ %Arg               ; %Arg is scalar immediate or [mem], e.g. 1 or [RBP+16].
             %ENDIF RbScRg
           %ELSE RbSc                   ; %Arg is relocatable (vector), e.g. Symbol or [Symbol+RSI].
RbVeM        %IF '%Arg[1]' === '['      ; Argument is passed by value, via a temporary GPR.
               PUSH RAX                 ; Original contents of the borrowed RAX must be kept.
               LEA RAX,%Arg             ; Use relative addressing frame for relocatable %Arg.
               MOV RAX,[RAX]            ; Dereference the argument value.
               XCHG RAX,[RSP]           ; Transfer the value.
             %ELSE RbVeM                ; Relocatable argument is passed by reference, e.g. Symbol.
               PUSH RAX                 ; Original contents of the borrowed RAX must be kept.
               LEA RAX,[%Arg]           ; Use relative addressing frame for relocatable %Arg.
               XCHG RAX,[RSP]           ; Transfer the pointer.
             %ENDIF RbVeM
           %ENDIF RbSc
         %ELSE Rb                       ; Fastmode=Yes and %ArgNr=4,3,2,1. Transfer via registers.
FaSc       %IF TYPE#(SEGMENT#(%Arg))='N'; %Arg is not relocatable (scalar).
FaScRg       %IF TYPE#(%Arg)='R'        ; It can be GPR,SIMD,imm@abs,[mem@abs].
FaScRgXm       %IF REGTYPE#(%Arg)='X'   ; %Arg is a GP or SIMD register.
                 %IF "%SIMD{%ArgNr}" !== "%Arg"  ; %Arg is XMM. Skip when it's already there.
                   MOV%suffix %SIMD{%ArgNr},%Arg ; Copy SIMD %Arg to other SIMD (XMM0..XMM3).
                 %ENDIF
               %ELSE FaScRgXm           ; %Arg is GPR.
                 %IF "%GPR{%ArgNr}" !== "%Arg"   ; Skip when it's already there.
                    MOV %GPR{%ArgNr},%Arg        ; Copy GPR to other GPR (R9,R8,RDX,RCX).
                 %ENDIF
               %ENDIF FaScRgXm
             %ELSE FaScRg               ; %Arg is scalar immediate or [mem], e.g. 1 or [RBP+16].
FaScIm         %IF '%suffix' === 'Q'
                 MOV %GPR{%ArgNr},%Arg  ; %Arg is integer value.
               %ELSE FaScIm
                 MOV%suffix %SIMD{%ArgNr},%Arg ; %Arg is FP value, e g. [RSI].
               %ENDIF FaScIm
             %ENDIF FaScRg
           %ELSE FaSc                   ; %Arg is relocatable (vector), e.g. Symbol or [Symbol+RSI].
FaVeM        %IF '%Arg[1]' === '['      ; Argument is passed by value, via this GPR.
               LEA %GPR{%ArgNr},%Arg
               MOV %GPR{%ArgNr},[%GPR{%ArgNr}] ; Dereference the argument value, transfer the value.
             %ELSE FaVeM                ; Argument is passed by reference, e.g. Symbol.
               LEA %GPR{%ArgNr},[%Arg]  ; Transfer the pointer.
             %ENDIF FaVeM
           %ENDIF FaSc
         %ENDIF Rb
        %ENDWHILE Arg                   ; All arguments are loaded|pushed.
%sfx    %SET                            ; A or W Function suffix. Empty by default.
reg     %IF REGTYPE# %Function = 'Q'    ; Function was specified as 64bit GPR.
          %IF "%Function"=="RCX"||"%Function"=="RDX"||"%Function"=="R8"||"%Function"=="R9"
            %ERROR ID=5956,'%0 function cannot be supplied in scratch register %Function when Fastmode=Yes.'
            %EXITMACRO Invoke
          %ENDIF
        %ELSE reg                       ; Function was specified by name.
fn        %FOR %WinANSI
found       %IF '%fn' === '%Function'
              %IF %Unicode
%sfx            %SETC 'W'
              %ELSE
%sfx            %SETC 'A'
              %ENDIF
              %EXITFOR fn               ; No need for further examination of the list.
            %ENDIF found
          %ENDFOR fn                    ; %sfx is now A or W or empty.
          IMPORT %Function%sfx, Lib=%Lib
        %ENDIF reg
Fa      %IF %Fast
          SUB RSP,4*8                   ; Make room for shadow space in fast mode. RSP is OWORD-aligned.
          CALL %Function%sfx            ; Call the function in fast mode.
          %IF %# > 5
            LEA RSP,[RSP+8*(%#-1)]      ; Discard transferred arguments, keep RFlags.
          %ELSE
            LEA RSP,[RSP+8*4]           ; Discard transferred arguments, keep RFlags.
          %ENDIF
          POP RSP                       ; Restore RSP to equilibrum from 1st or 2nd copy.
        %ELSE Fa                        ; In robust mode use helper runtime procedure WinABI@RT.
          PUSHQ %#-1                    ; Push the number of Function arguments.
RbRg      %IF REGTYPE# %Function = 'Q'  ; Function was specified as 64bit GPR.
            %IF '%Function' !== 'RAX'   ; Skip if it's already in RAX.
              MOV RAX,%Function
            %ENDIF
          %ELSE RbRg
            LEA RAX,[%Function%sfx]     ; RVA of the Function (pointer to its thunk in [.idata]).
          %ENDIF RbRg
          CALL WinABI@RT                ; Arguments+their number are on stack. Function is in RAX.
          LEA RSP,[RSP + 8 * %#]        ; Restore stack to equilibrum, preserving RFlags.
WinABI@RT: PROC1                        ; Macro emits the runtime subroutine, which is expanded only once in program.
            PUSH RSI,RDI,R12            ; Calee-save registers used by WinABI@RT procedure.
            PUSH RCX,RDX,R8,R9,R10,R11  ; Robust version preserves scratch GP registers.
            MOVQ RDX,XMM1
            MOVQ  R8,XMM2
            MOVQ  R9,XMM3
            MOVQ R10,XMM4
            MOVQ R11,XMM5
            PUSH RDX,R8,R9,R10,R11      ; Robust version preserves scratch SIMD registers.
            MOV ECX,[RSP+15*8]          ; Number of Function arguments.
            LEA RSI,[RSP+16*8]          ; Pointer to %Arg1 in WinABI@RT stack frame.
            CMP ECX,4                   ; Number of arguments is 0|1|2|3|4|5|6...
            JAE .AtLeast4:
            MOV CL,4                    ; Number is saturated to 4|4|4|4|4|5|6...
  .AtLeast4:MOV R12,RSP                 ; Save (perhaps unaligned) RSP to callee-preserved register.
            ; Align stack pointer as dictated by ABI specification.
            MOV EDX,ECX                 ; Saturated number of operands (4,5,6,,,).
            SHL EDX,3                   ; RDX bit 3 is set if saturated number of arguments is odd (5,7,9,,).
            XOR EDX,ESP                 ; RDX will be 8 when number of arguments is even and RSP is OWORD unaligned,
            AND EDX,0000_1000b          ;   or when it's odd and RSP is already OWORD aligned. Otherwise RDX=0.
            SUB RSP,RDX                 ; Align RSP to OWORD.
            ; Copy ECX arguments from RSI to the callee's shadow space.
            MOV EDX,ECX
            SHL EDX,3
            SUB RSP,RDX                 ; Alloc Function frame (shadow space + possible other arguments).
            MOV RDI,RSP                 ; RSP is OWORD aligned at this moment.
            REP MOVSQ                   ; Copy all pushed arguments.
            ; Load first four arguments to GP registers.
            MOVQ RCX,[RSP+00]
            MOVQ RDX,[RSP+08]
            MOVQ R8, [RSP+16]
            MOVQ R9, [RSP+24]
            ; Copy first four arguments to SIMD registers (for the case they were floating-point).
            MOVQ XMM0,RCX
            MOVQ XMM1,RDX
            MOVQ XMM2,R8
            MOVQ XMM3,R9
            CALL RAX                    ; Call the Function in robust mode.
            MOV RSP,R12                 ; Discard Function frame plus possible stack-alignment stuff.
            POP R11,R10,R9,R8,RDX
            MOVQ XMM5,R11
            MOVQ XMM4,R10
            MOVQ XMM3,R9
            MOVQ XMM2,R8
            MOVQ XMM1,RDX               ; Restore SIMD scratch registers.
            POP R11,R10,R9,R8,RDX,RCX   ; Restore GP scratch registers.
            POP R12,RDI,RSI             ; Restore used callee-save registers.
            RET
           ENDP1 WinABI@RT:
        %ENDIF  Fa
      %ENDMACRO WinABI
↑ GetArg   ArgNumber, Unicode=%^UNICODE

Macro GetArg retrieves ArgNumber-th parameter provided on command line.
Parameters on the command line may be separated with unquoted white spaces or commas. Single apostrophe cannot be used as quote.
Macro returns the executable name itself when ArgNumber is 0. It is taken verbatim from the console window or, if launched from Explorer, it may be expanded to a full pathname.

The returned argument is not zero terminated and it is not writable. Make a copy in local memory if you need to modify it. Quotes surrounding the argument are returned, too.

Value of EUROASM UNICODE= option specifies whether the returned string will be ANSI or WIDE.

Input
ArgNumber (64bit register or memory or immediate number) is ordinal number of the required parameter. The 0-th parameter is the executable file name itself.
Unicode=%^UNICODE is Boolean keyword specifying if the returned string should be ANSI or WIDE.
Output
CF=0,
RSI is pointer to the first character of argument,
RCX is the size of argument in bytes.
Error
CF=1 if odd number of quotes or if requested argument was not provided.
RSI=RCX=0
Depends on
WinABI
Tested by
twinabi.htm
Example
GetArg 1 ; Assume that our program should specify a filename. JC .BadArgument: ; Report error if no file was provided. StripQuotes RSI,RCX ; Get rid of quotes if they were used. MOV RDI,InputFileName$ ; Room for the filename. REP MOVSB ; Copy the filename. SUB AL,AL STOSB ; Zero terminate the string.
GetArg %MACRO ArgNumber, Unicode=%^UNICODE
          PUSHQ %ArgNumber
AorW      %IF %Unicode
             CALL GetArgWin64W@RT   ; WIDE variant.
GetArgWin64W@RT:: PROC1
    PUSHQ RAX,RDX,RSI,RDI,R8,R9,R10,R11
    WinABI GetCommandLineW, Fastmode=Yes
    MOV RSI,RAX      ; RSI = parser pointer.
    MOV RDI,RAX      ; RDI = end of current Arg.
    MOV R8,RAX       ; R8  = begin of current Arg.
    XOR ECX,ECX      ; RCX = current Arg ordinal.
    MOV R10,[RSP+72] ; R10 = requested Arg ordinal.
    MOV [RSP+40],RCX ; %ReturnRSI.
    DEC RCX
    CMP  R10,RCX     ; Requested ordinal is -1 when GetArg is invoked from GetArgCount.
    ADC  R10,0       ; Correction for the 0-th string on cmd-line.
    XOR EAX,EAX
    CMP EAX,ESI
    STC
    JZ .90:           ; If GetCommandLine returned FALSE.
    REPNE SCASW
    LEA RDX,[RDI-2]   ; RDX = end of command line.
    XOR ECX,ECX       ; RCX = current Arg ordinal.
.10:MOV RDI,RSI       ; RDI = brutto end of current argument.
    CMP RCX,R10
    JAE .50:          ; Jump if requested ordinal was just found.
.20:CMP RSI,RDX
    JNB .50:          ; If no more arguments available.
    LODSW
    CMP AX,' '
    JBE .20:
    SUB RSI,2
    INC RCX
    MOV R8,RSI       ; R8 is brutto beginning of RCX-th argument.
    MOV RDI,RSI
.30:CMP RSI,RDX
    JNB .10:
    LODSW
    CMP AX,'"'
    JNE .40:
.35:CMP RSI,RDX      ; Inside quotes look for ending quote.
    CMC
    JC .90:          ; Syntax error - unpaired quotes.
    LODSW
    CMP AX,'"'
    JNE .35:
    JMP .30:
.40:CMP AX,','
    JE .10:
    CMP AX,' '
    JA .30:
.45:CMP RSI,RDX
    JNB .10:
    LODSW
    CMP AX,' '
    JNA .45:
    CMP AX,','
    JE .10:
    SUB RSI,2
    JMP .10:
.50:CMP  R10,-1       ; Test if invoked from GetArgCount with ArgNr= -1.
    JNE .60:
    DEC RCX           ; Omit the 0-th argument (the executable itself).
    JMP .90:
.60:CMP RCX,R10
    MOV RCX,0
    STC
    JNE .90:          ; Return with CF and RCX=0 when the requested Arg is not provided.
    MOV RSI,R8        ; Brutto RCX-th argument found at RSI..RDI. Trim spaces and commas.
.65:CMP RSI,RDI
    JNB .75:
    LODSW
    CMP AX,' '
    JBE .65:
    CMP AX,','
    JE .65:
    SUB RSI,2
.70:SUB RDI,2
    MOV AX,[RDI]
    CMP AX,' '
    JBE .70:
    CMP AX,','
    JE .70:
    ADD RDI,2         ; Netto argument is now at RSI..RDI.
.75 MOV [RSP+40],RSI  ; %ReturnRSI.
    SUB RDI,RSI
    JC .80:
    MOV RCX,RDI
.80:CLC
.90:POPQ R11,R10,R9,R8,RDI,RSI,RDX,RAX
    RET 1*8
  ENDP1 GetArgWin64W@RT
     %ELSE AorW
       CALL GetArgWin64A@RT
GetArgWin64A@RT:: PROC1 ; Stdcalled with %Param1=ArgNumber.
    PUSHQ RAX,RDX,RSI,RDI,R8,R9,R10,R11
    WinABI GetCommandLineA, Fastmode=Yes
    MOV RSI,RAX      ; RSI = parser pointer.
    MOV RDI,RAX      ; RDI = end of current Arg.
    MOV R8,RAX       ; R8  = begin of current Arg.
    XOR ECX,ECX      ; RCX = current Arg ordinal.
    MOV R10,[RSP+72] ; R10 = requested Arg ordinal.
    MOV [RSP+40],RCX ; %ReturnRSI.
    DEC RCX
    CMP R10,RCX      ; Requested ordinal is -1 when GetArg is invoked from GetArgCount.
    ADC R10,0        ; Correction for the 0-th string on cmd-line.
    XOR EAX,EAX
    CMP EAX,ESI
    STC
    JZ .90:           ; If GetCommandLine returned FALSE.
    REPNE SCASB
    LEA RDX,[RDI-1]   ; RDX = end of command line.
    XOR ECX,ECX       ; RCX = current Arg ordinal.
.10:MOV RDI,RSI       ; RDI = brutto end of current argument.
    CMP RCX,R10
    JAE .50:          ; Jump if requested ordinal was just found.
.20:CMP RSI,RDX
    JNB .50:          ; If no more arguments available.
    LODSB
    CMP AL,' '
    JBE .20:
    DEC RSI
    INC RCX
    MOV R8,RSI        ; R8 is brutto beginning of RCX-th argument.
    MOV RDI,RSI
.30:CMP RSI,RDX
    JNB .10:
    LODSB
    CMP AL,'"'
    JNE .40:
.35:CMP RSI,RDX       ; Inside quotes look for ending quote.
    CMC
    JC .90:           ; Syntax error - unpaired quotes.
    LODSB
    CMP AL,'"'
    JNE .35:
    JMP .30:
.40:CMP AL,','
    JE .10:
    CMP AL,' '
    JA .30:
.45:CMP RSI,RDX
    JNB .10:
    LODSB
    CMP AL,' '
    JNA .45:
    CMP AL,','
    JE .10:
    DEC RSI
    JMP .10:
.50:CMP R10,-1        ; Test if invoked from GetArgCount with ArgNr= -1.
    JNE .60:
    DEC RCX           ; Omit the 0-th argument (the executable itself).
    JMP .90:
.60:CMP RCX,R10
    MOV RCX,0
    STC
    JNE .90:          ; Return with CF and RCX=0 when the requested Arg is not provided.
    MOV RSI,R8        ; Brutto RCX-th argument found at RSI..RDI. Trim spaces and commas.
.65:CMP RSI,RDI
    JNB .75:
    LODSB
    CMP AL,' '
    JBE .65:
    CMP AL,','
    JE .65:
    DEC RSI
.70:DEC RDI
    MOV AL,[RDI]
    CMP AL,' '
    JBE .70:
    CMP AL,','
    JE .70:
    INC RDI           ; Netto argument is now at RSI..RDI.
.75 MOV [RSP+40],RSI  ; %ReturnRSI.
    SUB RDI,RSI
    JC .80:
    MOV RCX,RDI
.80:CLC
.90:POPQ R11,R10,R9,R8,RDI,RSI,RDX,RAX
    RET 8
  ENDP1 GetArgWin64A@RT
     %ENDIF AorW
%ENDMACRO GetArg
↑ GetArgCount
counts arguments provided on the command line of the executed program. Arguments may be separated with unquoted spaces or commas. Multiple white spaces are treated like a single space. Comma-separated empty arguments are counted, too. Single apostrophe cannot be used as a quote.
Input
is taken from the command line which launched the program.
Output
CF=0
RCX=number of arguments on the command line which launched the program.
Error
CF=1 if odd number of quotes detected.
Depends on
GetArg
Tested by
twinabi.htm
Example
All examples of the command lines below will return RCX=4.
Program.exe arg1 arg2 arg3 arg4 Program.exe arg1,arg2, ,arg4 Program.exe , , , , , Program.exe arg1, "arg2,arg2" arg3 arg4
GetArgCount %MACRO
       GetArg -1, Unicode=%^UNICODE
     %ENDMACRO GetArgCount
↑ PutArg   ArgNumber, BufPtr, BufSize, IgnoreCase=Yes, Unicode=%^UNICODE

Macro PutArg retrieves ArgNumber-th argument from command-line and copies its content to the memory specified by BufPtr, BufSize,, including the NUL character at its end.

Input
ArgNumber is ordinal number of the required parameter. The 0-th parameter specifies the executable file itself. ArgNr can be immediate number, 64bit GPR or memory variable.
BufPtr is pointer to the caller-reserved memory for the argument value.
BufSize is size of the reserved memory in bytes. This 3rd parameter may be omitted, SIZE# %BufPtr will be used instead.
Unicode=%^UNICODE is extended Boolean value specifying if the string written to BufPtr should be ANSI or WIDE. By default it copies the value specified by EUROASM UNICODE=.
When the argument value expects e.g. not more than 10 characters and UNICODE=Yes, you should reserve BufSize at least 22 bytes (10+1 WIDE characters).
In €ASM you can define the output buffer as OutBuffer D (11 >> %^UNICODE) * BYTE , which will assemble as OutBuffer D 11 * BYTE when UNICODE=0 and as OutBuffer D (11 << 1) * BYTE when UNICODE= -1, always reserving room for 10+1 characters.
Output
CF=0 if the requested ASCIIZ value was successfully copied to Buffer.
RCX=number of bytes written to BufPtr, without the terminating NUL character.
RCX=0 when the ArgNumber-th argument does not exist.
Error
CF=1 if the value size overflowed BufSize and is therefore incomplete and not zero-terminated.
RCX=number of bytes written to BufPtr (never above BufSize).
Tested by
twinabi.htm
Depends on
GetArg
Example
; This example works with both ANSI and WIDE strings. EUROASM UNICODE=On ; or UNICODE=Off, no further intervention is necessary. [.bss] FileName$ DB (260 >> %^UNICODE) * BYTE ; Reserve 260 or 520 bytes. [.text] PutArg 1,FileName$
PutArg %MACRO ArgNumber, BufPtr, BufSize, Unicode=%^UNICODE
sized?  %IF %# = 2
mem?      %IF %^PASS > 1 && TYPE#(SEGMENT# %BufPtr) != 'A'
            %ERROR ID=5814, 'Please specify the size of output buffer.'
            %EXITMACRO PutArg
          %ELSE mem?  ; BufPtr is specified as a memory variable with size.
%PutArgSize %SETA SIZE# %BufPtr
           %ENDIF mem?
        %ELSE sized?  ; BufSize is explicitly specified.
%PutArgSize %SET %BufSize
        %ENDIF sized?
        PUSH RDX,RSI,RDI
          %IF "%BufPtr[1]"==="["
            MOV RDI,%BufPtr
          %ELSE
            %IF "%BufPtr" !== "RDI"
              LEA RDI,[%BufPtr]
            %ENDIF
          %ENDIF
          XOR EDX,EDX
          %IF %Unicode
            MOV [RDI],DL
          %ELSE
            MOV [RDI],DX
          %ENDIF
          GetArg %ArgNumber, Unicode=%^Unicode
          JC PutArgY%.:
          MOV EDX,%PutArgSize
          INC ECX
          %IF %Unicode
            INC ECX
          %ENDIF
          CMP EDX,ECX
          JAE PutArgX%.:
          MOV ECX,EDX
          REP MOVSB
          JMP PutArgY%.:
PutArgX%.:DEC ECX
          %IF %Unicode
            DEC ECX
            MOV EDX,ECX
            REP MOVSB
            MOV [EDI],CX
          %ELSE
            MOV EDX,ECX
            REP MOVSB
            MOV [EDI],CL
          %ENDIF
PutArgY%.:MOV ECX,EDX
         POP RDI,RSI,RDX
       %ENDMACRO PutArg
↑ PutEnv   EnvName$, BufPtr, BufSize, IgnoreCase=Yes, Unicode=%^UNICODE

Macro PutEnv retrieves environment-variable with zero-terminated name EnvName$ at run-time, and copies its content to the memory specified by BufPtr, BufSize,, including the NUL character at its end.

Input
EnvName$ is pointer to zero-terminated string with environment-variable name (ANSI or WIDE).
BufPtr is pointer to the caller-reserved memory for the variable value.
BufSize is size of the reserved memory in bytes. This 3rd parameter may be omitted, SIZE# %BufPtr will be used instead.
IgnoreCase=Yes is extended Boolean value specifying if PutEnv should search for EnvName$ case-insensitively.
Unicode=%^UNICODE is extended Boolean value specifying if EnvName$ and the string written to BufPtr are both ANSI or WIDE. By default it copies the value specified by EUROASM UNICODE=.
When the environment value expects e.g. not more than 10 characters and UNICODE=Yes, you should reserve BufSize at least 22 bytes (10+1 WIDE characters).
In €ASM you can define the output buffer as OutBuffer D (11 >> %^UNICODE) * BYTE , which will assemble as OutBuffer D 11 * BYTE when UNICODE=0 and as OutBuffer D (11 << 1) * BYTE when UNICODE= -1, always reserving room for 10+1 characters.
Output
CF=0 if the requested ASCIIZ value was successfully copied to Buffer.
RCX=number of bytes written to BufPtr, without the terminating NUL character.
RCX=0 when the environment variable is empty or does not exist.
Error
CF=1 if the value size overflowed BufSize and is therefore incomplete.
RCX=number of bytes written to BufPtr (never above BufSize).
Tested by
twinabi.htm
Example
; This example works with both ANSI and WIDE strings. EUROASM UNICODE=On ; or UNICODE=Off, no further intervention is necessary. [.bss] TempDir$ DB (260 >> %^UNICODE) * BYTE ; Reserve 260 or 520 bytes. [.text] PutEnv ="TEMP",TempDir$ ; Retrieve TEMP to TempDir$. LEA RDI,[TempDir$ + RCX] MOV RSI, ="\Cache.tmp" ; Netto 10 characters. MOV ECX, (10+1) >> %^UNICODE ; Size of the literal ="\Cache.tmp" in bytes. REP MOVSB ; Append the cache file name to TempDir$ name. ; TempDir$ now contains WIDE or ANSI string e.g. "C:\TMP\Cache.tmp",0.
PutEnv %MACRO EnvName$, BufPtr, BufSize, IgnoreCase=Yes, Unicode=%^UNICODE
sized?  %IF %# = 2
mem?      %IF %^PASS > 1 && TYPE#(SEGMENT# %BufPtr) != 'A'
            %ERROR ID=5814, 'Please specify the size of output buffer.'
            %EXITMACRO PutEnv
          %ELSE mem?  ; BufPtr is specified as a memory variable with size.
%PutEnvSize %SETA SIZE# %BufPtr
           %ENDIF mem?
        %ELSE sized?  ; BufSize is explicitly specified.
%PutEnvSize %SET %BufSize
        %ENDIF sized?
%PutEnvCase %SETB %IgnoreCase
        PUSHQ %PutEnvCase, %PutEnvSize, %BufPtr, %EnvName$
ansi?   %IF %Unicode
          CALL PutEnvWin64W@RT::
PutEnvWin64W@RT::PROC1
        XOR ECX,ECX
        PUSH RAX,RBX,RCX,RDX,RBP,RSI,RDI,R8
         MOV RBP,RSP
         XOR EAX,EAX
         MOV RDX,[RBP+10*8]   ; BufPtr.
         MOV RDI,[RSP+9*8]    ; EnvName$.
         MOV [RDX],AX         ; Prepare for the case when EnvName$ not found.
         NOT RCX
         MOV RSI,RDI
         REPNE SCASW
         NOT RCX
         MOV EDX,ECX          ; RDX=size of EnvName$ in unichars including the terminating NUL.
         LEA EDI,[2*RCX+7]
         AND EDI,-8           ; Round RDI up to DWORD.
         SUB RSP,RDI          ; Room for lowercase EnvName$.
         MOV RDI,RSP
.10:     LODSW
         CMP AX,'A'
         JB .20:
         CMP AX,'Z'
         JA .20:
         OR AL,'x'^'X'        ; Convert character to lowercase.
.20:     STOSW
         DEC ECX
         JNZ .10:
         WinABI GetEnvironmentStringsW,Fastmode=No
         MOV R8,RAX           ; Environment block.
         MOV RBX,RAX
.30:     MOV RDI,RBX
         XOR ECX,ECX
         XOR EAX,EAX
         NOT RCX
         MOV RSI,RDI
         CMP [RDI],AX         ; Test the end of environment block.
         JZ .90:              ; Return with RCX=0, CF=0, ZF=1.
         REPNE SCASW
         MOV RBX,RDI
         NOT RCX              ; RSI..RDI is ASCIIZ string EnvName=EnvVal, brutto size in unichars=RCX.
         MOV RDI,RSI
         MOV AX,'='
         REPNE SCASW
         JNE .30:
         SUB RDI,RSI
         SHR EDI,1
         CMP EDI,EDX
         JNE .30:
         LEA ECX,[EDI-1]      ; RCX is unichar-size of netto string EnvName.
         TESTB [RBP+12*8],1   ; PutEnvCase.
         JNZ .40:
         MOV RDI,[RBP+9*8]    ; EnvName$ - case sensitive search.
         REP CMPSW
         JNE .30:
         JMP .70:
.40:     MOV RDI,RSP          ; Case insensitive search.
.50:     LODSW
         CMP AX,'A'
         JB .60:
         CMP AX,'Z'
         JA .60:
         OR AL,'x'^'X'        ; Convert character to lowercase.
.60:     CMP AX,[RDI]
         JNE .30:
         ADD RDI,2
         DEC ECX
         JNZ .50:
.70:     ADD RSI,2            ; EnvName was found. RSI is now ASCIIZ EnvVal.
         MOV RDI,RSI
         XOR ECX,ECX
         XOR EAX,EAX
         NOT RCX
         REPNE SCASW
         SUB RDI,RSI          ; EDI= required-by-value brutto size of EnvVal in bytes.
         SUB EDI,2
         MOV [RBP+5*8],RDI    ; Return RCX=netto size of EnvVal in bytes.
         ADD RDI,2
         MOV RCX,[RBP+11*8]   ; BufSize. EnvVal allocated-by-user brutto size in bytes.
         CMP ECX,EDI
         XCHG ECX,EDI
         JAE .80:
         XCHG EDI,ECX
.80:     MOV RDI,[RBP+10*8]    ; BufPtr.
          REP MOVSB
.90:      PUSHFQ
           WinABI FreeEnvironmentStringsW, R8, Fastmode=No
          POPFQ
          MOV RSP,RBP
         POP R8,RDI,RSI,RBP,RDX,RCX,RBX,RAX
         RET 4*8
        ENDP1 PutEnvWin64W@RT::
       %ELSE ansi?
          CALL PutEnvWin64A@RT::
PutEnvWin64A@RT::PROC1
        XOR ECX,ECX
        PUSH RAX,RBX,RCX,RDX,RBP,RSI,RDI,R8
         MOV RBP,RSP
         XOR EAX,EAX
         MOV RDX,[RBP+10*8]    ; BufPtr.
         MOV RDI,[RSP+9*8]    ; EnvName$.
         MOV [RDX],AL         ; Prepare for the case when EnvName$ not found.
         NOT RCX
         MOV RSI,RDI
         REPNE SCASB
         NOT RCX
         MOV EDX,ECX          ; RDX=size of EnvName$ in bytes including the terminating NUL.
         LEA EDI,[2*RCX+7]
         AND EDI,-8           ; Round RDI up to QWORD.
         SUB RSP,RDI          ; Room for lowercase EnvName$.
         MOV RDI,RSP
.10:     LODSB
         CMP AL,'A'
         JB .20:
         CMP AL,'Z'
         JA .20:
         OR AL,'x'^'X'        ; Convert character to lowercase.
.20:     STOSB
         DEC ECX
         JNZ .10:
         WinABI GetEnvironmentStringsA,Fastmode=No
         MOV R8,RAX           ; Environment block.
         MOV RBX,RAX
.30:     MOV RDI,RBX
         XOR ECX,ECX
         XOR EAX,EAX
         NOT RCX
         MOV RSI,RDI
         CMP [RDI],AL         ; Test the end of environment block.
         JZ .90:              ; Return with RCX=0, CF=0, ZF=1.
         REPNE SCASB
         MOV RBX,RDI
         NOT RCX              ; RSI..RDI is ASCIIZ string EnvName=EnvVal, brutto size in bytes=RCX.
         MOV RDI,RSI
         MOV AL,'='
         REPNE SCASB
         JNE .30:
         SUB RDI,RSI
         CMP EDI,EDX
         JNE .30:
         LEA ECX,[EDI-1]      ; RCX is the size of netto string EnvName$.
         TESTB [RBP+12*8],1   ; PutEnvCase.
         JNZ .40:
         MOV RDI,[RBP+9*8]    ; EnvName$ - case sensitive search.
         REP CMPSB
         JNE .30:
         JMP .70:
.40:     MOV RDI,RSP          ; Case insensitive search.
.50:     LODSB
         CMP AL,'A'
         JB .60:
         CMP AL,'Z'
         JA .60:
         OR AL,'x'^'X'        ; Convert character to lowercase.
.60:     CMP AL,[RDI]
         JNE .30:
         INC RDI
         DEC ECX
         JNZ .50:
.70:     INC RSI              ; EnvName was found. RSI is now ASCIIZ EnvVal.
         MOV RDI,RSI
         XOR ECX,ECX
         XOR EAX,EAX
         NOT RCX
         REPNE SCASB
         SUB RDI,RSI          ; RDI= required-by-value brutto size of EnvVal in bytes.
         DEC RDI
         MOV [RBP+5*8],RDI    ; Return RCX=netto size of EnvVal in bytes.
         INC RDI
         MOV RCX,[RBP+11*8]   ; BufSize. EnvVal allocated-by-user brutto size in bytes.
         CMP ECX,EDI
         XCHG ECX,EDI
         JAE .80:
         XCHG EDI,ECX
.80:     MOV RDI,[RBP+10*8]   ; BufPtr.
          REP MOVSB
.90:      PUSHFQ
           WinABI FreeEnvironmentStringsA, R8, Fastmode=No
          POPFQ
          MOV RSP,RBP
         POP R8,RDI,RSI,RBP,RDX,RCX,RBX,RAX
         RET 4*8
        ENDP1 PutEnvWin64A@RT::
      %ENDIF ansi?
   %ENDMACRO PutEnv
↑ StdOutput String1, String2,,, Size=-1, Handle=-11, Eol=No, Console=No, Unicode=%^UNICODE

Macro StdOutput writes one or more concatenated strings to the standard output or to other equipment specified with the Handle identifier.

Strings are either zero-terminated, or the keyword Size= must specify its size in bytes. The terminating NULL character is never written.

If keyword Eol=Yes, macro writes CR+LF after all strings.

One of four possible runtime subprocedures is selected to emit, depending on the chosen ANSI/WIDE and File/Console options.
Input
String* is pointer to ANSI or WIDE string.
Size=-1 is the maximal possible string size in bytes. If its left to -1 (default), the strings must be zero-terminated. This parameter applies to all ordinal operads.
Handle=-11 is the Windows standard handle identifier. Possible output values are defined in winscon.htm: Eol=No. If Yes, two additional characters CR and LF will be written on output after all strings have been written.
Console=No (or Yes) is boolean specification whether the macro should use WinABI function WriteFile or WriteConsole.

Output of WriteFile (default) is redirectable, but it writes WIDE string as is; in OEM console are the UTF-16 encoded characters displayed as interlaced.
Output produced by WriteConsole (when Console=Yes) cannot be redirected by command-line operator > but it accepts WIDE Unicode strings and displays the text in TrueType console properly, including non-English characters.

When you want to use the Console (nondefault) mode in all StdOutput and StdInput invokations, you don't have to append ,Console=Yes to every invokation of StdOutput and StdInput if you set preprocessing %variable in the beginning of your program: %StdConsole %SETB Yes.

Unicode= %^UNICODE is boolean specification whether the Strings are in WIDE (UTF-16) encoding. By default (if omitted) it copies the global option EUROASM Unicode=.
Output
CF=0
Error
CF=1 if not all characters were written or if Handle was invalid.
Depends on
WinABI
Tested by
twinabi.htm
Examples
StdOutput Message, Eol=Yes StdOutput Eol=Yes ; Write new line (CR+LF) only. StdOutput ="Error writing to file ",FileName, Handle=STD_ERROR_HANDLE
StdOutput %MACRO  String1,String2,,,Size=-1, Handle=-11, Eol=No, Console=%StdConsole, Unicode=%^UNICODE
      IMPORT WriteFile,WriteConsoleA,WriteConsoleW
C     %IF %Console
U       %IF %Unicode
          %StdOutputFlags %SETA 2 + 1
        %ELSE U
          %StdOutputFlags %SETA 2 + 0
        %ENDIF U
      %ELSE C
W       %IF %Unicode
          %StdOutputFlags %SETA 0 + 1
        %ELSE W
          %StdOutputFlags %SETA 0 + 0
        %ENDIF W
      %ENDIF C
ArgNr %FOR 1..%#, STEP=1 ; Call the runtime for each String.
        PUSHQ %StdOutputFlags
S       %IF TYPE#(SEGMENT# %1) = 'N' ; String is specified as a scalar, e.g. RSI.
          PUSHQ %1
        %ELSE S          ; String is specified as a symbolic address, e.g. Msg or ="Msg".
          PUSH RAX
          LEA RAX,[%1]
          XCHG RAX,[RSP]
        %ENDIF S
        PUSHQ %Size, %Handle
        CALL StdOutputWin64@RT
        %SHIFT 1         ; The next string to output.
      %ENDFOR ArgNr
Eol   %IF %Eol
U       %IF %Unicode
          PUSHQ %StdOutputFlags + 4, 0, 4, %Handle
        %ELSE U
          PUSHQ %StdOutputFlags + 4, 0, 2, %Handle
        %ENDIF U
        CALL StdOutputWin64@RT
      %ENDIF Eol
StdOutputWin64@RT:: PROC1 ; Invoked in stdcall convention with parameters Handle,Size,Addr,Flags.
        PUSHQ RAX,RCX,RDX,RDI,R8,R9,R10,R11,0,0
         %StdOutputWritten %SET RSP+0   ; QWORD memory for characters-written number.
         %StdOutputEol$    %SET RSP+8   ; Local string CR+LF.
         %StdOutputHandle  %SET RSP+88
         %StdOutputSize    %SET RSP+96
         %StdOutputString  %SET RSP+104
         %StdOutputFlags   %SET RSP+112
         MOV RCX,[%StdOutputHandle]
         WinABI GetStdHandle,RCX,Fastmode=Yes
         CMP RAX,-1
         STC
         JZ .90:  ; Abort with CF when INVALID_HANDLE_VALUE (-1).
         MOV R8,RAX        ; StdOutput handle.
         MOV RCX,[%StdOutputSize]
         MOV RDI,[%StdOutputString]
         MOV RDX,[%StdOutputFlags] ; Bits 0=Unicode, 1=Console, 2=Eol.
         MOV R11,WriteFile::
         TEST DL,2         ; Select File or Console mode.
         JZ .10:
         MOV R11,WriteConsoleA::
         TEST DL,1         ; Select ANSI or WIDE variant.
         JZ .10:
         MOV R11,WriteConsoleW::
     .10:TEST DL,4         ; Test if the string should be Eol.
         JZ .20:
         LEA RDI,[%StdOutputEol$] ; EOL is requested instead of string.
         MOVD [RDI],0x000A_000D ; WIDE EOL.
         TEST DL,1
         JNZ .20:
         MOVW [RDI],0x0A0D ; ANSI EOL.
     .20:XOR EAX,EAX       ; RDI,RCX is ASCIIZ string.
         MOV R10,RDI
         TEST DL,1         ; ASCII | WIDE.
         JZ .40:
         SHR ECX,1         ; Convert size to length in characters.
         REPNE SCASW       ; Find the WIDE zero terminator.
         JNE .50:
         SUB RDI,2         ; Omit the zero terminator.
         JMP .50:
     .40:REPNE SCASB       ; Find the ANSI zero terminator.
         JNE .50:
         DEC RDI
     .50:SUB RDI,R10       ; RDI is now the string size in bytes.
         AND DL,3
         XOR DL,3
         JNZ .60:
         SHR EDI,1         ; RDI is now string size in WIDE characters for Console.
         MOV RDX,RSP
     .60:MOV RCX,R8        ; File/Console handle.
         MOV R9,RSP        ; Characters-written variable.
         MOV R8,RDI        ; String length in characters.
         MOV RDX,R10       ; Pointer to string.
         WinABI R11,RCX,RDX,R8,R9,RAX,Fastmode=Yes
         CMP [%StdOutputWritten],RDI ; Set CF if not all characters were written.
    .90:POP RAX,RAX,R11,R10,R9,R8,RDI,RDX,RCX,RAX
        RET 4*8
      ENDP1 StdOutputWin64@RT::
 %ENDMACRO StdOutput
↑ StdInput Buffer, Size=, Handle=-10, Console=No, Unicode=%^UNICODE

Macro StdInput reads a line of text terminated with CR from standard input device (usually the keyboard) specified by the Handle identifier.

Input
Buffer is offset of memory where the input string will be stored. It can be a GPR, too.
Size= is the Buffer size in bytes. If omitted (default), macro will use SIZE# attribute of the Buffer.
Handle=-10 is the Windows standard input handle identifier. Possible input values are defined in winscon.htm: Console=No (or Yes) is boolean specification whether the macro should use WinABI function ReadFile or ReadConsole.
Unicode=%^UNICODE is boolean specification whether the characters read from console should be in WIDE (UTF-16) encoding. It is ignored when Console=No.
Input of ReadFile (default) is redirectable, but it does not distinguish WIDE or ANSI characters. ReadConsole mode can only be used with a console input buffer handle (no redirection by command-line operators < or | is working) but it interprets WIDE characters properly.
Output
CF=0, RCX=number of bytes read.
Error
CF=1, RCX=0.
Tested by
twinabi.htm
StdInput %MACRO Buffer, Size=, Handle=-10, Console=No, Unicode=%^UNICODE
    IMPORT ReadFile,ReadConsoleW,ReadConsoleA
C   %IF %Console
U     %IF %Unicode
        %StdInputFlags %SETA 2 + 1
      %ELSE U
        %StdInputFlags %SETA 2 + 0
      %ENDIF
    %ELSE C        ; ReadFile variant. Ignores Unicode.
W     %IF %Unicode
        %StdInputFlags %SETA 0 + 1
      %ELSE W
        %StdInputFlags %SETA 0 + 0
      %ENDIF W
    %ENDIF C
    PUSHQ %StdInputFlags
B   %IF TYPE#(SEGMENT# %Buffer) = 'N' ; Buffer is specified as a scalar, e.g. RSI.
       PUSHQ %Buffer
    %ELSE B          ; Buffer is specified as a symbolic address, e.g. Answer or [Answer+RDI].
M     %IF '%Buffer[1]' === '['
        PUSH RAX
        LEA RAX,%Buffer
        MOV RAX,[RAX]
        XCHG RAX,[RSP]
      %ELSE M
        PUSH RAX
        LEA RAX,[%Buffer]
        XCHG RAX,[RSP]
      %ENDIF M
    %ENDIF B
S   %IF '%Size' === ''
      PUSHQ SIZE# %Buffer
    %ELSE S
      PUSHQ %Size
    %ENDIF S
    PUSHQ %Handle
    CALL StdInputWin64@RT
StdInputWin64@RT:: PROC1
    PUSHQ RAX,RDX,R8,R9,R10,R11,0
     %ReturnedRCX    %SET RSP+0
     %StdInputHandle %SET RSP+64
     %StdInputSize   %SET RSP+72
     %StdInputBuffer %SET RSP+80
     %StdInputFlags  %SET RSP+88
     MOV RCX,[%StdInputHandle]
     WinABI GetStdHandle,RCX,Fastmode=Yes
     CMP RAX,-1
     STC
     JZ .90: ; Abort with CF when INVALID_HANDLE_VALUE (-1).
     MOV RCX,RAX
     MOV RDX,[%StdInputFlags] ; Bits 0=Unicode, 1=Console.
     MOV R8,[%StdInputSize]
     MOV R11,ReadFile
     TEST DL,2
     JZ .20:
 .10:MOV R11,ReadConsoleA
     TEST DL,1
     JZ .20:
     MOV R11,ReadConsoleW
     SHR R8,1
 .20:CMP DL,3
     MOV RDX,[%StdInputBuffer]
     MOV R9,RSP    ; %ReturnedECX.
     PUSHFQ        ; Remember in ZF if Unicode&&Console.
      WinABI R11,RCX,RDX,R8,R9,0,Fastmode=Yes
     POPFQ
     CLC
     JNE .90:
     SALQ [RSP],1  ; Convert read character to bytes when Unicode&&Console.
.90:POPQ RCX,R11,R10,R9,R8,RDX,RAX
    RET 4*8
   ENDP1 StdInputWin64@RT::
 %ENDMACRO StdInput
↑ TerminateProgram Errorlevel=0
This macro provides exit from the running process and the return to the operating system.
It also specifies the Errorlevel (plain number) which can be used to inform the batch script which launched the program whether the program terminated normally or due to some error condition.
Input
Errorlevel= is the return code of the terminating program.
Beside the keyword Errorlevel=, this value may also be specified as an ordinal operand.
When this argument is omitted, it defaults to 0.
Output
is not applicable.
Example
TerminateProgram Errorlevel=[WorstErrLevel] ; Keyword value (from memory). TerminateProgram 8 ; Ordinal value.
TerminateProgram %MACRO Errorlevel=0
     %IF %#=1 ; If ordinal provided.
       MOV RCX,%1
     %ELSE    ; If keyword provided.
       MOV RCX,%Errorlevel
     %ENDIF
     SUB RSP,4*8
     IMPORT ExitProcess, Lib="kernel32.dll"
     JMP ExitProcess
   %ENDMACRO TerminateProgram
   ENDHEAD winabi

▲Back to the top▲