MasmBasic Quick Reference

extracted from \Masm32\MasmBasic\MbGuide.rtf version 20.02.2024
- see original RTF file for correct formatting and latest additions (RichMasm menu File/MasmBasic help)
- the invitation to 'select Init and hit F6 to test this snippet' works only when opening MasmBasic help in RichMasm

Download the library and RichMasm from the Masm32 forum - (you need the Masm32 SDK to use it: see Masm32 Tips, Tricks and Traps)

MasmBasic signed ErrLines EndOfCode For_ Step Fn mcs xmov jinvoke @LibUsed$ @AsmUsed$ @Upper$ @Lower$ @MbRet Admin MbWinVersion ComCtl32$ wComCtl32$ Switch_ Case_ Default_ Endsw_ Try Catch Finally TryRTE Init Exit CL$ wCL$ If_ Then If? Div_ IsTrue CodePage$ CodeSize CheckStackBalance DlgDefine DlgControl DlgShow SetGlobals Enum Enum$ Choose Let wLet Cat$ wCat$ StringBuild Lset Read $ DefStruct Dll Declare SetDllFolder HeapStrings deb fdeb ifdeb Err$ SetErrLine Sound Say Asc Cvi Cvl Odd Abs ModZero Min Max PopCount Bsr64 Rand Rand64 Encrypt Decrypt FolderOpen$ FileOpen$ FileSave$ wFileOpen$ wFileSave$ wFileOpenSetFolder Open wOpen Close Seek Lof Loc Eof Input LineInput Input$ wInput$ Prompt$ Rename WritePipe ClosePipe Launch ShEx SetLaunchTimeout Launch$ LaunchEndPos SetLaunchEnvironment ExitCode( Exist LastFileSize LastFileName$ LastFileDosName$ IsFolder IsRichEdit Kill Touch GetFiles AddFiles Files$ GetFolders AddFolders GfCallback SortFiles GfDate$ GfTime$ GfAgeHours GfAgeMinutes GfAgeMs GfLastWrite GfSize GfGetInfo GfSetInfo Age CurDir$ GetDrives$ Ini$ SetMru AddMru MruText$ ExpandEnv$ wExpandEnv$ ExeFromWin$ ExeFromExt$ GetFileProps FilePropName$ MakeDir ArcFiles ZipFiles UnzipInit UnzipFile( FileRead$ LastFileSize Download WM_DOWNLOADFINISHED NoTag$ FileWrite Replace$ ParentData$ CopyData$ SendData SendControlKey SendWordCommands OpenAdo xlsConnect xlsOpen xlsCommand xlsSysRead$ xlsRead$ xlsWrite xlsClose xlsDisconnect xlsHotlink ddeConnect ddeCommand ddeRequest$ ddeDisconnect Win$ wWin$ SetWin$ wSetWin$ AddWin$ wAddWin$ SetSel$ Sel$ wSel$ CreateAndSelect MakeBrush MakeFont FontExist( PickFont ImgPaint ToolTips WinByTitle WinByClass App16 Clip$ wClip$ ClipboardImageWH SetClip$ wSetClip$ SetHtmlClip$ MsgMonitor GuiImage GuiImageCallback SaveImageToFile GuiImageSpeed GuiImageFrame GuiControl GuiGroup Link$ Coloured EndOfEvents GuiEnd SetListbox SetCombobox Event Event Event Event Event Event Event Event Event Event GuiParas GuiMenu GuiColor GuiText GuiTextBox GuiCls GuiLine GuiCircle GuiEllipse GuiMs GuiSetFill icon MakePath GuiDraw MakePen Legend SetDoc$ GetDoc$ SetTitle$ SetStatus$ New$ Resize Alloc16 Free16 MemSet MemState Res$ wRes$ wRec$ ResFile$ Lg$ Geo$ MsgTable$ String$ SpaceToTab$ Inkey wInkey Print wPrint Print wPrint CurDir$ PrintLine wPrintLine PrintBuffer PrintRtf PrintCpu Cpu$ SetCpUtf8 SetCpAnsi SetCpUpperLower$ ConvertCp$ ConsoleColor Locate( At CColor crtbuf xCall CreateInvoke CRT QuadMath Quad Quad$ gsl SetPoly3 GetPoly3 GetLinReg LinRegY Dim Erase VarPtr ArrayFill wArrayFill ArrayStripDuplicates ArraySet StringToArray Split$ Join$ Filter$ ArrayMerge Swap Insert Delete GuidFromString Guid$ GuidsEqual CoInvoke Ole$ BSTR wChr$ Utf8$ Chr$ RichEd$ Len wLen uLen MbCopy Bmove SafeCopy MbCopyz MsgBox wMsgBox Alert( Clr Clr$ Cls ClearLastError ClearFileCache ClearLocals _Local Locals StackBuffer StackWalk SetWatch Instr_ Rinstr wInstr InstrOr Rinstr wRinstr RinstrX wCount LineCount LineNumber Clean$ Strip$ StringsDiffer wStringsDiffer FilesDiffer uLeft$ uMid$ uRight$ Left$ wLeft$ Mid$ wMid$ Right$ wRight$ Lower$ wLower$ Upper$ wUpper$ Trim$ Ltrim$ Rtrim$ Qtrim$ Ntrim$ Mirror$ Date$ wDate$ Time$ wTime$ IsoWeek Json$ GetTZ$ fDate$ fTime$ wfDate$ wfTime$ TimeSF TimeDiff Timer NanoTimer CyCtStart WinFromID WinFromID$ Align64 AlignX Recall Csv2Tab wRecall Store StoreUtf8 _Passed$ QSortDesc Scramble QSortMode BitSort ArrayMinMax ArrayInfo ArrayRead ArrayStore ArrayFind ArrayIndex ArraySearch ArrayLoadMap PaintMap MapColours CanvasMap( SetFolder MapRegion MapRegion$ WM_MAPCLICKED ArrayMapRegion CanvasZoom ArrayPlot ArrayPlotValues SetAxisX SetAxisY RgbCol CgaCol SysCol Red Green Blue Str$ wStr$ Quad$ SetFloat SetInt AddFloat Int64 Int128 shlXmm Float MulQQ Floor Ceil Sinus Cosinus rSinus rCosinus ArcSinus ArcCosinus ArcTangens FpuSave FpuRestore FpuPush FpuFill FpuSet FastMath MbMod LogE Log10 Exp10 Exp2 ExpE ExpXY Percent f4Percent f8Percent fpuPercent DefNum Hex$ HexDump$ Bin$ IntAsWords$ Qcmp Ocmp Oword16( Fsign Fcmp Val Val? HexVal MovVal Sqrt SetField GetField GetHash SetFlags Flags MouseX MouseY MouseK Delay void voidTrue voidFalse GetRegVal SetReg64 SetRegVal GetRegKeyArray GetRegArray SetReg64 GetProcessArray FindProcess GetDevicesArray PushText TitleCase$ Masm32

; The smallprint: this library is provided "as is", and the usual disclaimers apply.
; --------- This help file refers to MasmBasic version 15 February 2024 --------------------------
; It is assumed that you installed the library from http://masm32.com/board/index.php?topic=94.0
; To start a new project, go to ^ File/New Masm source ^ and click on one of the links, e.g. console.
; Once you see the file in front of you, hit F6 to assemble, link and run the example.
; For help on MasmBasic keywords, hover some seconds over e.g. Init until the mouse cursor turns
; into ? (i.e. a question mark), then right-click to see detailed examples. If you left-click quickly into
; the green area, you may select code for your own use. Click outside the green area to leave it.
; the following line substitutes the standard Masm32 include \masm32\include\masm32rt.inc:
; -----------------------------------------------------------------------------------------------------------------
; Hit F6 to assemble, link & run the Hello World example:
include \masm32\MasmBasic\MasmBasic.inc
Init
PrintLine "Welcome to assembly, friend from ", Geo$(), "..."
MsgBox 0, "Wow, it works!!!!", "Hi", MB_OK     ; It worked? Use a template, or try 99 snippets
PrintLine "OK - press any key"        ; It didn't? Give me feedback here.

EndOfCode
; -- same as dual 32-/64-bit assembly code (click here for a Masm64 SDK example): --
; select Init below, then hit F6 to build & run this example:
include \masm32\MasmBasic\Res\JBasic.inc    ; instead of JBasic, you might try the Masm64 SDK
    Init                ; OxPT_64 1    ; delete the x for 64 bit assembly
PrintLine "More examples under menu File/New Masm source: Dual 32/64 bit console/GUI templates"
MsgBox 0, Chr$("Built with ", @AsmUsed$(1), " in ", jbit$, "-bit format"), "Wow, it works:", MB_OK

EndOfCode
Practical hints for using MasmBasic
- You cannot use MasmBasic with the old ML.exe that comes along with Masm32 (version 6.14 doesn't know about SSE2...).
    Instead, you should use UAsm. When you hit F6 to assemble & link a MasmBasic source, RichMasm will invite you to
    install UAsm from the web; if that fails for some reason (e.g. antivirus software), download UAsm64 manually
    (64bit Binary Package), then copy UAsm64.exe from the archive to \Masm32\bin
- if you prefer another editor, like qEditor, you can either modify the build batch files, or a) rename \Masm32\bin\ml.exe
    to \Masm32\bin\ml_old.exe, b) then save UAsm64.exe as ml.exe
- Use the RichMasm editor: It helps if you can type opi (i.e. opi) and you get Open "I", #1, ...; for large sources, its
    search function is outstanding. For example, select Key and hit F3 to see a list of all shortcuts for MasmBasic keywords.
    Other features you may like are the bookmarks to the right, and the history: Press Alt left arrow and Alt right arrow to
    see where you recently edited your source. Press Alt K (K like keys) to toggle bookmarks with a list of keyboard shortcuts.
- To find commands, use the find box in the upper right corner of the RichMasm editor. The string .xMb lists all MasmBasic
    commands, while .xMbF lists commands starting with F. You can use wildcards, e.g. .Mxb*file will find commands that contain
    the word file. Note also the little Case box above the find box.
- You should associate the *.asc (Assembler Source Code) file extension with \Masm32\MasmBasic\RichMasm.exe
- RichMasm is a Unicode editor -    try a multilingual example
- RichMasm uses the RichEdit msftedit.dll; crashes are very rare but may happen. Save your work frequently, keep backups,
    reflect where to click if you see a "You may have had a crash" message, or try the menu File/Last good version - it opens
    the last version that assembled without errors. You will appreciate this function ;-)
- Colours are purely illustrative. RichMasm tries to colour MasmBasic commands in blue if you use the keyword shortcuts,
    e.g. sto (space) becomes Store "MyFile.txt", L$(), ct, but colours have no influence on how Masm reads your code.
- You can define your own shortcuts: select the string #box invoke MessageBox, 0, Str$(eax), Chr$("Title"), MB_OK, then
    right-click and choose Save shortcut. Afterwards, typing box expands the shortcut.
- Register preservation:    When calling Windows APIs, e.g. invoke MessageBox, 0, Str$(eax), Chr$("Title"), MB_OK,
    the registers esi edi ebx ebp esp will be preserved by Windows; eax will return a value, and ecx and edx will most probably
    contain garbage. Beginners stumble sooner or later over this problem    when they use ecx for a loop and see a crash.
    MasmBasic behaves like Windows, except that it does preserve ecx.    Therefore, use the values returned in eax, never rely
    on edx after any macro or invoke except if edx returns something, and rely on ecx only if you have not used any invokes to
    Windows APIs in your loop. Note that standard Masm32 macros like print, str$() etc do invoke Windows APIs and therefore
    do trash ecx. Use Print and Str$() instead.
- Preservation of xmm registers: All xmm registers are preserved, with the exception of three compare functions:
Fcmp, StringsDiffer and FilesDiffer, which use xmm0... xmm2. The regs xmm3..xmm7 will never be trashed by MasmBasic
    itself, but (WARNING) Windows in its 64-bit versions has the bad habit to trash xmm0 ... xmm5. MasmBasic preserves all
    xmm regs, but check carefully what happens if you use invoke MessageBox, ... instead of MsgBox 0, "text", "Hi", MB_OK !!
- Another popular error: Masm uses by default unsigned comparisons. This example demonstrates the effect:
    mov eax, -1        ; definitely a lot smaller than 99, right?
    .if eax<99
        MsgBox 0, Str$(eax), "-1 is less than 99:", MB_OK
    .else
        MsgBox 0, Str$(eax), "Surprise, surprise: -1 is bigger than 99!", MB_OK
    .endif
    The problem is that Masm reads -1 as 4294967295, i.e. 2^32-1, or 0FFFFFFFFh. To avoid this, MasmBasic provides
    the signed equate: .if signed eax<99 will produce the behaviour you expect.
- You may see cryptic error messages; for example, Let esi=Trim$(FileRead$("Test A.txt")) will fail with "forced error".
    Often, reducing the nesting level, i.e. splitting in Let esi=FileRead$(...), then Let esi=Trim$(esi) helps.
- MasmBasic uses the Masm MACRO engine. As with all libraries using macros (e.g. print, str$, len... in Masm32), be aware
    that 0pmacro expansion takes place before the current line. Normally, this is no problem, but in rare cases incorrect code may be
    produced. One such case is .if Len(My$)==1 ... .exlseif Len(My$)==2 - this does indeed generate code for the second statement,
    but this code will never be executed. To avoid this scenario, use for example:
    .if Len(My$)==1
        ...
    .else
        .if Len(My$)==2    ; code will be inserted before the .if and after the .else, and produce the expected result
            ...
        .else
            .if eax==3    ; hint: Len returns eax, so instead of calling Len once more, you may continue to test eax
                ...
            .endif
        .endif
    .endif

Error messages

ErrLines
When debugging, use ErrLines to see the line where a runtime error occurs; if you still
get only question marks in runtime error messages, use SetErrLine in the suspect zone.


EndOfCode
    include \masm32\MasmBasic\MasmBasic.inc
    Init        ; select Init and hit F6
    MsgBox 0, "Easy!!", "Hi", MB_OK
   
EndOfCode
Rem    does some cleanup; use instead of "end start"


For_ .. Next
    include \masm32\MasmBasic\MasmBasic.inc
    SetGlobals fct:REAL8
    Init        ; select Init and hit F6
    For_ ct=0 To 3
        Print Str$("\nouter loop %i\t", ct)        ; nested loops are ok
        For_ cta=0 To 2
            Print Str$("A%i ", cta)
            For_ ecx=0 To 2
                Print Str$("B%i ", ecx)
            Next
        Next
        For_ ecx=0 To 2
            Print Str$("C%i ", ecx)
        Next
    Next
    Recall "\Masm32\include\Windows.inc", My$()    ; get a string array
    Delete My$(10), -1        ; keep 0...9, delete the rest
    mov eax, 2000        ; note it is not necessary to define the
    For_ n=0 To eax-1000        ; counter (here: n ) in the data section,
        Print Str$("\nLoop %i", n)        ; if the counter is an integer
        .Break .if n==9
    Next n
    Print
    For_ ct=10 To 0 Step -1        ; Step may be negative
        Print Str$(ct), " "        ; print a countdown
    Next
    Print
    For_ fct=1.5 To 2.0 Step 0.1        ; counter is a float, and therefore needs to be           
        Print Str$(fct), Spc2$        ; previously defined in .data? or with SetGlobals
    Next
    For_ each esi in My$()        ; loop through elements of an array
        PrintLine Str$(ForNextCounter), Tb$, esi    ; print the counter and each element
    Next
    GetFiles *.ini        ; fill the Files$() array
    For_ each edi in Files$() : PrintLine edi        ; one-liner: for..each only, needs a register
    For_ each ecx in Files$() : <PrintLine Str$("file %__i\t", ForNextCounter), ecx>    ; use for complex statements
   
EndOfCode
Rem    - if begin>end (or beginStep), the loop will not be started; use MbForReject=0 to change this
    - the optional Step variable must be an immediate integer (or real, if the counter is defined as REAL4/8/10)
    - in case you have too many For_ ... Next loops, MbForMax can be increased
    - For_ each ... can be used for string, numeric and structure arrays; for some types, edi is being used; with usedeb=1,
        a warning will be shown. Note that For_ each esi in somearray() is valid code, too.
    - For_ n=0 To eax-1 is valid code; you can nest several For_ loops, but you must use different counters.
Key    for_


Fn
    Dim floats() As REAL8
    Print Str$("%i items converted", Fn(StringToArray "12 34 56", floats()))
Rem    use Fn() as "function format" for commands that do not "return" anything


mcs    multiple commands
    mcs mov eax, 100 : add eax, 30 : sub eax, 7: deb 4, "sum:", eax : Inkey "-- press any key --"    ; OK
    mcs mov eax, 100 : add eax, 30 : sub eax, 7: <Print Str$("result: eax=%i\n", eax)>    ; avoid, or test carefully
    mcs For_ ecx=0 To 19: <Print Str$("%i ", ecx+1)>: Next        ; a MasmBasic loop
Rem    - mcs allows to put several commands on one line, separated by a colon as in some Basic dialects
    - macros that depend on previous commands, like e.g. Print Str$(...) should start in a new line, or be put
        ; due to a rare incompatibility, ML and UAsm/AsmC may produce different results


xmov    extended mov
    xmov MyR8, 123456.7890123456    ; assign a double directly
    xmov MyR4, MyR8    ; mem to mem, different operand size
    xmov someSQ, someSD
    xmov someSD, someSQ
    xmov ct, -5
    xmov MyR4, 32767
Rem    - use if in doubt about the best way to shove a value into a memory location (check the disassembly)
    - works with MasmBasic and 64-/32-bit JBasic


jinvoke
    jinvoke MyTest, &v0, *v1, addr v2, offset v3, &v4, v5
Rem    - jinvoke is a macro that works like invoke but assembles with 32- and 64-bit code alike
    - see menu File/New Masm source/Dual 32/64 bit console/GUI templates


@LibUsed$(), @AsmUsed$(), @Upper$(), @Lower$()
    include \Masm32\MasmBasic\Res\JBasic.inc    ; needs uppercase opt_64 1
    Init
    % echo @AsmUsed$(0)        ; no quotes
    tmp$ CATSTR , @Upper$(Norwegian)    ; for use in macros
    % echo tmp$
    PrintLine Chr$("This code was assembled with ", @AsmUsed$(), " in ", jbit$, "-bit format using the ", @LibUsed$(), " library")
   
EndOfCode
Rem    - output for the example: This code was assembled with UAsm64 in 64-bit format using the JBasic library
    - with no args, i.e. (), quoted strings are returned; use e.g. @AsmUsed$(0) to return the plain string without quotes


@MbRet
    SomeAlgo proc uses esi arg1, arg2
    push ecx
    @MbRet            ; shout foul if the stack is not balanced
    SomeAlgo endp
Rem    - use instead of ret; active for usedeb=1 or useMbRet=1, otherwise only a ret will be generated
    - if the stack is not balanced, an error message will be written to the console
    - do NOT use at the end of callback functions


Admin
        If_ not Admin() Then PrintLine "This program must be run as admin"


MbWinVersion
    include \masm32\MasmBasic\MasmBasic.inc
    Init    ; select and hit F6
    Print Str$("This is Windows version %i", MbWinVersion()), Str$(".%i", ecx)
    Print Str$(", build %i\n", MbWinVersion(build))
    Print Str$("The long story: Windows version %i", MbWinVersion()), Str$(".%i ", ecx),\
    GetRegVal("HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "ReleaseID", "(no version #)"),\
    Str$(", build %i\nrunning on ", MbWinVersion(build)), Cpu$()
   
EndOfCode
Rem    - returns major version in eax, minor version in ecx, and build in dx
    - HKLM ... works on Win10, but older Windows versions do not have the ReleaseID value
    - for structures that require bytes, use mov xy.byte, MbWinVersion(b)


ComCtl32$     test your manifest - returns the DLL version
    include \masm32\MasmBasic\MasmBasic.inc    ; download
    Init    ; select and hit F6
    Print "CC: ", ComCtl32$()
    MsgBox 0, ComCtl32$("This program uses common controls version %3f"), "Hi", MB_OK
   
EndOfCode XP    ;
EndOfCode with the optional XP adds a manifest (polink only)
Rem    - returns 5.82 (no or bad manifest) or 6.?? for XP and higher
    - use wComCtl32$ for GuiControl
    - you may use a format string containing %3f



Switch_, Case_, Default_, Endsw_
    The MasmBasic Switch_ macro (note the understroke) is more powerful than its C or GfaBasic equivalents, since
    it can handle even variables or registers in the "cases". Under the hood it creates, depending on the range covered
    and the number of cases, either an if .. elseif .. endif chain or a jump table that is for long lists of cases (more than 4)
    much faster than the Masm32 macro. Below an example for MasmBasic Switch_
    include \masm32\MasmBasic\MasmBasic.inc
    SetGlobals int somevar=5
    Init            ; ## Switch with jump table ##
    m2m ecx, -5
    PrintLine "----------------------------- testing the new MasmBasic Switch_ macro -----------------------------"
    .Repeat
        Print Str$(ecx), Tb$
        m2m edx, -127    ; don't trigger the edx case...
        If_ ecx==11 Then mov edx, ecx    ; ... except for testing the Case_ edx at position 11
        Switch_ ecx
        Case_ lt -2
            PrintLine "Case less than -2"
        Case_ somevar: <PrintLine Str$("Case var=%i", somevar)>    ; needed if a macro returns something
        Case_ edx    ; this case triggered if ecx==edx; takes preference over 'immediate'
            PrintLine "Case edx ###"    ; cases but must come before lt or gt cases
        Case_ -2
            PrintLine "Case -2"
        Case_ 0
            PrintLine "Case NULL"
        Case_ 10, 12
            PrintLine "Case 10 or 12"
        Case_ 18
            PrintLine "Case 18"
        Case_ 14 .. 16 : PrintLine "Case 14 .. 16 (one-liner)"    ; OK if only one instruction is needed
        Default_
            PrintLine "---"    ; no matching case found
        Endsw_
        inc ecx
    .Until signed ecx>20
   
EndOfCode
Rem    - Switch_ trashes edx but not eax (unless Case_ does it)
    - do not forget the understroke (Switch_, Case_, Default_, Endsw_), otherwise you get the Masm32 switch macro
    - Inside a case, the stack is 4 bytes off; if you really need values on the stack, use e.g. mov eax, stack[4] to compensate this.
    - You can force Endsw_ chain or Endsw_ table; with few cases that are short and far apart, e.g. case -1000, case 1, case 1000,
        Endsw_ table creates a huge jump table with many default entries; the chain mode will be slower but more size-efficient.
Key    Swi


Try/Catch/Finally
MasmBasic supports Structured Exception Handling (SEH).
See \masm32\MasmBasic\Res\Templates.asc for a detailed example.



TryRTE
include \masm32\MasmBasic\MasmBasic.inc    ; download
    Init    ; select init and hit F6 to build this
    ErrLines    ; add extra code to get source code line where error occurred
    Dim My$(3)
    TryRTE BadElement, con    ; optional: con means errors to console, key means con+(wait for a keypress); default is box
    Let My$(5)="This won't work"    ; the index is too high (incrementing gradually to 4, 5, 6 would be OK)
BadElement:
    cmp edx, $    ; simple error check
    .if Zero?
        PrintLine "Bad index!!"
    .endif

EndOfCode

Rem    MasmBasic can throw a variety of runtime errors, such as "file not found" etc.; note that after
    showing them, MB will normally call ExitProcess. This can lead to tricky situations if you use
    MB in a dll. To prevent killing your main process, use TryRTE as shown above.



Error    A2121: Symbol not defined : Use_mov_z$_Left$
Cause    You tried to use mov esi, Left$("Test", 3)
Fix    Use Let esi=Left$("Test", 3), or use z$ as follows:
    MsgBox 0, z$(Right$("Hello Masm32", 6)), "z$:", MB_OK

Error    A2148: invalid symbol type
Cause    You tried to store an array that was not yet defined in the line where you call Store
Fix    Put the Store into a proc, and move that proc below the line that defines the array, e.g. below the Recall


*** Basic instructions ***
RichMasm:    To test the examples with white background, copy them into the nop after start, then copy them into the
        nop after start, make sure required variables are present (e.g. MyReal10 REAL10 123.456 in the .data
        section), then press F6 to see how the example works.
        If the include MasmBasic.inc line and Init are present, do not copy the content; instead, select the
        Init and hit F6, as in the example below (or, if there is no Init, select the whole white zone and hit F6).

Init    This is perhaps the smallest possible complete Masm application:
    include \masm32\MasmBasic\MasmBasic.inc
    Init            ; <<<<<<< select and hit F6
    Inkey "Hello World"    ; print text to console and wait for a key
   
EndOfCode        ; combines Exit and 'end start'
Rem    - the Init macro inserts at least the following two lines:
    .code
    start:    call MbBufferInit
    - using Init is optional; it declares the code section, the start label, and preloads some strings (CrLf$, Tb$ etc.);
        however, the check for these preloaded strings will be performed in the first Print or Let statement, too.
    - use Init tc [, con/key/box] to install a Structured Exception Handler with Try/Catch (\MasmBasic\details).


Exit    The correct way to leave a MasmBasic application
    Exit
    Exit eax
    Exit 123
    Exit debug        ; checks if all Dims and string allocations have had matching deallocations on exit
    Exit debug, 2000    ; same but waits two seconds
    Exit debug, box    ; same but displays a box instead
Rem    - releases memory allocated for arrays or strings, then invokes ExitProcess
    - after using strings or arrays, you should use Exit to quit an application
    - do not confuse MasmBasic Exit with the lowercase Masm32 library exit
Key    exit


CL$(), wCL$()        command line args
    MsgBox 0, CL$(), "Hi", MB_OK        ; display all args in the command line (the normal MsgBox trashes ecx - this one doesn't!)
    wMsgBox 0, wCL$(), "Hi", MB_OK        ; same as wide (Unicode) version
    MsgBox 0, Cat$("Arg1: "+Tb$+CL$(1)+CrLf$+"Arg2:"+Tb$+CL$(2)), "Args with Cat$:", MB_OK
    Let esi="Arg1: "+Tb$+CL$(1)+CrLf$+"Arg2:"+Tb$+CL$(2)
    MsgBox 0, esi, "Args with Let:", MB_OK
    Print "Testing the MasmBasic CL$() function:"
    xor ebx, ebx
@@:    ; this snippet prints all args on screen:
    mov ecx, CL$(ebx)        ; do not put this line directly to the right of the @@ label (more)
    jecxz @F
    Print Str$("\narg #%i = [", ebx), ecx, "]"
    inc ebx
    jmp @B
@@:    Inkey Str$("\n%i valid args found", ebx-1)    ; instead, you can also use CL$(?) to get the number of arguments
Rem    - returns pointer in eax, or zero if there is no argument
    - if used with Print, Let or Cat$(), and there is no arg, a question mark will be inserted
    - if no arg is supplied, i.e. CL$(), then one arg is assumed, even if spaces are present
        (useful e.g. to open C:\Documents and Settings\user\Documents\My File.txt);
        in this case, the commandline must not exceed 512 characters
    - CL$(0) yields the path of the executable
    - if you need args permanently, use Let MyArg$=CL$(1)


If_ ... Then     One-line if with one or more statements
    ; Warning: this macro is powerful but read the Rem section carefully
    If_ eax Then inc ecx    ; If_ condition Then action
    If_ not eax Then dec ecx
    If_ NOT eax==1 && ecx>eax Then Print "Hello"
Rem    - use for single-line if statements
    - If_ !eax Then is not possible (unless you use four exclamation marks...); instead, use
        If_ not eax Then ...
    - If_ and Then are case-sensitive, not is not
    - Warning: function macros (e.g. Val(...)) are evaluated from left to right before entering the If_ macro,
        so eax and edx are definitely trashed before e.g. If_ eax==123 - caution, it will fail miserably!! To prevent
        this behaviour, you can block premature expansion with around the function:
            If_ eax==5 Then Print <Str$("eax=%i", eax)>



If?    ternary operator
    Syntax: mov eax, If?(comparison, result_true, result_false)
    mov ecx, 123
    mov edi, ecx
    Print Str$("ecx eq 123: %i\n", If?(ecx eq edi, 111, 222))                    ; if ecx equals edi, take 111, otherwise 222
    Print Str$("ecx ne 123: %i\n", If?(ecx ne 123, 111, 222))
    PrintLine "ecx above 123: ", If?(ecx ab 123, "above", "not above")            ; unsigned comparison, string output
    PrintLine "ecx below 123: ", If?(ecx bl 123, "below", "not below")
    PrintLine "ecx ge 123: ", If?(ecx ge 123, "greater or equal", "not greater or equal")    ; signed comparison, string output
    PrintLine "ecx gt 123: ", If?(ecx gt 123, "greater", "not greater")
    invoke ShowWindow, hEdit1, If?(rv(IsWindowVisible, hEdit1), SW_HIDE, SW_SHOW)    ; toggle visibility for edit control #1
    invoke ShowWindow, hEdit2, If?()                        ; repeat the action for edit control #2
    .if FileSave$("Plain Asm=*.asm|MasmBasic=*.asc||Rich text=*.rtf|All files=*.*")        ; saves quite some bytes with complex functions
        FileWrite FileSave$(), stream:hMyEdit, If?(InstrOr(FileSave$(), ".rtf" or ".asc", 1), SF_RTF, SF_TEXT)    ; choose the right format
    .endif
    xor ecx, ecx
    Print Str$("zero flag set: %i\txor ecx, ecx\n", If?(Zero?, 111, 222))            ; you may use the Zero? ...
    or ecx, -1
    Print Str$("zero flag set: %i\tor ecx, -1\n", If?(Zero?, 111, 222))
    stc
    Print Str$("carry set: %i\tstc\n", If?(Carry?, 111, 222))                    ; ... or Carry? flag
    clc
    Print Str$("carry set: %i\tclc\n", If?(Carry?, 111, 222))
Rem    - the comparison follows the macro syntax, with some deviations (in red) for technical reasons:
        - eq=equal, ne=not equal
        - ab=above, ae=above or equal    (unsigned)
        - bl=below, be=below or equal
        - gt=greater, ge=greater or equal    (signed)
        - lt=less than, le=less or equal
    - if one of the options is "quoted text", strings will be returned, otherwise integers
    - !Zero? and !Carry? are not allowed; swap arguments instead
    - when used with no arguments, i.e. If?(), the last result will be used - see ShowWindow example


Div_
    include \masm32\MasmBasic\MasmBasic.inc
    SetGlobals R4:REAL4=100.0, R8:REAL8=1000.0, R10:REAL10=10000.0
    Init    ; select and hit F6
    mov eax, 1000
    Print Str$("Imm real\t%i\n", Div_(22.222222222222222222))
    mov eax, 12300000
    Print Str$("Imm real\t%i\n", Div_(R4))
    mov eax, 12300000
    Print Str$("Imm real\t%i\n", Div_(R8))
    mov eax, 12300000
    Print Str$("Imm real\t%i\n", Div_(R10))
    mov ecx, 1230000
    Print Str$("Imm real\t%i\n", Div_(ecx/R10))
    Print Str$("Imm real\t%i\n", Div_(R10/R4))
   
EndOfCode
Rem    returns result in eax



IsTrue            ; test condition with floats
    include \masm32\MasmBasic\MasmBasic.inc
    SetGlobals x1:REAL8, x2:REAL10
    Init    ; << select Ixnit and hit F6 to test this snippet
    SetGlobals    ; the two loops differ only regarding their precision
    fld FP8(123.456)    ; put 123.456...
    fstp x1    ; into double x1
    MovVal x2, "123.457"
    PrintLine "x1", Tb$, Tb$, "x2"
    .Repeat
        fld x1    ; load x1
        fadd FP8(0.0005)    ; add 0.0005
        fstp x1    ; save it
        PrintLine Str$(x1), Tb$, Str$(x2)    ; print it
    .Until IsTrue(x1 gt x2, top)    ; loop until x1 is greater than 123.457
    PrintLine Str$("x1 gt x2: x1=%If", x1), Str$(", x2=%If", x2), " (top precision)", CrLf$
    MovVal x1, "123.456"    ; put 123.456... into double x1
    PrintLine "x1", Tb$, Tb$, "x2"
    .Repeat
        fld x1    ; load x1
        fadd FP8(0.0005)    ; add 0.0005
        fstp x1    ; save it
        PrintLine Str$(x1), Tb$, Str$(x2)    ; print it
    .Until IsTrue(x1 gt x2)    ; loop until x1 is greater than 123.457
    PrintLine Str$("x1 gt x2: x1=%If", x1), Str$(", x2=%If", x2), " (default precision)", CrLf$
    Exit
        end start
Rem    - returns Zero? or Sign?, depending on the condition
    - almost any numerical argument is allowed, including immediate floats and integers - see Fcmp(); the second
        argument (i.e. top, medium, low precision) is optional; avoid using eq with top as in IsTrue(x1 eq x2, top)!
    - use with .if ... and .Repeat ... .Until but not with .elseif or .While; RichMasm will warn you but other IDEs won't.


CodePage$
    include \masm32\MasmBasic\MasmBasic.inc
    Init    ; <<< select init and hit F6 to test this snippet
    PrintLine CodePage$(?)    ; returns DOS cp, e.g. 65001 (UTF-8)
    PrintLine CodePage$(w)    ; e.g. Windows cp, e.g. 1252    (ANSI - Latin I)
    PrintLine CodePage$(1251)    ; 1251    (ANSI - Cyrillic)
   
EndOfCode


CodeSize & CheckStackBalance
    include \masm32\MasmBasic\MasmBasic.inc
    Init    ; <<< select init and hit F6 to test this snippet
    CodeSize MyTest
    call MyTest
    useCsb=1    ; put 0 to see a crash
    Exit
    MyTest_s:
    MyTest proc
        CheckStackBalance
        nops 98
        push eax
        CheckStackBalance
        ret
    MyTest endp
    MyTest_endp:
    CodeSize MyTest, echo
    end start
Rem    - CodeSize shows the size of code in bytes between name_s: and name_endp: (
    - it does not trash any registers, and the echo version does not generate code
    - if the stack is not balanced, StackBalance corrects it and tells you what to look for; activate it with useCsb=1


DlgDefine, DlgControl, DlgShow
    include \masm32\MasmBasic\MasmBasic.inc
    Init
    DlgDefine "Please enter your data, tab for next line:", 0, 0, 150, -4, , 14
    DlgControl dcEdit,    "First name", WS_BORDER or WS_TABSTOP, 1, -2        ; first control gets the focus
    DlgControl dcEdit,    "Family name", WS_BORDER or WS_TABSTOP, 1, -4                ; x, y only, the macro will assign width, height and ID
    DlgControl dcStatic,    "Type your first name:", SS_LEFT, 1, -1, 70.0                    ; 70 means 70% - the buttons need space
    DlgControl dcButton,    "OK", BS_DEFPUSHBUTTON or WS_TABSTOP, 71.0, -1, 12.0, , IDOK            ; x=71%, y, width=14%, height, ID
    DlgControl dcButton,    "Quit", BS_PUSHBUTTON or WS_TABSTOP, 84.0, -1, 16.0, , IDCANCEL
    DlgControl dcStatic,    wCat$("Type your family name: (it's "+wTime$+" now)"), SS_LEFT, 1, -3
    ; DlgHandler MyHandler            ; optional - PM me for details
    DlgShow
    .if eax==IDOK
        wMsgBox 0, wCat$(Dlg$(0)+wCrLf$+Dlg$(1)), "Please confirm:", MB_OKCANCEL
    .endif
   
EndOfCode
Rem    - see advanced dialog \MasmBasic\examples in \masm32\MasmBasic\Res\Templates.asc
    - the dialog closes for IDOK, IDCANCEL and any other control whose ID is in the range 100...120; the ID will be in eax
    - you can use registers or variables for the strings, but make sure they are Unicode (e.g. wChr$(ecx) or wRes$(123) are fine)
    - esi cannot be used between DlgDefine and DlgShow, but ecx can be used and will not be trashed


SetGlobals    ; declares global variables relative to ebx, syntax as in LOCAL
.data?
    whatever    dd ?
    SetGlobals hMain, hStatic, hEdit
    SetGlobals MyDw=123, TheFactor:REAL4=123.456, FileName$="Test.txt"    ; variables can be initialised
    SetGlobals hMenu, hM1, hM2, rc:RECT
    SetGlobals int MyInt=111, MyInt2, float MyR4a=444.44, MyR4b, double MyR8a=888.88    ; C-style
    SetGlobals OWORD xm0, xm1, xm2    ; use with xmm registers - if this is the first SG statement, all xm? are aligned 16
    SetGlobals msg:MSG, wc:WNDCLASSEX, exeBuffer[MAX_PATH]:BYTE, gBuffer[1024]:BYTE
    .code
    ; no args: set ebx to the right offset, and initialise vars; must be used in callbacks (WndProc, SubEdit, ...)
    SetGlobals
Rem    - variables return [ebx+x]

, where x=-128 ... +128 and higher
    - if SetGlobals declarations are present, the Init macro sets ebx to the .data? block reserved for the variables
    - for a range of 256 bytes, SetGlobals produces very size-efficient code
    - as with LOCAL variables, put large buffers at the end
    - WORD, DWORD, REAL4, REAL8 variables and strings with '$' ending can be initialised


Enum        ; create a list of IDs or constants
    Enum    IdMenuNew, IdMenuSave, IdMenuCopy, IdTimer
    Enum    20:IdEdit, IdButton1, 30:IdButton2, IdStatic, IdFind, IdFindStatic    ; using a start ID
    Enum    b0:opt1, opt2, opt3                        ; bitwise: first element 0, then 1, 2, 4, ...
    Enum    b100:opt1, opt2, opt3                        ; elements 100, 200, 400, ...
    Enum    40:Button*10                        ; create Button0=40, ..., Button9=50
    Enum    #MYSUBLANG_ENGLISH_, 1:US, UK, AUS, CAN, NZ, EIRE        ; use a prefix
    Print Str$("NZ=%i\n", MYSUBLANG_ENGLISH_NZ)    ; 5; see SUB_LANG... in Windows.inc
Rem    - default start is 10, but (as shown above) new start values can be specified with nn:
    - multiple IDs can be specified as Enum Button*10, MyEdits*3 etc
    - with #, a prefix can be specified


Enum$            ; return numeric constant as text
            Case WM_CREATE
                Enum 20:IdEdit, IdButton1, 30:IdButton2, IdStatic, IdFind, IdFindStatic        ; create a list of IDs
                ; use with CreateWindowEx
            Case WM_COMMAND
                ; ID is loword(wParam), print it as Enum$(ID, list of numeric constants)
                PrintLine "command for control ", Enum$(word ptr wParam, IdEdit, IdButton1, IdButton2, IdStatic, IdFind, IdFindStatic)
                mov ecx, Enum$(uMsg, WM_CREATE, WM_PAINT, WM_SIZE, WM_SIZING, WM_COMMAND)
                .if byte ptr [ecx]!="?"    ; if no matching entry is found, Enum$() returns a question mark
                    PrintLine "Message: ", ecx    ; output e.g. Message: WM_PAINT
                .endif
Rem            for debugging, returns string for use with Print or Let


Choose, Choose$
    include \masm32\MasmBasic\MasmBasic.inc    ; download
    .data
    tx123    db "This is tx123", 0
    MyNum    dd 1111111
    Init        ; select init and hit F6 to test this snippet
    mov esi, Chr$("esi and edi are treated as strings")
    mov ecx, 987654321
    .While 1
            Inkey "Gimme a number: "
            .Break .if eax==VK_ESCAPE
            .if Choose(eax-"0", 100, esi, 102, "abc", 123456789, ecx, 12345.67, 12345678.90123456789, addr tx123, MyNum)!=ChooseString
                .if eax==ChooseReal
                        Print Str$("You chose the real number %Jf\n", ST(0)v)    ; trailing v: fstp st
                .elseif eax==ChooseError
                        PrintLine "No such entry"
                .else
                        Print Str$("You chose the integer %i\n", eax)
                .endif
            .else
                PrintLine "You chose the string [", eax, "]"
            .endif
    .Endw
    For_ ecx=0 To 2
        PrintLine Str$("C%i$=[", ecx), Choose$(ecx, "off", "on"), "]"    ; 0=off, 1=on, 2...=?
    Next
   
EndOfCode
Rem    first arg expects an integer from 0...#args-1, followed by a list of return values. Choose() returns a flag in edx:
    - ChooseString:    pointer in eax
    - ChooseReal:    value in ST(0)
    - ChooseError:    out of range
    Text in "quotes" is returned as string in eax, same for esi, edi and global vars like addr tx123.
    Otherwise, eax contains an integer.


Let, wLet        assign text to a string
    Let Test$="A little test"+CrLf$+"with two lines"
    Let My$(123)=MyOther$(ebx)
    Let My$(123)=Str$("A little test: %i", ebx*3+100)
    Let esi="Another "+"example"    ; using a register is possible but see remarks
    wLet MyUnicode$="A test with Unicode: "+FileRead$("Unicode.txt")
    invoke MessageBoxW, 0, MyUnicode$, wChr$("Title"), MB_OK
    wMsgBox 0, wCat$("The file content:"+wCrLf$+FileRead$("Unicode.txt")), "Unicode is easy:", MB_YESNO
    wLet esi=wChr$("A Unicode Str$: ")+wStr$("%i little bugs", 123)+wChr$(" are waiting for you")
    invoke MessageBoxW, 0, esi, wChr$("Title"), MB_OK
Rem    - returns pointer to resulting string in eax
    - examples for using the Let Left$(...)=... syntax:
        Dim My$(123)    ; we create an array
        Let My$(123)="A small test"    ; we use an array element (but single strings work, too)
        Let Mid$(My$(123), 5, 3)="oke"    ; we replace small with smoke    ->A smoke test
        Let Left$(My$(123), 7)="A funny"    ; first 7 chars are A funny    ->A funny test
        Let Mid$(My$(123), 3)="futile attempt to crack the buffer"    ; result    ->A futile att
        Let Right$(My$(123), 3)="act"    ; we replace the last 3 chars    ->A futile act
    - caution when using registers:
        Let esi=eax works if eax is a valid pointer, but Let checks whether esi is in the table of heap pointers; if yes, the previous string
        will be HeapFree'd, which is the desired behaviour allowing multiple Let esi="abc"+esi+"def" lines; however, if esi was set to
        some other memory location before the Let, then there will be an attempt to free the other one.    To avoid such problems, either
        stick to global (or zeroed local) variables (Let My$=...), or insert xor esi, esi before Let esi=... other code ... followed by Clr$ esi
Key    let



Cat$, wCat$
    MsgBox 0, Cat$("The content of the editbox is"+CrLf$+Win$(hEdit)), "Info", MB_OK
Rem    returns ptr in eax and MbCat$
    - Cat$ is meant to replace Let My$=... in MessageBox or similar situations
    - only for fans of AsmC: Cat$(<Left$(MbExeFolder$)>+":\Masm32\bin\")
    - every use of Cat$ overwrites content in the dedicated Cat$ buffer; mov myptr, Cat$() is
        therefore pretty useless. If you want permanent strings, use Let


StringBuild    fast string concatenation
include \masm32\MasmBasic\MasmBasic.inc
SetGlobals my$
Init            ; < < select Init and hit F6 to test this snippet
    StringBuild my$
    Let my$="Start: "    ; optional
    For_ ecx=1 To 50000
        Let my$="string "+Str$("#%i\n", ecx)    ; 7-11 bytes per string
    Next
StringBuild
PrintLine Left$(my$, 40), " ... ", Right$(my$, 40)

EndOfCode
Rem    - use to concatenate strings really fast
    - by default, 64kB are allocated; if you need more, use e.g. StringBuild some$, 20000h
    - with usedeb=1, overflow will be signalled, with a 1k tolerance


Lset
    include \masm32\MasmBasic\MasmBasic.inc
    Init        ; << select Ixnit and hit F6 to test this snippet
    Let esi=Space$(50)
    Lset esi="Let's test this today, "+fDate$(0)+", "+fTime$(0)
    PrintLine "[", esi, "]"
    Lset esi="Lset a much longer string, like this one: "+String$(100, "abc")
    PrintLine "[", esi, "]"
   
EndOfCode
Rem    use to set a string to a fixed length


Data and Read
    include \masm32\MasmBasic\MasmBasic.inc
    Data 123, "Hello World, how are you?", 'single quotes are allowed', My$, sq$, 456
    SetGlobals My$, sq$, c1$, c2$, MyByte, MyDword, MyR4:REAL4, MyR8:REAL8, Last$, MyQ:QWORD
    Data 789, 111, 1234567890123456789, 1234567890.123456789, 12345.6789, 12345.6789
    Init        ; select Init and hit F6 to test this example
    Data 33333, "Last string item"    ; Data statements can go almost everywhere...
    Read ecx, My$, sq$, c1$, c2$, eax    ; once My$+sq$ are read, c1$ and c2$ can be copied
    Read edx
    Read esi   
    Rewind 2        ; go back 2 items and read them again
    Read edx
    Read esi   
    Data 1234567890123456789        ; ... but values must be defined before they are read
    Data "This", "is", "an", "entire", "string", "array", 111, 222
    Read xmm0, f:xmm1, MyR8, MyR4, edi, Last$, MyQ
    deb 4, "Read variables:", ecx, eax, $My$, $sq$, $c1$, $c2$, edx, esi, xmm0, f:xmm1, MyR8, MyR4, edi, MyQ, $Last$
    Read my$()
    For_ ecx=0 To my$(?)-1
        Print my$(ecx), "."
    Next
    Read eax, ecx
    deb 4, "Read again", eax, ecx
    $Data No, quotes, in, this, string, array, 333, 444        ; use $Data for a series of strings without quotes
    Read my$()    ; you can re-use an array, here for the $Data items
    For_ ecx=0 To my$(?)-1
        Print my$(ecx), "."
    Next
    $Data "But, if you really, really need commas, 'quotes' are needed for $Data"
    Read ecx
    PrintLine CrLf$, ecx
   
EndOfCode
Rem    - this is for fans of good ol' BASIC; in general, Recall does a better job
    - the Data statements can be in code or data sections
    - with string arrays, Read stops when it encounters a non-quoted item, e.g. 111 as shown above
    - use the variant $Data for non-quoted strings, but note that 333 and 444 are then treated as strings



DefStruct
include \masm32\MasmBasic\MasmBasic.inc
DefStruct PETS: puppyname, gender: BYTE, cuteness:BYTE, baseprice, DogOrCatProc
Data "Cooper", 0, 33, 100, Dog, "Yorky", 0, 99, 101, Dog
Data "Bella", 1, 77, 128, Cat, "Daisy", 1, 60, 100, Cat
Data "Charlie", 1, 55, 90, Dog, @, 1, 2, 3, "a string"    ; @ separates pets section from the following data
Init        ; select Init and hit F6 to test this example
Dim pets() As PETS
Read pets()
For_ ecx=0 To eax-1
    xCall pets(ecx, DogOrCatProc), ecx    ; pass the registered function and the index
    PrintLine Str$("\t(cuteness %i", pets(ecx, cuteness)), Str$(" x base %i)", pets(ecx, baseprice))
Next
Inkey "done"
Exit
Dog proc inx
    movzx eax, pets(inx, cuteness)
push eax
    fild pets(inx, baseprice)
    fimul stack
    fistp stack
Print pets(inx, puppyname), cfm$("\tis a dog and costs ")
pop eax
Print Str$("%i euros\t", eax)
    ret
Dog endp
Cat proc inx
    movzx eax, pets(inx, cuteness)
push eax
    fild pets(inx, baseprice)
    fimul stack
    fistp stack
Print pets(inx, puppyname), cfm$("\tis a cat and costs ")
pop eax
Print Str$("%i euros\t", eax)
    ret
Cat endp

EndOfCode
Rem Output:
Cooper    is a dog and costs 3300 euros    (cuteness 33 x base 100)
Yorky    is a dog and costs 9999 euros    (cuteness 99 x base 101)
Bella    is a cat and costs 9856 euros    (cuteness 77 x base 128)
Daisy    is a cat and costs 6000 euros    (cuteness 60 x base 100)
Charlie is a dog and costs 4950 euros    (cuteness 55 x base 90)


Dll & Declare, SetDllFolder
include \masm32\MasmBasic\MasmBasic.inc        ; include this library
.data
MyLongLong    LONGLONG 12345678901234567890    ; the standard Masm32 crt lib is not enough to handle this
    Init                    ; initialise the app
    Dll "msvcrt"        ; good ol' CRT
    Declare double sin, C:1        ; the crt sinus function returns a double aka REAL8 in FPU register ST(0)
    Print Str$("Sinus(3)= %Jf from CRT\n", sin(3.0))   
    fstp st        ; since doubles are returned via the FPU, ST(0) must be popped
    Dll "shimgvw"        ; load the shell image view dll aka Windows Picture and Fax Viewer Library
    Declare void ImageView_Fullscreen, 4        ; ImageView_Fullscreen expects 4 dwords but returns nothing useful
    ImageView_Fullscreen(0, 0, wCL$(1), SW_SHOW)    ; we need the wide version of the commandline arg
    Err$(1, "ImgView")        ; there is no retval - you may test here for errors
    SetDllFolder "\Python"        ; adjust to your setup; downloaded from here
    Dll "python3.dll"        ; has many dependencies, therefore the SetDllFolder
    Declare PyList_New, C:1
    Declare void Py_Initialize
    SetDllFolder        ; no args=go back to previous folder after declaring the functions
    Dll "ntoskrnl.exe"
    Declare RtlRandomExNtos, 1 Alias "RtlRandomEx"    ; same but native API - will crash in user mode
    Dll "msvcr100"
    Declare void myprintf, C:? Alias "printf"    ; don't return anything, C calling convention, vararg (note: vxoid followed by one space)
    printf(cfm$("MyLongLong is %llX aka %llu\n"), MyLongLong, MyLongLong)    ; MyLL as hex and decimal figure
    ; Print "MyLongLong is ", Hex$(MyLongLong), Str$(" =    %u\n", MyLongLong)    ; standard MasmBasic syntax, for comparison
    DllRTE=0
    Dll "%ProgramFiles%\FreeArc\Addons\InnoSetup\unarc"                ; use environment variables for multilingual apps
    Declare FreeArcExtract, C:?    ; C calling convention, variable number of arguments
    .if eax
        Print Str$("\nResult=%i", FreeArcExtract(0, "l", "--", "testfile.arc"))        ; pCallback, listing, no more options, archive name
    .else
        Print "unarc.dll not present?"
        .endif

EndOfCode            ; do a clean exit, inter alia FreeLibrary
Rem    - Dll performs LoadLibrary, Declare initialises the macro with GetProcAddress, Exit frees the libraries
    - to perform runtime checks if certain functions are available to your application, you may use the flags DllRTE=? and DecRTE=?
        with values 1 meaning throw a runtime error if Dll or Declare not found, 0 to return zero in eax in case of error (default: 1)
    - use Declare void SomeFunction, ... if you don't need the return value
    - with e.g. Declare SomeFunction, 2 passing a string and an immediate as in SomeFunction("Test", 123.456) will pass a
        REAL4 as second argument; to force a double, use SomeFunction("Test", REAL8:123.456)
    - the number of arguments is independent of the size of arguments; pass e.g. a RECT stucture counts as one argument
    - use Declare SomeFunction, C:3 for C calling convention with three args
    - use Declare SomeFunction, C:? for C calling convention with a variable number of arguments
    - the Alias keyword allows to declare a self-defined name; use in case of "already defined" errors
    - use Declare #123=SomeFunction, 2 to access a function by ordinal number; then mov ecx, SomeFunction(arg1, arg2)
    - if the function cannot be found, try the decorated name:
        Declare _SomeFunction@12, C:3    ; three args, C calling convention
        then use e.g. mov ecx, SomeFunction(arg1, arg2, arg3)
    - do not use Dll & Declare for the static libraries of the Masm32 package (user32, kernel32, ..., see Masm32rt.inc)
    - up to 9 libraries can be loaded


HeapStrings (for debugging)
    include \masm32\MasmBasic\MasmBasic.inc
    Init            ; select Init and hit F6
    For_ ecx=0 To 99        ; 0 To 100 would produce an error
        Let esi=Str$("This is string #%i", ecx)
        lodsb        ; forbidden, increments esi!
    Next
    HeapStrings        ; display all strings
   
EndOfCode
Rem    By default, you can create 100 different strings (if you need more, use string arrays). These strings can be reassigned.
    This works because Let x$="..." checks if x$ is already assigned; if yes, it will be freed. When using registers (Let esi="..."),
    however, you need to be careful not to change the pointer - this would create a fresh string without freeing the previous one.
    If you suspect such a problem because you got an error message, use HeapStrings before the error occurs to check which
    of the strings misbehave

deb, fdeb, ifdeb        Never was debugging easier...$db
deb 1, "The first loop", ecx, $esi, $edi, MyReal10, ST, ST(5)    ; show a MsgBox ($esi means show ptr esi as a string)
deb 2, "Second loop:", al, ecx, $esi, $My$:60, $MyArray$(n)    ; show another MsgBox, limit the display of My$ to 60 bytes
deb 3, "Third loop:", al, ecx, $esi, xmm0, xmm1, ST, ST(5)    ; xmm in lowercase, FPU regs in uppercase
deb 4, "#4 will show in the console:", xmm0, f:xmm0        ; display xmm0 as integer (default) and float with f: prefix
deb 7, "Loop A:", ecx, $$addr MyLocalWide$        ; display the string in a loop, but no more than 7 times
deb 5, "#5 will be written to DebLog.txt:", ebx, $My$, $MyArray$(n)
deb 4, "OWORD Bin$() spaced", b:MyOword:4, b:xmm0:4, b:xmm0:3, b:xmm0:2, b:xmm0    ; show 4 DWORDs as Bin$()
deb 4, "OWORD Bin$() dense", b:MyOword:4d, b:xmm0:4d, b:xmm0:3d, b:xmm0:2d, b:xmm0    ; same but no spaces
    usedeb=0            ; disable debugging completely (no code inserted - very handy...)
deb 1, "This box will never pop up", eax
ifdeb push eax: push edx        ; this line is active only for usedeb=1; see mcs for multiple commands
fdeb 1, "But this one will", eax        ; forced deb overrides usedeb=0
ifdeb pop edx: pop eax        ; note the order
    usedeb=16            ; force hex display
deb 4, "Hexadecimal:", eax, xmm1, ST(3)        ; limited to 32 bits, i.e. low dword of xmm regs, FPU as int 32
    usedeb=2            ; force binary display
deb 4, "Binary:", eax, xmm1, ST(3)        ; limited to 32 bits
    usedeb=1            ; decimal display (default)
deb 4, "Multiple:", eax, x:eax, b:eax        ; override usedeb: show arguments in decimal, hexadecimal and binary format

Rem    - the debug macro preserves ordinary reg32, xmm and all FPU registers, and the flags (caution with macros - test it...)
    - it can show xmm and FPU registers ST(0)...ST(7); see also MbHexQ
    - numerical global and local variables can be displayed, but do not use different numerical arrays in the
        same deb line (numerical arrays use edx, which will be set by the last element and used by all others)
    - global and local variables as well as arrays can be shown as strings by using e.g. $ptr
    - the string content of registers can be shown by using $eax, $ecx, $esi etc.; with e.g. $esi:50, you can limit the amount
        of bytes displayed. Note that console output is limited to 53200 bytes under Win XP, 62600 under Windows 7.
    - displaying macro results as strings is possible, as e.g. in $Win$(hWnd), but multiple expansion may occur;
        check if this affects results of your code, and if yes, use a register instead: mov ecx, Win$(..) -> deb... $ecx
    - cancelling deb 1, ... does not cancel deb 2, ..., so you can test several loops in one go
    - deb 1, ... deb 3, ... are being displayed as MsgBox, while deb 4 writes to console, and deb 5 writes to file:
        deb 5, "ToFile", eax, $esi, $edi saves contents (without showing them) to DebLog.txt
    Remember that you can interrupt console output (deb 4) by pressing Ctrl C
    - if FPU regs display incorrectly as 0.0, use Init, or try a void Str$(0) earlier in your code to initialise Str$()
    - debCrLfDots=1     ; replace invalid chars with dots
    - debTabDots=0    ; 0=keep tabs, 1=insert _ instead
    - xmm display precision is REAL8, while FPU registers display with REAL10 precision, i.e. 18 digits
    - structures can be used: deb 1, "The bottom value of element 20:", MyRectStruc(20, bottom)
    - with structures created e.g. with Dim MyStruc(123) As RECT, in rare cases you need % and :
        % deb 1, "String in highest element of MyStruc.ms1:", $
        Note that with early Masm versions you cannot display xmm registers at the same time
    - the chg: keyword allows to monitor which WM_ messages have changed a global variable or register; example
        (you can insert the code above from the AutoCode menu - place the cursor at the end of the WndProc line):
        WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
        LOCAL rc:RECT, whatever
        inc msgCount        ; increment the default counter
        deb 4, "# msg #", chg:msgCount        ; this console deb will only be shown if the variable behind chg: has changed
        SWITCH uMsg
        Note that deb picks the messages to discard from an equate; you can adapt it to your needs:
        NoDebMsg equ
Key    deb


Err$()
    Err$(1)            ; call GetLastError, and show a MessageBox with a formatted string if there was an error
    Err$(0)            ; call GetLastError, and show a MessageBox even if there was no error
    Err$(1, "some text")    ; show a MessageBox titled "some text" (1: only if there was an error)
    PrintLine "Result=", Err$()    ; print the formatted string
    void Err$()        ; no arg: returns plus the zero flag
    .if !Zero?            ; for use with .if, .Break etc
        MsgBox 0, eax, "Hey coder, there is an error:", MB_OK
    .else
        deb 4, "Loop C", $eax    ; will write "The operation completed successfully" to the console
    .endif
Rem    - use wErr$(..) for Utf-16 errors
    - the blank argument version returns a pointer to the formatted error desription in eax



SetErrLine
    Dim My$(99)
    SetErrLine
    Print My$(98)    ; sooner or later, MasmBasic...
    SetErrLine
    Print My$(99)    ; ... will show you a nice little ...
    SetErrLine
    Print My$(100)    ; ... runtime error message ;-)
Rem    - inserts mov MbErrLine, @Line+1 into your code - a fat 10-byte instruction
    - see also Init SEH above; use mov MbFlags[4], 1 to see errors in console
    - no code will be inserted for MbUseErrLine=0 (case-sensitive)
    - if you want to see error lines in runtime error messages, put MbUseErrLine=1 early in your code
    - many macros (string arrays, Input etc) set error lines automatically for MbUseErrLine=1


Sound
    Sound "880:200"    ; plays frequency 880Hz for 200 ms; allowed separators are space, tab, / and :
    Sound "880/200/150"    ; plays frequency 880Hz for 200 ms, followed by 150 ms of silence
    Sound 111    ; plays a wav resource with ID 111 (in rc file: 111 WAVE "hello.wav")
    Sound "hello.wav"    ; plays a file directly (wav only)
    Sound "hello.mp3"    ; plays a file in its associated player
    Sound "大桥在混乱的水.mp3"    ; Unicode names are allowed
    Sound wCL$()    ; even if passed via the commandline
Rem    - The simple "frequency:duration" syntax works on Windows 7 and higher but not on Windows Vista;
        a minimum duration of about 90...100 ms is required
    - PlaySound/Beep/ShellExecute results can be checked with Err$()


Say
    include \masm32\MasmBasic\MasmBasic.inc
    Init            ; select Init and hit F6
    Say "Hello World"
    Say "Low and slow", SetVolume(50), SetRate(-7)    ; you can use some methods of the ISpVoice interface
    ; Say wWin$(hEdit)        ; any Unicode string is ok
    ; Say FileRead$("TTS_Unicode.txt")
    ; Say wRec$(FileRead$("TTS_Ansi.txt"))        ; conversion necessary
   
EndOfCode
Rem    uses ISpVoice


Asc
    movzx ecx, byte ptr Asc(My$)
    mov dl, Asc(esi)
Rem    returns BYTE in al



Cvi
    movzx ecx, word ptr Cvi(My$)
    mov dx, Cvi(esi)
Rem    returns WORD in ax



Cvl
    mov ecx, Cvl(My$)
    mov edx, Cvl(esi)
Rem    returns DWORD in eax



Odd
    .if Odd(123)...
    .if Odd(eax)...
    .if Odd(ax)...
    .if Odd(al)...
    .if Odd(MyDword)...
Rem    returns !Zero?


Abs
    PrintLine Str$(Abs(123))
    PrintLine Str$(Abs(-123))
    mov eax, -12345
    PrintLine Str$(Abs(eax)+1000)
Rem    returns positive DWORD in eax


ModZero
    For_ ecx=0 To 19
        PrintLine Str$(ecx), Tb$, Str$(ModZero(ecx, 3))
    Next
Rem    returns 1 if the modulo is zero



Min, Max
    mov eax, Max(12, eax)    ; return value is in edx
    mov eax, Min(12, eax-2)    ; valid syntax
    .if Min(eax, MyDword)>99
        MsgBox 0, Str$("Too high: %i", Min(eax, MyDword)), "Sorry", MB_OK
    .endif
    For_ n=0 To Min(9, eax-1)    ; GetFiles returns #files in eax: use eax-1 with null-based index
        PrintLine Files$(n)
    Next
Rem    returns DWORD in edx, not eax (since eax is return value of many functions)
Key    min(, max(



PopCount
    PopCount(MySource, ecx)            ; standalone: set count to ecx
    mov ebx, 88888888h
    Print Str$("In ebx, %i bits are set", PopCount(ebx))    ; for use with Str$() etc
    void PopCount(ebx)            ; returns bitcount in eax
    mov MyVar, PopCount(MySource)
Rem    runs in about 7 cycles on a Celeron M; requires Init or at least one Str$(0) before


Bsr64
    movlps xmm0, q3
    Print Str$("Bsr64 of xmm0 \t%i\n", Bsr64(xmm0))
    Print Str$("Bsr64 of q1 \t%i\n", Bsr64(q1))
    For_ ecx=0 To 2
        PrintLine Str$("Bsr64 of MyQ(%i)\t", ecx), Str$(Bsr64(MyQ(ecx)))
    Next
Rem    returns position of msb for 64-bit values; without arguments, edx::eax is assumed


Rand , Rand64
    Rand()            ; no args = initialise using rdtsc
    Rand(seed:abcd)    ; use "abcd" as seed (4 letters)
    Rand(seed:0FCDFDEABh)    ; use any binary seed (5+ letters or numbers)
    Rand(0, 100, xmm1)    ; 0....+100, dest can be local/global REAL8 var or xmm
    Rand(0, ebx, MyReal8)    ; 0....ebx, dest can be local/global REAL8 var or xmm
    Rand(0, 1)        ; 0....+1, left on FPU in ST(0)
    Rand(-5.5, 9.3)    ; -5.3...+9.3, left on FPU in ST(0)
    Print Str$(Rand64())    ; the 64-bit variant returns a QWORD (no args allowed)
    void Rand(123)    ; leaves dword in eax
    mov ecx, Rand(123)    ; range 0...123
    mov ecx, Rand(MyDword)    ; range passed as dword variable
    mov ebx, 1000    ; we want 1000 elements (we'll get 1001: 0...1000)
    Dim MyArray(ebx) As REAL4    ; could be also REAL8, QWORD, DWORD, WORD, BYTE
    Dim MyDW(ebx) As DWORD
    .Repeat
        Rand(-888.888, 999.999, MyArray(ebx))    ; put random number into Real4 array
        mov MyDW(ebx), Rand(1000)
        dec ebx
    .Until Sign?
    Rand(pt:pTable())        ; set probability table: uses a DWORD array (->example)
    mov somevar, Rand(pt)        ; return index weighted by probabilities
    Rand(1, 50, numbers() As DWORD, unique:6)    ; min, max, array, unique:n: get 6 unique elements in the range 1...49
Rem    - high-speed RNG generates pseudo random sequence with excellent randomness
    - for testing, do not initialise; for the release version, use Rand() before the innermost loop (seed is in MbRndSeed[4])
    - credits to Alex Bagayev for his AxRand algo


Encrypt & Decrypt
    include \masm32\MasmBasic\MasmBasic.inc
    Init
    Let esi=CL$()        ; get filename from commandline
    .if Exist(esi)
        Let edi=Input$("Password: ", "Masm is great")    ; ask for a password
        .if dword ptr [esi+Len(esi)-4]==Mirror$(".enc")    ; encrypted file
            Decrypt esi, edi        ; restore the original
            ShEx eax        ; and show it
        .else
            Encrypt esi, edi        ; filename, password
            PrintLine "Encoded as ", eax
        .endif
    .else
        PrintLine "No commandline found: ", esi
    .endif
   
EndOfCode
Rem    - returns path in eax, or zero if the Decrypt password was wrong
    - with Decrypt, an existing original will be renamed to ~OldFile.tmp
    - MbCryptTS=1: timestamp will be kept (default); 0: use current date+time
    - password may contain any characters, but only A-Z, a-z, 0-9, /#*$&:+-?\ are interpreted


FolderOpen$
    include \masm32\MasmBasic\MasmBasic.inc    ; select Init and hit F6 to test the examples
    Init        ; FolderOpen$(prompt [, title] [, path] [, BIF flags])
    Let esi=FolderOpen$()        ; no args = use Windows defaults, start with current folder
    Print "Selected: [", esi, "]", CrLf$        ; if the user cancelled, esi will be an empty string
    PrintLine "Selected: ", FolderOpen$("Pick a folder:", "Masm32 is great")    ; a prompt and a nice title, start with current folder
    PrintLine "Selected: ", FolderOpen$("Pick a folder:",, "\Masm32\Examples")    ; just a prompt, default title, specific folder
    PrintLine "Selected: ", FolderOpen$("9Pick a big folder:")            ; a big prompt (font 9), default title, current folder
    ; prompt, title, path with environment variables, flags
    Let esi=FolderOpen$("What is the edit box for?","Locate Office:", "%ProgramFiles%\Microsoft Office", BIF_USENEWUI)
    PrintLine esi
    PrintLine "Selected folder=[", FolderOpen$("Where are your tools?",, "D:\Masm32\bin"), "]"    ; fully qualified initial path
    ;    if you need to check whether the user cancelled, use void and check the zero flag:
    void FolderOpen$("5Pick the bin folder, please:","Getting help", "\Masm32\bin")    ; use current drive plus initial path
    .if !Zero?
        MsgBox 0, Launch$(Cat$(eax+"\link.exe /Lib")), "Library manager help:", MB_OK
    .else
        PrintLine "Nothing selected: [", eax, "]"    ; Zero? set = user cancelled, eax points to a Null$
    .endif
   
EndOfCode


FileOpen$, FileSave$, wFileOpen$, wFileSave$
    .if FileSave$(offset MyFilter, "Test save:")    ; needs MyFilter db "RTF", 0, "*.asc", 0, [...etc, 0], 0
        Let My$=FileSave$()        ; assign to a string by using empty brackets
        PrintLine "File openend: [", My$, "]"        ; do whatever you need the filename for
    .else
        PrintLine "You cancelled"        ; see Win32 docu of OPENFILENAME, lpstrFilter
    .endif
    ; you may specify the filter using the following syntax, i.e. separate by "|":
    .if FileOpen$("Rich sources=*.asc|All sauces=*.as?;*.inc|Resources=*.rc")
        PrintLine "You opened    [", FileOpen$(), "]"    ; empty bracket: return the result
    .else
        PrintLine "You cancelled"
    .endif
    ; this example uses two more options: a default file and an extra OFN_xx flag, and returns an array of selected files
    .if FileOpen$(offset txFilter, "Test open", "MyCurrentFile.txt", ofnFlagsO or OFN_ALLOWMULTISELECT)
        push edx        ; # of files selected is in edx
        mov esi, FileOpen$()        ; empty brackets = return pointer to the buffer
        .if edx==1
            PrintLine "One file selected: [", esi, "]"
        .else
            PrintLine "Folder: [", esi, "]"
        .endif
        .While 1
            lodsb        ; same as mov al, [esi], then inc esi
            .if !al
                .Break .if !byte ptr [esi]        ; two zero bytes means end of file array
                PrintLine "File:", Tb$, esi
            .endif
        .Endw
        pop edx
        Print Str$("%i files selected\n", edx)
        sub esi, FileOpen$()
        Print Str$("%i bytes of ", esi), Str$("%i available bytes used\n", MbBufSize/4-1000)
    .else
        PrintLine "You cancelled"
    .endif
   
Rem    - if used with arguments, returns !Zero? for testing
    - full syntax: FileOpen$("filter" [, "title"] [, "current"] [, flags] [, sortcolumn])
    - if used without arguments, returns a pointer to the filename
    - uses two predefined OFN_xx flags called ofnFlagsO and ofnFlagsS that you can modify if really needed
    - uses a predefined ofnSortFlags in format 2*column+1, where 1 means down and 0 means sorted up; default is 2*3+1
    - see Win32 docu of OPENFILENAME, in particular lpstrFilter and OFN_xx flags
    - for example, you may use and MbOfn.nFilterIndex, 0 to clear users' choices
    - you may force a different parent window with m2m MbOfn.hwndOwner, MyWindowHandle before .if FileOpen$()
    - start folder is where the executable resides; force a different folder with, for example,
        FileOpenSetFolder "\masm32\m32lib" (Unicode: wFileOpenSetFolder "\masm32\examples\unicode_extended\UnicodeTest_GUI")
Key    fo$, fs$


Open, wOpen
    Open "I", #1, "MyFile.txt"        ; open for Input
    Input #1, offset MyBuffer, Lof(#1)
    Close #1
    Open "O", #1, "MyOtherFile.txt"        ; open for Output
    Print #1, "Test: ", offset MyBuffer
    Close
    Open "U", #1, "MyOtherFile.txt"        ; open for Update
    Print #1:4, offset MyBuffer        ; write 4 bytes
    Close
    Open "A", #ecx, "MyOtherFile.txt"        ; open for Append (you can use #register instead of an immediate)
    Print #ecx, CrLf$, "oops, forgot the end!"
    Close
    wOpen "O", #1, wRes$(MyFileID)        ; use a Unicode resource string as filename - could be Arabic, Chinese, ...
    wPrint #1, wChr$("Hello there")        ; writes once a Unicode BOM, then Unicode "Hello there"
    Close
Rem    Open returns the handle in eax - you may check for INVALID_HANDLE_VALUE
Keys    opi, opo



Close
    Close #3        ; close file #3
    Close            ; close all open files
Rem    returns DWORD in eax
Key    clo1, clo



Seek
    Open "O", #1, "TestShort.txt"        ; open file for output
    Print #1, "This is a pretty long string"
    Close
    Open "U", #1, "TestShort.txt"        ; open file for updating
    Print Str$("The file has %i bytes\n", Lof(#1))
    mov ecx, 5        ; just for fun
    Seek #1, ecx+7        ; abspos 5+7=12
    Seek #1, +ecx        ; relseek: 12+5=17, file pointer on long
    Print Str$("The file pointer is now at byte %i\n", Loc(#1))
    Print #1, "short STRING"        ; result: This is a pretty short STRING
    Seek #1, -6        ; relseek: move file pointer 6 bytes back to STRING
    Print #1, "string"        ; result: This is a pretty short string
    Print Str$("Now the file has %i bytes\n", Lof(#1))
    Close
    PrintLine "The result: [", FileRead$("TestShort.txt"), "]"
Rem    Seek returns in eax the Win API SetFilePointer return value
    Note that:
    - you can use one + or - operator, as in Seek #1, eax+20 or Seek #2, Lof(#2)-200 (but not with QWORD offsets)
    - if + or - are the first char, however, it means "relative to current pointer": Seek #1, -ecx
    - for huge files, you can use Seek #1, MyQword and Seek #1, +MyQword or Seek #1, -MyQword (but not MyQ+eax etc)
    - remember that e.g. Let esi=Input$(#1, n) advances the pointer, too


Lof
    mov ecx, Lof(#1)
    Print Str$("File #1 has %i bytes\n", ecx)
Rem    - returns length of file in eax; see example under Seek above
    - in addition, the high dword is returned in edx, therefore this is valid syntax:
        Print Str$("The file has %i bytes\n", edx::Lof(#1))
    - Lof() doesn't throw runtime errors, but you can use .if !Sign? for error checking


Loc
    Print Str$("The file pointer is now at byte %i\n", Loc(#1))
Rem    - returns current file pointer in eax (and high dword in edx); see example under Seek above
    - if needed, use .if !Sign? for error checking


Eof
    Open "I", #1, "TypeTest.udt"    ; file with user-defined type data
    .Repeat
        Recall #1, MyUDT
        deb 4, "Test", MyUDT.mydouble, MyUDT.myfloat, MyUDT.mydword
    .Until Eof(#1)    ; no more data
Rem    returns the sign flag


Input
    Input #1, offset MyBuffer, Lof(#1)
Rem    - returns bytes read in eax, zero for failure
    - edx returns the start of the buffer
    - in contrast to the WinAPI ReadFile, Input zero-delimits the read bytes, so that you
        can use the string directly; you can suppress this by adding one more argument
    - Input reads data from a file; for reading a line from the console, see Input$ below


LineInput
    include \masm32\MasmBasic\MasmBasic.inc
    Init            ; select Init and hit F6
    Open "I", #1, "\Masm32\include\Windows.inc"
    Dim My$()
    xor ecx, ecx
    .Repeat
        LineInput My$(ecx)
        .Break .if !edx    ; Eof(#1)
        PrintLine Str$("Line %i\t", ecx), My$(ecx)
        inc ecx
    .Until ecx>20
   
EndOfCode
Rem    - provided for compatibility with older Basic dialects; use Recall, it's faster, simpler and safer
    - for text files with a max string len of ca. 8k chars
    - eax returns ptr to the string, edx flags Eof()
    - works only with file #1


Input$, wInput$
    Let esi="["+Input$("Type something and hit Enter: ")+"]"        ; input from console
    Print "You typed ", esi
    Let esi=Input$("Your hobby: ", "Asm", flush max 20)            ; you may use flush as third para to flush the
    wLet esi="["+wInput$("Type something and hit Enter: ")+"]"        ; input queue, and/or max nn to limit the input
    wMsgBox 0, esi, "This is Unicode:", MB_OK
    Print "You typed [", Input$("Type something and hit Enter: "), "]"
    Open "O", #1, "YourData.txt"
    PrintLine #1, Input$("Hobbies:\t",    "Assembler, ")        ; the prompt can contain a tab escape,
    PrintLine #1, Input$("Profession:\t",    "Programmer")        ; and you can suggest a prefilled string
    Close #1
    Open "I", #2, "YourData.txt"
    Seek #2, Lof(#2)-10                ; set file pointer to EOF-10
    Print "The end of your data: ", Input$(#2, 10)            ; input last 10 bytes from file
    Close
Rem    returns temporary pointer in eax; to get a permanent string, use Let


Prompt$
    Let esi="Returned: ["+Prompt$("Title", "proposal")+"]"    ; title, suggestion, hFont, x, y, width, height
Rem    provides an edit control; for console apps only, 160k max


Rename
    Rename "MbGuide.rtf", "MbGuide.asc" [, flags]    ; source, dest
Rem    - uses MoveFileEx, flags e.g. MOVEFILE_REPLACE_EXISTING
    - use MOVEFILE_COPY_ALLOWED if dest is on different drive
Key    ren


WritePipe
    Launch "cmd.exe /C time", SW_RESTORE, cb:hEdit    ; launch an app that requires console input; show its output in the edit control
    WritePipe "20:40:50"        ; set the time
    ; you may add a 0 as second argument if you don't want a CrLf sequence to be appended:
    WritePipe esi, 0        ; write zero-delimited string in esi, do not append CrLf
Rem    - will show an error message if you try writing to a pipe that was already closed
    - you may use ClosePipe to interrupt a task
    - by default, data is written to a handle obtained by Launch ... cb:handle_to_edit; however, you may try writing to multiple pipes
        (e.g. for a chat type app) by storing & exchanging the global launch structure variable ls.lsPipeWrite


Launch
    Let My$="Notepad.exe"        ; cmd, show, timeout in ms, flags
    Launch My$        ; defaults: SW_NORMAL, 0, 0
    Launch "Notepad.exe MyNewFile.txt"
    ; if you prefer to open the doc via ShellExecute, you can use ShEx instead:
    ShEx "MyNewFile.txt"
    Launch "Notepad.exe MyNewFile.txt", SW_MINIMIZE
    Launch "Notepad.exe MyNewFile.txt", SW_MAXIMIZE, 2000
    Launch "Notepad.exe MyNewFile.txt", SW_MAXIMIZE, 2000, CREATE_NEW_CONSOLE
    ; you can specificy a handle to an edit control that receives console output:
    Launch "SendStringsToConsole.exe", SW_MAXIMIZE, cb:hEdit
    ; while the launched app is active, you may send strings: for example, you can launch the commandline interpreter
    Launch "cmd.exe /C time", SW_RESTORE, cb:hEdit    ; and see in the edit control a request to enter the new time
    WritePipe "20:40:50"            ; set the new time and confirm with Return, i.e. CrLf
    ; using the keyword passdata, Launch can pass a block of memory to the child process - see ParentData$:
    Launch "MyApp.exe /some /options", passdata, pointer_or_quoted_string [, numbytes]
    Launch "MyApp.exe /some /options", passdata, Chr$("Hello Jochen", 13, 10, "...this is great")
Rem    - returns CreateProcess pinfo.hProcess in eax, and the GetExitCodeProcess error code in edx
    - expects a Utf8$ that is passed to CreateProcessW as Utf16
    - if you specify a callback edit control instead of the timeout, e.g. with cb:hEdit, you may need to send EM_LIMITTEXT
        to this control
    - if you use cb:hEdit or you specify a timeout of 1 (one millisecond), the process handle will not be closed; you
        can then poll the process status e.g. in a WM_TIMER handler: .if ExitCode()!=STILL_ACTIVE ... do cleanup etc
    - the default timeout for a "normal" Launch is ten seconds; if you mostly need asynchronous launches,
        you can modify this value with e.g. SetLaunchTimeout 1 (in ms), up to a value of 65535, i.e. 65 seconds
    - in contrast, the passdata variant returns after 100 ms; typically, the receiving app needs 5-10 ms to grab the buffer;
        if this timeout is too long or too short, use e.g.
        Externdef MbLaMs:DWORD
        mov MbLaMs, 200
        before calling Launch
    - when passing a Chr$() or any other zero-terminated buffer, the length para is not needed


Launch$        get string from executable
    ; the line below launches Arc.exe with option v and returns what SdtOut produces:
    Let esi=Launch$(ExpandEnv$(Chr$(34, "%ProgramFiles%\FreeArc\bin\Arc.exe", 34, " v Lib32.arc")))    ; see FreeArc
    StringToArray esi, FreeArc$()    ; translate linear output to an array
    ; shorter: StringToArray Launch$(ExpandEnv$(Chr$(34, "%ProgramFiles%\FreeArc\bin\Arc.exe", 34, " v Lib32.arc"))), FreeArcListing$()
    For_ ebx=0 To eax-1
        PrintLine Str$(ebx), Tb$, FreeArc$(ebx)
    Next
    ; this line appends the current date and time, retrieved via the commandline interpreter, to an edit control:
        AddWin$ hEdit=CrLf$+"["+Trim$(Launch$("cmd.exe /C date /T"))+", "+Trim$(Launch$("cmd.exe /C time /T"))+"]"
        ; a bad example with a user-defined timeout of 2000 ms - don't launch console processes expecting user input:
        MsgBox 0, Launch$("cmd.exe /C time", SW_RESTORE, 2000), "Current time:", MB_OK    ; will ask for user input and hang
Rem    - default timeout is five seconds, use args as in Launch above
    - returns string in eax (La$? for failure), and the child process' ExitProcess argument as ExitCode()


LaunchEndPos
    mov esi, Win$(hEdit)
    mov eax, LaunchEndPos()    ; get the position inside the edit control where the last pipe read ended
    add eax, esi
    MsgBox 0, eax, "This part added by user:", MB_OK
Rem    for use with Launch ... cb:hEdit


SetLaunchEnvironment
    StringToArray Chr$("msga=Just a test", 10, "msgb=it works!"), my$()    ; create a string array: use linefeed as separator
    SetLaunchEnvironment my$()
    Launch "test.bat"    ; test it with e.g. echo %msga%
Rem    - sets temporary environment variables for use with batch files
    - Recall "MyVars", some$() works only if the file is in Unix format, i.e. linefeed-separated strings


ExitCode()
    Let My$=Launch$("GetInfo.exe")    ; imagine a little proggie that writes something useful to console and ...
    .if ExitCode($)==IDYES    ; ... finishes with a Yes/No/Cancel MsgBox plus invoke ExitProcess, eax
        ... do something with My$ ...
    .endif
Rem    - returns a global variable with the para passed with ExitProcess, i.e. the DOS-style errorlevel
    - arguments:
        ExitCode() without arguments returns the exit code of the last "standard" Launch with timeout
        ExitCode($) returns the outcome of the last Let My$=Launch$("...") attempt
        ExitCode(cb) returns the current status of the last Launch "MyConsoleApp", SW_SHOW, cb:handle
    - for Launch with timeout (i.e. not the Launch$() or Launch ... cb:handle variants), this value is also returned in edx


Exist
    .if Exist("\masm32\include\winextra.inc")
        MsgBox 0, "winextra.inc is present, yeah!", "Hi", MB_OK
    .endif
    mov esi, Chr$("NoTest.txt")
    .if !Exist(esi)
        MsgBox 0, esi, "No such file:", MB_OK
    .endif
Rem    - returns - do not use Exist("...")==0, use .if !Exist("...") instead
    - after Exist(), you can use LastFileSize, LastFileName$, LastFileDosName$
        to get more info on the found file, e.g. with Print or debug:
        .if Exist("\masm32\include\sh*")    ; v v with deb, $ in front means "show as string" v v
        Print "Match found: ", LastFileName$, Str$(" with size %i bytes\n", LastFileSize)
        deb 1, "Last file:", LastFileSize, $LastFileName$, $LastFileDosName$, $MbExeFolder$, $CurDir$()
        .endif
        - if the file does not exist, eax and LastFileSize contain -1, otherwise both return the 32-bit size
Keys    ex(, exist(


IsFolder
    .if IsFolder("\masm32\MasmBasic\")    ; with or without trailing backslash
        Print "It's a directory"
    .endif
Rem    returns like Exist(); under the hood is GetFileAttributes


IsRichEdit
GuiParas equ "controls demo", w180, h120    ; select guiparas and hit F6 to build this code
include \masm32\MasmBasic\Res\MbGui.asm
    GuiControl Edit1, "RichEdit", w500
    GuiControl Edit2, "Edit", x500, w500
    .if IsRichEdit(hEdit1)
        SetWin$ hEdit1="This is a RichEdit control"
    .else
        SetWin$ hEdit1="This is a poor edit control"
    .endif   
    .if IsRichEdit(hEdit2)
        SetWin$ hEdit2="This is a RichEdit control"
    .else
        SetWin$ hEdit2="This is a poor edit control"
    .endif

Event Key        ; dummy event
GuiEnd
Rem    - returns Zero?



Kill
    Kill "Myfile.dat"
Rem    returns eax



Touch
    Touch "Rec_Test.dat"    ; set the file's timestamp to current time
    Touch My$(99)    ; same if filename is in a string array
    Open "U", #7, esi    ; open file for updating
    Touch #7        ; set the file's timestamp to current time
    Close 7            ; close it
    .if Exist("MyFile.exe")
        Touch "MyFile.ini", GfLastWrite(-1)    ; synchronise timestamp of two files
    .endif
    ; if usedeb=1 (default!!), Touch will throw a runtime error if the file doesn't exist or is not accessible
    usedeb=1
    Touch "Test.dat"    ; make sure you are not in the middle of something important
    TouchFcs=1    ; you may also change the file creation stamp (default: 0, last write only)
    Touch "Test.dat"    ; make sure you are not in the middle of something important

Rem    - returns SetFileTime result in eax
    - second arg may be a FILETIME in xmm0, e.g. as returned by GfLastWrite():
        Touch Files$(ecx), GfLastWrite(ecx)        ; example: restore timestamp to a modified Files$(ecx)
        Touch Files$(ecx), TimeSF("01.02.2013 12:34:56")    ; TimeSF returns FILETIME value in xmm0
    - Warnings:
        - Open "O", #1, ... Touch #1 will destroy your file if you don't write content to it
        - Open "I", #1, ... Touch #1 will not change the timestamp
        - Touch will display a message if it encounters an error, followed by invoke ExitProcess; disable with usedeb=0



GetFiles, AddFiles
    GetFiles creates the Files$() array , containing full paths of all files found:
    GetFiles filter [, startpattern] [, endpattern    or lines matching] [, case sensitivity and full mode]
    GetFiles *.inc        ; fill the MbFiles$() array with *.inc files of the current directory
    AddFiles Help\*.hlp|*.chm        ; add to the Files$() array hlp or chm files from the Help subfolder
    mov ebx, eax        ; eax=# of files found
    Print Str$("\nFound %i files matching *.inc:", ebx)
    For_ n=0 To ebx-1
        Print CrLf$, Files$(n)
    Next

    ; --- search a folder for all files containing the text "Winsock", and return found lines in Files$(1/3/5 etc): ---
    GetFiles \Masm32\include\*.inc, "Winsock", 1, 1        ; 1 = return first match only, 1 = case-insensitive
    GetFiles \Masm32\include\*.inc, "Winsock", 3, 0        ; 3 = return up to three matches, 0 = case-sensitive

    ; --- search a folder for a block of text delimited by "rect" and "ends", and return it in Files$(1): ---
    GetFiles \Masm32\include\*.inc, "rect struct", "ends", 1
    .if eax
        Let Files$(0)=Mid$(Files$(0), 5)            ; cut off the "it's a file" flag (SpTbSpSp)
        Print "The text was found in ", Files$(0), ":", CrLf$
        Print Files$(1), CrLf$            ; RECT STRUCT ... ENDS
        Print Files$(2)            ; SMALLRECT STRUCT ... ENDS
    .endif
Rem    - returns # of files both in eax and MbGetFileCount
    - the Files$() array is filled with UTF8-encoded strings; you may have to use the SetCpUtf8 macro once to display non-English filenames   
    - you may use the following switches:
        GfNoPaths=    0/1    ; 0=include the path specified by user (default); 1=use file names only
        GfNoRecurse=    0/1    ; 0=include subfolders (default); 1=search only in current folder
        GfNoUtf8=        0/1    ; 0=write a UTF8 BOM when storing Files$() (default); 1=don't
    - the long version returns in Files$(0) the full path of the file where the block of text was found.
        The text may have been found in more than one file. Each new file is marked by
        space tab space space in the first 4 characters.
    - if endpattern is omitted, CrLf will be used, and the first matching line only will be returned;
        in this case only, you may used Odd(n) for getting the matching lines in Files$(n)
    - if endpattern=1...126, GetFiles returns up to endpattern matches (1: same as omitted)
    - if endpattern=-1, all matching lines will be returned in Files$()
    - if endpattern=0, GetFiles looks for matches but returns only filenames in Files$()
    - you may use GetFiles WM_DROPFILES in the respective handler; the array will contain both files and folders
    - similarly, GetFiles CL (Unicode: GetFiles wCL) transfers a list of files in the commandline to the
        Files$() array; SortFiles works as expected, also the Gf*** functions; if no arguments are present, eax will be zero
    - case & mode (bitwise flag):
        0=case-sensitive, +1=insensitive, +2=intellisense (Name=name),
        +4=full word search, +8=include start of line in text block search
    - see GfCallback below for user-defined filtering
Key    gf


GetFolders, AddFolders
    GetFolders "\Masm32\Examples"    ; fill the Files$() array with folder names
    GetFolders         ; no arg: start with current directory
    AddFolders "\Masm32\include"    ; add to Files$() folders & subfolders of the specified directory
Rem    - returns # of found folders in eax and MbGetFileCount
    - can be combined with GetFiles/AddFiles


GfCallback        define a callback function to monitor progress in GetFiles or GetFolders
    include \masm32\MasmBasic\MasmBasic.inc
    Init    ; <<< select init and hit F6 to test this snippet
    Let esi=ExpandEnv$("%WINDIR%\SysWow64\drivers")    ; usually C:\Windows\...
    PrintLine "Searching ", esi
    GfCallback cbGetFiles    ; define a callback function
    GetFolders esi
    For_ ecx=0 To eax-1
        PrintLine Str$(GfSize(ecx)), Tb$, GfDate$(ecx), Spc2$, GfTime$(ecx), Tb$, Files$(ecx)
    Next
    Print Str$("\n%i folders found\n", ecx)
    Exit
cbGetFiles:     test ecx, 1023    ; file or folder counter
        If_ Zero? Then Print "*"    ; console mode progress bar ;-)
        .if wInstr(edi, "\nti\", 1)    ; check Unicode \nti\ in edi (=full path) -> exclude files in nti folder
                or ecx, -1    ; combined with the inc ecx below, this sets Zero?
        .endif
        inc ecx
        ret
    end start
Rem    - The callback function gets invoked every time a file or folder is found and receives the following data:
            edx    pointer to the WIN32_FIND_DATAW structure used for FindFirstFileEx
            ecx    current file or folder counter
            edi    full path used in FindFirstFileExW
            esi    filename only
    - to exclude a file from Files$(), set the zero flag before the ret; recommended, as shown above:
        use or ecx, -1 if the exclusion condition is met, plus an inc ecx before the ret
    You can do whatever you need in this callback, and you don't have to preserve any registers


SortFiles            sort the Files$() array
    GetFiles \masm32\*.asm    ; create the Files$() array
    SortFiles        ; default: sort the Files$() array by date, most recent files first
    For_ ecx=0 To eax-1    ; print the results
        PrintLine Str$(GfSize(ecx)), Tb$, GfDate$(ecx), Spc2$, GfTime$(ecx), Tb$, Files$(ecx)
    Next
    SortFiles date, asc    ; sort Files$() array by date, oldest files first
    SortFiles size    ; sort Files$() array by size, biggest files first
    SortFiles size, asc    ; same but smallest files first
    SortFiles name, asc    ; sort alphabetically
Rem    - returns elements sorted in eax
    - after a sort by name, Files$() cannot be resorted by date or size


GfDate$ , GfTime$, GfAgeHours, GfAgeMinutes, GfAgeMs, GfLastWrite , GfSize, GfGetInfo
    include \masm32\MasmBasic\MasmBasic.inc
    Init    ; <<< select init and hit F6 to test this snippet
    GetFiles *.as?    ; get all asm or asc sources in the current folder and its subfolders
    SortFiles            ; sort by date, latest first
    xchg eax, ecx    ; save #files
    For_ ebx=0 To ecx-1
        .if GfAgeHours(ebx)<=24        ; pick your latest code
            mov esi, Cat$(Files$(ebx)+Space$(40))    ; we simulate LSET        edx::eax for QUADWORD size v v
            Print Str$("\n#%i ", ebx+1), Left$(esi, 40), GfDate$(ebx), ", ", GfTime$(ebx), Spc2$, Str$("%i bytes", edx::GfSize(ebx))
        .endif
    Next
   
EndOfCode
Rem    - all numerical arrays associated with GetFiles return eax, except for GfLastWrite, which returns:
        a) the ftLastWriteTime FILETIME member of the WIN32_FIND_DATA structure in the lower qword of xmm0
        b) the size in the upper qword of xmm0
    - GfSize returns the low quad in eax, the high quad in edx
    - GfLastWrite returns size and last write time in xmm0, for any index in Files$() and -1 for the last Exist(). In contrast,
        GfGetInfo(x) returns size and last x time in xmm0 for the last Exist(), where x can be created, modified, accessed
    - Gf....(-1I or Gf...(), i.e. index -1 or no arg: all functions return last write data for the last Exist()
    - index -2: GfDate$(-2) and GfTime$(-2) return creation time data for the last Exist()
    - you can copy file time and size with e.g. GfSetInfo 7, GfLastWrite(-1)    ; Files$(7) receives time and size of last Exist()
    - GfAge...() is calculated from the moment when GetFiles was launched, except for index=-1, which returns age
        relative to the time of the last Exist() call
    - for any other SYSTEMTIME structure, use Age(pSystime, unit)


Age
    .if Age(esi, h)<7*24            ; h=hours (valid units: d/h/m/s/ms/µs)
        Print "The item in the SYSTEMTIME structure is younger than seven days"
    .endif
Rem    - returns in eax the age in days, hours, minutes, seconds, milliseconds or nanoseconds/100 units
    - if you get unexpected values, try PrintLine Str$(", %i µs\t", edx::Age(pMySystime, µs)), as 32 bits
        might not be enough
    - you can use e.g. PrintLine Str$(", %2f days, ", Age(pMySystime, h)/24) to get fractions of units


CurDir$
    Let esi=CurDir$()+"MyFile.txt"        ; e.g. D:\masm32\MasmBasic\Res\MyFile.txt
    Let esi=CurDir$(0)+"\MyFile.txt"        ; same output, the (0) means "do not append a backslash"
Rem    - for use with Let & Print, returns DWORD in eax
    - in general identical to MbExeFolder$, but may be different for apps invoked by shortcuts that set the folder


GetDrives$
    include \masm32\MasmBasic\MasmBasic.inc
    Init
    GetDrives$    ; fill the Drives$() array
    Print Str$("%i drives are present: ", Drives$(?))
    For_ ecx=0 To Drives$(?)-1
        Print Drives$(ecx), " "
    Next
   
EndOfCode
Rem    before using the Drives$() array, refresh it with GetDrives$


Ini$ , SetIni$
    include \masm32\MasmBasic\MasmBasic.inc
    Init        ; select init and hit F6
    .if !Ini$()
        SetIni$ "crtDate", fDate$()
        SetIni$ "crtTime", fTime$()
    .endif
    PrintLine "Created    ", Ini$("crtDate"), ", ", Ini$("crtTime")
    SetIni$ "modTime", Cat$(fDate$()+", "+fTime$())
    PrintLine "Modified ",    Ini$("modTime")
   
EndOfCode
Rem    - searches the .ini file for the given string, in mode case-sensitive & full word
    - if no ini file is found, it will be created


SetMru, AddMru, MruText$
    SetMru "EditorDemo.ini"     ; load text file with most recently used files (in WM_CREATE handler, after menu creation)
    ...
   
Event Menu        ; (similar for a manual WM_COMMAND handler)
        Switch_ MenuID
        Case_ 2    ; "save as" menu
            .if FileSave$("Assembler=*.asc;asm|Includes=*.inc|Resources=*.rc|All files=*.*")
                Let CurrentFile$=FileSave$()            ; change name of current document
                FileWrite CurrentFile$, Win$(hMyEdit, xxl)        ; the xxl means "use a big buffer"
                AddMru CurrentFile$
            .endif
        Case_ MruFirst .. MruLast            ; get a file path from Most Recently Used menu
            Let CurrentFile$=MruText$()            ; load a document from the menu
        Endsw_
   
Event Close
        StoreUtf8 Cat$(MbExeFolder$+"\EditorDemo.ini"), Mru$(), 8    ; on exit, write up to 8 strings to the ini file   
Rem    see the "editor with toolbar" template for a full example


ExpandEnv$, wExpandEnv$
    PrintLine "Architecture is ", ExpandEnv$("%PROCESSOR_ARCHITECTURE%, the OS is %OS%, and the CPU is"), CrLf$,\
    ExpandEnv$("%PROCESSOR_IDENTIFIER%")
    PrintLine "This is the full path: [", ExpandEnv$("%ProgramFiles%\Microsoft Office\OFFICE11\WINWORD.EXE"), "]"
    Let esi="This is the full path: ["+ExpandEnv$("%ProgramFiles%\Microsoft Office\OFFICE11\WINWORD.EXE")+"]"
    PrintLine esi
    MsgBox 0, Cat$("This is the full path: ["+ExpandEnv$("%ProgramFiles%\Microsoft Office\OFFICE11\WINWORD.EXE")+"]"), "Hi", MB_OK
    ; The Unicode version is preceded by a w, as usual:
    wPrint wChr$("Program Files path in Unicode: ["), wExpandEnv$("%ProgramFiles%", 1), wChr$("]"), wCrLf$
    wPrint wChr$("Program Files path in Unicode: ["), wExpandEnv$("%ProgramFiles%"), wChr$("]"), wCrLf$
    ; Both ANSI and Unicode versions allow an optional flag "...", 1 that forces the macro to yield the full name:
    Program Files path in Unicode: [C:\Programmi]    ; output with full name flag set (here the Italian version)
    Program Files path in Unicode: [C:\PROGRA~1]    ; output without the flag

Rem    returns ptr to temporary buffer in eax; for use with Launch, Print, Let and Cat$



ExeFromWin$, ExeFromExt$
    include \masm32\MasmBasic\MasmBasic.inc
    Init
    Let esi="MbGuide.rtf"
    PrintLine esi, " was launched by ", ExeFromWin$(WinByTitle(esi))
    PrintLine "files with the ending 'rtf' get launched by [", ExeFromExt$("html"), "]"
    PrintLine 'ExeFromWin$("Recent"):', Tb$, ExeFromWin$("Recent Unread", 8)
    PrintLine 'ExeFromExt$("html"):', Tb$, ExeFromExt$("html")
   
EndOfCode
Rem    returns ptr to temporary buffer in eax; ExeFromWin$ returns in edx the PID, or zero signalling an error


GetFileProps            ; get an array of file properties
    include \masm32\MasmBasic\MasmBasic.inc
    Init
    GetFileProps ExeFromExt$("rtf")            ; get the props of RichMasm.exe
    For_ ecx=0 To FileProp$(?)-1
        Print Str$("%_i    ", ecx), FilePropName$(ecx)    ; show the predefined names
        wPrintLine At(30) wRec$(FileProp$(ecx))        ; descriptions may be Utf8
    Next
    GetFileProps "\Masm32\qEditor.exe"
    Inkey "Description of qEditor.exe: [", FileProp$(FileDescription), "]"
   
EndOfCode

Rem    - GetFileProps creates the FileProp$() array
    - to read it, you may use a counter or one of the predefined version info names, e.g. Comments, FileDescription
    - when using a counter, as shown above, FilePropName$(ecx) shows the predefined names


MakeDir
    include \masm32\MasmBasic\MasmBasic.inc
    Init
    MakeDir "tmp\new folder"
    .if Zero?
        PrintLine "[", eax, "] successfully created"
    .else
        PrintLine "Could not create '", eax, "', sorry"
    .endif
    mov esi, Chr$("\Masm32\A new folder\")    ; this path ends with a backslash
    MakeDir esi
    jne BadFolder        ; handle an error
    PrintLine "[", eax, "] created"        ; [A new folder\] created
BadFolder:   
EndOfCode
Rem    - zero flag set (i.e. .if Zero?) signals success
    - returns always pointer to folder in eax
    - path may or may not finish with a backslash
    - absolute and relative paths allowed
    - will (among others) trigger an error if a file (i.e. not a folder) exists with the same name


ArcFiles                         ; requires a FreeArc installation
    GetFiles \masm32\m32lib\*.asm        ; fill the Files$() array with the desired files
    ArcFiles "The Masm32 lib"        ; the minimum: just the name of the *.arc archive
    ; with archive name, file count, show, Launch console mode:
    ArcFiles "The_Masm32_lib", 10, SW_MINIMIZE, CREATE_NEW_CONSOLE    ; use first 10 files and a minimised new console
    ArcFiles "The_Masm32_lib", 0, SW_MINIMIZE, CREATE_NEW_CONSOLE    ; use all files in Files$() and a minimised new console
Rem    - requires FreeArc in its default location (e.g. C:\Program Files\FreeArc\bin\Arc.exe)
    - creates archive in *.arc format
    - return value: see Launch
    - use MbZipLog = 1 to see the command lines in ZipLog.txt


UnArcFiles                    ; requires a FreeArc installation
    UnArcFiles "The_Masm32_lib"        ; unzip The_Masm32_lib.arc, restoring the tree on the current drive
    ; with archive name, a new destination folder, show, Launch console mode:
    UnArcFiles "The_Masm32_lib", "\masm32\ZipTest", SW_MINIMIZE, CREATE_NEW_CONSOLE
    UnArcFiles "The_Masm32_lib", '"C:\Program Files\FreeArc\bin"'    ; spaces in dest folder need double quotes
Rem    - requires FreeArc in its default location (e.g. C:\Program Files\FreeArc\bin\Arc.exe)
    - decompresses archive in *.arc format
    - return value: see Launch
    - use MbZipLog = 1 to see the command lines in ZipLog.txt


ZipFiles
include \masm32\MasmBasic\MasmBasic.inc
Init
    GfNoRecurse=1    ; get folders at current level only
GetFolders "\Masm32\examples\exampl01\qikpad"
AddFiles "\Masm32\examples\exampl01\qikpad\*.asm|*.inc|*.rc|*.ico|*.bmp"
ZipFiles "MyQikpad.zip"

EndOfCode
Rem    - uses the Files$() array generated by GetFiles & AddFiles
    - returns #files zipped
    - no third party library needed


UnzipFile
include \masm32\MasmBasic\MasmBasic.inc
Init
UnzipInit "test.zip"    ; UnzipInit expects a filename or URL, returns a comment (if present); edx has #records
    .if Sign?
    Print eax    ; print an error message
    .else
    push eax    ; UnzipInit returns a comment or an empty string
    For_ ecx=0 To edx-1    ; #files returned in edx
        mov esi, Files$(ecx)
        .if Rinstr(esi, "/")    ; zipfiles use forward slashes
            xchg eax, esi    ; we don't display the full path
            inc esi
        .endif
        PrintLine Str$(GfSize(ecx)), Tb$, GfDate$(ecx), Spc2$, GfTime$(ecx), Tb$, esi
        .if Instr_(esi, ".asm", 1)    ; assembler source, plain text?
            PrintLine "##First 60 chars: [", Left$(UnzipFile(ecx), 60), "]"    ; see snippets for an example with UnzipFile(index, pathtowrite)
        .endif
    Next
    pop eax
    Print "Zipfile comment: [", eax, "]"    ; before UnzipExit
    UnzipExit
    .endif

EndOfCode
Rem    - decompresses files in zip archive to a buffer returned by UnzipFile(ecx)
    - you may save the file as FileWrite Cat$("\SomeFolder\"+Files$(ecx)), UnzipFile(ecx)
    - even better: UnzipFile(ecx, "D:\SomePath\")    ; \SomePath would extract to root+path of the current drive
    - UnzipFile(counter, 0)        ; extracts file #counter to the current folder
    - eax returns either the archive's comment or #error#; a 404 will show as "Archive not found" or as "Archive download failed"   
Key    uzi


FileRead$
    Let esi=FileRead$("MyFile.dat")        ; creates a string on the heap and reads in the specified file
    PrintLine FileRead$("NoSuchFile.txt")        ; shows a MsgBox and terminates program
    PrintLine FileRead$("NoSuchFile.txt", err:#)     ; prints the # character, program continues
    Let esi=FileRead$(77)        ; assigns content of RCDATA resource with ID 77 to esi (allowed IDs are 1 ... 126)
    ; read a file from the Internet, strip all HTML tags, scripts and styles, and display it on the screen:
    Inkey NoTag$(FileRead$("http://www.masm32.com/board/index.php?action=unread;all"))
    ; translate two files and one webpage into a string array:
    StringToArray FileRead$("\Masm32\include\Windows.inc")+FileRead$("http://masm32.com/board/index.php?action=help")+FileRead$("\Masm32\include\WinExtra.inc"), x$()
Rem    - returns a pointer to heap memory in the reg32 or dword variable after Let
    - for asynchronous downloads see Download
    - directly after FileRead$(), you can get the numbers of bytes read using mov eax, LastFileSize
Key    fr$(


Download
    Download "http://somesite.com/somefile.txt"    ; [, msg:hWnd] [, cb:callback]
    Download "http://somesite.com/somefile.txt", cb:MyCallback
    ...
    MyCallback:
    SetWin$ TBarControl(2)=Str$("%i bytes\ndownloaded", edx)    ; currrent state in edx
    Delay 1
    invoke GetKeyState, VK_SHIFT
    test ah, ah        ; stop download if Sign? flag set
    retn
Rem    - for use with
Event Download (triggers a WM_DOWNLOADFINISHED message)
    - if msg: is not specified, hWnd is assumed
    - optional callback function: no paras, bytes read in edx, cancel if the sign flag is set
    - wParam points to content, lParam holds length in bytes
    - if content starts with #D-, an error occurred; #D-U indicates no URL (offline?), #D-R means reading failed



NoTag$
    Let esi=NoTag$(FileRead$("http://www.masm32.com/board/index.php?action=unread;all"))
    Let esi=NoTag$(FileRead$("somefile.html"), table, swcd)    ; extract the table, swap commas and dots
Rem    strips HTML tags, scripts and styles; don't expect miracles - reducing a perfectly styled webpage
    to pure text will not look pretty, but it's handy to filter webpages by text content



FileWrite
    FileWrite "MyFile.txt", "MyString"        ; opens a file and writes a string to file
    FileWrite "MyFile.txt", "MyString", 2        ; same but writes only first 2 bytes
    FileWrite "MyFile.rtf", stream:hRichEdit    ; save contents of the RichEdit control
    FileWrite "MyFile.uctxt", stream:hRichEdit, SF_TEXT or SF_UNICODE    ; same but specify a format
    If_ Not Exist("build.bat") Then FileWrite "build.bat", res:99    ; extract a file from RCDATA resource #99
    FileWrite "MyFile.txt", "MyString", xmm0    ; sets the timestamp in xmm0
Rem    returns Close retval in eax and bytes written in edx
Key    fw



Replace$
    Open "O", #1, Chr$("\masm32\MasmBasic\WinIncs.htm")
    ; This example joins two major include files, adds html structures and highlights some elements
    Let ebx=FileRead$("\masm32\include\Windows.inc")+FileRead$("\masm32\include\WinExtra.inc")
    ; The '<' and '>' chars will be misinterpreted as html tags and must therefore be removed
    Let ebx=Replace$(ebx, offset txBracketLeft, "<")        ; src, search, repl [, case] [, count]
    Let ebx=Replace$(ebx, offset txBracketRight, ">")        ; > becomes 'greater than'
    Let ebx="Windows.inc and WinExtra.inc"+ebx+""
    Let ebx=Replace$(ebx, CrLf$, offset txBrCrLf)            ; HTML needs

    Let ebx=Replace$(ebx, "struct", "Struct", 1+4)    ; ignore case, whole word
    Let ebx=Replace$(ebx, "union", "Union", 1+4)
    Let ebx=Replace$(ebx, "qword", "qword", 1+4)
    Print #1, Replace$(ebx, "PROTO", "Proto", 1+4)            ; MixedCase looks nicer ;-)
    Close
Rem    - must be used with Let, Print or Inkey
    - Replace$ uses the same case flags as Instr_
    - the optional count parameter allows to limit substitution; 0 or omitted means replace all
Key    rep$(


ParentData$
    mov My$, ParentData$()    ; the parent process may have passed data through Launch xx, passdata...
Rem    - returns DWORD in eax
    - console apps can receive data
    - the sending program can use e.g.
        Let esi=FileRead$("\Masm32\include\Windows.inc")
        Launch "receiving.exe", passdata, Left$(esi, 400)    ; note the console's printing limits around 32k
    - receiving Windows apps should use the SendData/CopyData$ combi



CopyData$
    SetWin$ hEdit="Just received:"+CrLf$+CopyData$+CrLf$+"--- end of data ---"        ; use directly to set a window content
    Let My$="Just received:"+CrLf$+CopyData$+CrLf$+"--- end of data ---"        ; store away for later use
Rem    - for receiving data from other apps; for use inside a WM_COPYDATA message handler, for example:
        SWITCH uMsg
        CASE WM_COPYDATA
        Let My$=CopyData$+CrLf$+"received "+Time$
    - for sending data, use SendData
    - on IPC, see also MSDN: Interprocess Communications
    - for examples, see File/New Masm source, MB client & server


SendData
    SendData "MyClient", Win$(hEdit)
    SendData "MyClient", "How are you?"
Rem    - returns DWORD in eax: 0=no client found
    - can also be used from a Win32 console application


SendControlKey
    SendControlKey hWin, VK_V    ; paste something to Notepad etc
Rem    receiving window must have keyboard focus



SendWordCommands
        SendWordCommands    ; prepare a DDE session; Word must be running
    .if eax
        ; adjust J: to your needs; use single quotes outside to allow double quotes inside:
        SendWordCommands '[FileNewDefault:InsertFile "J:\Masm32\include\Windows.inc"]'
        SendWordCommands Chr$("[InsertFile ", 34, "J:\Masm32\include\WinExtra.inc", 34, "]")
        Let esi=FileRead$("\masm32\MasmBasic\Res\OpenDocInWord.bas")
        SendWordCommands Replace$(esi, "#:\", Left$(MbExeFolder$, 3))    ; send the contents of the BAS file
        SendWordCommands '[MsgBox "Cute"]'
    .else
        MsgBox 0, "MS Word doesn't answer", "Sorry", MB_OK
    .endif
    SendWordCommands exit    ; finish DDE session
Rem    - swc init returns the DdeConnect retval in eax
    - DDE uses ancient WordBasic; get the help file here ; to make it appear in
        RichMasm's Help menu, save it as \masm32\MasmBasic\Help\WrdBasic.hlp
    - a sequence is included in [squared brackets]
    - multiple commands can be separated by a colon (:)
    - for strings, use either the escape sequence °\" (Alt 248, 92, 34) or Chr$("xx", 34, "xx") - see above
    - you can also use Let or Cat$ to send commands to MS Word
    - it is your responsibility to respect legality when using this feature


OpenAdo         Advanced Data Objects
include \masm32\MasmBasic\MasmBasic.inc
    Init                ;    v v v can be xls, xlsx, mdb, dbf, wks or wk4 file
    .if OpenAdo("\Masm32\examples\IczelionSQL\jj\LifeExOECD.xls")
    For_ ecx=0 To Min(9, OpenAdo(0))-1    ; 0=first sheet
        Print Str$("%_i    ", ecx), AdoTable$(ecx, 0)    ; row ecx, column 0
        PrintLine At(60) Spc2$, AdoTable$(ecx, 1)    ; row ecx, column 1
    Next
    Store "MyData.tab", AdoTable$()    ; save your data
    ShEx "MyData.tab"        ; have a look with the default application
    .else
    MsgBox 0, AdoErr$(), "OpenAdo:", MB_OK
    .endif
EndOfCode
Rem    - first call has a filename or URL as argument, e.g. CL$()
    - you may use the AdoSheet$() array to identify the right sheet
    - with usedeb=0, the query string will not be shown
    - second call returns #rows
    - the AdoTable$() array is two-dimensional (row, column)
    - you may use Store "MyData.tab", AdoTable$()


xlsConnect, xlsOpen, xlsCommand, xlsSysRead$(), xlsRC$()

xlsRead$(), xlsWrite, xlsClose, xlsDisconnect, xlsHotlink
    These macros provide an easy-to-use DDE interface to MS Excel. Click below to see
    detailed examples: Work with data, Excel and Unicode, and Xls viewer with hotlinks.
    ; A typical conversation would look like this:
    xlsConnect        ; no args=Excel, System
    .if !Zero?        ; non-zero means success
        xlsOpen "MyFile.xls"        ; we open a file
        .if !Zero?        ; non-zero signals success
            xlsCommand "[app.activate()]"            ; optional: activate Excel
            xlsCommand '[Run("Personal.xls!!SayHi")]'    ; run a VBA macro
            xlsConnect "MyDataSheet"        ; tell Excel to which sheet you want to talk
            .if !Zero?        ; non-zero signals success
                Print "Current selection=[", xlsRead$(), "]", CrLf$                ; you may print to the console...
                SetWin$ hEdit='R1C1:R9C5=['+xlsRead$("R1C1:R9C5")+']'+CrLf$        ; ... or set window content
                AddWin$ hEdit="Extract #2=["+xlsRead$(xlsRC$(1, 1, 9, 5))+"]"+CrLf$    ; and add bits & pieces
                AddWin$ hEdit="Current selection=["+xlsRead$()+"]"+CrLf$        ; no args means current selection
                xlsWrite "R1C1", Cat$("This sheet was modified on "+Date$+", "+Time$)    ; writing is allowed, too
            .endif
            xlsClose 0        ; close the file without saving (0=don't save, 1=save, no arg: ask)
        .endif
        xlsDisconnect        ; say bye to Excel
    .endif
Rem    - all macros return !Zero? for success

; in case of failure, macros return Zero? plus an error string in eax
    - Excel macros are language sensitive: instead of xlsRead$("R1C1"), you may have to use L1C1 (French: ligne),
        Z1S1 (German: Zeile1:Spalte1) or F1C1 (Spanish: fila); try this multilingual page in case of problems


ddeConnect, ddeCommand, ddeRequest$(), ddeDisconnect
    ; These macros provide the DDE interface for non-Excel apps, such as browsers:
include \masm32\MasmBasic\MasmBasic.inc
    Init            ; select Init and hit F6 to test this code
    ddeConnect "Firefox|WWW_GetWindowInfo"    ; server|topic: connect e.g. to FF with the WWW_GetWindowInfo topic
    .if !Zero?
    PrintLine "URL=", ddeRequest$("URL")    ; request the current URL
    ddeDisconnect        ; say bye to window info
    ddeConnect "Firefox|WWW_OpenURL"    ; connect to the open URL topic
    .if !Zero?
        Inkey "Open a web page?"
        If_ eax=="y" Then ddeCommand "http://www.webalice.it/jj2006/MasmBasicQuickReference.htm"    ; open a frequently used page
        ddeDisconnect
    .endif
    .endif
EndOfCode     ; combines invoke ExitProcess & end start
Rem    see xls macros


Win$, wWin$
    MsgBox 0, Win$(hWnd), "The title of the main window:", MB_OK
    wMsgBox 0, wWin$(hWnd), "The title of the main window in Unicode:", MB_OK
    .if WinByTitle("somefile.inc - Notepad")
        xchg eax, ecx        ; move handle to a safe register
        wPrint "Content=", wWin$(ecx, 15)        ; ecx is the parent, 15 is the ID of the Notepad edit control
        wInkey wCrLf$, "Title=", wWin$(ecx)    ; delete the w if you prefer ANSI
Rem    - returns pointer in eax
    - length limit is 159,999 bytes; if that is not enough, use e.g. FileWrite "test.txt", stream:hRichEdit, SF_TEXT or SF_UNICODE
    - for getting Unicode from RichEdit controls, use wSetWin$ hUcEdit=wRec$(Win$(hRichEdit))
    - optional second argument is the ID of a child window; see also App16()
Key    win$(


SetWin$, wSetWin$
    SetWin$ hWnd="A new title for my app"
    SetWin$ rv(GetConsoleWindow)="Hello Masm Forum"     ; for console applications
    SetWin$ ecx, 15="If ecx is Notepad's handle, then we can set its edit control because its ID is 15"
    wSetWin$ hEdit=wRes$(1)+wCrLf$+wCrLf$+wRes$(2)        ; Unicode from resources
    wSetWin$ hEdit=uChr$("Добро пожаловать")        ; Unicode directly with UTF-8 build (Ctrl F6 in RichMasm)
    Let esi="Добро пожаловать"            ; assign string as UTF-8
    wSetWin$ hEdit=uChr$(esi)            ; translate to wide format using uChr$()
Rem    for main windows and their controls; you can use string concatenation as in Let resp wLet


AddWin$, wAddWin$
    AddWin$ hEdit=CrLf$+"[one line more]"    ; append some text to an edit control
    ; this line appends the current date and time to an edit control:
    AddWin$ hEdit= Rem    for plain edit controls only; use if the string contains functions such a Launch$() in the above example


SetSel$
    SetSel$ hRichEdit="доброе утро"
    SetSel$ hRichEdit=Cat$("File=["+Files$(0)+"">")
Rem    - works with richedit controls only
    - direct assembly of UTF8 text (e.g. Russian as shown above) is possible in RichMasm by using Ctrl F6 for the first build
    - default mode is "replace selected text"
    - users may modify the internal SETTEXTEX structure with, for example, SetSel$ cp:CP_UTF8, flags:ST_DEFAULT


Sel$, wSel$                ; select the white zone and hit F6
GuiParas equ "wSel$ demo", w240, h120
GuiMenu equ <@Edit, Selection>
include \masm32\MasmBasic\Res\MbGui.asm
    GuiControl MyRich, "richedit", "С Sel$ макрокоманды может показать любой раздел."
    invoke UpdateWindow, hMyRich
Event Menu
    .if MenuID==0    ; first menu item clicked
    wMsgBox 0, wSel$(hMyRich, 35, 47), "Hi", MB_OK
    SetWin$ hMyRich="Same with English: Click the menu again to show the range 35...47"
    .endif
GuiEnd
Rem    works with richedit controls only; if start and end are omitted, the current selection will be returned


CreateAndSelect
GuiParas equ "CreateAndSelect demo", w160, h160, b LiteBlueGreen, icon Globe ; width+height, bgcol
include \masm32\MasmBasic\Res\MbGui.asm
Event Paint
    push PtDC
    call DrawMyRect
EndOfEvents
MyFont LOGFONT <-40, 0, 0, 0, FW_BOLD, 0, 0, 0, 0, 0, 0, 0, 0, "Times New Roman"
DrawMyRect proc hDC
Local hBrush, whatever
    CreateAndSelect(hDC)                ; one arg: set the DC (right after the locals)
    mov hBrush, CreateAndSelect(brush, Blue)    ; the hBrush is not really needed
    invoke Rectangle, hDC, 3, 3, 202, 78
    void CreateAndSelect(brush, LiteYellow)
    invoke Rectangle, hDC, 9, 9, 196, 72
    invoke SetBkMode, hDC, TRANSPARENT
    void CreateAndSelect(font, addr MyFont)
    invoke TextOut, hDC, 20, 18, fTime$(), 9
    ReleaseObjects                ; no leaks please ;-)
    ret
DrawMyRect endp
GuiEnd
Rem    - use casThrowErrors=1 to see if your bitmap etc works
    - a clean way to create, select and release Gdi objects


MakeBrush
    MakeBrush hRed, RgbCol(255, 0, 0)            ; use with Gdi32
    MakeBrush hBlue, 0FF8080h                ; note the inverse BGR notation in hex format
    MakeBrush hRed, RgbCol(200, 255, 0, 0)            ; use with GdiPlus; "200" is the alpha
    MakeBrush brushes(ecx), SysCol(ecx+1), gdip        ; last arg must be gdip if used with gdi+
Rem    - place in WM_CREATE handler; no need to define hBrush
    - for use with gdi+ (->MakePath, GuiFill, GuiDraw), use either RgbCol() with four parameters, or (e.g. in loops)
        SysCol(index) with an additional gdip as shown above; using Gdi32 brushes with gdi+ causes exceptions


MakeFont
    MakeFont hHandFont, Height:40, Underline:TRUE, "Lucida Handwriting"
    MakeFont hVertFont, Height:32, Escapement:900
    MakeFont hFixedFont, PitchAndFamily:FIXED_PITCH
    .if FontExist("Segoe UI Emoji")    ; check if the font face exists
        MakeFont hEmoji, "Segoe UI Emoji"
    .else
        MakeFont hEmoji, "Arial"
    .endif
Rem    - in order to facilitate creating font variants, MakeFont keeps settings between calls
    - place in WM_CREATE handler


PickFont
    PickFont addr hFont    ; use the font dialog to change the font whose handle is in hFont
    PickFont addr hFont, hEdit    ; apply to edit control (if user didn't cancel)
Rem    returns font handle in eax


ImgPaint
    GetFiles *.jpg        ; load all jpg files in this folder and below
    AddFiles *.png        ; add all png files
    ImgPaintInfo 0, Files$(ImgCounter)        ; load image file in slot 0 and return image dimensions - width in eax, height in edx
    ImgPaint hStatic, 0, Files$(ImgCounter)        ; use in the WM_PAINT handler to fill static control with Globe.ico; take slot 0 (of 30)
    ImgPaintClr 2        ; unload image file in slot 2
Rem    - ImgPaintInfo can be used to adjust the dimensions of the control that takes the image
    - ImgPaint should be called from the WM_PAINT handler
    - ImgPaint accepts most common image formats, e.g. bmp, gif, png and jpg
    - ImgPaintClr frees memory, if really needed. The Exit macro frees all loaded images automatically
    - 30 slots are available, i.e. you can fill 30 static controls with images; should be enough for a card game ;-)


ToolTips
    ToolTips TTS_BALLOON            ; optional: use a specific style
    ; sm edi, TTM_SETTITLE, TTI_INFO, chr$("Hello")        ; optional: set a title
    ToolTips hEdit, "Type something"            ; immediate text for tooltip
    ToolTips hButton1, Res$(100)            ; use resource string from rc file
    ToolTips hButton2, wRes$(101)            ; in case you want Russian or Chinese...
    ToolTips end            ; not optional: close the tooltips definition
    --- below the WM_CREATE handler, you can change the text e.g. using multilingual resources as follows: ---
    SetLanguage proc uses ebx
        .if eax>=IdMenuEN && eax<=IdMenuCH        ; e.g. eax set by a menu event
        sub eax, IdMenuEN
        imul ebx, eax, 400            ; string table uses ID 1, 2,3/401, 402, 403/801, 802, 803 etc
        ToolTips hEdit, wRes$(ebx+1)            ; type here in various languages
        ToolTips hButton1, wRes$(ebx+2)            ; "click on this button"
        ToolTips hButton2, wRes$(ebx+2)            ; same
        .endif
        ret
    SetLanguage endp

Rem    - you can created them in a loop, but make sure you don't use esi for own purposes; on the other hand, you can
        send messages directly before ToolTips end:
        invoke SendMessage, [esi-4">, TTM_SETMAXTIPWIDTH, 0, 200        ; forces use of Cr, Lf or CrLf
        sm [esi-4], TTM_SETTITLE, TTI_INFO, Chr$("I am a tooltip:")        ; sm stands for invoke SendMessage,
    - must be placed at the very end of the WM_CREATE handler, i.e. when all controls have been created


WinByTitle
include \masm32\MasmBasic\MasmBasic.inc
    Init            ; select Init and hit F6 to test this code
    Print Chr$(13, 10, "Open browser window: ")
    ; optional bit mask: 0+1 for case-insensitive search, +4 for any position in title; e.g. 5 finds also - moZilla
    .if WinByTitle("Mozilla F", 4)
    wPrint "Firefox: ", wWin$(eax), 13, 10
    .else
    .if WinByClass(0, "MozillaWindowClass")        ; WinByClass finds e.g. Thunderbird
        Print "Mozilla: ", Win$(eax), 13, 10
    .else
        Print "no browser found", 13, 10
    .endif
    .endif
EndOfCode
Rem    returns handle in eax


App16
    .if App16(hWin)
        Print "This is a legacy 16-bit app"
    .endif
Rem    useful to check if you can read text from child windows of other apps (you can't read from a 16-bit app)


Clip$, wClip$, uClip$, ClipboardChanged
    Print "[", Clip$(), "]"                ; displays the content of the clipboard
    MsgBox 0, Clip$(), "Text on the clipboard:", MB_OK            ; the same as MessageBox
    MsgBox 0,\
    Cat$("The text on the clipboard:"+CrLf$+String$(33,"-")+CrLf$+Clip$(20)+CrLf$+String$(0,0)),\
    "MasmBasic:", MB_OK                ; displays a MessageBox with two horizontal delimiters
    MsgBox 0, Clip$(40), "The text on the clipboard, truncated to 40 chars:", MB_OK
    invoke lstrcpy, offset my40charbuffer, Clip$(40-1)            ; yep, don't forget space for the zero delimiter
    .Repeat
        invoke Sleep, 100
        .if ClipboardChanged()
            PrintLine "New clipboard content=[", Clip$(30), "]"        ; non-zero means new valid content available
        .endif
    .Until signed rv(GetKeyState, VK_ESCAPE)<0
    Let esi=HtmlClip$()                ; for use e.g. with Thunderbird and Firefox
    deb 1, "Image on clipboard, width & height", ClipboardImageWH, edx    ; for use with GuiImage clipboard
Rem    - returns ptr in eax, to Null$ if clipboard is empty
    - see also SetClip$ below
    - you can truncate the content for security reasons, e.g. for use in a MessageBox


SetClip$, wSetClip$, SetHtmlClip$
    SetClip$ "Today is the "+Date$        ; replace curent clipboard with a text, format CF_TEXT
    SetClip "\Masm32\MyPhoto.bmp", CF_BITMAP    ; put a bitmap on the clipboard
    SetHtmlClip$ "This is bold"        ; for use e.g. with Thunderbird
    wSetClip$ "Today is the "+wDate$+", "+wTime$    ; the Unicode version
    wSetClip$ wRec$("Добро пожаловать")        ; if pasting yields ???, hit Ctrl F6 and confirm
    SetClip #start    ; ---- set multiple clipboard formats ----
    SetHtmlClip$ "Text in HTML format"    ; use e.g. for Thunderbird and Excel
    SetClip 100, CF_BITMAP    ; 100 = ID of bitmap resource
    SetClip$ offset txTest, CF_RTF    ; Rich Text Format
    SetClip$ wRes$(123)    ; use resource string #123, "This is a sub-title" in Russian (Unicode)
    SetClip$ "This is ANSI text"
    SetClip #end    ; ---- end of multiple clipboard formats ----
Rem    returns SetClipboardData retval in eax, copied bytes in edx if successful



MsgMonitor
    WndProc proc uses ebx hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    LOCAL rc:RECT, hM1:HWND, hM2:HWND
        MsgMonitor
        SWITCH uMsg
        ...
Rem    - writes all messages to Messages.txt, in format
    - you may add PrintLine #7, "whatever" to certain messages
    - do not use file #7 when using the monitor


GuiImage
    GuiParas equ "demo"    ; select GuiParas and hit F6
    include \masm32\MasmBasic\Res\MbGui.asm
    Event Paint    ; can be Event CanvasPaint if you have a GuiControl xyz, "canvas"
        ; args are imgsrc$ or resource ID, optional: no args=upper left corner, original size, x=fit: use full canvas, or x, y [, w, h]
        GuiImage "\Masm32\examples\exampl04\car\car.jpg", fit            ; use full canvas (background image)
        GuiImage "\Masm32\examples\exampl04\car\car.jpg", 50, 400, 160, 900    ; use specific x, y, w, h
        GuiImage "\Masm32\examples\exampl04\car\car.jpg", 50, 200        ; use x, y, original w, h
        GuiImage "\Masm32\MasmBasic\Res\FileOpen.png", 200, 7, 40, 40    ; use specific x, y, w, h
        ; GuiImageSet L=1, z=50, rf=1                    ; crop left 10, zoom 50%, rotateflip 1
        GuiImage "\Masm32\MasmBasic\Res\MasmLogo.png"            ; upper left corner, original size
        GuiImage "http://masm32.com/board/index.php?action=dlattach;attach=6;type=avatar", 100, 0    ; from the Internet ;-)
        if 0            ; you may select the white zone and hit F6, but the files v v v need to be provided
        GuiImage "Это тест.jpg", fit, RgbCol(255, 0, 0)        ; Unicode is allowed; with fit, you can specify a background colour
        GuiImage wCL$(), 315, 7            ; also via the commandline
        GuiImage 125, 84, 7, 70, 70            ; 125 is a RC_DATA resource ID (IDs must be 48...127)
        ;GuiImage hdc#edi            ; paint to hdc, filename in edi (for example)
        GuiImage Files$(ecx), 238, 7, 70, 70            ; you can dynamically load images
        GuiImage clipboard, fit            ; display what's on the clipboard
        endif
    GuiEnd
Rem        - images can be animated GIFs
        - use GuiImage xxx, fit, RgbCol(...) if you need the background to be cleared, i.e. with transparent images
        - with usedeb=1, errors are printed to console

GuiImageCallback, SaveImageToFile, GuiImageSpeed, GuiImageFrame
        For use with GuiImage:
        GuiImageSpeed 50 [, 2000]    ; sets playing speed to 50% [leave a 2000 ms pause after the last frame]
        GuiImageFrame    ; force display of a specific frame in an animated GIF image
        SaveImageToFile    ; for use in GuiImageCallback; attention, only the current frame gets saved - the others are lost
Rem        - inside the callback, giImage (=GpImage) and giGraphics (=GpGraphics) are available; plus GuiWidth, GuiHeight etc
        - to save an image from the clipboard to file, use this code:
        GuiParas equ "Right-click into the image", w600, h600
        include \masm32\MasmBasic\Res\MbGui.asm
        GuiControl MyEdit, "canvas"
        ArraySet MyRect() As DWORD=1, 2, 3, 4    ; dummy values
        Event CanvasPaint
            ArrayPlot MyRect()
            GuiImage clipboard, fit    ; GuiImage must be embedded in ArrayPlot
            ArrayPlot exit    ; right-click into the image brings up a popup menu
        GuiEnd ;


GuiControl
    ; to test the GuiControl snippet, select GuiParas below and hit F6
    GuiParas equ "Test window", x650, y20, w300, h200
    include \masm32\MasmBasic\Res\MbGui.asm
    GuiControl MyEdit, "richedit", "Rich text looks better", y0+20, h1000-20    ; correct y and h for SysLink control
    GuiControl VisitMoscow, "syslink", x6, h0+20, w1000-64, "Info on Moscow: Развлечения, история, ..."    ; Unicode is ok
    GuiControl B1, "button", img 77, x=1000-30, h=0+24, w=0+24, "Eye"            ; 77: see Rsrc below
    GuiControl B2, "button", "B2", bcol LiteYellow, x=1000-60, h=0+24, w=0+30, font -14:FW_BOLD, +WS_BORDER
    GuiControl MySbar, "statusbar"                ; see SetStatus$
    GuiControl MyStatic, "static", icon 77                ; display icon with resID 77
    SetWin$ hMySbar="Uses ComCtl32 version "+ComCtl32$()        ; returns the dll version used, e.g. 5.82 (XP) or 6.16 (W7)
    GuiEnd
Rem        - create with GuiControl a full-fledged GUI application in a few lines; see Event below for a list of standard events
        - controls implemented are button, canvas, edit, listbox, richedit, scintilla, static, statusbar, syslink,
            toolbar (template), progressbar, trackbar, date, time; click here for a full example including GuiGroup
        - the syslink control has tooltips, but only the first link will be used; opening with Link$() works correctly
        - dimensions can be set as [x/y/w/h]=[per mille value]+fixed offset
        - resizing is automatic
        - first control has the focus; to override this, use e.g. guiSetFocus equ invoke SetFocus, hEditFind before the first Event
        - button controls may have images and text; use guiTxtOnClick=1 to show the text when clicked
        To create a toolbar GuiControl:
        - provide per-button gif files in a folder
        - build CreateTbInfo
        - create a link to the executable and move this link into the folder with the gif files
        - drag one of the gif files over the link and follow the instructions (PM the author in case of problems)
        - RichEdit controls pick the best version found on your machine. You can override that e.g. with two lines before the GuiControl:
            guiRichVersion$ equ <"RichEdit60W"
            mov RichEditUsed, Chr$("C:\Program Files (x86)\Common Files\Microsoft shared\Office16\Riched20")
Keys        gcb, gcc, gce, gcl, gcr, gcs, gcsb


Coloured rounded buttons
GuiParas equ "Colourful buttons", w320, h110    ; 32% of window width, 11% of height
; GuiButtonPen equ ; white, half transparent
include \masm32\MasmBasic\Res\MbGui.asm    ; select GuiParas and hit F6
    GuiControl Button1, "button", "A normal pushbutton", w250, h0+40    ; width 25% of client area, height 0%+40px
    GuiControl Button2, "button", "Lite Turquoise"/"Blue", bcol LiteBlueGreen/Blue, x250, w250, h0+40, font -14
    GuiControl Button3, "button", "Lite Yellow"/"Full Yellow", bcol LiteYellow/Yellow, fcol Red/Black, x500, w250, h0+40, font -14:FW_BOLD
    GuiControl Button4, "button", "Rounded button", bcol LiteGrey/Red, fcol Black/White, rr40, x750, w250, h0+40, font -12:FW_SEMIBOLD
GuiEnd
Rem    To define a button, use:
    - x, y, w, h for its position, followed by n 1000th of the window width or height
    - bcol RgbCol(...) to define the background colour; bcol Blue/Red means "blue, but red if pressed"
    - fcol Black/White to define the foreground (text) colour; white if pressed
    - "normal text"/"pressed text"
    - MbBtnR=3    ; button moves right when pressed
    - MbBtnD=2    ; button moves down when pressed   
    - rr0=no rounded corners, rr100=a circle; use guiRoundRect=n to set the default rounding
    - you may define a pen: put GuiButtonPen before the include line


EndOfEvents, GuiEnd
    ; to test this snippet, select the white zone from GuiParas to GuiEnd, then hit F6
    GuiParas equ "See Windows messages", x650, y20, w200, h200
    GuiMenu equ @File, &Open, &Save, -, E&xit, @Edit, Undo, Copy, Paste
    include \masm32\MasmBasic\Res\MbGui.asm
    ... add e.g. GuiControl, or your own WM_CREATE stuff ...
    Event Menu
        deb 1, "You clicked", MenuID
    Event Message
        inc msgCount
        .if uMsg_==WM_MOUSEMOVE
            call myproc
        .else
            deb 4, "msg", chg:msgCount
        .endif
    EndOfEvents
    myproc proc
        Print Str$(msgCount), Cr$
        ret
    myproc endp
    GuiEnd
Rem    A MasmBasic Gui program has a very simple structure, as shown above:
    - GuiParas and GuiMenu declarations (click here for a menu with bitmaps)
    - the include line
    - any WM_CREATE code
    - Event sections: Menu, Key, Size, Paint, Timer, Command, Notify, or Message for any other uMsg
    - EndOfEvents, followed by user procedures
    - GuiEnd, followed by comments or RichMasm options



SetListbox, SetCombobox
    GuiParas equ "
Event-based programming is easy", x750, y20, w250, h120, b RgbCol(255, 255, 160)
    include \masm32\MasmBasic\Res\MbGui.asm
    GuiControl MyLb, "listbox", y 60, h 940
    GuiControl MyEd, "static", text "Double-click to open a file - Дважды щелкните, чтобы открыть файл", h 50
    GetFiles *.as?|*.rc    ; asc, asm, rc
    SortFiles    ; latest files on top
    SetListbox Files$()    ; Fill the listbox with UTF-8 encoded file names
    Event Command
    .if NotifyCode==LBN_DBLCLK
        ShEx LbSel$    ; open selected file with ShellExecuteW (u=assume UTF-8 for LbSel$)
    .endif
    GuiEnd
Rem    - fills a listbox or combobox with the elements of a string array, e.g. Files$()
    - if you need to reset the listbox, use e.g.    SetListbox other$(), clear    to force a LB_RESETCONTENT message
    - SetListBox is speed-optimised; use e.g.    SetListbox Files$(), short    to optimise for code size (55 bytes less)
    - use the LBN_DBLCLK, LBN_SELCHANGE or CBN_SELCHANGE notify codes of Event Command to perform specific actions
    - you can pass a callback function to SetListBox which decides via the sign flag if a string (in esi) should be added or not:
        SetListbox x$(), cb:MyCb
        ...
        EndOfEvents
        MyCb:    ; a simple filter function
        dec Instr_(esi, s1$)    ; does esi have a match for string 1?
        .if !Sign?
        dec Instr_(esi, s2$)    ; if yes, match for string 2?
        .endif
        retn    ; return the sign flag
    - current selection is returned as LbSel$ or CbSel$, UTF-8 encoded


Event
    ; to test this snippet, select GuiParas and hit F6
    GuiParas equ "
Event-based programming is easy", x750, y20, w250, h120, b RgbCol(255, 255, 160)
    GuiMenu equ @File, &Open, &Save, -, &Download, E&xit, @Edit, Undo, Copy, Paste        ; define a menu (paras & menu before the include line)
    include \masm32\MasmBasic\Res\MbGui.asm
    GuiControl MyEdit, "edit"
    ; ... implicit Create event here: any code needed once during window creation, e.g. GuiControl, or CreateWindowEx, if you prefer it manually ...
    Event Message    ; generic event, use e.g. Switch_ uMsg to handle specific messages
        inc msgCount
        NoDebMsg CATSTR NoDebMsg, <, WM_ENTERIDLE, WM_MOUSEMOVE>    ; exclude some messages
        deb 4, "msg", chg:msgCount    ; simple message monitoring (needs console)
    Event Menu    ; WM_COMMAND, WM_NOTIFY
        .if MenuID==2
            Download "http://www.jj2007.eu/Masm32_Tips_Tricks_and_Traps.htm"
        .elseif MenuID<=7    ; MenuID is zero for the first entry, 1 for the second etc
            MsgBox 0, Str$("You clicked menu #%i", MenuID), "Hi", MB_OK
        .endif
    Event Timer
        Print "Current time is ", Time$, Cr$    ; put e.g. guiTimerMs=200 before the include line to change the frequency; default is 20 ms
    Event Download    ; WM_DOWNLOADFINISHED
        SetWin$ hMyEdit=NoTag$(wParam_)    ; lParam holds length, not needed for a textfile
    Event DropFiles    ; loads the Files$() array; use wRec$() to convert Utf8 file names to true Unicode
        wMsgBox 0, wCat$(wStr$("%i files dropped, first one is\n[", Files$(?))+wRec$(Files$(0))+"], last one is "+CrLf$+"["+wRec$(Files$(Files$(?)-1))+"]"), "
Event Dropfiles:", MB_OK or MB_TOPMOST
        StoreUtf8 "~tmpDropped.txt", Files$()    ; write dropped filenames to a text file in Utf8 format
    GuiEnd
Rem    - a MasmBasic Gui application provides a simple interface to the standard Windows message loop
    - implemented events are Menu, Key, Size, FullPaint, Message, Timer, Command, Notify, Close, DropFiles, Data, Download, CanvasPaint, CanvasMessage and TableMessage


Event Message
    GuiParas equ "
Event-based programming is easy", w200, h120        ; select GuiParas and hit F6
    GuiMenu equ @File, &Open, &Save, -, &Download, E&xit, @Edit, Undo, Copy, Paste    ; define a menu
    include \masm32\MasmBasic\Res\MbGui.asm
    GuiControl Button1, "button", "Button 1", h=0+30, w=500     ; 50% of width, 30px
    GuiControl Button2, "button", "Button 2", h=0+30, w=500, x=500
    GuiControl StatusBar, "statusbar", "Select a menu entry, or click on one of the buttons"
    Event Message
        inc msgCount
        NoDebMsg CATSTR NoDebMsg, <, WM_ENTERIDLE>    ; don't show this message
        deb 4, "msg", chg:msgCount    ; this console deb will only be shown if chg:xxx has changed; needs OPT_Susy Console
    GuiEnd

Event Timer
    GuiParas equ "Timer demo", x20, w500, h320, b LiteGreen
    guiTimerMs=500        ; default is 20; place before the include line
    include \masm32\MasmBasic\Res\MbGui.asm
    SetGlobals imgY
    Event Timer
        mov edx, GuiHeight
        add imgY, 30
        .if imgY>edx
        Clr imgY
        .endif
        GuiCls
    Event Paint        ; show the Masm forum logo
        GuiImage "http://masm32.com/board/Themes/default/images/smflogo.png", 20, imgY
    GuiEnd


Event Command, Event Menu
    GuiParas equ "
Event-based programming is easy", w200, h120        ; select GuiParas and hit F6
    GuiMenu equ @File, &Open, &Save, -, &Download, E&xit, @Edit, Undo, Copy, Paste    ; define a menu
    include \masm32\MasmBasic\Res\MbGui.asm
    GuiControl Button1, "button", "Button 1", h=0+30, w=500     ; 50% of width, 30px
    GuiControl Button2, "button", "Button 2", h=0+30, w=500, x=500
    GuiControl StatusBar, "statusbar", "Select a menu entry, or click on one of the buttons"
    Event Command    ; same as Event Menu
        Switch_ MenuID
        Case_ 0 .. IDMLAST
            SetWin$ hStatusBar=Str$("You selected menu %i", MenuID)
        Default_
            .if nCode==BN_CLICKED
                .if MenuID==Button1
                        SetWin$ hStatusBar="You clicked Button1"
                .else
                        SetWin$ hStatusBar="You clicked Button2"
                .endif
            .endif
        Endsw_
    GuiEnd
Rem    MenuID contains the identifier of the control or the menu entry ranging from 0 .. IDMLAST


Event Close
    GuiParas equ "Close me", w200, h120, bLiteGreen        ; select GuiParas and hit F6
    include \masm32\MasmBasic\Res\MbGui.asm
    Event Close
        MsgBox 0, "Sure to close?", "Hi", MB_OKCANCEL
        sub eax, IDCANCEL
    GuiEnd
Rem    if eax is zero, the program will not be closed


Event Data
    GuiParas equ "
Event-based programming is easy"    ; select GuiParas and hit F6
    include \masm32\MasmBasic\Res\MbGui.asm
    Event Key
        SendData hWnd_, Chr$("Добро пожаловать")    ; send a Utf8 string to yourself
    Event Data
        uMsgBox 0, CopyData$, "You received a WM_COPYDATA string:", MB_OK
    GuiEnd
Rem    - the string received in CopyData$ may contain binary data


Event Key
    GuiParas equ "
Event-based programming is easy", h100, w240    ; select GuiParas and hit F6
    include \masm32\MasmBasic\Res\MbGui.asm
    GuiControl MyEdit, "richedit", h=0+36, text "Select me and hit Return", font -16:FW_SEMIBOLD
    Event Key
        .if VKey==-VK_RETURN        ; -13
            MsgBox 0, Cat$("You hit return in the control with text"+CrLf$+Win$(hWnd_)), "Hi", MB_OK
        .else
            fdeb 4, "key", CtrlVKey(), VK_S
            .if CtrlVKey()==-VK_S
                SetWin$ hMyEdit="You pressed Ctrl S"
            .else
                SetWin$ hMyEdit=Str$("You pressed key #%i", VKey)
            .endif
        .endif
    GuiEnd
Rem    - returns the VK_? code in eax
    - the internal variable ReChanges counts the number of edits in a RichEdit control; clear it when saving a document
    - if the key was pressed in a child window, e.g. a single line edit control (h=0+36 or less), the negative VK_? code will be returned


Event CanvasPaint
    GuiParas equ "
Event-based programming is easy", w400, h300, bLiteBlue    ; select GuiParas and hit F6
    include \masm32\MasmBasic\Res\MbGui.asm
    GuiControl First, "canvas", y=0+20, x200, h=250, w300, +WS_EX_CLIENTEDGE
    GuiControl Second, "canvas", x300, y400, w300+120, h180+100, +WS_THICKFRAME
    Event CanvasPaint    ; this event returns the ID in ecx
    Switch_ ecx
    Case_ First: GuiEllipse 50.0, 50.0, 49.0, 49.0
    Case_ Second
        GuiImage "\Masm32\examples\exampl04\car\car.jpg", fit
        GuiTextBox 40.0, 50.0, 70, 32, "Masm32's car.jpg", bcol RgbCol(255, 255, 202), font -14
    Endsw_
    GuiEnd
Rem    - the canvas control allows to paint one or more areas of the main window separately, using a Switch_ ecx
    - if you paint using ArrayPlot RgbCol(...) ...(your code)... ArrayPlot exit, then the image can be copied & saved with a right-click
    - use in particular with GuiImage and ArrayPlot


Event Paint
    GuiParas equ "
Event-based programming is easy"    ; select GuiParas and hit F6
    include \masm32\MasmBasic\Res\MbGui.asm
    Event Paint
        GuiText 20.0, 50.0, "Hello World in a Paint event", bcol RgbCol(255, 255, 255), font 48
    GuiEnd



GuiParas, GuiMenu, GuiColor, GuiText, GuiTextBox, GuiCls, GuiRefresh, GuiLine, GuiCircle, GuiEllipse
    ; to test this snippet, double-click on GuiParas and hit F6
    GuiParas equ "Hello jj2007", x650, y20, w200, h222, icon Flower, cblack, b LiteGrey    ; xpos, ypos, width, height, color, bgcolor (see RgbCol)
    ; x and/or y omitted=center; xr123=123px from the right
    GuiMenu equ @File, &Open, &Save, -, E&xit, @Edit, Undo, Copy, Paste
    include \masm32\MasmBasic\Res\MbGui.asm        ; select Init and hit F6 to test this snippet
    Event Menu
    MsgBox 0, Str$("You clicked menu #%i", MenuID), "Hi", MB_OK    ; MenuID is zero for the first entry, 1 for the second etc
    Event Message
    inc msgCount
    deb 4, "msg", chg:msgCount            ; , wParam, lParam    ; see deb
    Event Paint
    GuiTextBox 99.9-90, 10, 60, auto, "Simple text box", bcol RgbCol(0, 255, 255)        ; 99.9-90: lower border-90px
    GuiColor Blue, RgbCol(255, 255, 222)
    For_ ct=0 To 9
        imul ecx, ct, 25
        mov ecx, RgbCol(ecx, 0, 0)
        GuiText ct*8+7, ct*16+7, Str$("Line %i ", ct+1), fcol ecx
    Next
    Print Str$("painting took %i ms\n", GuiMs)            ; GuiMs measures the duration of the WM_PAINT handler
    GuiEnd            ; replaces end start here
    other Gui macros (see extended example here):
        GuiColor foreground, background
        GuiCls clears the canvas
        GuiRefresh updates a specific GuiControl
        GuiTextBox left, top, width, height, "text" [, style]         ; style: e.g. DT_LEFT or DT_WORDWRAP
        GuiText x, y, text [, style]            ; style: e.g. transparent/opaque, fcol, bcol
        GuiLine x0, y0 [, x1, y1]            ; 4 args: from ... to, 2 args: ... to
        GuiEllipse x, y, radiusX, radiusY
        GuiCircle x, y, radius
        GuiSetFill brush
        GuiControl MyID, "type" [, "text or file"] [, x, y, w, h]    ; type is e.g. edit, richedit, button, ...
Rem    A quick-and-dirty way to write a Windows application in a few lines - click here for a full example


icon in GuiParas
        GuiParas equ "Hello World", w200, h99, icon Butterfly    ; <<< select GuiParas and hit F6 to test this snippet
        include \masm32\MasmBasic\Res\MbGui.asm
        GuiEnd
Rem        - predefined names are Apple, Ball, Bluetooth, Bulb, Butterfly, Clock, Dice, Flower, Football, Globe, Guard, Info, Joystick, Keys, Lens
            Monitor, Movie, Music, Music2, Newspaper, Painting, Plot, Printer, Star, Swords, Trafficlight, Tree, Trumpet, Umbrella, Walkman, Wall
        - build and use IconExplorer to get more ideas; use e.g. icon Shell32:14 when building a Gui app


MakePath & GuiDraw            ; for use with gdi+ in GUI applications
        MakePath MyBezier, Bezier(150:0, 0:90, 0:120, 300:300)    ; 4/7/10 etc * x:y
        MakePath MyCircle, Circle(333)            ; radius
        MakePath MyEllipse, Ellipse(500:300)        ; x, y, width, height
        MakePen hPen, RgbCol(100, 0, 0, 255), width 3    ; blue, half transparent
        MakeBrush hBrush, RgbCol(100, 255, 255, 0)
        ...
        Event Paint
        GuiDraw MyCircle, hPen, 100, 200        ; draw outline at x=100, y=200
        GuiFill MyEllipse, hBrush, 10.0, 20.0    ; fill ellipse at x=10%, y=20% of client area
Rem        - MakePath creates a Gdi+ path object, for use with GuiDraw or GuiFill ; see here for a full example
        - Gdi+ has the bad habit to trash the FPU; if you use it, preserve your data between Guixx calls
        - use MakePen and MakeBrush for outline and interior, and make sure you specified 4 args in RgbCol(...)
        - see PlotData for an example using Legend
        - see here for a pie chart example using PieLegend
        - available shapes are Arc, Bezier, Circle, ClosedCurve, Ellipse, Pie, Polygon, PolyLine and Rect, see
            Graphics Paths in GDI+ for details. The shape 'Text' is not implemented, use GuiTextBox instead


SetDoc$, GetDoc$, SetTitle$, SetStatus$    ; for use with Gui apps
    .if FileSave$("Assembler=*.asm|MasmBasic=*.asc|All files=*.*")
        FileWrite FileSave$(), stream:hMyEdit, If?(InstrOr(FileSave$(), ".rtf" or ".asc", 1), SF_RTF, SF_TEXT)
        SetDoc$ FileSave$()    ; assign new document name and set window title accordingly
    .endif
    SetStatus$ GetDoc$()    ; set doc name without path to first column of status bar
    SetStatus$ GetDoc$(full)    ; same but will full path
    SetStatus$ "Добрый Вечер", 1    ; just a test - Unicode is no problem
    SetStatus$ GetDoc$(full), 2    ; set doc name with full path to third column of status bar
    ...
    Switch MenuID
    Case 3 .. 12    ; the Masm32 Case macro accepts a range
        SetMenuLanguage MenuID-3    ; change menu language on the fly
        SetTitle$    ; use current document name and menu language
Rem    check here for an example


New$
    Let A$=New$(100)        ; 100 zero-initialised bytes
    mov ecx, 500        ; reg32 except eax+edx are allowed to specify size
    Let My$(0)=New$(ecx)        ; destination can be an array element
    Let My$(0)=My$(0)+New$(1000)+My$(0)+"#bye"    ; and concatenation is legal code
    Clr$ My$(0)            ; it is recommended to clear the old string before a new huge allocation,
    mov ecx, 900000000        ; precisely because concatenation of the old and the new string is allowed
    Let My$(0)=New$(ecx)        ; 900 MB may work on many machines, but do thorough testing!
Rem    - returns pointer to zero-initialised memory in eax
    - you can also use Let Some$=String$(n, 0)
    - Resize Some$:nBytes performs a HeapReAlloc (+ or -) on Some$; added nBytes will be zeroed
    - you may free this memory with Clr Some$, but it will also be freed automatically with Exit
Key    new$(


Alloc16, Free16
include \masm32\MasmBasic\MasmBasic.inc    ; download
    Init
    Dim PtrSSE() As DWORD
    For_ ct=0 To A16Max-1    ; 100 aligned pointers
        Alloc16 Rand(10000)
        mov PtrSSE(ct), eax
        Print Hex$(al), " "
    Next
    For_ ct=0 To A16Max-1
        Free16 PtrSSE(ct)
    Next
    EndOfCode
Rem    for use with SSE instructions that require 16-bit alignment


MemSet
    MemSet offset somebuffer, 0, 1000            ; dest. pattern, #bytes
    MemSet offset somebuffer, "x", 1000            ; 1000 * x
    MemSet offset somebuffer, Mirror$("abcd"), 1000    ; 250 * abcd, slightly faster
    mov eax, Chr$("Masm32 is great ")            ; string must have (at least) 16 bytes
    movups xmm0, oword ptr [eax]
    mov edx, offset somestring
    MemSet edx, xmm0, 99
Rem    returns end of buffer in edx


MemState        ; detect leaks
    MemState("leaked kBytes: %i")                ; prints to console the leaked bytes using Str$("...") format
    MemState("leaked %i kB", abs del)            ; prints both the current absolute value and the leaked bytes
    MemState("leaked kBytes: %i\n", always)            ; prints both the current absolute value and the leaked bytes
    MemState(dump)                    ; prints sums and averages for all monitoring points
    MemState(reset)                    ; resets sums and counters
    PrintLine "PagefileUsage: 0x", Hex$(MemState(PagefileUsage))    ; print a member of PROCESS_MEMORY_COUNTERS
Rem    - use in loops to detect memory leaks
    - standalone format prints only if a change was detected; use e.g. msTrigger=8192 to change the threshold (default is 4k)
    - function format returns, standalone format leaves all registers intact
    - second arg may be a combi of absolute, delta, reset and always (=print even if there was no change); default is del
    - with msUse=0, no code will be generated (the function format, e.g. MemState(PagefileUsage), returns -127)
    - default unit is kBytes; use msUnit=10 for kBytes, msUnit=20 for MBytes and msUnit=0 for bytes


Res$, wRes$, wRec$, ResFile$
    Let esi=wRes$(401)        ; get the resource string with ID 401 from the *.rc string table
    invoke MessageBoxW, 0, esi, wRes$(402), MB_OK
    Open "O", #1, "TestUnicode.txt"
    wPrint #1, wChr$("Unicode, oh yeah!", 13, 10)
    wPrint #1, wRes$(401), wTb$, wRes$(402), wCrLf$
    Close
    Recall "TestUnicode.txt", rc$()        ; file is Unicode
    For_ ecx=0 To eax-1
        wPrintLine wRec$(rc$(ecx))        ; Recall translates Unicode text files to UTF-8; use wRec$() to get Unicode back
    Next
    Print "A file in resources defined as 77 RCDATA "HelloWorld.txt:", ResFile$(77)     ; file content returned in eax
    Inkey "bye"
Rem    - returns DWORD in edx
    - if the resource string does not exist, a "No such string" runtime error will be triggered unless
        you set MbUseErrLine = 0. In this case, missing strings will be shown as R? (Ansi and Unicode).
    - you can embed resources in your *.asc file by using two Rsrc bookmarks
        (case-sensitive) below end Start, for example:
Rsrc
32512 ICON "\\masm32\\MasmBasic\\icons\\Globe.ico"    ; Asm, House, Keys, Globe, Hammer, Setup, Disc, Eye, ...
1 24 "\\Masm32\\MasmBasic\\Res\\XpManifest.xml"    ; adding a manifest may help to avoid AV problems
STRINGTABLE
BEGIN
    401, "Нажмите на эту кнопку"    ; "Click on this button" in Russian
    402, "Добро пожаловать"             ; "Welcome" in Russian
END
Rsrc


Lg$    multilingual strings
    include \masm32\MasmBasic\MasmBasic.inc
    Init                ; *** select Init and hit F6 ***
    Lg$(File:"\Masm32\MasmBasic\Res\GuiData\GuiData.tab")        ; file:"path" loads the multilingual matrix; or File:nn if you have RCDATA nn in resources
    For_ ct=0 To 5
        Lg$(Language: Mid$("ENESBRITDEFR", ct*2+1, 2))        ; example: Lg$(Lang:IT) sets Italian
        wPrintLine wLg$(LgTx), ":", CrLf$, Tb$, wLg$(_ASM), CrLf$, Tb$, wLg$("_BEL")    ; Unicode works better
        wPrintLine Tb$, wLg$("_USA"), CrLf$, Tb$, wLg$(_AUS), CrLf$, Tb$, wLg$(_AUT)    ; with many languages
    Next
   
EndOfCode
Rem    - designed for use with multilingual GUIs; matrices should be edited as spreadsheets, and must be saved in tab format
    - Lg$(Language:FR) sets the column defined in the first row of the spreadsheet
    - only the first four characters are being used to identify the string.


Geo$
    Print "I live in ", Geo$()
Rem    your computer knows roughly where you live ;-)


MsgTable$
    ; returns Unicode string describing common errors. for example "no such path" in Kernel32:
    wPrint MsgTable$(3, "kernel32.dll"), wCrLf$        ; can't find the path
    wPrint MsgTable$(80000431h, "user32.dll"), wCrLf$    ; attempt xx failed
    wPrint MsgTable$(3, "ntdll.dll"), wCrLf$            ; STATUS_WAIT_3
    wInkey MsgTable$(6BAh, "kernel32")            ; RPC server not available



String$
    Let Files$(n)=String$(32767, 0)            ; assigns a zero-initialised buffer to Files$(n)
    mov eax, String$(32768-1, "A")            ; assigns a buffer initialised with A to eax
    Print String$(15, "+x")            ; prints +x+x+x+x+x+x+x+x+x+x+x+x+x+x+x
    Print String$(0, 0), CrLf$            ; String$(0, x) means repeat last result
    Print String$(40, Cat$("["+String$(20, "abc")+"]"+CrLf$))        ; fill a screen
Rem    - returns DWORD ptr eax; max. allowed size is 32767 bytes
    - the buffer remains valid until a new call to String$, except for String$(0,0) which just returns the last result
    - two or more String$ in the same Print or Let line will print only the last version
    - see also MemSet
Key    string$(


.MbSpace$
    Let My$=Space$(20)
    Print "Twenty spaces: [", Space$(20),"]"
    ; make sure that FamilyName$ gets printed in column 21 (except if FirstName$ is longer):
    PrintLine Space$(20, FirstName$(ecx)), " ", FamilyName$(ecx)
Rem    returns pointer in eax


SpaceToTab$
    include \masm32\MasmBasic\MasmBasic.inc
    Init
    PrintLine "123        56 78        90"
    Inkey SpaceToTab$("123        56 78        90")
   
EndOfCode
Rem    use to convert a fixed size database to tab-delimited


Inkey, wInkey
    ; Inkey behaves exactly like Print but waits for a keystroke; it returns the code of the pressed char in eax:
    Inkey "One more loop (y/n)?"
    .Break .if eax=="n"
    Inkey Str$("\nThis file has %2f kBytes\nhit any key to get outta here", Lof("MbGuide.rtf")/1024)
    wInkey wRes$(ID_1_Chinese)
Rem    - returns DWORD in eax
    - in contrast to the Masm32 inkey, which returns scan codes, Inkey and wInkey return either a char ("a", "A") or, if the
        key does not have a char associated, the virtual key code (VK_F1, VK_LEFT) that a Windows app would return.
    - the virtual key code is always returned in edx


Print, wPrint

Print #n, wPrint #n
    Print "Test", CrLf$, Chr$("the library", 13, 10)
    Print #1, esi, " is ", My$
    wPrint wRes$(123)        ; Unicode defined in a resource stringtable
    Print #1:25, "There are many chars here but I print only a few"
    Print #1:100, FileRead$("\masm32\include\Windows.inc")    ; print the first 100 chars of that file
Rem    - predefined strings do not bloat the exe (the same applies to Let):
    (w)CrLf$        same as an offset to db 13, 10, 0
    (w)Tb$        9 aka tab - see also \n and \t in Str$()
    Spc1$        32
    Spc2$        32, 32
    Spc4$        four spaces
    MbExeFolder$    the folder of the current executable, e.g. D:\Masm32\
    CurDir$()            the current directory, e.g. D:\Masm32\
    CurDir$(0)         same but no trailing backslash, e.g. D:\Masm32
    - Unicode can be printed as e.g. wPrint #1, wCrLf$, wChr$("This is Unicode") or by defining a
        string like this: wData MyWide$, "This is Unicode", 0 , followed by wPrint #1, offset MyWide$
    - Unicode printing to the console (e.g. Russian, Chinese) is possible but do not assume
        it will work on other systems; specific language packs need to be installed.
        You may test if the following line works on your system if you assemble with Ctrl F6 and click Yes:
        uPrint "Нажмите на эту кнопку"    ; "Click on this button" in Russian
    - Important: the first occurrence of wPrint in your code triggers (hidden, but you can see it in Olly):
        invoke SetConsoleOutputCP, CP_UTF8    ; codepage 65001, Unicode
        ConsoleColor cGray, cBlack        ; grey on black
        Without these two API calls, Unicode strings will not display properly. Note that the
        console colors are not automatically set if there is an earlier ConsoleColor statement.
        You can try different colours by using for example, before the wPrint, wPrintColor = cYellow
        (see full list under ConsoleColor below). Strangely enough, Chinese and Arabic fonts may work
        with some colours but not with others - Windows mysteries... ;-)
    - Print is powerful and versatile, but it may fail with a HeapAlloc runtime error for huge buffers; in
        these cases, use PrintBuffer (see below)
   
Key    pri, pri1



PrintLine, wPrintLine
    PrintLine "Test", Chr$(" the library")    ; appends a CrLf$
    PrintLine        ; no arg prints just a CrLf$ (same as Print without arguments)
Rem    see Print above



PrintBuffer
    PrintBuffer #1:eax, esi    ; #file:bytes, pBuffer    ; corresponds to Print #1:eax, esi syntax
    PrintBuffer #1, esi, eax    ; #file, pBuffer, bytes    ; same code generated
Rem    - returns # of bytes written in edx, Win API WriteFile return code in eax
    - use this macro instead of e.g. Print #1:eax, esi to avoid memory problems
Key    pb1


PrintRtf        ; send the contents of a RichEdit control to a printer
    GuiParas equ "Test window", x650, y20, w300, h200
    include \masm32\MasmBasic\Res\MbGui.asm            ; select the white zone and hit F6 to test this
    GuiControl MyEdit, "RichEdit"                ; create a control
    SetWin$ hMyEdit=String$(20, cfm$("select some text and hit Ctrl P to print this\n"))
   
Event Key
        If_ VKey==VK_P || VKey==-VK_P Then <MsgBox 0, Str$("%i pages printed", PrintRtf(hMyEdit, 2)), "PrintRtf:", MB_OK>
    GuiEnd
Rem    - returns #pages printed
    - requires a handle to a RichEdit control
    - if text is selected, then only the selection will be printed
    - optional second arg: 0=use default printer, 1=use once the printer dialog, 2=use it always, 4+x=use 'no setup' dialog
    - optional: margins in mm, e.g. void PrintRtf(hMyEdit, 0, 20, 20, 15, 20)        ; left, top, right, bottom
    - printer settings like orientation etc are kept while the program runs


PrintCpu, Cpu$
        PrintCpu            ; print full info to the console
        PrintCpu 0             ; print basic info
        PrintLine "[", Cpu$(), "]"             ; basic info, for use in Gui apps
Rem        useful for benchmarking different CPUs



SetCpUtf8, SetCpAnsi, SetCpUpperLower$
    SetCpUtf8        ; set codepage to UTF8
    SetCpAnsi        ; force ANSI codepage
    SetCpUpperLower$ CP_UTF8    ; assume source for e.g. Let esi=Upper$(esi) is UTF-8 encoded; decode to same codepage
    SetCpUpperLower$ CP_UTF8, 1252    ; encode as UTF-8, decode as standard Windows codepage
    SetCpUpperLower$    ; reset to user's systemwide codepage setting (usually CP 1252)
Rem    - internal macros for switching between normal Ansi and Unicode Print
    - SetCpUpperLower$ source [, dest] may be needed if text comes from a file, and does not have the same encoding
    - in RichMasm, by pressing Ctrl F6 the user can decide to build the project entirely in UTF-8; codepages will be set accordingly


ConvertCp$
    Let esi=ConvertCp$(esi, CP_UTF8, 1252)            ; convert esi from UTF-8 to standard Windows
    SetCpAnsi                ; set codepage 1252 for printing
    PrintLine "Test: [", ConvertCp$(esi, CP_UTF8, 1252), "]"    ; print convert esi from UTF-8 to standard Windows
Rem    - may be handy e.g. with UTF-8 encoded Files$()
    - note that Recall returns UTF8 when opening a Unicode text file
    - for UTF-8 -> Unicode, use wChr$()
    - for Unicode -> UTF-8, use Utf8$()
    - limit is 40k; use Recall and a loop for bigger files


Cls        ; ## miscellaneous console functions ##
    Cls            ; clear the screen
Rem    console apps only, of course


ConsoleColor
    ConsoleColor cBlue, cWhite        ; blue text on white background
    ConsoleColor cDarkRed        ; dark red text on black
    ConsoleColor invert        ; fore- and background colour inverted
    ConsoleColor eax        ; any colour that suits SetConsoleTextAttribute
Rem    available colors (see also http://support.microsoft.com/kb/319883)
    cBlack, cWhite, cGray, cBlue, cGreen, cCyan, cRed, cMagenta, cYellow
    cDarkGray, cDarkBlue, cDarkGreen, cDarkCyan, cDarkRed, cDarkMagenta, cDarkYellow


Locate()
    mov ebx, Locate(y)        ; get current Y position; same for Locate(x)
    Locate(0, ebx)        ; place the console cursor in column 0, line ebx
Rem    SetConsoleCursorPosition retval in eax


At, CColor
    Print At(20) "Hello"            ; one argument: print in column 20, current row
    Print At(ct+1, Locate(y)+1) CColor(cBlack, cYellow) "Hello"    ; one column right of some counter, next line, black on yellow:
    Print At(5, 5) CColor(cBlack, cYellow) "Attention, no commas after At() and CColor() !!"
    Print At(#esi) esi    ; esi points to ABCD sometext, where A=col, B=row, C=foreground, D=background colour
Rem    for use with Print only; separate with blanks, not commas, from the first Print argument



crtbuf
    crtbuf ThisExe$, MAX_PATH            ; create a buffer in the uninitialised data section
    invoke GetModuleFileName, 0, ThisExe$, MAX_PATH    ; use it...
    crtbuf pBuffer, 1000000, 16            ; create a 1 Mio bytes buffer in .data?, align 16 for use with SSE2
Rem    uses label and ORG $+size, therefore even big values will not make ml.exe freeze


xCall             ; call a label with arguments
    include \masm32\MasmBasic\MasmBasic.inc
    .code
    somelabel:
    mov ecx, [esp+4]
    MsgBox 0, ecx, "Hi", MB_OK
    retn 4
    Init
    xCall somelabel, Chr$("Hello World")
   
EndOfCode
Rem    use for passing invoke-style arguments to a label


CreateInvoke
    Init
;    whateverproc = any proc or result of GetProcAddress etc
    mov CreateInvoke(whatever1, 3*dword, REAL8), whateverproc
;    mov CreateInvoke(whatever2, vararg), whateverproc    ; Error A2094: Vararg requires C calling convention
    mov CreateInvokeC(whatever3, dword, REAL8, dword, REAL4), whateverproc
    mov CreateInvokeC(whatever4, vararg), whateverproc

;    example how to use the created invoke:
    invoke whatever3, 123, FP8(123.456), 456, FP4(456.789)
    Inkey "OK"
    Exit
    whateverproc proc    thedd1, thereal8:REAL8, thedd2, thereal4:REAL4
        PrintLine Str$("arg2=%f", thereal8)
        PrintLine Str$("arg3=%f", thedd2)
        PrintLine Str$("arg4=%f", thereal4)
        ret
    whateverproc endp
    end start
Rem    make sure the proc specifies the non-dword args correctly


CRT
include \masm32\MasmBasic\MasmBasic.inc
    SetGlobals timeinfo:SYSTEMTIME, f$="Now it's %I:%M %p"
    Init            ; select init and hit F6 to test this snippet
    Inkey "strftime(): ** ", CRT(strftime, buffer, 80, f$, addr timeinfo), " **"
   
EndOfCode        ; for parameters, see e.g. C++ strftime()
    Output: strftime(): ** Now it's 12:00 AM **
Rem    - allows to use C RunTime functions inter alia with Let and Print, in a format close to C
    - note that some C functions return numbers in ST(0); use void ..., then Print Str$(ST(0)v) to display them
    - if one para is called buffer as shown above, it will be allocated and freed automatically


QuadMath, Quad, Quad$
include \masm32\MasmBasic\MasmBasic.inc
    SetGlobals REAL16 quadNumA, quadNumB, quadResult
    Init quad
    MovVal quadNumA, "2.141592653589793238462643383279502884"    ; string to flt128
    movups quadNumB, Quad(FP4(1.0))                ; REALx to flt128
    movups quadResult, QuadMath(__addtf3, quadNumA, quadNumB)
    Inkey "PI=", Quad$(quadResult, "%#*.32Qg")            ; Quad$ instead of Str$()
   
EndOfCode
Rem    - GCC QuadMath DLLs needed
    - over 100 REAL16 functions
    - click here for an example how to use the GCC QuadMath library


   

gsl    (GNU Scientific Library interface) #mat
include \masm32\MasmBasic\MasmBasic.inc
; define the gsl function(s) you want to use with the syntax of the GNU Scientific Library Reference
gslvar    int gsl_rng_default()
gsl    double gsl_stats_mean(const double data[], size_t stride, size_t n)
gsl    double gsl_stats_variance(const double data[], size_t stride, size_t n)
gsl    double gsl_stats_sd(const double data[], size_t stride, size_t n)

.data
MyData    REAL8 5.0, 6.0, 3.2, 1.8, 9.0        ; define a double precision array
MyReal8    REAL8 ?            ; for saving results with fstp
    Init                    ; select init and hit F6 to test this snippet
    SetFloat MyReal8=gsl_stats_mean(offset MyData, 1, 5)    ; ptr, stride 1, 5 elements
    PrintLine Str$("Mean        \t%7f", MyReal8)    ; print with 7 digits precision
   
    PrintLine Str$("Variance\t%7f", gsl_stats_variance (&MyData, 1, 5))
    fstp st                ; just print, then cleanup the FPU
   
    PrintLine Str$("Standard Dev\t%7f", gsl_stats_sd (addr MyData, 1, 5)v)    ; a trailing v tells Str$() to fstp st
    mov eax, gsl_rng_default()        ; you can access gsl global variables defined with gslvar
   
EndOfCode
    Output:
    Mean            5.000000
    Variance        7.620000
    Standard Dev    2.760435
Rem    - if the GSL dll files are not found, an attempt will be made to get them from the oscats site; in case
        of problems, check if your antivirus software interferes with the download, and if yes, act accordingly
    - note that MasmBasic is copyrighted and therefore cannot be distributed together with
        code that uses GNU components; make sure you understand the legal implications


SetPoly3, GetPoly3
    Dim My3Pts(5) As DWORD        ; create an array with 3 XY pairs, i.e. 6 elements (0 .. 5)
    ArraySet My3Pts() = 1, 100, 2, 300, 4, 150    ; assign XY values (MbArraySet can be handy but any other array works, too)
    SetPoly3 My3Pts()        ; create coefficients for a 3-point polynomial, i.e. Y=a0+a1*X+a2*X2
    Dim AllPts(11) As REAL4        ; create a destination array with 12 elements
    Print "N", Tb$, "X", Tb$, "Y", Tb$, "Y(ecx)"    ; the last column uses the "direct" variant GetPoly3(X)
    GetPoly3(AllPts())        ; fill starting from X=0, create coefficients for Y=a0+a1*X+a2*X2
    add eax, eax
    push eax
    xor ecx, ecx
    .Repeat        ; print N, X, Y=f(x), Y=f(2*X)
        Print Str$("\n%i\t", ecx/2), Str$("%2f\t", AllPts(ecx)), Str$("%3f\t", AllPts(ecx+1)), Str$("%3f", GetPoly3(ecx))
        fstp st        ; pop the return value from the FPU
        add ecx, 2
    .Until ecx>=stack
    pop eax
Rem    - GetPoly3() returns #XY pairs in eax
    - GetPoly3(array()) sets the whole destination array
    - GetPoly3(X) returns a single value in ST(0)


GetLinReg , LinRegY    linear regression
    ArrayRead x() As REAL8, 90    ; get x+y arrays
    ArrayRead y() As REAL8, 91    ; from resources (or from file)
    Dim y2() As REAL8    ; this array will hold the regression line
    GetLinReg x(), y()    ; set the A and B coefficients
    For_ ecx=0 To eax-1    ; #elements in eax
        SetFloat =LinRegY(x(ecx))    ; brackets needed here
    Next
Rem    - GetLinReg returns #elements in eax
    - LinRegY(x) returns the corresponding Y value in ST(0)


Dim
    Dim My$()        ; create a string array (expand the number of elements as necessary)
    Dim x$(100000)    ; preallocate 100000 elements, i.e. 0...100000 (slightly faster)
    Dim My$(tab)    ; create a two-dimensional string array, e.g. for a table (expand as necessary, use Tb$ as delimiter)
    Dim My$(csv)    ; same but using comma-separated values for storing to a csv file
    For_ ecx=0 To 99    ; start from index zero to enable autoexpansion
        Let My$(ebx)=Str$("This is array element %i", ecx)
    Next
    MsgBox 0, My$(50), "Re-Dim an array:", MB_OK
    Dim My$()        ; simply Dim it again
    Dim wc(3) As WNDCLASSEX    ; an array of structures
    mov wc(0, size), SIZEOF WNDCLASSEX    ; use mov or m2m as appropriate
    Dim rc(3) As RECT
    For_ n=0 To 3
            m2m rc(n, left), n    ; n is a global variable, so you need m2m
    Next
    Dim MyBytes(999) As BYTE    ; a 1000-byte array
    Dim MyWords(3, ebx) As WORD    ; a two-dimensional WORD array
Rem    - see Let, Erase, Swap, Recall and Store
    - no Erase() needed before a redimension (string arrays only)
    - string and fixed size arrays may have one or two dimensions; the second dimension count must be
        greater than zero and must not exceed 255, i.e. Dim My$(99999, 255) is ok, Dim My$(2, 256) is not.
        The columns limitation does not apply to Dim My$(tab) arrays.
        Note that Dim My$(12345678, 255) is formally ok but will trigger a HeapAlloc runtime error
        because you need a really big computer to allocate 12345679*256*8=25,283,950,592 bytes ;-)
    - ?id$(n, ecx) where n is e.g. My$(id) can be used to pass an array to a proc
    - My$(?) returns the number of strings; My$(123, ?) returns #columns in row 123 of a two-dimensional arrays; use My$(?columns) to
        get the max #columns, testing only the first 127 elements; the line index with the highest column count is in edx
    - with numerical arrays, use MyNum(?) for total #elements, MyNum(?rows) for #rows and MyNum(?cols) for #columns
    - use MyNum(?mean), MyNum(?median), MyNum(?sum), MyNum(?min) and MyNum(?max) to get more info about the array
    - the max #elements is limited by HeapAlloc, e.g. 88 Mio REAL8, 170 Mio REAL4 or DWORD elements on Win7-64 with 4MB RAM
    - with two-dimensional string arrays, Insert and Delete may    show buggy behaviour; test yourself
Key    Dim


Erase
    Erase My$()        ; free a string array; Dim My$(new:ct) is possible
    Erase MyReal8Array()    ; free a numeric array; at
    Erase MyStructureArray()     ; present, no redim possible
Rem    no return value; will throw a runtime error if HeapFree fails
Key    erase


VarPtr
    Dim MyR8(5, 6) As REAL8
    mov eax, VarPtr(MyR8(3, 3))    ; get the address of element 3, 3
    fld REAL8 ptr [eax]
Rem    - returns DWORD in edx
    - for string arrays use mov eax, My$(123) instead


ArrayFill, wArrayFill
    include \masm32\MasmBasic\MasmBasic.inc
    Init        ; < < select Init and hit F6 to test this snippet
    ArrayFill Years() As WORD, 2000, 5, 2    ; create a WORD sized array and fill it; start value is 2000, 5 elements, increment 2
    For_ ecx=0 To eax-1
        PrintLine Str$("Year #%_u\t", ecx), Str$(Years(ecx))
    Next
    Dim rent(11) As DWORD    ; create a fixed-size DWORD array with 0...11=12 elements
    ArrayFill rent(), 1000    ; fill it with the value 1000
    Dim MyRect(2) As RECT    ; create an array with 0...2=3 elements
    .data
    rect RECT <12, 34, 56, 78>
    .code
    ArrayFill MyRect(), rect    ; fill array with values from one .data section structure
    For_ ecx=0 To eax-1
        Print Str$("rect %i: ", ecx), Str$("left=%i", MyRect(ecx, left)), Str$(", right=%i\n", MyRect(ecx, right))
    Next
    Dim My$(2)        ; fixed size string array
    ArrayFill My$(), "String created "+Date$+"!!"
    xchg eax, ecx
    .Repeat
        Let My$(ecx)=Str$("String #%i added", ecx)
        inc ecx
    .Until ecx>=5
    For_ ecx=0 To My$(?)-1
        PrintLine Str$("String #%i\t", ecx), My$(ecx)
    Next
   
EndOfCode
Rem    - use ArrayFill array() As SIZE, start, #elements [, step] to create and fill numeric arrays (BYTE...DWORD, REAL4...REAL10)
    - strings and structures like RECT must first be Dim'ed as fixed size arrays; after the ArrayFill, they are dynamic
    - see also Let, Erase, Swap, Recall and Store


ArrayStripDuplicates
    include \masm32\MasmBasic\MasmBasic.inc
    Init        ; < < select Init and hit F6 to test this snippet
    Dim MyNumbers() As DWORD
    For_ ecx=0 To 99999    ; create 100,000 elements
        mov MyNumbers(ecx), Rand(20)    ; random values 0...19
    Next
    ArraySort MyNumbers(-)    ; sort descending
    ArrayStripDuplicates MyNumbers()
    For_ ecx=0 To eax-1
        Print Str$(ecx), Str$("\t%i\n", MyNumbers(ecx))
    Next
   
EndOfCode
Rem    - removes all duplicate elements from a sorted numerical array
    - returns new #elements in eax

ArraySet
    Dim My3Pts(2) As DWORD        ; create an array with 3 elements (0 .. 2)
    ArraySet My3Pts() = 12, 34, 56        ; assign three values
    Dim MyR8(3) As REAL8        ; create an array with 4 elements (0 .. 3)
    ArraySet MyR8() = 1.0, 2.0e3, 2.0, 4,0e3    ; same for REAL4 or REAL8
    ArraySet My$() = "abc", "def", "ghij", "klmn"    ; same with strings: no Dim before, empty brackets
    Let My$(My$(?))="Only strings support"    ; My$(?) is the current number
    Let My$(My$(?))="dynamic auto expansion"    ; of elements in the string array
    For_ ecx=0 To My$(?)-1
        PrintLine Str$(ecx), Tb$, My$(ecx)
    Next
    ArraySet t$()="Hello ", "World, ", "how ", "are ", "you?"
    For_ each esi in t$(): Print esi
Rem    - numerical arrays: allowed sizes are DWORD, REAL4, REAL8; no autoexpand, no boundary
        check, #elements must be sufficient, no return value, does not trash any registers
    - strings must be initialised with empty brackets, #elements in eax; autoexpanded if needed


StringToArray
    ; convert string to a string array:
    StringToArray Win$(hEdit), My$()        ; converts content of the edit control to a MasmBasic string array
    Let esi="A string"+Tb$+"in two columns"+CrLf$+"another"+Tb$+"string"    ; tab-delimited text
    StringToArray esi, My$(), tab        ; convert to a two-dimensional array
    StringToArray Clip$(), L$()        ; converts content of the clipboard to an array
    StringToArray 123, L$()        ; converts resource;    use e.g. 123 RCDATA "names.txt" in the Rsrc section
    ; convert string to a numerical array:
    include \masm32\MasmBasic\MasmBasic.inc
    SetGlobals a1$="123 27.5    28.49    -56.78 '20h' 0x40 11111b/123456789"    ; string with a wild mix of number formats
    Init    ; < < select Init and hit F6 to test this snippet
    Dim MyDw() As DWORD
    For_ ecx=0 To Fn(StringToArray a1$, MyDw())-1    ; strings to dwords conversion
        PrintLine Str$("MyDw(%i)=", ecx), Str$(MyDw(ecx))
    Next
   
EndOfCode
Rem    - returns #elements in eax; in For_ ... Next loops, you may use Fn(...)-1 as shown above
    - numeric arrays must be declared (BYTE ... QWORD, REAL4/8/10), but there is no need to Dim a string array before


Split$
    include \masm32\MasmBasic\MasmBasic.inc
    Init    ; < < select Init and hit F6 to test this snippet
    For_ ct=0 To Split$("Masm32 is great", " ", My$())-1
        PrintLine "[", My$(ct), "]"
    Next
   
EndOfCode
Rem    converts a string to an array using the specified delimiter, and returns #elements in eax; under the hood, StringToArray is being used



Join$
        ; converts a string array to one string
    include \masm32\MasmBasic\MasmBasic.inc
    Init   
    Dim My$()
    Let My$(0)="Masm32"
    Let My$(1)="is"
    Let My$(2)="great"
    PrintLine "[", Join$(My$()), "]"    ; with no second arg, output is [Masm32\nis\ngreat], i.e. items separated by 13, 10
    Print "[", Join$(My$(), " "), "]"    ; shows [Masm32 is great] with " " as second arg
    Exit
   
EndOfCode
    Join$(MyArray$(), Dest$)    ; convert a string array to one linear Dest$ separated by CrLf$
Rem    - returns ptr in eax when used in function form, i.e. mov var, Join$(array$(), "fillstring")
    - standalone, i.e. Join$(array$(), dest$) can be slightly faster than Let dest$=Join$(array$())


Filter$
    include \masm32\MasmBasic\MasmBasic.inc
        Init        ; select Init and hit F6 to list all structures in Windows.inc
        Recall "\Masm32\include\Windows.inc", L$()
        Print Str$("%i lines loaded, now filtering for STRUCT:", eax)
        For_ ecx=0 To Filter$(L$(), "STRUCT", 1, 5)-1    ; 1=keep if match, 1=case-insensitive+4=full word
        Print Str$("\n%i\t", ecx), L$(ecx)        ; lists all strings that contain STRUCT as full word
        Next
   
EndOfCode
    include \masm32\MasmBasic\MasmBasic.inc
        Init        ; select Init and hit F6 to list all macros in \Masm32\macros\macros.asm
        Recall "\Masm32\macros\macros.asm", L$()
        If_ Filter$(L$(), "MACRO", 1, 4) Then <For_ each esi in L$(): <PrintLine Trim$(esi)>>
   
EndOfCode
Rem    - returns remaining #strings in eax
    - syntax: mov ecx, Filter$(array$(), match$ [, include] [instr mode]
    - include: 0 (exclude matching strings) or 1 (include, default)
    - instr mode: see Instr_(); 1=case-insensitive, 4=full word
    - may be needed to prevent early expansion
    - in general, Extract$() offers more and better options



ArrayMerge
    include \masm32\MasmBasic\MasmBasic.inc
    Init        ; << select Ixnit and hit F6 to test this snippet
    Dim A$()        ; first array
    Dim B$()        ; second array
    For_ ecx=0 To 9        ; ten loops
        Let A$(ecx)=Str$("String A %i", ecx)        ; fill with more or less...
        Let B$(ecx)=Str$("String B %i", ecx)    ; ... meaningful stuff
    Next
    ArrayMerge A$(), B$()        ; A$(): 10xA, then 10xB; array B$() gets erased
    For_ ecx=0 To 19        ; twenty loops
        PrintLine Str$("Element %_i\t", ecx), A$(ecx)
    Next
    Exit debug        ; debug = check integrity of allocations on exit
    end start
Rem    - returns nothing
    - destination array cannot be a Recall array
    - destination array cannot be Files$()
    - use Delete My$(n), all in case you want to truncate the destination before merging


Swap
    Swap L$(), Files$()
    Swap MyA$, MyB$
Rem    - exchanges two arrays or two variables of the same type
    - allowed numeric types: DWORD, QWORD, REAL4, REAL8, REAL10
    - mixing arrays and strings will fail and trigger an error message
    - to Swap two strings of the same array, use Swap #x$(ct), #x$(ct2) (works only with UAsm and AsmC!)
Key    swap


Insert
    Insert My$(5)    ; move My$(5) up to My$(6)
    Let My$(5)="This is the new string"
    Insert My$(6)="This is the new string #6"    ; inserts a string and assigns a value
    Insert =Str$("String #%i", ecx)    ; needs because Str$() returns eax
    Insert My$(5), 10    ; move My$(5) up to My$(15), insert 10 empty strings
    Dim D2$(tab)    ; create a two-dimensional, tab-delimited string array
    Insert D2$(0, 1), 3    ; insert 3 cells at row 0, column 1
    Dim MyRc() As RECT    ; Insert and Delete work with autoexpanding numerical arrays
    Insert MyRc(ecx+1)    ; triggers runtime error if ecx+1 is beyond the bounds
Rem    no return value; does not change any registers (not even eax)
Key    ins


Delete
    Delete My$(5)    ; move My$(6) down to replace My$(5)
    Delete My$(5), 2    ; move My$(7) down, delete also My$(5+6)
    mov ecx, 3        ; regs are allowed but not eax or edx
    Delete My$(5), ecx    ; move My$(8) down to replace My$(5), delete also My$(6+7)
    Dim MyDw() As DWORD    ; Insert and Delete work with autoexpanding numerical arrays
    Delete MyDw(99)    ; triggers runtime error is ecx+1 is beyond the bounds
Rem    - no return value, all regs unchanged
    - to delete an array element without moving the other elements, use Clr$ My$(5)
    - deleting the last element, e.g. with Delete My$(My$(?)), triggers autoexpansion of the array,
        i.e. the new array will have (nold+128) and 4095 elements
    - with 2-dimensional arrays, Delete My$(1, 2) etc is possible but check carefully the result,
        especially if you need to use Delete more than once
    - to delete a file, use Kill "MyFile.dat"
Key    del


GuidFromString
    .data
    CLSID_InternetExplorer    GuidFromString("0002DF01-0000-0000-C000-000000000046")    ; with quotes
    IID_IWebBrowser2    GuidFromString(D30C1661-CDAF-11D0-8A3E-00C04FC9E26E)        ; plain
    IID_IWebBrowser2a    GuidFromString({D30C1661-CDAF-11D0-8A3E-00C04FC9E26E})    ; registry format
Rem    use in .data, .const or .code section, as replacement for MyVar GUID


Guid$            create a GUID/UUID string
    include \masm32\MasmBasic\MasmBasic.inc
    Init        ; << select Init and hit F6 to test this snippet
    MsgBox 0, Cat$("A Guid: ["+Guid$()+"]"), "Hi", MB_OK
   
EndOfCode
Rem    returns a pointer to the string, plus a pointer to the GUID itself in edx


GuidsEqual
    ; compare two GUIDs for equality - for use with OLE
    .if GuidsEqual(offset IID_IUnknown, IID_IUnknown)    ; offset, direct
        PrintLine "EQ"
    .else
        PrintLine "NE"
    .endif

    .if GuidsEqual(IID_IOleObject, IID_IUnknown)        ; 2*direct
        PrintLine "EQ"
    .else
        PrintLine "NE"
    .endif

    mov eax, offset IID_IOleClientSite            ; one offset in reg32
    .if GuidsEqual(eax, IID_IOleObject)
        PrintLine "EQ"
    .else
        PrintLine "NE"
    .endif
Rem    returns Zero?, trashes xmm0 but no other register



CoInvoke
    mov edi, offset WebInterface
    lea edx, vEmpty
    CoInvoke [edi], IWebBrowserVtbl.Navigate, Ole$("www.google.com"), edx, edx, edx, edx
Rem    for use with COM; rvCoinvoke(...) returns eax (and S_OK=0)


Ole$ aka BSTR
    mov ecx, Chr$("Just a test")
    For_ esi=0 To 4
        wPrint wStr$("%i [", esi+1), Ole$(ecx), "]", wCrLf$
    Next
    wPrint "[", Ole$("another test"), "]", wCrLf$
    wInkey wChr$(13, 10, "["), Ole$(ecx), " again]"
Rem    returns a BSTR in eax; the string is not permanent, since it uses MasmBasic's fat circular buffer. Advantage: There is
    no need to free it. However, avoid using it in or after a loop that might eventually overwrite the string's location:
        ; mov edi, Ole$("will never get overwritten")    ; immediate string would be safe because it uses the data section
        mov edi, Chr$("trashed after ca. 1500 loops")    ; the Utf8 source string
        mov edi, Ole$(edi)        ; translates a Utf8 (or ANSI) string to a Utf16 BSTR
        For_ esi=0 To 1505        ; Str$ uses the circular buffer, and will eventually return to edi
        wPrint wStr$("Testing the limits of MasmBasic's circular buffer: %i [", esi+1), edi, "] ", wCrLf$
        Next
        wPrint "Now what happened to edi? Here it is: [", edi, "] ", wCrLf$



wData

wChr$

Utf8$

Chr$
    mov ebx, "A"
    Print CrLf$, Chr$("The alphabet from a loop: ", ebx)
    .Repeat
            inc ebx
        Print Chr$(ebx+32)
    .Until ebx>="Z"
    For_ ct=0 To 9
        Rand("a", "z", c1)    ; create random text
        Rand("a", "z", c2)
        Rand("a", "z", c3)
        Rand("a", "z", c4)
        Rand("a", "z", c5)
        Let some$(ct)=Str$(ct)+Tb$+Chr$("_", c1, c2, c3, c4, c5, "_")
        PrintLine some$(ct)
    Next
    PrintLine Utf8$(wCL$())    ; translate the Unicode commandline to Utf-8
    wPrintLine wCL$()    ; same effect in this case
    mov ebx, 60
    Print Chr$(13, 10, "This is ", ebx, "great", ebx+2)    ;
    wPrint #1, wChr$(13, 10, "This is Unicode"")    ; to files or the console (see wPrint for details)
    mov eax, Chr$("This is an ANSI string")        ; you can use a pointer to an ANSI string...
    wPrint wChr$("Not true: "), wChr$(eax)        ; ... to print a wide string with wChr$(reg32)
    wData MyWide$, "This is Unicode", 0        ; use in code section, access as mov eax, offset MyWide$
    invoke TextOut, PtDC, 7, 2, Chr$("Hello"), c$Len    ; some functions need the length; use c$Len
Rem    returns pointer in eax
Key    c$(


RichEd$         ; get text from RichEdit control
    MsgBox 0, RichEd$(), "Current selection:", MB_OK
Rem    returns edi (preserve as needed)



Len, wLen, uLen        get string length
    mov ebx, Len(My$)
    mov eax, Len("123")        ; Len returns 3 in eax, so Olly will show mov eax, eax
    void Len("123")        ; put the result into eax (void avoids the mov eax, eax)
    mov eax, wLen(MyUnicode$)        ; wideLen for Unicode strings
    void wLen(wChr$("123"))        ; returns 3 chars in eax
    Let esi="Добро пожаловатьäöü"        ; assign a UTF-8 string
    uPrint "[Добро пожаловатьäöü]"        ; or print it directly
    Print Str$(" is %i characters long (expected: 19)\n", uLen(esi))
Rem    returns length in eax; bytes for ANSI, chars for Unicode; for UTF-8 encoded strings, Len returns bytes, uLen returns chars



MbCopy, Bmove, SafeCopy
    invoke MbCopy, ptrDest, ptrSource, ctBytes
    invoke MbCopyz, ptrDest, ptrSource        ; zero-delimited
    Bmove ptrSource, ptrDest [, ctBytes]
    SafeCopy ptrDest, ptrSource, maxBytes
Rem    - returns a pointer to the end of the destination in eax (for fast string concatenation)
    - Bmove is identical but uses like Masm32 szCopy the inverted src to dest logic; ctBytes can be omitted
    - for ctBytes=-1, MbCopy calculates the length of the source; it thus does the same as lstrcpy
        but is (e.g. on an Intel Core i5 for ct=one Million) over three times as fast
    - for ctBytes=-2, MbCopy calculates the length of the source but does not include the zero delimiter when
        copying; use this for cat$ situations but note that the last element should have have ctBytes=-1.



MsgBox, wMsgBox, Alert()
    MsgBox 0, "MasmBasic is cute", "Welcome:", MB_YESNOCANCEL
    .if Alert("text", "title", MB_YESNO)==IDYES
        ... do something ...        ; same as MsgBox but returns a value
    .endif
    Dim My$(9)            ; create a string array
    ArrayFill My$(), "Ciao"        ; set all strings to Ciao
    Let My$(9)="Good morning"        ; make an exception for #99 ;-)
    MsgBox 0, Cat$(My$(3)), My$(9), MB_OK
    wArrayFill My$(), "Ciao as Unicode"        ; reset all strings to Unicode Ciao
    wLet My$(9)="Good morning as Unicode:"        ; different exception...
    wMsgBox 0, wCat$(My$(3)), My$(9), MB_OK
    wMsgBox 0, wCat$("Now:"+wCrLf$+wDate$+", "+wTime$), "Unicode is easy:", MB_OK
Rem    MsgBox returns result in eax; ecx is preserved
Key    mb, wmb, alert


Clr, Clr$, Cls     free variables and clear console
    Clr MyVar1         ; clears local and global dword variables
    Clr MyVar1, MyVar2    ; multiple arguments allowed
    Clr eax, MyVar1, MyVar2    ; the first one may be a register
    Clr$ My$(123)    ; clear an array element
    Clr$ a$, b$, c$    ; clear one or more strings (free heap mem, assign Null$)
    Cls                ; clears the console
Rem    - Clr: if a register is specified as first of several arguments, code is shorter and the register will be zeroed, too
    - to remove an array element, use e.g. Delete My$(123)


ClearLastError
    ClearLastError
    invoke GetTickCount
    PrintLine Err$()
Rem    for debugging; same as invoke SetLastError, 0, but ecx is safe, and no code will be generated for usedeb=0


ClearFileCache
    include \masm32\MasmBasic\MasmBasic.inc
    Init
    Let esi="\Masm32\include\Windows.inc"
    For_ ecx=0 To 9
        ClearFileCache esi    ; tell the OS to expel the file from the cache
        NanoTimer()
        Let edi=FileRead$(esi)
        PrintLine Str$("%i bytes read in ", LastFileSize), NanoTimer$()
    Next
    Print
    For_ ecx=0 To 9
        NanoTimer()    ; no caching this time
        Let edi=FileRead$(esi)
        PrintLine Str$("%i bytes read in ", LastFileSize), NanoTimer$()
    Next
    EndOfCode
Rem    for benchmarks of algos that involve disk I/O; returns CreateFile error code in eax


ClearLocals, _Local
    include \masm32\MasmBasic\MasmBasic.inc
    .code
    MyTest proc uses edi esi ebx arg1:DWORD, arg2, TheString
    LOCAL v1, v2, rc:RECT, buffer[100]:BYTE        ; ordinary Locals first
    _Local v3=123, v4:REAL4=123.456
    _Local x$="Hello World", y$=TheString        ; strings can be initialised, too
        ClearLocals        ; first line after the LOCALs
        deb 1, "Perfect:", v1, v2, v3, v4, $x$, $TheString
        ret
    MyTest endp
    Init        ; << select Ixnit and hit F6 to test this snippet
    invoke MyTest, 123, 456, Chr$("String passed")
    EndOfCode
Rem    - very fast, leaves all registers intact
    - the order is important: "normal" locals first, then numeric _Local vars, finally strings
    - for buffers over 2kB StackBuffer is a tick faster
    - you can initialise DWORD, REAL and string variables with _Local (use after the "normal" locals)
Key    clv


StackBuffer
    MyTest proc uses edi esi ebx arg1:DWORD, arg2:RECT
    LOCAL rc:RECT, sbuf1, sbuf2, sbuf3, whatever[100]:BYTE
    ; optional: ClearLocals    ; first line after the LOCALs
        mov sbuf1, StackBuffer(100000)    ; allocate two fat buffers, and make sure
        mov sbuf2, StackBuffer(4000h)    ; they are 16-byte aligned for use with SSE2
        invoke GetFileSize, hFile, 0    ; you may use a register or any other variable to specify the buffer size
        mov sbuf3, StackBuffer(eax, nz)    ; option nz means "no zeroing" - much faster (the buffer end is zeroed anyway)
        PrintLine "Start buffer 1:", Tb$, Hex$(sbuf1)
        PrintLine "Start buffer 2:", Tb$, Hex$(sbuf2)
        StackBuffer()    ; release all buffers (sb without args = free the buffer)
        ret
    MyTest endp
Rem    - buffer size is limited by start address of stack; normally, you can use close to one MB
    - if you see an "unmatched block nesting" error, you forgot the StackBuffer() before the ret
    - the start address is aligned to 64 bytes for use with SIMD instructions


    - you can use StackBuffer anywhere (not only at proc start & end), but make sure esp is unchanged
    - StackBuffer zero-inits the buffer, unless option nz is specified (much faster)
    - with option nz, only the end of the buffer (+/- 2 bytes, one DWORD) is zeroed
    - can be combined with ClearLocals
    - StackBuffer does the stack probing for you; up to about half a megabyte, it is significantly faster than New$()/HeapAlloc


StackWalk
include \masm32\MasmBasic\MasmBasic.inc
SetWatch ct         ; use SetWatch to find out where this variable gets changed
StackWalk profile, trace     ; keep track of calls (e.g. before a crash)
.data?
ct    dd ?
.code
LevelTwo proc arg1, arg2
    invoke Sleep, 1
    ret
LevelTwo endp
MainLevel proc arg1, arg2, arg3
    PrintLine "+"
    For_ ct=0 To 999
    invoke LevelTwo, ct, 12345678h
    Next
    ret
MainLevel endp
    Init            ; select Init and hit F6
    Cls
    For_ ecx=0 To 2
    invoke MainLevel, ecx, 222h, 333h
    Next
    StackWalk show
    PrintLine "bye"
EndOfCode
Rem    - allows bug chasing and profiling at the same time
    - after each PROLOGUE:NONE, use StackWalk again after endp
    - in case of a crash, Olly will display the start of the .data section; right-click on the 3rd DWORD (after CiaoCiao) and
        Follow DWORD in Disassembler; then press Ctrl *, F9 to trigger a StackWalk show


Instr_, Rinstr, wInstr, InstrOr
    Print "The current drive is ", Left$(ThisExe$, Instr_(ThisExe$, "\")-1)
    mov pos, Instr_(1, L$(n), "equ", 1+4)    ; 1=start pos, 1=case-insensitive + 4=full word
Rem    - returns relative pos in edx, absolute in eax
    - if no match is found, zero is returned in edx, and eax points to the start of the 'haystack'; same if 'needle' is empty
    - six syntax variants allowed:
        A: has startpos:    Instr_(1, "Test", "Te", 2)            ; 4 args
        B: no startpos:    Instr_("Test", "Te", 2)            ; 3 args, last one immediate = mode
        C: has startpos:    Instr_(1, "Test", "Te")            ; 3 args, last one not immediate
        D: no startpos:    Instr_("Test", "Te")                ; 2 args
        E: test several patterns:    InstrOr("Test", "Te" or "st", 1)        ; 3 args (startpos is 1)
        F: extra fast mode:    Instr_(FAST, My$(ecx), "Hello", 0)        ; 4 args, no startpos, modes 0+2 only
    - case & mode (bitwise flag):
        0=case-sensitive, +1=insensitive, +2=intellisense (Name=name, i.e. case of first char ignored),
        +4=full word search, +8=include start of line in text block search, +4+16=not full word
        Note: full word search returns failure for chars above ASCII 64 to the left or right
        Example: Nostructfoundhere, this123struct456isfound, this@struct, too
    - note that, for performance reasons, FAST does not preserve xmm0 and xmm1
    - the FAST option is typically about twice as fast as CRT strstr, but 3..4 times as fast when used with
        string arrays (Intel Core i5 timings for counting a rare word in a file with 800 MB, 6 Mio lines):
        232 ms    for fast Instr_
        795 ms for "normal" Instr_
        999 ms for Masm32 InString
        929 ms for CRT strstr
    - Instr_(some$(ecx), "sometext") uses internally the FAST mode
    - using FAST, binary search in haystacks containing zeros is possible by assigning the buffer size to edx:
            mov edx, LastFileSize    ; any info on length of buffer can be used with edx
            Print Str$("Pos in executable: %i", Instr_(FAST, esi, "kernel32", 2 or 64)    ; 2=first char case-insensitive, 64=len in edx
    - Rinstr is about 10% slower than Instr_ but about 10% faster than Masm32 InString
    - wRinstr is available but only as wRinstr(src, pattern) with a one-byte pattern (e.g. "\")

Key    instr(


RinstrX         for use with file extensions
include \masm32\MasmBasic\MasmBasic.inc
    Init
    Print Str$("The mode 1 index by RinstrX is %i\n", RinstrX("\somepath\somefile.DOCX", 1, .xsl,.xslx,.doc,.docx,.htm))
EndOfCode
Rem    args: file.ext, mode (1=ignore case), list of extensions


Count, wCount
    include \masm32\MasmBasic\MasmBasic.inc
    Init
    Let esi=FileRead$("\Masm32\include\Windows.inc")            ; read Windows.inc into a buffer
    Print Str$("Count equ=\t%i\n", Count(esi, "equ", 1))            ; Count equ (1=case-insensitive)
    Print Str$("Count equ=\t%i", Count(esi, "equ", LastFileSize))    ; same but faster and case-sensitive
        EndOfCode
Rem    - counts occurrences of a pattern in a string
    - with no args, the count is case-sensitive; otherwise, options as in Instr_/wInstr,


        except for LastFileSize, which is very fast but always case-sensitive


LineCount
    mov ecx, LineCount(pBuffer)    ; count the number of carriage returns (Ascii 13, 0Dh) in a zero-delimited buffer
    mov ecx, LineCount(pBuffer, 1000)    ; count CRs in the first 1000 bytes of the buffer
    mov ecx, LineCount(pBuffer, 1000, lf)    ; count linefeeds (Ascii 10, 0Ah)
    mov ecx, LineCount(pBuffer, 1000, "a")    ; count the char a
Rem    - returns #lines in eax
    - use instead of EM_EXLINEFROMCHAR (which returns wrapped lines)


LineNumber
    include \masm32\MasmBasic\MasmBasic.inc
    Init
    Print Str$("The string 'Duplicate' is in line %i of Windows.inc", LineNumber(FileRead$("\Masm32\include\Windows.inc"), "Duplicate")+1)
    EndOfCode
Rem    - returns the zero-based line in which the string was found in eax, or -1
    - the address of the match is in edx


Clean$
    include \masm32\MasmBasic\MasmBasic.inc
    Init        ; select and hit F6
    Let esi="index.php?PHPSESSID=74e64f1f&topic="
    PrintLine "before: [", esi, "]"
    PrintLine "after:    [", Clean$(esi, "PHPSESSID=", "&", 0), "]"    ; string, left, right match, mode
EndOfCode
Rem    - "cleans" a given string depending on left and right matches
    - mode as in Instr_()
    - if no right match is given, Cr$ is assumed, i.e. until end of line


Strip$
    include \masm32\MasmBasic\MasmBasic.inc
    Init        ; select and hit F6
    PrintLine Strip$("7@89-#456h x123", "1234567890")    ; prints 789456123
    PrintLine Strip$("7@89-#456h x123", exc "@-# xh")    ; 789456123 (exclude chars)
EndOfCode
Rem    see Clean$ for comparison


Extract$     extracts a substring based on left and right matches
    ; simple example:
    Let esi='This is a link to that can be extracted'
    Print "The URL for ", Extract$(esi, Chr$(34, 62), "
"), " is "    ; 34, 62 = ">
    Inkey Extract$(esi, "http://", Chr$(34), xsIncL)            ; you could use '"' (single/double/single quote) instead of Chr$(34)
    ; result: The URL for Google is http://www.google.com

    ; syntax: Extract$(pSrc, "left match" [, "right match"] [, xsFlags] [, maxlines*)] [, startIndex])
    ; right match: if omitted, end of line is assumed
    ; xsFlags: if omitted, search for left match is case-sensitive, left and right matches are excluded
    ; maxlines: default is 1, i.e. right match must be in same line; for extracting structures etc, put a reasonable value, e.g. 100
    ; startIndex: default is 1, i.e. beginning of string; if xsLoop is set, search for the left match restarts where the last right match
    was found; see also the options for Instr_ - Extract$ uses Instr_ for the left match
    xsCaseI    ; case-insensitive search for left match (right: always case-sensitive)
    xsIs            ; intellisense; search is case-insensitive for 1st char only, i.e. Hello = hello
    xsI1c    ; ignore 1st char in left match, e.g. ?:\Masm32\...
    xsFullW    ; full word (left match only)
    xsIncL    ; include left pattern (e.g. http://)
    xsIncR    ; include right pattern
    xsExcL    ; exclude left pattern, e.g. {url= ... }
    xsExcR    ; exclude right pattern
    xsTrimL    ; trim left side, i.e. strip everything <=ASCII 32 (spaces, tabs, CrLf$...)
    xsTrimR    ; trim right side (after excluding right match if xsExcL is set)
    xsTrim=xsTrimL or xsTrimR    ; trim both sides
    xsTrim39    ; trim everything <=ASCII 39 aka single quote
    xsLineL    ; include line of the left match
    xsLineR    ; include rest of line after the right match (must include right match...)
    xsRinstrL    ; search first match from the right, exclude left & right patterns
    xsScan    ; scan line sequentially for e.g. spaces; left match must equal right match
    xsLoop    ; let Instr_ start behind the last position, for use in loops
    xsWildcard    ; allow a wildcard for left pattern e.g. for finding in HTML pages
    *) if the right pattern contains a linefeed (LF, Ascii 10), the maxlines counter will never stop the pattern search
    ----------------------------------------------------------
    The last flag, xsLoop, is used in the following demo, a Windows console application that extracts
    all structures from the two main Masm32 include files. Do not use the result for work, as there are
    problems with nested structures (e.g. unions ending with ends) and some structures ending with
    lowercase ends.
   
include \masm32\MasmBasic\MasmBasic.inc    ; download
    Init
    ; First, let's get a useful source string:
    Let ecx=FileRead$("\Masm32\include\Windows.inc")+FileRead$("\Masm32\include\WinExtra.inc")
    Open "O", #1, "Structures.txt"    ; open a file for output
    xor ebx, ebx        ; reset counter
    .While 1
    inc ebx
    PrintLine #1, Extract$(ecx, "STRUCT", 'ENDS', xsFullW or xsLineL or xsIncR or xsLoop, 100)
    .Endw
    Close #1            ; file #1 closed
    Inkey Str$("%i structures found\n", ebx)
EndOfCode

Rem    - returns pointer in eax, and len of result in edx
    - Let esi=Extract$(CL$(), "\", ".", xsRinstrL)+".inc"    ; if arg is path\somefile.txt, esi will be somefile.inc
    - can be used with Print and Let even if no match found, i.e. eax=0; in this case, Extract$ will print as ?
Key    ex$



StringsDiffer, wStringsDiffer
    .if StringsDiffer("Hello", "Hallo")
        MsgBox 0, "The two strings are different", "Hi", MB_OK
    .endif
    test eax, StringsDiffer("Hallo", "HALLO", 1)    ; 1=case-insensitive
    .if Zero?
        MsgBox 0, "The two strings are equal", "Hi", MB_OK
    .endif
Rem    - returns byte difference as DWORD in eax, and relative position in edx
    - trashes xmm0...xmm2
    - you may get a "please split" error message, e.g. with
        .if StringsDiffer(Str$(esi), Str$(edi))
        The reason for the error is that Str$() uses eax as return value - in both
        cases, therefore they would always seem "equal" (eax==eax). To avoid this,
        split the line by using a global var or a free register (not edx or eax) as follows:
        mov ecx, Str$(esi)
        .if StringsDiffer(ecx, Str$(edi))



FilesDiffer
    .if FilesDiffer("MyFileA.txt", "MyFileB.txt")
        MsgBox 0, "The two files are different", "Hi", MB_OK
    .endif
    Let esi="MyFileA.txt"
    test eax, FilesDiffer(esi, offset MyFileB)
    .if Zero?
        MsgBox 0, "The two files are equal", "Hi", MB_OK
    .else
        MsgBox 0, "The two files are different", "Hi", MB_OK
    .endif
Rem    - returns DWORD in eax
    - you may get a "please split" error message, e.g. with
        .if FilesDiffer(Files$(esi), Files$(edi))
        The reason for the error is that Files$() uses eax as return value - in both
        cases, therefore they would always seem "equal" (eax==eax). To avoid this,
        split the line by using a global var or a free register (not edx or eax) as follows:
        mov ecx, Files$(esi)
        .if FilesDiffer(ecx, Files$(edi))


uLeft$, uMid$, uRight$        ; UTF-8 string extraction
    include \masm32\MasmBasic\MasmBasic.inc
    SetGlobals r$="Введите текст здесь"    ; "Enter text here" in Russian
    SetGlobals c$="在這裡輸入文字"        ; "Enter text here" in Chinese
    Init        ; select and hit F6
    PrintLine "[", r$, "] (original string)"
    PrintLine "[", uRight$(r$, 5), "_", uMid$(r$, 9, 5)), "_", uLeft$(r$, 7), "] (right_mid_left, fixed)"
    PrintLine "[", uRight$(r$, 5), "_", Mid$(r$, Instr_(r$, "текст"), 2*5)), "_", uLeft$(r$, 7), "] (right_mid_left, Instr)"
    wMsgBox 0, wRec$("["+uLeft$(c$, 5)+"]"), "Chinese, uLeft$(5):", MB_OK
    wMsgBox 0, wRec$("["+uRight$(c$, 3)+"]"), "Chinese, uRight$(3):", MB_OK
    EndOfCode
Rem    - use uLeft$(src, chars) if you know the #UTF-8 chars needed
    - use "normal" Left$() etc if you got the #chars from Instr_(); but note the need to calculate bytes, see 2*5 above


Left$, wLeft$
    Let My$(123)=Left$("A little test", 8)
    Let Left$(My$(123), 5)="A small "    ; valid also for Mid$, Right$
    Print Left$("test", 20, ".")    ; optional: use a padding char if the string is shorter
    mov eax, z$(Left$("Test", 3))
Rem    - for use with Let and Print; if you need the mov version, use z$()
    - with UTF-8 encoded strings, either get the index via Instr_(..), uLen, etc, or use uLeft$()
    - with tab-delimited strings, you can also obtain cells from columns 0...n (Left$) or n ... end (Mid$) using @n:
        For_ ecx=0 To L$(?)-1
            Print Str$("\n%i    [", ecx), Left$(L$(ecx), @2), "] [", Mid$(L$(ecx), @5), "]"
        Next
Key    left$(


Mid$, wMid$
    Let My$(123)=Mid$("A little test", 3, 6)
    mov eax, z$(Mid$("Test", 2))
Rem    for use with Let and Print; if you need the mov version, use z$()
Key    mid$(


Right$, wRight$
    Let My$(123)=Right$("A little test", 4)
    mov eax, z$(Right$("Test", 3))
Rem    for use with Let and Print; if you need the mov version, use z$()
Key    right$(


Lower$, wLower$

Upper$, wUpper$
    Print Lower$("This is a test"), CrLf$, Upper$("This is a test")
    mov esi, z$(Upper$(eax))
Rem    - returns pointer to converted copy of string in eax
    - for use with Let and Print; if you need the mov version, use z$()
    - example for Unicode message boxes:
        wLet esi="Hello, What Is The Purpose Of This String?"
        wMsgBox 0, wCat$(wLower$(wLeft$(esi, 5))), "hello", MB_OK
        wMsgBox 0, wCat$(wUpper$(wMid$(esi, 8, 4))), "WHAT", MB_OK
        wMsgBox 0, wCat$(wLower$(wRight$(esi, 7))), "string?", MB_OK
    - attention Left$(Lower$(...)) will throw a "not allowed" error; use Lower$(Left$(...))
    - see also SetCpUpperLower$ for dealing with codepage problems
Key    lower$(, upper$(


Trim$        remove white space to the left and right

Ltrim$        trim left

Rtrim$        trim right

Qtrim$        trim quotes

Ntrim$        trim anything below a number
    Print CrLf$,        "                |                Trim a string                |",13, 10
    Print "LTRIM$: |", Ltrim$("            Trim a string                "), "|",CrLf$
    Print "RTRIM$: |", Rtrim$("            Trim a string                "), "|",CrLf$
    Print "TRIM$:    |", Trim$("                Trim a string                "), "|",CrLf$, CrLf$
    Let My$=Trim$("    Test ")
    Let My$=Trim$(Chr$(9, "    Test ", 13, 10))
    Let My$=Qtrim$(Chr$(9, '    "quoted"    ', 13, 10))    ; removes "double quotes"
    Let My$=Qtrim$(CL$())        ; same for complete quoted commandline
    Let My$=Ntrim$("$+abc123+'")        ; strip chars below the number "0": !"#$%&'()*+,-./
Rem    - for use with Let and Print
    - removes trailing spaces, tabs and other characters below or equal Ascii 32 (Q:34, N:47)
    - old Gfa Ztrim$ syntax possible but has no effect
Key    trim$(


Mirror$
    cmp eax, Mirror$("Masm")        ; easier than cmp eax, "msaM"
    PrintLine Mirror$("Masm32 is great")        ; uses an entry in the .data section
    mov edx, Mirror$("Test")
    Let esi=Mirror$("Masm32 is great")
    PrintLine esi        ; shows     taerg si 23msaM
    Let esi=Mirror$(esi)
    PrintLine esi        ; back to    Masm32 is great
Rem    - with immediate args, Mirror$ returns an immediate string, and does not by itself create any .data entry
    - Mirror$(esi) returns a pointer to a temporary buffer that can be used with Let and Print



Date$, wDate$, CrtDate$
    Print "Today is the ", Date$
    Print "This file was created on ", CrtDate$
Rem    - by default, returns in eax a string in format dd.MM.yyyy, e.g. 31.12.2013
    - in Print or Let lines that use e.g. Str$(), Date$ may misbehave; use fDate$(0) instead
    - except for CrtDate$, you can change this format e.g. with
        MbDateDmy equ "MMMM dd, yyyy" to get August 02, 2013 (see MSDN GetDateFormat for details)
    - with MbDateDmy equ esi (or any other reg32) you can programmatically specify a different format



Time$, wTime$, CrtTime$
    Print "Now it is ", Time$
Rem    - by default, returns in eax a string in format HH:mm:ss, e.g. 13:45:56
    - in Print or Let lines that use e.g. Str$(), Time$ may misbehave; use fTime$(0) instead
    - except for CrtTime$, you can change this format e.g. with
        MbTimeHms equ "hh:mm tt" to get e.g. 03:45 PM (see MSDN GetTimeFormat for details)
    - with MbTimeHms equ esi (or any other reg32) you can programmatically specify a different format


IsoWeek
    include \masm32\MasmBasic\MasmBasic.inc
    Init        ; select and hit F6 to test this example
    Print "Yesterday, ", fDate$(-1, "dddd dd MMMM yyyy "), fTime$(0, "HH:mm"), Str$(", we were in ISO week %i\n", IsoWeek(-1))
    Print "Today, ", fDate$(0, "dddd dd MMMM yyyy "), fTime$(0, "HH:mm"), Str$(", we are in ISO week %i\n", IsoWeek())
    Let esi="11.12.2017"
    Print esi, Str$(" will be in week %i", IsoWeek(esi))
   
EndOfCode
Rem    returns ISO 8601 week; you can specify a negative or positive offset, e.g. IsoWeek(-1) for yesterday's week


Json$     extract text and numbers from Json files
include \masm32\MasmBasic\MasmBasic.inc
Init            ; make sure res #91 and txt files are present, then select and hit F6
Cls 7            ; instead of clearing everything, insert 7 lines
    .if Json$(file:"JsonMenu.txt")        ; JsonMenu.txt
    .While 1
        .Break .if !Json$(Obj:"menu")        ; search only inside the {menu object}
        PrintLine "menu ", Json$("value")
        .While 1
            .Break .if !Json$("value")
            Print "menu item ", eax        ; strings are returned in eax
            PrintLine At(20) Json$("onclick")
        .Endw
        Print
        Json$(ObjEnd)        ; search full text
    .Endw
    .endif
    .if Json$(file:91)        ; RCDATA resource #91; has numbers in ppu variable
    .While 1
        .Break .if Json$("ID")==0
        Print "Price per unit of ", Json$("name")
        Print At(32) Str$(": %2f€\n", Json$(num:"ppu")v)    ; v means pop st(0) after printing
    .Endw
    .endif
    .if Json$(file:"JsonHolidaysIT.txt", "https://jj2007.eu/HolidaysIT.txt")
    .While 1
        .Break .if Json$("date")==0
        Print eax, " is "
        wPrint wRec$(Json$("localName"))
        PrintLine At(36) "    ", Json$("name")
    .Endw
    .endif
Inkey cfm$("\nJson is fun")

EndOfCode


GetTZ$
    PrintLine "Time zone is UTC", GetTZ$()        ; Time zone is UTC+1
    PrintLine "Time zone solar = ", Utf8$(GetTZ$(StandardName))    ; StandardName as Unicode string
    PrintLine "Time zone legal = ", Utf8$(GetTZ$(DaylightName))
Rem    uses GetTimeZoneInformation; values for other TIME_ZONE_INFORMATION members are returned in xmm0


fDate$(), fTime$(), wfDate$(), wfTime$()
    ; formatted date and time; takes a pointer to a SYSTEMTIME structure and an optional format string
    include \masm32\MasmBasic\MasmBasic.inc
    Init                            ; select Init and hit F6 to test this code
    PrintLine "Tomorrow is the ", fDate$(1, "dd MMM yy")                ; formatted date
    PrintLine "In ten minutes, i.e. at ", fTime$(10, "HH:mm"), ", I will go to bed"    ; formatted time
    Print "Today, ", fDate$(0, "dddd dd MMMM yyyy "), fTime$(0, "HH:mm"), Str$(", we are in ISO week %i\n", IsoWeek())
    Print "HKCU\Software\...\Explorer entries changed last week:"
    GetRegKeyArray "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer", Explorer$(), LastMod()
    For_ ecx=0 To eax-1
        .if Age(LastMod(ecx), h)<=7*24
            Print Str$("\nKey %i\t", ecx), fDate$(LastMod(ecx), "dd MMMM yyyy"), ", ", fTime$(LastMod(ecx))
            Print Tb$, Explorer$(ecx)
        .endif
    Next
    Print fDate$(0, "dddd dd MMMM yyyy "), fTime$(0, "HH:mm:ss.fff")    ; long format including milliseconds
    Print CrLf$, fDate$(0, "dddd dd MMMM yyyy ", russian), fTime$(0), Str$(", мы находимся в ИСО неделе %i", IsoWeek())
    wMsgBox 0, wCat$(wfDate$(0, "dddd dd MMMM yyyy ", hindi)+wfTime$(0)), "This is Unicode:", MB_OK
   
EndOfCode
    ; if the registry value is of type REG_QWORD, it might be a FILETIME; you can display it as follows:
    void GetRegVal("HKLM\SOFTWARE\Microsoft\ASP.NET\4.0.30319.0", "LastInstallTime", 0)
    ; if this key and its value exist, then the REG_QWORD (a FILETIME) will be returned in xmm0:
    PrintLine "LastInstallTime: [", fDate$(xmm0, "dd MMMM yyyy"), ", ", fTime$(xmm0), "]"
    wPrint "LastInstallTime: [", wfDate$(xmm0, "dd MMMM yyyy"), ", ", wfTime$(xmm0), "]"   
Rem    - for use with SYSTEMTIME structures or arrays, e.g. those filled by the second arg of GetRegKeyArray()
    - default is user locale, but you can force a language as shown above with russian and hindi
    - see also TimeSF
    - immediates -128 to +127 are interpreted as offsets: days for fDate$(dx), minutes for fTime$(dx); fTime$(s:dx) uses seconds


TimeSF        ; translate date string to system and file time
    include \masm32\MasmBasic\MasmBasic.inc
    Init        ; select and hit F6 to test this example
    void TimeSF("01.02.2013 12:34:56")    ; returns FILETIME value in xmm0 for further use
    Print Str$("%i days since 1st of January\n", Age(TimeSF("01.01."), d))    ; Age accepts a FILETIME in xmm0; year, hour, minutes as of 'right now'
    PrintLine fDate$(TimeSF("11.11.2011"), "dd MMMM yy"), ", ", fTime$(TimeSF("12:34:56.789"), "HH:mm:ss:fff")    ; 11 November 11, 12:34:56:789
    PrintLine "date+time, day.month.year format: ", fDate$(TimeSF("31.12.2015, 12:34:56")), ", ", fTime$(xmm0)
    PrintLine "date+time, year-month-day format: ", fDate$(TimeSF("2015-12-31 12:34:56", ymd)), ", ", fTime$(xmm0)
    Print Str$("One minute ago means %i seconds ago\n", TimeDiff(s, now:TimeSF(fTime$(-1, "HH:mm:ss.fff"))))
    Print Str$("One day ago means %i minutes ago\n", TimeDiff(m, now:TimeSF(Cat$(fDate$(-1)+", "+fTime$(0, "HH:mm:ss.fff")))))
   
EndOfCode
Rem    returns FILETIME in xmm0 and pointer to SYSTEMTIME structure in eax


TimeDiff     ; difference between two times
    include \masm32\MasmBasic\MasmBasic.inc
    Init        ; select and hit F6 to test this example
    movaps xmm4, TimeSF("11:22:33")
    psubq TimeSF("11:22:53"), xmm4
    Print Str$("The two dates are %i seconds apart\n", TimeDiff(s))
    movaps xmm1, TimeSF("01.08.20")
    psubq TimeSF("02.08.20"), xmm1
    Print Str$("A day is %i seconds long", TimeDiff(s)), Str$(", that is %i hours\n", TimeDiff(h))
    movaps xmm1, TimeSF("01.02.")
    psubq TimeSF("01.03."), xmm1
    Print "In ", fDate$(0, "yyyy"), Str$(" February had %i days\n", TimeDiff(d))
    Print Str$("It's %i days until Christmas\n", -TimeDiff(d, now:TimeSF("24.12.")))    ; minus...
    Print Str$("The year is %i days old\n", TimeDiff(d, "01.01."))
   
EndOfCode
Rem    - returns in edx::eax days, hours, minutes, seconds or milliseconds (ms)
    - xmm0 must hold the difference using e.g. TimeSF()


Timer
    push Timer        ; put current mlliseconds on stack   
    Delay 500        ; make a little pause of 500 ms
    sub Timer, [esp]    ; subtract from eax the value on stack (eax is Timer's retval)
    pop edx            ; correct the stack
    Print Str$("\nTime elapsed: %i\n", eax)
Rem    returns DWORD in eax; uses GetTickCount but preserves ecx


NanoTimer
    NanoTimer()    ; start timing without arguments
    Delay 500        ; simulate a loop...
    Print Str$("Time elapsed: %4f seconds\n", NanoTimer(s))        ; s, ms or µs
    NanoTimer()
    Delay 15
    Print "The Delay 15 took ", NanoTimer$()    ; returns e.g. 15 ms, i.e. a string with a unit adapted to the result
Rem    returns DWORD in eax; uses QPC, the effective resolution is about 0.3 microseconds


CyCtInit, CyCtStart , CyCtEnd    cycle count macros (use with PrintCpu)
    include \masm32\MasmBasic\MasmBasic.inc
Init            ; select Init and hit F6 to run this snippet
CyCtInit
CyCtStart
    fldpi
    fmul FP8(100.0)
    fdiv FP4(10.0)
    fstp st
CyCtEnd PI*100/10    ; describe what the code does
   
EndOfCode
Rem    - output is e.g. 17 Cycles for PI*100/10
    - without description, 1,000 individual cycle counts are accessible through the CyCt() DWORD array
    - using CyInit store, the counts are saved to the CyCt$() array


WinFromID, WinFromID$         find a window associated with a process ID
    include \masm32\MasmBasic\MasmBasic.inc
    Init
    Inkey "The window associated with ID 1332 has the title ", WinFromID$(1332)
   
EndOfCode
Rem    - get an ID from your TaskManager, and find out which window belongs to it


Align64
    include \masm32\MasmBasic\MasmBasic.inc
    Init    ; select Init and hit F6 to run this snippet
    nops 123
    Align64    ; align 64 is not available in MASM or JWasm
    mov eax, $    ; get current location
    Print Hex$(eax)
    xor ecx, ecx
    Align64
    .Repeat    ; loop start is aligned to 64 bytes
        inc ecx
    .Until ecx>100
    Exit
   
EndOfCode
Rem    may help to fit a loop into an instruction cache line; use e.g. AlignX 32 for other alignments



Recall    ; fill a string array with content from file or URL
    Recall "\masm32\include\winextra.inc", MyRec$()    ; translate file input to an array of strings
    mov lc, Min(eax, 20)
    Print Str$("%i lines found", lc)
    For_ n=0 To lc-1
        Print Str$("\nRec %i\t", n)
        Print Left$(MyRec$(n), 50)
    Next
    Recall 123, some$()        ; converts resource;    use e.g. 123 RCDATA "names.txt" in the Rsrc section
    Recall "MyFile.csv", MyRec$(), csv        ; loads a spreadsheet in comma separated values format
    Csv2Tab                ; optional: replace commas with tabs (needed for QSort by column)
    Recall "MyFile.txt", MyRec$(), tab        ; loads a spreadsheet in tab-delimited format
    Recall "MyRects.dat", myrc() As RECT        ; load all previously stored RECT values to the myrc() array
Rem    - returns lines in eax (null if an error occurred), and the number of total bytes read in edx
    - autodetects Windows (CrLf), Linux (Lf) and Mac (Cr) text files
    - see Store below for saving arrays and Eof() for checking end of data
    - instead of a filename, you can specify a URL (select init and hit F6 to test this example):
        include \masm32\MasmBasic\MasmBasic.inc
        Init    ; show a data set from the World Health Organisation
        Recall "https://extranet.who.int/tme/generateCSV.asp?ds=mdr_estimates", who$(), csv
        Print Str$("%i records downloaded", eax)
        For_ ct=0 To who$(?)-1
        Print CrLf$, who$(ct, 0)
        Print At(33, Locate(y)) Spc2$, who$(ct, 4), Space$(40)
        For_ ecx=8 To 14    ; columns 8...14 contain the data
            Print At(ecx*8-25, Locate(y)) Spc2$, who$(ct, ecx)
        Next
        Next
       
EndOfCode
    - note the tab and csv versions do not allow direct string concatenation with Print or Let:
        PrintLine "fails: A2=", MyCellArray$(1,0), Tb$, "A3=", MyCellArray$(2, 0)    ; fails miserably showing twice the same cell
        Print "works: A2=", MyCellArray$(1,0), Tb$, "A3="        ; workaround: separate the two ...
        PrintLine MyCellArray$(2, 0)            ; ... cells by printing over two lines
    - there is no wRecall, but if Unicode is detected, Recall converts strings to UTF8; use Print to display them
    - with the spreadsheet variants, single cells can be accessed via Let My$=MyRec$(row, column);
        for usage, see the spreadsheet demo
    - instead of a filename, you can pass the ID of a RC_DATA resource file
Key    Rec


Store     save text array to file
    Store "MyWin1.inc", MyRec$()        ; store the whole array excluding trailing empty strings
    Store "MyWin1.inc", MyRec$(), 1000        ; store the first 1000 strings of the array
    StoreUtf8 "MyWin1.inc", MyRec$()        ; StoreUtf8 prepends a UTF-8 BOM
    Open "O", #1, MyFileName$
    Store #1, MyWc        ; store a WNDCLASSEX from the .data? or .data segment
    Store #1, MyRc()        ; store an array of RECT structures (->Dim)
    Store #1, MyRec$(), 20        ; store the first 20 strings
    Store #1, MyOtherRec$()        ; add the complete array not including trailing empty strings
    Close #1
Rem    - will trigger runtime error if file cannot be opened
    - if a file name is being used, the file will be closed after writing the strings
    - for #n, the file remains open for writing, allowing e.g. to write several arrays to the same file
Key    sto


_Passed$            pass a string array to a procedure
include \masm32\MasmBasic\MasmBasic.inc
.code
PrintStringArray proc arrID, rows    ; you can pass it any string array
    xor ecx, ecx
    .Repeat
    Print Str$("\nRow #%i\t", ecx), _Passed$(arrID, ecx)
    inc ecx
    .Until ecx>=rows
    ret
PrintStringArray endp

Init        ; select and hit F6 to test this example
Recall "\Masm32\include\Windows.inc", wi$()
Recall "\Masm32\include\WinExtra.inc", wx$()
Print "First 10 lines of Windows.inc:"
    invoke PrintStringArray, wi$(), 10
Print CrLf$, "First 10 lines of WinExtra.inc:"
    invoke PrintStringArray, wx$(), 10

EndOfCode
Rem    - _Passed$(id, args) allows most of the operations of normal string arrays
    - first argument is the ID of the array, as passed on the stack; other arguments as usual


QSort, QSortDesc    string array sort
    Recall "\Masm32\include\Windows.inc", L$()    ; load wininc into a string array
    mov ecx, eax        ; save the linecount
    shr eax, 2            ; just for fun, we sort only ...
    QSort L$(), eax        ; ... one quarter of the strings
    Open "O", #1, "SortedAscending.txt"
    xor esi, esi
    For_ n=0 To ecx-1
        .if Len(L$(n)) && esi<5000        ; we skip the empty strings, and write the first 5000
            Print #1, L$(n), CrLf$
            inc esi
        .endif
    Next
    Print Str$("%i lines written to SortedAscending.txt\n", esi)
    QSortDesc L$(), 0        ; now sort descending, all
    Store "SortedDescending.txt", L$()        ; and write them back to file
    ; spreadsheet mode - you can sort tab-delimited files by column:
    Recall "MyDataBase.tab", db$(), tab        ; tab = tell Recall it's a tab-delimited file, Excel style
    xchg eax, ecx        ; keep #rows read in ecx
    QSort db$(), 0, 3    [, 0]        ; sort db$(), all rows, by column 3; [optional 0: do not skip the header row]
    QSort db$(), 0, 2003h        ; same but 2000h added: sort using Val() of column 3
    QSort db$(), 20:30, 5        ; starting with row 20, sort 30 rows, by column 3
    QSort db$(), 50:, 4        ; sort all rows after row 50 by column 4
Rem    - returns #elements sorted
    - for sorting the Files$() array, see SortFiles
    - for testing, use Scramble My$() to randomise the string array
    - by default, comparisons are case-insensitive; to make sorts case-sensitive, use
        QSortMode casemode, skipmode
        where casemode can be 0 (=case-sensitive), 1 or cis, and skipmode can be 0 (=don't skip), 1 or sls. Examples:
        QSortMode 0, sls    ; case-sensitive, skip leading spaces
        QSortMode cis, 0    ; case-insensitive, don't skip leading spaces
    - QSortMode may take, as a third parameter, a DWORD array:
        QSortMode casemode, skipmode, MyIndex()
        On return, this array contains the original index of the sorted strings. Note: when using Insert or Delete on the
        string array, do the same for the index array, otherwise a new sort will recreate it.
    - when sorting by column, the header row is skipped by default; use 0 as 3rd arg after the column to override this
    - when sorting by column in case-insensitive mode, QSort is a stable sort (under the hood, it's a MergeSort)
    - csv files can be sorted by column only if you use Csv2Tab directly below Recall .., csv
    - sorting by column and skip leading spaces (sls) mode cannot be combined
    - case-insensitive sorting depends on a 256 byte buffer; ExternDef qsCaseTable:BYTE gets filled using CharLowerBuff
Key    qst



BitSort
include \masm32\MasmBasic\MasmBasic.inc
Init            ; select and hit F6 to test this example
ArraySet MyDw() As DWORD=12, 456, 99, 123, 20, 99
BitSort MyDw()
For_ ecx=0 To eax-1
    Print Str$(ecx), Str$("\t%i\n", MyDw(ecx))
Next

EndOfCode
Rem    - returns #of sorted elements in eax
    - eliminates duplicates
    - DWORD only
    - blazing fast for positive, not too high numbers


ArraySort
    mov ebx, 1000        ; we want 1000+1 elements (arrays are zero-based, 0...1000=1001)
    Dim MyR4(ebx) As REAL4        ; numerical arrays only; for strings, use QSort
    Dim MyDW(ebx) As DWORD
    Dim MyR8(ebx) As REAL8
    Dim MyQW(ebx) As QWORD
    Dim KeyArr(ebx) As DWORD
    .Repeat
        Rand(-888.888, 999.999)        ; fill the Real4 array with random numbers between -888 and +999
        fstp MyR4(ebx)
        mov MyDW(ebx), Rand(1000)        ; same for the dword array, numbers between 0 and 1000
        mov KeyArr(ebx), ebx        ; we may need to know the original position before sorting
        dec ebx
    .Until Sign?
    lea edi, MyDW(0)        ; get the start address of the dword array
    ; ArraySort edi        ; illegal - arrays are not zero terminated, so we must know the count
    ArraySort edi:Elements        ; edi is pointer to a dword array; Elements is # of dwords
    lea edi, MyR4(0)                        ; get the start address of the Real4 array
    ArraySort REAL4 ptr edi:Elements        ; same but with single floats array (and we must declare them)
    ArraySort MyR4()        ; sort a MasmBasic Real4 array ascending
    ArraySort MyR4(+)        ; same, the + is optional
    ArraySort MyR4(+:123)        ; same but first 123 elements only
    mov ecx, 123
    ArraySort MyR4(+:ecx)        ; same but using a register (or any other dword variable)
    ArraySort MyR4(-)        ; sort a MasmBasic Real4 array descending
    ArraySort MyR4(-:123)        ; same but first 123 elements only
    lea esi, KeyArr(0)        ; load start address of an array containing keys (e.g. original position)
    ArraySort MyR4(-), esi        ; sort Real4 array descending, keep key values with Real4 values
    ArraySort MyR4(+), KeyArr(), fill        ; use key array directly, fill with original unsorted order (0, 1, 2, ... n)
Rem    - returns #of sorted elements in eax
    - use for signed DWORD, signed QWORD, REAL4 and REAL8 arrays; for strings, see QSort
    - ArraySort sorts ascending if no - is found in the first argument. In case you need to determine the order based on
        a runtime parameter, you need to use the invoke syntax as follows:
        MbArrSort PROTO :DWORD, :DWORD, :DWORD, :DWORD
        invoke MbArrSort, ptr to first element, #elements, ptr to key array, mode
        with mode=size (4, 8) or (32 and real) or (64 and ascending) or (1 and MinMaxOnly) or (2 and fill the key)
    - uses a very fast algo inspired by Marwin's site, often faster than QuickSort (and much faster than the crt qsort)
    - Real4 and Dword use the same algo; the only difference is that the Real4 variant applies an extra
        pass to invert the order of negative elements. Speedwise there is no measurable difference


ArrayMinMax
include \masm32\MasmBasic\MasmBasic.inc
SetGlobals REAL4 min4, max4, DWORD min32, max32, REAL8 min8, max8
Init            ; select and hit F6 to test this example
ArraySet My4() As REAL4=1.1, 2.2, 3.3, 4.4
ArrayMinMax My4(), min4, max4
Print Str$("Min=%f", min4), Str$(", max=%f\n", max4)
ArraySet My8() As REAL8=5.5, 6.6, 7.7, 8.8
ArrayMinMax My8(), min8, max8
Print Str$("Min=%f", min8), Str$(", max=%f\n", max8)
ArraySet My32() As SDWORD=4, 1, 2, -9, 4, 9, 2
ArrayMinMax My32(), min32, max32
Print Str$("Min=%i", min32), Str$(", max=%i\n", max32)
    ; ArrayMinMax MyR4(XY)    ; uppercase XY: the array consists of x,y coordinates

EndOfCode
Rem    - if destination variables are not specified:
        - minimum in eax, maximum in edx for DWORD arrays and REAL4 arrays
        - minimum in xmm0, maximum in xmm1 for REAL8 array
    - with XY, x and y minmax values are returned separately: the x values will be on the FPU


ArrayInfo
include \masm32\MasmBasic\MasmBasic.inc
Init            ; select and hit F6 to test this example
Dim MyArray() As REAL4    ; can be DWORD, REAL4 or REAL8
For_ ecx=0 To 99
    Rand(-888.8, 999.9, MyArray(ecx))    ; put random numbers into the array
Next
PrintLine Str$("The elements:\t%i", MyArray(?))
PrintLine Str$("The columns:\t%i", MyArray(?cols))    ; will return 0: this array is one-dimensional
PrintLine Str$("The minimum:\t%f", MyArray(?min)v)    ; the functions marked with v return the
PrintLine Str$("The maximum:\t%9f", MyArray(?max)v)    ; value in ST(0); the v tells Str$() to discard
PrintLine Str$("The median:\t%f", MyArray(?median)v)    ; ST(0) with fstp st after printing
PrintLine Str$("The mean:\t%9f", MyArray(?mean)v)    ; ?average does the same as ?mean
PrintLine Str$("The sum: \t%9f", MyArray(?sum)v)        ; sum of all elements

EndOfCode

Rem    returns mean(s) on the FPU and total number of elements in eax



ArrayRead
    Dim rc4() As REAL4
    ArrayRead rc4(), "MyReal4.dat"   
    ArrayRead somearray() As REAL8, "MyReal8.dat"    ; no need to Dim the array before
Rem    - returns #elements in eax
    - if a two-dimensional array was saved using Store "MyFile.dat", nums(), then ArrayRead gets a two-dimensional array back


ArrayStore
    ArrayStore #1, rc4()
Rem    writes array to disk; same as Store #1, rc4()


ArrayFind        find matches in a string array
    include \masm32\MasmBasic\MasmBasic.inc
    Init        ; select init and hit F6
    Recall "\Masm32\include\Windows.inc", L$()
    Print Str$("%i lines found in Windows.inc\n", eax)
    .if ArrayFind(L$(), "Hutch")>=0        ; find one string in an array
        PrintLine Trim$(L$(eax))        ; Original file 1998                hutch
    .endif
    xor ecx, ecx        ; find all matches
    .While 1
        .Break .if ArrayFind(L$(), "MACRO", 1, ecx)<0    ; mode 1: case insensitive; see Instr_(...)
        xchg eax, ecx
        PrintLine Str$("line %i\t", ecx), L$(ecx)
        inc ecx
    .Endw
   
EndOfCode
Rem     returns the index of the string, or -1 if no match was found


ArrayIndex
    include \masm32\MasmBasic\MasmBasic.inc
    SetGlobals tmp8:REAL8    ; binary search demo
    Init            ; select init and hit F6
    Dim table() As REAL8    ; create an array (could also be As DWORD)
    Rand()    ; generate a random seed
    For_ ecx=0 To 255
        Rand(-1000, 1000, table(ecx))    ; fill array with random values
    Next
    ArraySort table()    ; sort ascending
    For_ ecx=0 To 9
        Rand(-500, -400, tmp8)            ; check a narrow range for tmp8
        Print Str$("position=%i\n", ArrayIndex(table(), tmp8))    ; should be around 70 +/- 10
    Next
   
EndOfCode


Rem    - returns the position of a value in a sorted array of REAL8 or DWORD numbers
    - for use with MasmBasic arrays; with pointers to other arrays, use ArrayIndex(pArray, pattern, lengthoftable))
    - binary search, over twice as fast as repnz scasd; see also ArraySearch
    - if an exact match is found, dl will be zero; if not

ArraySearch
    ; mov eax, ArraySearch(pSrc, sizeof Src, pattern [, size])            ; src, len(src), pattern, byte/word/dword
    Print Str$("Len %i\n", ArraySearch(esi, sizeof Src, 0, BYTE))        ; look for a nullbyte
    mov eax, dword ptr bins                    ; where bins is db "al", 0, 0
    Print Str$("Pos %i\n", ArraySearch(esi, sizeof Src, eax, WORD))        ; word given in eax
    Print Str$("Pos %i\n", ArraySearch(esi, sizeof Src, 6c61h, WORD))    ; immediate word
    Print Str$("Pos %i\n", ArraySearch(esi, sizeof Src, "al", WORD))        ; same but as string
    Print Str$("Pos %i\n", ArraySearch(esi, sizeof Src, MyPattern, word))    ; taken from mem, i.e. "al" is MyPattern dd "la"
    Print Str$("Pos %i\n", ArraySearch(esi, sizeof Src, "algo", DWORD))    ; dword as string
    Print Str$("Pos %i\n", ArraySearch(esi, sizeof Src, 'algo'))        ; same, dword assumed
    Print Str$("Pos %i\n", ArraySearch(esi, sizeof Src, "olgo", DWORD))    ; this one won't be found
Rem    - returns offset into first occurrence of pattern: if src is "This is my algo", then
        pattern algo as dword will not be found (eax=-1), but for "This was my algo", pos 12 would be returned
        pattern my as word will be found at pos 8
        pattern i as byte will be found at pos 2
    - ArraySearch uses SSE2 and is fast: it searches a 400 byte string more than four times
        as fast as repne scasd and 5 times as fast as BinSearch
    - by specifying zero as a byte pattern, you can use ArraySearch as a Len() substitute;
        however, MasmBasic Len() is twice as fast
    - do not confuse with Masm32 BinSearch, which finds patterns at "odd" positions, too, i.e.
        in the example above, invoke BinSearch, 0, chr$("This is my algo"), 16, chr$("al"), 3 would
        return a valid position 11


ArrayLoadMap, PaintMap, MapColours
    GuiParas equ "Map viewer"            ; double-click on GuiParas and hit F6 to build this example
    include \masm32\MasmBasic\Res\MbGui.asm
    GuiControl map, "canvas"            ; define a canvas control
    GuiControl Sbar, "statusbar"            ; status bar
    ; CanvasMap(0, 0)            ; canvas control 0 gets initially map 0 (default)
    SetFolder "\Masm32\MasmBasic\Res"        ; you may use SetFolder to set the map's folder
    ArrayLoadMap 0, "Europe"
    MapColours(0, "abcdefghiabcdefghiabcdefghiabcdefghiabcdefghiabcdefghi") ; set colours to map 0 (a=red, i=green)
    MapColours(0, 25, 20)            ; give blue to Spain
   
Event Message            ; use MapRegion/MapRegion$ in a WM_MAPCLICKED handler
    If_ uMsg_==WM_MAPCLICKED && MapRegion>=0 Then SetWin$ hSbar=Str$("You clicked on region %i, ", MapRegion)+MapRegion$
   
Event CanvasPaint
        ArrayPlot RgbCol(0, 240, 255)            ; init and set background colour
        PaintMap RgbCol(127, 127, 127), lines=2        ; map with grey borders 2px thick
        ArrayPlot exit, "Europe"
    GuiEnd
Rem    - requires a map in *.map/*.dmi format; the Europe map is included in MasmBasic (PM the author for details)
    - to see the numbers of individual regions, use this loop:
        lea edi, Maps(0, numPoly)
        For_ ecx=0 To MapName$(?)-1
            PrintLine Str$("Region %i\t", ecx), MapName$(ecx)
        Next
    - use ArrayMapRegion in a WM_SETCURSOR handler to check if the mouse is hovering over a particular state or region
    - colours can be set for all regions, as shown above with the "abc" string, or for individual regions


CanvasZoom
   
Event Key
    Switch_ VKey
    Case_ VK_I: CanvasZoom MyImg        ; type "i" to zoom the image, "m" to zoom the map
    Case_ VK_M
        CanvasZoom MyMap, MyList, MyEdit    ; zoom the map, hide two non-canvas controls
    Endsw_
Rem    - if more than one GuiControl , "canvas" are present, CanvasZoom can be used to zoom one of them
    - calling it repeatedly toggles the effect
    - if non-canvas controls are present, add their IDs as shown above


ArrayPlot, ArrayPlotValues, SetAxisX, SetAxisY
GuiParas equ "Plot sinus & cosinus curves", x600, y100, w600, h500, bRgbCol(192, 255, 255)
    include \masm32\MasmBasic\Res\MbGui.asm    ; select the white zone and hit F6
Dim MySinus() As REAL4    ; REAL4 precision is enough ...
Dim MyCosinus() As REAL8    ; ... but REAL8 is ok, too
For_ ecx=0 To 360
    SetFloat MySinus(ecx)=Sinus(ecx)    ; fill an array
    SetFloat MyCosinus(ecx)=Cosinus(ecx)    ; you may use ArrayRead for reading data from a file
Next
MakeFont hTextFont, Height:16, Weight:FW_SEMIBOLD
MakeFont hHorzFont, Height:18, Italic:TRUE
MakeFont hVertFont, Escapement:900, Italic:FALSE
MakePen hPenAxis, RgbCol(255, 0, 0)
MakePen hPenGrid, RgbCol(255, 255, 222), width 2
SetAxisX "degrees", s 0, d 15.0/3, xmax 300, grid 1, penx hPenAxis, peng hPenGrid, font hHorzFont, format "%i"    ; s=start value, d=difference between gridlines
SetAxisY "sinus & cosinus", s -1, d 0.2/5, grid 2, font hVertFont, format "%2f"    ; the text can be Unicode
    $Data "Plotting an array of points is very easy: All you need is the array itself, plus a Paint event that draws it."
    $Data "The axes can be set using SetAxisX and SetAxisY, taking arguments as shown in the source. Now resize this window to see how it behaves"
Read desc$()        ; read a string array from $Data
ArraySet myLeg$()="Sinus", "Cosinus"    ; define legend strings
   
Event Paint
        ArrayPlot MySinus(), RgbCol(255, 222, 160), 2, 00000110h, 0, -1, 1     ; red sinus, 2px wide; use margins left top right bottom, min0, max-1, filled=1
        Legend myLeg$(), 950:300            ; x:y; you may use ll=lower left, ur=upper right etc
        ArrayPlotValues "%2f", 8, -12, 15            ; format$, dx & dy positions, step (here: 8px right, 12px up, every 15th value)
        ArrayPlot MyCosinus(), RgbCol(0, 0, 255), 2            ; plot the cosinus array in blue, same margins as previous plot
        ArrayPlot exit, 0150006028# "Sinus and Cosinus", fcol Red    ; finish with a title; optional: xxxxyyyyFF#: x, y pos, font size
        GuiTextBox 12, 100.0-80, 440, 66, Join$(desc$(), " "), bcol RgbCol(192, 255, 192), font hTextFont
GuiEnd
Rem    - try resizing the window to see how the plots behave
    - array elements above #100000 will not be displayed
    - to combine several plots with a common range you can set an arbitrary range e.g. with
        ArraySet range() As REAL4=113.05, 70.0, 180, 260.0    ; create an array with xmin, ymin, xmax, ymax values
       
Event Paint
        ArrayPlot range(XY), 0, 0, 05052020h, setrange    ; use the array to set fixed ranges; use lefttoprightbottom margins
        ArrayPlot TheRealData(), RgbCol(..), ...        ; plot the data as usual
    - optional: use xxxxyyyyhh# text to modify title position and font, e.g. ArrayPlot exit, -120015024# "title to the left and deeper, font 24"
Key    apl


RgbCol, CgaCol, SysCol
    mov eax, RgbCol(ebx, 0, 0)        ; any mix of reg32, vars and immediates is allowed
    mov bl, 127        ; only bl will be used
    invoke SetTextColor, hDC, RgbCol(ebx, 0, 0)    ; Red Green Blue for Gdi32 functions
    invoke SetTextColor, hDC, SysCol(ebx)        ; use the system palette
    invoke SetTextColor, hDC, RgbCol(?)        ; use the colour picker
    mov hPG, rv(GdipCreatePen1, RgbCol(alpha, red, green, blue), FP4(3.0), UnitWorld, addr pPen)
Rem    - Gdi functions want RGB values, i.e. three parameters
    - in contrast, GdiPlus expects ARGB, i.e. four parameters; the first one, alpha, defines
        transparency ranging from 0=fully transparent to 255=opaque
    - predefined colours are Red, Green, Blue, GreenBlue, LiteRed, LiteGreen, LiteBlue, LiteYellow,
        LiteGrey, LiteGreenBlue, DarkRed, DarkGreen, DarkBlue, DarkYellow, LiteGrey, Grey, DarkGrey
    - CgaCol(index) may be used with console colours; see ConsoleColor for identifiers
    - SysCol(index) returns a GetSystemPaletteEntries colour for use in Gui applications


Str$, wStr$
    Print Str$(ebx)        ; simple
    Print Str$(MyReal10)        ; works for most kinds of arguments
    Print Str$(ebx*MyReal8+123.0e45)        ; multiply two arguments, add an immediate float
    MsgBox 0, Str$("Profits increased by %3f per cent", 103.45/100-1*100), "Three digits precision:", MB_OK
    Print Str$("The number PI is %Jf", PI)        ; precision J = 19 digits, f=float
    Print Str$("The number PI is %Je", PI)        ; precision J = 19 digits, e=force exponential
    Print Str$("The number 19 is %__i", 19)        ; integers only: two understrokes to get one leading space with a two-digit number
    Print Str$("The number 9 is %000i", 9)        ; integers only: 3 zeros to get four leading zeros with a one-digit number
    mov eax, Val(Str$(12*34-8))        ; slow and not very elegant, but it works
    Print "Real4: ", Str$(MyR4)        ; Real4 variable
    Print "qWord: ", Str$(MyQword)        ; print a qword; see also edx::eax in remarks
    mov eax, 31416        ; you can mix xmm registers with FPU and ordinary registers and
    movd xmm0, eax        ; directly print the result
    fldpi                    ; load 3.14159 onto the FPU
    mov ecx, 123        ; \n is CrLf, \t is tab in Str$()
    Print Str$("\nresult=\t%f", xmm0/ST(0)*ecx)     ; output: [newline] Xmm0=    1230003.0
    fstp st                ; cleanup the FPU
    fldpi                    ; PI again
    Print Str$("\nresult=\t%f", xmm0/ST(0)v*ecx)     ; same but cleanup done by the v after ST(0)
    invoke TextOut, APs.apMemDC, 25, ptY, Str$("%3f", fct), s$Len    ; s$Len returns chars used for Str$()
    Str$(ecx, dest:edi)        ; DWORD and QWORD integers only; pass start of buffer e.g. in edi, get end back in edx
Rem    - returns address to buffer; default precision is 7 digits for floats, 16 for doubles, 19 for REAL10 variables and ST(0); range 1.0e+-309
    - if a string is given as input, %i (integer) or %f (float) mark its insertion point, and e.g. %9f its precision
    - floating point precision is 1...9A...J digits, with J meaning 19 digits. If the number starts with 9.23 or higher,
        digit #19 may be incorrectly rounded up or down; use DefNum 18 if this is unacceptable; at 19 digits precision, rounding
        may be incorrect, and the last digit may be one higher or lower than expected.
    - Warning: Str$() takes up to 5 numeric arguments. However, they are processed
        in order of appearance, i.e. Str$("Expected: 5+6*7-8*9=-25, result=%i", 5+6*7-8*9)
        yields 621: 5+6=11, *7=77, -8=69, *9=621; workaround: do multiplications first.
    - Str$ trashes the FPU registers ST(6) and ST(7); in case you have valuable data on the FPU and do not want to
        trash it, use a FpuSave/Str$/FpuRestore sequence
    - you can also print qwords moved into the edx::eax pair as follows:
        mov eax, 123456789    ; some code that generates an edx::eax pair
        mov ecx, eax
        mov edx, 1000000000
        mul edx
        Print Str$(edx::eax+ecx)
    - for REAL16 variables, use Quad$(MyReal16)
Key    s$(


SetFloat, SetInt , AddFloat , Int64 , Int128
    Dim MySinus() As REAL8
    For_ ct=-400 To 400
        SetFloat MySinus(ct+400)=Sinus(ct)
        AddFloat MySinus(ct+400)=0.1
    Next
    fldpi
    fmul FP4(100.0)
    SetInt ecx    ; mov ecx, 314
    movups xmm0, Int128(0DDDDDDDDCCCCCCCCBBBBBBBBAAAAAAAAh)
    movlps xmm0, Int64(0BBBBBBBBAAAAAAAAh)
Rem    - use SetFloat with functions that return values in ST(0), e.g. Float(); do not use fstp st afterwards
    - use SetInt to convert ST(0) to a reg32, xmmreg or a DWORD variable


shlXmm
shrXmm
    shlXmm xmm0, ecx     ; shift xmm0 ecx bytes left
    shrXmm xmm0, eax        ; shift xmm0 eax bytes right
Rem    - there is no SIMD equivalent to shl/shr reg32, cl
    - these macros use pslldq and psrldq
    - self-modifying code, slow and potentially unsafe


Float    returns integer as float in ST(0)
    include \masm32\MasmBasic\MasmBasic.inc
    SetGlobals MyR4:REAL4, MyW:WORD=-123, MyDw:DWORD=-123, MyQ:QWORD=123456789012345678
    Init                ; select init and hit F6
    Dim float() As REAL4
    For_ ct=0 To 9
        SetFloat float(ct)=Float(ct)        ; converts integer ct to ST(0)
        Print Str$("i=%i", ct), Str$(", f=%2f\n", float(ct))
    Next
    Inkey "hit any key"
   
EndOfCode
Rem    int->float cast, returns ST(0); for use with SetFloat


MulQQ        multiply two QWORDs
    include \masm32\MasmBasic\MasmBasic.inc
    .data
    MyQ1    dq 12345678901
    MyQ2    dq 11111111111
    result    dq ?
    Init            ; select init and hit F6
    MulQQ(MyQ1, MyQ2, addr result)
    Print Str$("MyQ1*MyQ2\t%u \t(expected: 137174210009739369011 - overflow!!)\n", result)
    Print Str$("12345*Q2\t%u    \t(expected: 137166666665295, OK)\n", MulQQ(12345, MyQ2))
    movlps xmm0, MyQ2
    mov eax, 12345
    movd xmm1, eax
    Print Str$("12345*xmm0\t%u    \t(expected: 137166666665295, OK)\n", MulQQ(xmm1, xmm0))
    movlps xmm0, MyQ2
    mov eax, 12345
    Print Str$("12345*xmm0\t%u    \t(expected: 137166666665295, OK)\n", MulQQ(eax, xmm0))
    MulQQ(12345, addr MyQ2, edx::eax)        ; if you need top speed, use the edx::eax pair as destination
    Print Str$("12345*Q2\t%u    \t(expected: 137166666665295, OK)\n", edx::eax)    ; Str$() allows edx::eax as QWORD arg
   
EndOfCode
Rem    - multiplies two QWORDs
    - first arg can be an immediate DWORD or a 32-bit register
    - use MulQQ(q1, q2, pDest) to transfer the result to a QWORD destination
    - xmm regs are valid args, but xmm0 and xmm1 will be trashed after the call
    - beware of overflow: the result is a QWORD, not an OWORD; the difference is irrelevant in many cases,
        e.g. for random number generation, but do not expect miracles here!


Floor, Ceil
    include \masm32\MasmBasic\MasmBasic.inc
    SetGlobals MyR4:REAL4, MyW:WORD=-123, MyDw:DWORD=-123, MyQ:QWORD=123456789012345678
    Init            ; select init and hit F6
    PrintLine cfm$("\nnumber\t\tFloor(number)\tCeil(number)")
    For_ ecx=0 To 29
        Rand(-99.9, +99.9, MyR4)
        PrintLine Str$("%+4f ", MyR4), Str$(" \t%+5f", Floor(MyR4)v), Str$("    \t%+5f", Ceil(MyR4)v)    ; v = fstp st
    Next
    Floor(123.456, MyDw)    ; second arg is the destination (int or real variable)
    Print Str$("dw(123.456)=%i\n", MyDw)
   
EndOfCode
Rem    - returns a double in ST(0)
    - source can be immediate or WORD ... QWORD, resp. REAL4 ... REAL10 variable


Sinus, Cosinus, rSinus, rCosinus
    include \masm32\MasmBasic\MasmBasic.inc
Init        ; select Init and hit F6 to run this snippet
Print "x", Tb$, "sin x", Tb$, Tb$, Tb$, "error"    ;    ## compare results to fsin ##
FpuFill
    testStep=30    ; change as needed
For_ ecx=-90 To 450 Step testStep
    Print CrLf$, Str$(ecx), Tb$, Str$(Sinus(ecx)), Tb$
    void Sinus(ecx, 1)    ; 1=fpu fsin
    fsub
    Print Str$("%3f", ST(0))
    fstp st
Next
PrintLine CrLf$
Print "x", Tb$, "cos x", Tb$, Tb$, Tb$, "error"
FpuFill
For_ ecx=-90 To 450 Step testStep
    Print CrLf$, Str$(ecx), Tb$, Str$(Cosinus(ecx)), Tb$
    void Cosinus(ecx, 1)    ; 1=fpu fcos
    fsub
    Print Str$("%3f", ST(0))
    fstp st
Next
Print Str$("\nSinus(60)=\t%5f\n", Sinus(60)v)            ; the v means "pop the fpu", i.e. fstp st
Print Str$("Sinus(1.0472)=\t%5f\n", rSinus(1.0472)v)        ; pass the angle as a radian, here: 60*PI/180
   
EndOfCode
Rem    - returns REAL10 value in ST(0); store it to a variable with fistp or fstp. If you use it in Str$(), it must either
        be followed by fstp st, or you use Str$(Cosinus(ecx)v)
    - if your degrees are rad values like e.g. 0.5, use rSinus(0.5) or rCosinus(0.707)


ArcSinus, ArcCosinus, ArcTangens
include \masm32\MasmBasic\MasmBasic.inc
Init        ; select Init and hit F6 to run this snippet
Print Str$("The angle returned by ArcSinus(0.866) is %i degrees\n", ArcSinus(0.5)v)
Print Str$("The angle returned by ArcCosinus(0.5) is %i degrees\n", ArcCosinus(0.5)v)
Print Str$("The angle returned by ArcTangens(9.9) is %i degrees\n", ArcTangens(9.9)v)
Print Str$("The angle returned by ArcSinus(0.866) is %4f radians\n", rArcSinus(0.5)v)

EndOfCode
Rem    - returns the angle in degrees in ST(0)
    - ArcSinus() etc (without argument) uses the value in ST(0)
    - if you need radians, use rArcSinus() etc


FpuSave, FpuRestore
    FpuSave 3    ; save three FPU regs, i.e. store ST(5) ... ST(7) on the stack
    ... do FPU calculations - stack pointer must be the same afterwards ...
    FpuRestore    ; get ST(5) ... ST(7) back, correct the stack
    FpuSave    ; no args means save 2 regs: ST(6) and ST(7)
    Print Str$("A test with eax=%i", eax)    ; Str$ trashes ST(6) and ST(7)
    FpuRestore    ; get ST(6) and ST(7) back, correct the stack
Rem    returns nothing, trashes nothing, but is e.g. for 4 saved FPU regs about 10 times faster than fsave/frstor


FpuPush
    FpuPush 123    ; pushes 123 onto ST(0)
    FpuPush FP8(123.456)    ; pushes a REAL8 value onto ST(0)
MyQ    dq 1234567890123456789
    ...
    movlps xmm1, MyQ
    FpuPush xmm1    ; pushes the QWORD in xmm1 into ST(0)
    FpuPush MyReal16    ; requires a GCC installation
    movlps xmm0, MyR8
    FpuPush f:xmm0    ; pushes the DOUBLE in xmm1 into ST(0)
    Inkey Str$("\nOn FPU:\t%If", ST(0))
    FpuPush f:xmm1
Rem    does not trash any reg32; valid args as in Str$()


FpuFill
    FpuFill    ; no args means push 1001...1008 on the FPU
    FpuFill 5    ; push 1001...1005
    deb 4, "FPU 5:", ST(0), ST(1), ST(2), ST(3), ST(4), ST(5), ST(6), ST(7)
Rem    for testing purposes; does not trash any reg32


FpuSet
    FpuSet MbNear64    ; set full precision, rounding near
    FpuSet            ; no arg = set back to previous state
    FpuSet MbDown24, exall    ; set low precision, rounding down, force all exceptions except precision
Rem    - trashes only edx; uses fldcw & fnstcw with equates composed of rounding mode and precision:
        Mb[Round][Prec] with Round=Near, Trunc, Up or Down and Prec=24, 53 or 64
    - Windows initialises the FPU to MbNear53, but the Init macro sets MbNear64 (i.e. the finit default - full precision)
    - exall as second arg sets FPU exception flags to zero; since the precision flag would trigger exceptions
        all the time, exall leaves it set, so that only division by zero and similar errors are being caught
    - if you need a different set of exception flags, just use them as second arg, e.g. FpuSet MbNear64, 11010y


FastMath         create a fast mathematical function
include \masm32\MasmBasic\MasmBasic.inc
SetGlobals fct:REAL10
Init            ; select and hit F6
FastMath FastLog10    ; define a math function
    For_ fct=0.0 To 10.0 Step 0.5    ; max 10,000 iterations
        fld fct    ; X
        fstp REAL10 ptr [edi]
        void Log10(fct)    ; Y (built-in MasmBasic function)
        fstp REAL10 ptr [edi+REAL10]
        add edi, 2*REAL10
    Next
FastMath
Print Str$("Log(5.0)=%Jf", FastLog10(5)v)    ; roughly 3.3 times faster then the FPU

EndOfCode
Rem    - between the two lables, X/Y pairs must be saved to edi as REAL10
    - if you save the REAL10 pairs to disk, you can use e.g. the one-liner FastMath MyLog10, "log10.dat"
    - the new function expects an X value and sets ST(0)
    - for usedeb=1, range errors are printed to the console


MbMod
    include \masm32\MasmBasic\MasmBasic.inc
SetGlobals Res8:REAL8
Init    ; select and hit F6
    m2m eax, 11
    MbMod(123.45, eax, Res8)    ; standalone with destination
Print Str$("123.45 mod 11=%3f", Res8)    ; v v function v v
MsgBox 0, Str$("800 mod 60=%i", MbMod(800, 60)v), "Modulus:", MB_OK
   
EndOfCode
Rem    - returns , or saves to destination


Log2, LogE, Log10
    include \masm32\MasmBasic\MasmBasic.inc
    SetGlobals ct:REAL8
    Init    ; select and hit F6
    For_ ct=1.5 To 10.0 Step 0.5
        Print Str$(" Log2(%2f)", ct), Str$("\t%Jf ", Log2(ct)v), Str$("\t%Jf ", LogE(ct)v), Str$("\t%Jf\n", Log10(ct)v)
    Next
   
EndOfCode
Rem    these functions return the logarithms in ST(0); use the v before the closing bracket of Str$() to pop ST


Exp10, Exp2, ExpE and ExpXY
    ; four exponential functions are available, for 10^y, 2^y, e^y and x^y
    Exp10(0.5, MyDest10)    ; calculates 10^0.5 and saves it to a REAL10 variable called MyDest10
    Print Str$("Res=%Jf\n", MyDest10)
    ExpXY(4, 0.5, MyDest8)    ; calculates 4^0.5 and saves it to a REAL8 variable called MyDest8
    Print Str$("Res=%Jf\n", MyDest8)    ; 4^0.5 = 2

    Print Str$("Exp10    \t%Jf\n", Exp10(0.5))    ; if no destination is specified as last arg, the result is returned in ST(0)...
    fstp st    ; ... and you should pop it when no longer needed
    Print Str$("Exp2        \t%Jf\n", Exp2(0.5))    ; 2^0.5 = 1.414
    fstp st
    Print Str$("ExpE        \t%Jf\n", ExpE(3)v)    ; 2.718^3 = 20.085 (the v replaces fstp st)
Rem    - returns result in ST(0) if no destination specified
    - non-zero edx signals infinity error
    - beware of precision problems; FpuSet MbNear64 is recommended, although the rounding mode has no
        influence when 64bit precision is set



Percent, f4Percent, f8Percent, fpuPercent
include \masm32\MasmBasic\MasmBasic.inc
.data
srcr10    REAL10 12345.6789
pcr10    REAL10 12.3
srcdd    dd 123

    Init
    Print Str$("f4pc(srcr10, 33.33)=\t%If\n", f4Percent(srcr10, 33.33))        ; f4: returns REAL4
    Print Str$("f8pc(srcr10, 33.33)=\t%If\n", f8Percent(srcr10, 33.33))        ; f8: returns REAL8 e.g. for use with xmm regs
    Print Str$("fpupc(srcr10, pcr10)=\t%If\n", fpuPercent(srcr10, pcr10))    ; fpu: leaves result in ST(0)
    Print Str$("fpc(12345.678, ebx)=%f\n", f8Percent(12345.678, ebx))
    Print Str$("fpc(12345.6, ebx)=%f\n", f8Percent(12345.6, ebx))
    Print Str$("pc(12345.0, ebx)=%f\n", Percent(12345.0, ebx))        ; with immediate real source and reg32 percentage
    Print Str$("pc(srcdd, ebx)=%f\n", Percent(srcdd, ebx))            ; with DWORD source and reg32 percentage
    Print Str$("pc(12345, 20)=%f\n", Percent(12345, 20))            ; with immediate integer source and percentage
    movq xmm0, f8Percent(srcr10, 33.33)
    Print Str$("xmm0=%f\n", f:xmm0)        ; use the f: prefix to force interpretation as float
    push 0AB54A98Ch        ; 12345678901234567890
    push 0EB1F0AD2h        ; pushed as two dwords
    movlps xmm1, qword ptr [esp]
    add esp, QWORD
    Print Str$("xmm0=%u\n", xmm1)        ; use %u in the format string to force interpretation as unsigned
    movd xmm0, Percent(srcr10, 33.33)
    Print Str$("xmm0=%f\n", xmm0)
    Inkey "ok"
    Exit
end start
Rem    source can be almost anything, same for percentage


DefNum
    DefNum 3
    Print Str$("PI=%f", PI)    ; print PI at 3 digits precision
    Print Str$("PI=%4f", PI)    ; print PI at 4 digits precision, i.e. override DefNum
    DefNum -1
    Print Str$("PI=%f", PI)    ; print PI at max digits precision (i.e. 18)
    DefNum 19
    Print Str$("PI=%f", PI)    ; tickle out one more digit (same as Str$("PI=%Jf", PI), may yield rubbish)
Rem    sets default precision; override with Str$("%nf", number), where n=12...ABCDEFGHIJ as shown above



Hex$
    Print Hex$(eax)    ; reg32, 12345678
    Print Hex$(cx)    ; reg16, 1234
    Print Hex$(dl)    ; reg8, 12 - same for dh, ah etc
    Print Hex$(123)    ; immediate
    Print Hex$(MyDword)    ; global and local variables
    Print Hex$(MyWord)
    Print Hex$(MyByte)
    Print Hex$(MyQword)    ; QWORD will be displayed with one space as 12345678 90123456
    Print Hex$(xmm1)    ; if QWORD is not enough, get e.g. 11AA22BB 33CC44DD 55AA66BB 77CC88DD
    Print Hex$(MyReal8)    ; same as QWORD
    Print Hex$(MyR4(ecx))    ; numerical arrays can be used, too
    Let H$="Hex="+Hex$(1a2b3c4dh)+"h"    ; in case you need the trailing h or a leading 0x, use Let or Cat$()
    PrintLine "FastHex =", Hex$(eax, fast)    ; dwords only
    movlps somebuffer, Hex$(123h, xmm0)    ; dwords only - 8 bytes are returned in xmm0
    Hex$(0BAADF00Dh, edi)    ; write to memory
    Hex$(12345678h, somebuffer)    ; write to memory
Rem    - returns DWORD in edx
    - MbHexSpaces=0: no spaces between DWORDs
    - MbHexQ=1: limit xmm0 output to the two lower DWORDs


HexDump$
    Print HexDump$(pBuffer)    ; prints 512 bytes in format 00000010 ab cd ef ... text
    Print cfm$("\nGetModuleHandle(0):\n"), HexDump$(rv(GetModuleHandle, 0), 90h)
    Output:
    GetModuleHandle(0):
    00400000    4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00 MZ.........ÿÿ..
    00400010    B8 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 ¸.......@.......
    00400020    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
    00400030    00 00 00 00 00 00 00 00 00 00 00 00 80 00 00 00 ............€...
    00400040    0E 1F BA 0E 00 B4 09 CD 21 B8 01 4C CD 21 54 68 ..º..´.Í!¸.LÍ!Th
    00400050    69 73 20 70 72 6F 67 72 61 6D 20 63 61 6E 6E 6F is program canno
    00400060    74 20 62 65 20 72 75 6E 20 69 6E 20 44 4F 53 20 t be run in DOS
    00400070    6D 6F 64 65 2E 0D 0D 0A 24 00 00 00 00 00 00 00 mode....$.......
    00400080    50 45 00 00 4C 01 03 00 FB BB BF 59 00 00 00 00 PE..L...û»¿Y....
    Let edi=HexDump$(esi, eax, 0)    ; assigns eax bytes to edi with offset 0
    Let edi=HexDump$(esi, 128, db)    ; uses db 0ABh, 0CDh for use in code
    Let edi=HexDump$(esi, 4096, notext)    ; assigns 4kB, no ascii textreout
    Let edi=HexDump$(esi, file, dq)    ; esi holds filename, translate whole file, use dq for code   
Rem    returns ptr in eax; maxlen 40,000 bytes


Bin$
    Print Bin$(123)
    Let esi=Bin$(-1)+"y"
    MsgBox 0, Bin$(ebx, f), "The Bin$", MB_OK
Rem    returns DWORD in edx - use only once in Let
    option f adds a formatted bit counter


IntAsWords$        translate integer to English text
    include \masm32\MasmBasic\MasmBasic.inc
    Init        ; select and hit F6 to test this example
    PrintLine "8575", Tb$, IntAsWords$(8575)    ; eight thousand five hundred and seventy-five
    mov eax, -1234
    PrintLine "-1234", Tb$, IntAsWords$(eax)    ; minus one thousand two hundred and thirty-four
   
EndOfCode
Rem    range is -10999...+10999


Qcmp, Ocmp
.data    ; for testing
qSmall    qWORD 7700000000000001h
qBig    qWORD 7700000000000003h
oSmall    OWORD 77000000000000000000000000000001h
oBig    OWORD 77000000000000000000000000000003h    ; OWORD for JWasm and higher ML.exe versions
; oBig    qWORD 00000000000000003h, 7700000000000000h    ; 2 QWORDS for ML 6.15
.code
    Qcmp qBig, qSmall    ; compare two global variables
    mov ecx, offset oBig    ; use a pointer (not edx, please) ...
    Ocmp ecx, oSmall    ; ... for one (or both) of them
    oqDeb=1    ; if this flag is set, Qcmp or Ocmp will print e.g.
            "ecx GREATER xmm0" or "MyOWORD LESSER xmm2" to console
    movups xmm0, OWORD PTR oSmall    ; ML 6.15 accepts OWORD PTR as used here, but not in .data
    Ocmp ecx, xmm0    ; a pointer and an XMM reg (xmm0...xmm2 will be trashed)
    deb 4, "Result", flags    ; CzSo, i.e. Carry? and Sign? set
Rem    - returns flags as in a normal cmp eax, edx comparison (control for overflow!)
    - you may assign immediates with e.g. movaps xmm0, Oword16(1234567890); the "16" means that data is aligned
    - will trash eax and edx, xmm0 and xmm1; do not use edx as input pointer
    - you cannot use both ecx and ebx as input pointers (an error will be thrown)


Fsign
    .if Fsign(MyRealVar)
        MsgBox 0, "MyRealVar is negative", "Fsign:", MB_OK
    .else
        MsgBox 0, "MyRealVar is not negative", "Fsign:", MB_OK
    .endif

Rem    returns Sign?, and works with all sizes (Real4, Real8, Real10)



Fcmp
    MyPI_hi    REAL4    3.14160
    ...
    Fcmp MyPI_hi, PI, medium    ; PI is what you think it is
    .if FcmpLess
        Print Str$("MyPI_hi at %f is lower than the real PI\n", MyPI_hi)
    .elseif Zero?
        Print Str$("MyPI_hi at %f is exact\n", MyPI_hi)
    .else
        Print Str$("MyPI_hi at %f is higher than the real PI\n", MyPI_hi)
    .endif
    Fcmp st, st(1), xtra    ; compare st(0) and st(1) with maximum precision
Rem    - returns Zero? and Sign? flags (and only these are valid): Sign? means "first arg below second arg"
    - you may use FcmpGreater and FcmpLess (aka !Sign? and Sign?)
    - the desired precision can be indicated as third argument: low, medium, high, top and
        xtra precision; caution in loops that exit only if Zero? is set!
    - single arg, e.g. Fcmp xmm1, tests for zero
    - see also QCmp and Ocmp for comparing QWORDs and OWORDs
    - almost any number formats can be compared, including xmm registers etc



Val, Val?, HexVal
    mov eax, Val(Chr$("123"))    ; eax=123, edx=3
    mov eax, Val("123")    ; eax=123, edx=3
    mov esi, Chr$(9, "    12345")    ; eax=12345, edx=9 (5 plus a tab and three spaces)
    mov ebx, Val(esi)
    mov ebx, Val("12345h")    ; hex
    mov ebx, Val("0x12345")    ; hex, C notation
    mov ebx, Val("$12345")    ; hex, leading $ notation
    mov ebx, Val("10101b")    ; binary
    mov ebx, Val("12:34:56", 3)    ; take the third value, e.g. 56 seconds
    Dim MyArray(1000) As DWORD
    mov MyArray(n), Val("12345678")    ; assign to a dword array member
    mov ebx, Val("$12345")
    .if signed edx>0    ; signed is an equate for sdword ptr
        .if dh==1    ; dh set means it was a Bin$; use movzx edx, dl if you need the real # of chars used
            MsgBox 0, Str$("We found a Bin$: %i", ebx), "Val:", MB_OK    ; 10101b or 10101y
        .elseif dh==2
            MsgBox 0, Str$("We found a Hex$: %i", ebx), "Val:", MB_OK
        .else
            MsgBox 0, Str$("We found a decimal: %i", ebx), "Val:", MB_OK
        .endif
    .else
        MsgBox 0, "Sorry, the format was no good", "Val:", MB_OK
    .endif
    Print Str$("#%i digits used", Val?("123h")    ; 4 digits used
    deb 4, "Hex strings only", x:HexVal("1111aBcd")    ; result in eax; for HexVal(pStr, 64) and HexVal(pStr, 128) in xmm0
Rem    - Val returns value in eax, and the number of used characters in dl (i.e. the lowbyte of edx)
    - Val accepts a variety of formats, see examples above. Make sure, though, that you do not feed more than 32 bits to a
        dword; for example, Val("1234567890") is ok but Val("9876543210") needs a QWORD destination, see MovVal below
    - for floats, use MovVal, see next entry
    - non-zero dh signals a Bin$ (dh=1) or Hex$ (dh=2), edx==-127 signals an error, i.e. not a number format
    - you can do simple calculations by combining Val and Str$:
        mov eax, Val(Str$(12*34-8))    ; slow and not very elegant, but it works
    - ultrafast HexVal accepts only 123Abc or 123AbcH formats; HexVal(pStr, 64) and HexVal(pStr, 128) return results in xmm0
Key    val(



MovVal
    MovVal MyDword, Chr$("123.4567")        ; assign a dword
    MovVal MyDword, esi        ; variable from
    MovVal MyDword, offset MyString        ; a string
    Print Str$("MyDword=\t%f\n", MyDword)     ; and print it
    MovVal MyR4, Input$("Type a number and hit Enter:    ")    ; get a number from the console
    Print Str$("MyR4=\t%f\n", MyR4)
    MovVal f:xmm0, Left$(Chr$("123.4567"), 6)    ; to use xmm regs in float mode, use the
    Print Str$("MyXmm=\t%f\n", f:xmm0)        ; f: prefix both for MovVal and Str$
    MovVal xmm0, Left$(Chr$("123.4567"), 6)     ; same xmm reg but in integer mode
    Print Str$("MyXmm=\t%f\n", xmm0)
    MovVal MyQword, "123.4567"        ; qwords
    Print Str$("MyQword=\t%f\n", MyQword)
    MovVal MyR4, Chr$("123.4567")        ; REAL4
    Print Str$("MyR4=\t%f\n", MyR4)
    MovVal ST(0), "12345789123456789120000000000000000000000000000000"
    Print Str$("V=%Jf\n", ST(0)v)        ; V=1.234578912345678912e+50; v means "pop ST with fstp st"
    push eax        ; create a DWORD slot
    MovVal stack, "12345678"        ; looks elegant but mov ecx, Val(...) is shorter
    pop ecx        ; pop a DWORD
    Print Str$("Popped from stack: %i\n", ecx)
    MovVal eax, Chr$("-1234")        ; same result as Val(..)
    MovVal MyR10, Chr$(9, "    -123.45678901234567890e-123 cute, insn't it?")    ; edx=30 (incl. tab + 2 spaces)
Rem    returns # of used chars in dl - see Val() above



Sqrt
    Print "All tests for a value of 32", CrLf$
    mov edx, 32
    Print Str$("Square root reg32 \t%If\n", Sqrt(edx))
    MovVal xmm0, "32"
    MovVal f:xmm1, "32"
    Print Str$("Square root xmm0 \t%If\n", Sqrt(xmm0))    ; xmm0 in integer mode
    Print Str$("Square root xmm1 \t%If\n", Sqrt(f:xmm1))    ; xmm1 in float mode
    Print Str$("Square root Word \t%If\n", Sqrt(V32W))        ; Word variable
    Print Str$("Square root DWord \t%If\n", Sqrt(V32DW))    ; Dword
    Print Str$("Square root QWord \t%If\n", Sqrt(V32QW))    ; Qword
    Print Str$("Square root Real4 \t%If\n", Sqrt(V32R4))    ; Real4
    Print Str$("Square root Real8 \t%If\n", Sqrt(V32R8))    ; Real8
    Print Str$("Square root Real10    \t%If\n", Sqrt(V32R10))    ; Real10
    Print Str$("Square root left on FPU \t%If\n", Sqrt(2, ST(0)))    ; you may provide a destination as second arg,
    fstp st                    ; e.g. ST(0); for the latter, fstp st cleanup is needed

Rem    returns a Real10 variable (named MbDebugR10) for use with Str$()



SetField
    MyRecord RECORD SlotHigh:4, SlotMid:7, SlotLow:7, SlotRest:32-4-2*7    ; the order is high to low
    .data?
    MyRec    MyRecord <>    ; define an empty 32-bit record with 4 fields
    .code
    SetField MyRec.SlotHigh, 15    ; set all 4 bits
    SetField MyRec.SlotMid, eax    ; 7 bits available
    SetField MyRec.SlotLow, 10, clear    ; clear all 7 bits, then set value 10
Rem    - argument must be immediate or DWORD (register or variable)
    - important: if there is any third argument, the existing field bits will be cleared with and MyRec, not mask field;
        then, bits will set with or MyRec, eax. The and costs 10 bytes (!) for a global variable, and can easily be
        avoided by clearing the entire record (and MyRec, 0) before setting individual fields
    - for immediate arguments, an error will be thrown if the value exceeds the available range



GetField
    Print Str$("The content of MyRec.SlotHigh is %i", GetField(MyRec.SlotHigh))
    mov MyVar32, GetField(MyRec.SlotLow)
Rem    returns field value in eax


GetHash
include \masm32\MasmBasic\MasmBasic.inc
    Init
    If_ GetHash(FileRead$(), LastFileSize, md5) Then PrintLine "The MD5 of ", CL$(), " is ", Hex$(xmm0)
    Let esi=FileRead$("\Masm32\qEditor.exe")
    .if GetHash(esi, LastFileSize, sha)                    ; FileRead$ sets LastFileSize, also for binary files
        PrintLine "The SHA of qEditor.exe is    ", Tb$, Hex$(xmm0), " ", Hex$(ecx)
    .else
        PrintLine "Hashing failed: ", Err$()
    .endif
    void GetHash(esi, LastFileSize)
    mov ecx, edx                    ; fifth word returned in edx
    Print "Fast MD5 of qEditor.exe is ", Tb$, Hex$(xmm0), CrLf$
    GetHashRev=1                    ; use official MD5 byte order
    .if GetHash(FileRead$("\Masm32\qEditor.exe"))
        PrintLine "qEditor, byte order MD5 specs: ", Tb$, Hex$(xmm0)    ; MD5, len of string will be calculated
    .endif

EndOfCode
Rem    - returns success (1) or failure (0) in eax
    - four dwords are returned in xmm0
    - in case of SHA, the fifth dword will be in edx
    - results can be displayed as a Hex$(xmm0), plus Hex$(edx) for SHA. The byte order will be reversed, compared to
        online hash calculators. You may use GetHashRev=1 to follow the MD5 specs, but more code will be generated
    - with only one para, GetHash calculates the length; this is meaningful only for text files
    - uses CryptGetHashParamfiles; try also e.g. PrintLine Launch$(Cat$("certutil -hashfile "+CL$()+" MD5"))


SetFlags
    SetFlags 3, 1    ; Set flag #3
    SetFlags 3, 0    ; Clear flag #3
    FileIsDirty = 31    ; define your own name (0...31)
    SetFlags FileIsDirty, 1
Rem    - use with Flags(), see below
    - returns previous flag value in Carry?



Flags
    .If Flags(3)
        MsgBox 0, "Flag 3 was set", "Hi", MB_OK
    .else
        MsgBox 0, "Flag 3 was not set", "Hi", MB_OK
    .endif

Rem    returns current flag value in Carry?



MouseX, MouseY, MouseK
    .if MouseK==1 && MouseY>600 && MouseX<9
        MsgBox 0, "You clicked in the lower left corner", "Hi", MB_OK
    .endif
Rem    returns result in eax


Delay
    Delay 1000        ; wait a second...
    PrintLine fTime$(0, "HH:mm:ss.fff"), Tb$, "started"
    Delay until fTime$(s:1, "HH:mm:ss.123")        ; use time$(current plus one second)
    PrintLine fTime$(0, "HH:mm:ss.fff "), Tb$, NanoTimer$(), " restarted"
Rem    - uses Sleep but preserves ecx
    - the until variant uses a time string to wait until a precise moment; use e.g. fTime$(1, "HH:mm") to
        suspend running until the current minute is finished; fTime$(s:10, ...) would mean 10 seconds


void, voidTrue, voidFalse
    void Len("a test")                    ; use Len but avoid a mov eax, eax
    voidTrue Len(ecx)                    ; same but print "zero eax in line xx" if Len is zero and usedeb is on
    voidFalse rv(SendMessage, hEdit, WM_GETTEXTLENGTH, 0, 0)    ; prints "non-zero eax in line xx" to console if usedeb!=0 and eax!=0
Rem    returns DWORD in eax



GetRegVal
    PrintLine GetRegVal("HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment","Path", "no path found"), CrLf$
    EnvVars equ "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment"    ; long paths: use an equate
    Print GetRegVal(EnvVars,"PROCESSOR_IDENTIFIER", "unknown"), CrLf$            ; get to know your CPU...
    PrintLine GetRegVal("HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment","PROCESSOR_IDENTIFIER", "unknown"), CrLf$    ; same with full path
    PrintLine GetRegVal("HKCR\.doc", 0, "documents"), " are files with ending *.doc"
    ; this line would fail with a run-time error because the key does not exist and there is no default value given:
    ; PrintLine GetRegVal("HKEY_CLASSES_ROOT\.bax", 0), " are files with ending *.bax"
    PrintLine "Your default console font is ", GetRegVal("HKCU\Console","FaceName", "Arial")
    ; you can use the MbRegValRTE equate to determine the behaviour in case of errors:
    ; by default, you see a box "Line n: Get/SetRegVal failed" if you gave no default and the key or value does not exist
    MbRegValRTE = 1        ; default mode: bark if there is a problem
    void GetRegVal("HKCR\.dox", 0)        ; no default, MbRegValRTE = 1: box "registry access failed in line xx"
    void GetRegVal("HKCR\.dox", 0, "dox files")    ; default, MbRegValRTE = 1: default value returned
    PrintLine eax, " are files with ending *.dox"
    MbRegValRTE = 0        ; silent mode - no Run-Time Error in case of problems
    void GetRegVal("HKCR\.dox", 0)        ; no default, MbRegValRTE = 0: pointer to RV? in eax
    .if dword ptr [eax]==Mirror$("RV?")        ; not found
        PrintLine "Failure: [", eax, "]"
    .else
        PrintLine "Success: [", eax, "]"
    .endif
    ; REG_QWORD values and REG_BINARY are returned in xmm0; you need void plus fDate$(xmm0) to see them:
    void GetRegVal("HKLM\SOFTWARE\Microsoft\ASP.NET\4.0.30319.0", "LastInstallTime", 0)
    PrintLine "LastInstallTime: [", fDate$(xmm0, "dd MMMM yyyy"), ", ", fTime$(xmm0, "HH:mm"), "]"
Rem    - returns in eax either a pointer to a string, or a DWORD, and in edx the REG_xx type or failure (0)
    - in case you expect REG_NONE (=0), check [eax] for the string Rg? indicating failure
    - for types REG_QWORD (i.e. FILETIMEs) and REG_BINARY, eax is a pointer to the data, while
        the first 16 bytes are returned in xmm0, e.g. for use with fDate$() and fTime$()
    - remember that Print and Let trash eax and edx. Use void GetRegVal to test for edx, then Let xx=eax
    - returned values can be up to 128 kBytes long
    - if you provide a default value, GetRegVal returns it even if the key and/or the value are not present
    - if a default was given, edx is always 1, without default string or dword edx=0 means failure
    - error codes for RegOpenKeyEx are in rvRegKey, those for RegQueryValueEx are in rvRegQuery
    - if you can't see certain keys on 64-bit systems, try SetReg64
Key    grv(



SetRegVal
    SetRegVal "HKCU\TheKey","TheValueName", "NewString"
    SetRegVal "HKEY_CURRENT_USER\Console\JJ", "NewValue", 12345        ; creates new REG_DWORD
    SetRegVal "HKCU\Console\JJ", "NewValue", 54321                ; changes existing value
    SetRegVal "HKEY_CURRENT_USER\Console\JJ", 0, "New default value"    ; 0: sets (Default) to a string
    ; do not replace an existing REG_SZ with a dword - this will produce an exception:
    ; SetRegVal "HKEY_CURRENT_USER\Console\JJ", 0, 123
    ; existing REG_DWORD receives a DWORD string pointer - this works but is meaningless and bug-prone:
    ; SetRegVal "HKEY_CURRENT_USER\Console\JJ", "NewValue", "A string"
    ; tries to create a new key, will fail with a run-time error:
    ; SetRegVal "HKEY_CURRENT_USER\Console\NoSuchKey", "NewValue", 123
Rem    - returns in eax the original new value, in edx success (1) or failure (0); see also remarks for GetRegVal
    - you are not allowed to create a new key, but you can create a new REG_SZ or REG_DWORD value
    - with MbRegValRTE=1, attempts to create a new key will trigger a run-time error; =0 will fail, and edx will be zero


GetRegKeyArray, GetRegArray
    PrintLine "Keys in ... \CurrentVersion\Explorer:"
    ; key name, names array, optional: last modification array; the latter is a SYSTEM_TIME array;    you can
;     get the time elapsed since the last modification with Age(LastMod(index), x), with x=d, h, m, s, ms or µs
    ; SetReg64    ; optional: force seeing 64-bit registry values
    GetRegKeyArray "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer", My$(), LastMod()
    For_ ecx=0 To eax-1
        .if Age(LastMod(ecx), h)<=7*24            ; h=hours (valid units: d/h/m/s/ms/µs)
            Print fDate$(LastMod(ecx), "dd MMMM yyyy"),\        ; the dd MM... format is optional
            ", ", fTime$(LastMod(ecx)), Tb$, My$(ecx), CrLf$    ; default time format is e.g. HH:mm:ss
        .endif
    Next
    PrintLine "My HKCU environment variables:"
    GetRegArray "HKCU\Environment", MyEnv$(), MyData$()    ; the optional second array takes the values
    For_ ecx=0 To eax-1
        PrintLine MyEnv$(ecx), Tb$, MyData$(ecx)    ; print names and values
    Next
    PrintLine CrLf$, "HKLM:"
    GetRegArray "HKLM\SYSTEM\ControlSet001\Control\Session Manager\Environment", MyEnv$(), MyData$()    ; one more
Rem    - returns #added strings in eax, i.e. not the total; if you use the same array repeatedly, either keep track
        of the start value, or use some$(?) to get the total #elements
    - if regedit.exe shows more items, try SetReg64
    - see also two detailed examples in MbSnippets.asc


GetProcessArray
    include \masm32\MasmBasic\MasmBasic.inc
    Init        ; select init and hit F6 to test this snippet
    Print "ct", Tb$, "ID", Tb$, "path"
    For_ ecx=0 To GetProcessArray(?)-1
        Print Str$("\n%i\t", ecx), Str$(MbProcID(ecx)), Tb$, MbProc$(ecx)
    Next
    Inkey CrLf$, "--- hit any key ---"
   
EndOfCode
Rem    - creates a string array MbProc$() and an array of process IDs, MbProcID(), using GetProcessIoCounters
    - returns # strings when used with (?)
    - or use it standalone without args: GetProcessArray()


FindProcess
    include \masm32\MasmBasic\MasmBasic.inc
    Init        ; select init and hit F6
    If_ FindProcess("Explorer.exe") Then MsgBox 0, Str$("FindProcess returned ID %i\n", [eax.PROCESSENTRY32.th32ProcessID]), "Hi", MB_OK
   
EndOfCode
Rem    - returns pointer to a PROCESSENTRY32 structure, or zero if no process found
    - accepts UTF-8 process names



GetDevicesArray
    include \masm32\MasmBasic\MasmBasic.inc
    Init        ; select init and hit F6 to test this snippet
    Print "ct", Tb$, "device"
    For_ ecx=0 To GetDevicesArray(?)-1
        Print Str$("\n%i\t", ecx), MbDevices$(ecx)
    Next
    Inkey CrLf$, "--- hit any key ---"
   
EndOfCode
Rem    - creates a string array MbDevices$()
    - returns # strings when used with (?)
    - or use it standalone without args: GetDevicesArray()


PushText
include \masm32\MasmBasic\MasmBasic.inc
Init
PushText "The new PushText macro provides a simple\noption to place strings on the stack"
PrintLine esp
PushText "Добро пожаловать", 1        ; Unicode is ok; an optional 1 obscures the text
PrintLine esp
PushText        ; no args: balance the stack
   
EndOfCode
Rem    pushes text as dwords on the stack


TitleCase$
include \masm32\MasmBasic\MasmBasic.inc
Init
PrintLine TitleCase$("hello world, how are you today?")    ; Hello World, How are You Today?
   
EndOfCode
Rem    from the series "macros that the World didn't need" ;-)



Masm32 macros:
print    use Print
fopen    use Open
fread    use Input #
fclose    use Close
inkey    use Inkey
mtxt    use Mirror$
szRev    use Mirror$
str$    use Str$
hex$    use Hex$
cat$    use Cat$
exit    use Exit
len    use Len


Resources must be put below the end label; when pressing F6, content between the two Rsrc lines will be written to [filename].rc
ID 59 is reserved for the toolbar in GuiData.zip. Note opt_icon will be ignored - what counts is the icon specified in the resource file.
You may delete these three lines once you don't need them any more.
Rsrc
#include "resource.h"
IDI_APPLICATION ICON    "\\Masm32\\MasmBasic\\icons\\Smiley.ico"    // Calc, Disk, Editor, Eye, Globe, MasmBasic, Smiley
77 RCDATA "\\Masm32\\MasmBasic\\icons\\Eye.ico"
01 RT_MANIFEST    "\\Masm32\\MasmBasic\\Res\\XpManifest.xml"        // works with tooltips
Rsrc

RichMasm options
OPT_Susy    Console
OPT_Tmp2Asm    1
OPT_arg1    "first argument passed"
OPT_arg2    "second argument passed"