News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

MasmBasic

Started by jj2007, October 06, 2009, 08:24:57 PM

Previous topic - Next topic

dedndave

well - i did things differently - lol
in that example, i used a circular buffer - filled it with the 4 WndProc parameters for each message
when the message loop started, i stopped putting things into the buffer and displayed them

the idea was to avoid possible infinite loops
now, i have a program that outputs messages in a seperate window/instance

ToutEnMasm


I have try to test masmbasic.
Having two masm32 directories in two differents disk,I have renamed the masm32 in richmasm.
And it isn't a supported option   "couldn't find ......."
If i can made a suggestion ,rename your masm32 directory (easy way ,an editor made it)
                                     or use this functions (allow use of relative path to the application)
Quote
   invoke  GetModuleFileName,hInstance,addr appPath,sizeof appPath
   invoke GetFullPathName,addr appPath,sizeof appPath,addr CheminCompose,addr PointeNomCourt

jj2007

Cher Yves,

The archive has path names, and must be extracted to the root of your masm32 drive. Requesting that MasmBasic should work with different folder names is like requesting that Masm32 should work with \bin renamed to \mybin - it won't work.

Besides, MB just adds two folders to your \masm32 installation (\masm32\RichMasm and \masm32\MasmBasic), with one exception: \masm32\bin\mspdb_MasmBasic_Readme.txt
So there is actually no risk that it would interfere with your Masm32 installation. That is indeed the whole point of having MB: It should work smoothly in parallel to Hutch' Masm32 package, so that coders can profit from both worlds.

Thanks anyway for trying. At a certain point, I got inspired by your EditMasm project. RichMasm took a slightly different direction then, but as far as I know we are the only two guys in the assembler world who insist that individual formatting à la MS Word is a good idea :bg

Cheers,
Jochen

ToutEnMasm


I know that you don't agree with me.I have tried to do it.
Something is wrong
Quote
Requesting that MasmBasic should work with different folder names is like requesting that Masm32 should work with \bin renamed to \mybin - it won't work.
It is possible (but without interest),just change all path in your source code (a baby game for cherche,a tool of mine) and your batch path (same tool).
:P


jj2007

Yves,

There is nothing wrong with MasmBasic provided that you extract it with "use folder names" to the root of the drive where your masm32 package is installed, e.g. to D:\

Practically all sources in \masm32\examples work just fine if you replace the usual entry stuff as follows:
include \masm32\MasmBasic\MasmBasic.inc
;    include \masm32\include\masm32rt.inc


Sometimes the include files are hidden away in extra files, e.g. \masm32\examples\exampl08\barchart\barchart.inc - in these cases, you can delete the whole section...
      include \masm32\include\windows.inc
...
      includelib \masm32\lib\msvcrt.lib

... although it is not necessary - if you leave the *.inc as is, the assembler will issue some warnings about duplicate files but it still works fine if you add this in line 8 of \masm32\examples\exampl08\barchart\barchart.asm:

      option casemap :none      ; case sensitive
      include \masm32\MasmBasic\MasmBasic.inc
      include barchart.inc      ; local includes for this file

MB will not work if you use the assembler that is included with Masm32, i.e. ml.exe version 6.14. Version 6.15 is the required minimum because of the SSE2 code used in MasmBasic. You can use JWasm as well.

Of course, if other people had similar problems in getting MasmBasic running, I would have to dig into detail. But that seems not to be the case, so I assume that your installation is somewhat special. Or does somebody have the same problems as Yves? Grateful for feedback - here is the simplest available test app:

include \masm32\MasmBasic\MasmBasic.inc   ; download
   Init
   
Credits
   Exit
end start

If you like a more interesting example, here is one with the debug macro:

include \masm32\MasmBasic\MasmBasic.inc   ; download

.data
xx0   QWORD 123456789012345678
xx1   REAL8 1234567.890

   Init
   movlps xmm0, xx0   ; load a large integer
   movlps xmm1, xx1   ; load a double
   fldpi         ; feed the FPU
   
push eax
   push eax
   fst REAL8 PTR [esp]   ; load a well-known double
   movlps xmm2, REAL8 PTR [esp]
   deb 1, "Some of your regs:", eax, ebp, ST(0), xmm0, f:xmm1, f:xmm2
   Exit
end start

jj2007

Update 6 October: Count counts the number of occurrences of a pattern inside a buffer. The Unicode equivalent is called wCount, as usual. Count is pretty fast, it processes more than 400 MB per second, see attachment.

Example #1 counts how many equ can be found in Windows.inc:

include \masm32\MasmBasic\MasmBasic.inc   ; download
 
Init[/color]
  Let esi=FileRead$("\Masm32\include\Windows.inc")      ; Create a buffer for Windows.inc
  Print Str$("%i chars read\n", Len(esi))
  Print Str$(" Count equ=\t%i\n\n", Count(esi, Chr$("equ"), 1))      ; Count equ (1=case-insensitive)
  .if Exist("\Masm32\include\WindowsWide.inc")      ; to test wCount, open the ANSI version in an editor and save it as UNICODE
      wLet esi=FileRead$("\Masm32\include\WindowsWide.inc")      ; Let or wLet are the same for a plain FileRead$()
      Print Str$("%i chars read, including BOM", wLen(esi)), Str$(", filesize=%i\n", LastFileSize)
      Print Str$("wCount equ=\t%i\n", wCount(esi, wChr$("equ"), 1))
  .endif
  Inkey "Hit any key"
 
Exit
end start

Output:
849788 chars read
Count equ=     14955
849789 chars read, including BOM, filesize=1699578
wCount equ=    14955


Example #2 extracts function names from all *.inc files and counts the DWORD paras for each of them:

include \masm32\MasmBasic\MasmBasic.inc   ; Download & install, then press F6 to assemble & link
   
Init
   push Timer
   Dim
Proto$(200000)   ; 55,000 would be enough
   xor edi, edi
   GetFiles
\masm32\include\*.inc   ; put all *.inc filenames into the Files$() array
   For_ n=0 To eax-1
      Let Proto$(edi)="### "+Files$(n)
      inc edi
      Recall Files$(n), IncFile$()   ; load all strings from Files$(n) into a new IncFile$() array
      For_
ecx=0 To eax-1
         mov esi, IncFile$(ecx)   ; Let not needed, since we will not modify the string
         .if Instr_(esi, "PROTO", 5)   ; edx is pos of first PROTO - we subtract 2 to arrive at the last char of the function name
            
xchg edx, ebx   ; we need a non-volatile reg32
            Let Proto$(edi)=Left$(esi, ebx-2)+Str$("@%i", Count(esi, "DWORD", 5))   ; e.g. CreateWindowExA@12
            inc edi
            .Break .if edi>=Proto$(?)
         .endif
      Next
      .Break .if edi>=Proto$(?)
   Next
   Store
"\masm32\RichMasm\Res\MbProtos.dat", Proto$(), edi   ; write edi strings to file
   pop ecx
   Inkey Str$("Analysing %i files", n), Str$(" took %i ms\n", Timer-ecx), Str$("%i PROTOs found, written to\n\masm32\RichMasm\Res\MbProtos.dat\n", edi-n)
   
Exit
end start

Output:
Analysing 345 files took 312 ms
53518 PROTOs found, written to
\masm32\RichMasm\Res\MbProtos.dat


The file \masm32\RichMasm\Res\MbProtos.dat:
### \masm32\include\1394bus.inc
Bus1394RegisterPortDriver@1
### \masm32\include\acledit.inc
DllMain@3
FMExtensionProcW@3
SedDiscretionaryAclEditor@13
SedSystemAclEditor@12
SedTakeOwnership@14
### \masm32\include\aclui.inc
CreateSecurityPage@1
...
### \masm32\include\xactsrv.inc
XsCaptureParameters@2
XsCheckSmbDescriptor@2
XsConvertServerEnumBuffer@8

jj2007

Update 10 Oct: Open and Print accept now a non-immediate #number, so that you can create and write multiple files in a loop.

include \masm32\MasmBasic\MasmBasic.inc   ; download
   Init[/size]
   For_ ebx=0 To 3
      Open "O", #ebx, Str$("File_%i.dat", ebx)
   Next
   For_ ebx=0 To 3
      wPrint #ebx, wStr$("This is file %i", ebx)   ; Unicode, just for fun
   Next
   Close
   Inkey "4 files created & written"
   
Exit
end start

jj2007

Update 20 October - download on top of thread:

- bugfix: mov eax, Val("123.456") would return the right value (123) but wrong # of chars used in edx.
- no easy fix for this one: cells in a two-dimensional string array loaded from a tab-delimited textfile cannot be directly concatenated in Let or Print. Example:

include \masm32\MasmBasic\MasmBasic.inc   ; download
   Init
   Recall "MyTabFile.txt", MyCellArray$(), tab   ; load a tab-delimited text file (same for csv format)
   PrintLine "A1=", MyCellArray$(0,0)   ; show the cell in the upper left corner

   Print
"fails:", Tb$
   PrintLine "A2=", MyCellArray$(1,0), Tb$, "A3=", MyCellArray$(2, 0)   ; fails miserably showing twice the same cell

   Print
"works:", Tb$
   Print "A2=", MyCellArray$(1,0), Tb$, "A3="   ; workaround: separate the two cells
   PrintLine MyCellArray$(2, 0)

   Dim MyArray$(3, 3)      ; "hand-made" two-dimensional string arrays are organised differently
   Let
MyArray$(0, 1)="This is 01"
   Let MyArray$(1, 0)="this is 10"
   PrintLine MyArray$(0, 1), ", and ", MyArray$(1, 0)   ; no problem for the hand-made array
   Exit
end start

- new function CurDir$():
   Let esi=CurDir$()+"MyFile.txt"      ; e.g. D:\masm32\RichMasm\Res\MyFile.txt
   Let esi=CurDir$(0)+"\MyFile.txt"   ; same output, the (0) means "do not append a backslash"

- new function ExpandEnv$():
   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")+"]"
Architecture is x86, the OS is Windows_NT, and the CPU is
x86 Family 6 Model 14 Stepping 8, GenuineIntel
This is the full path: [C:\PROGRA~1\Microsoft Office\OFFICE11\WINWORD.EXE]


- last but not least, following some tests in the RevString thread (based on a much older thread), Mirror$() accepts now non-immediate args:
   cmp eax, Mirror$("Masm")      ; easier than cmp eax, "msaM"
   PrintLine Mirror$("Masm32 is great")   ; uses an entry in the .data section
   Let esi=Mirror$("Masm32 is great")
   PrintLine esi         ; shows    taerg si 23msaM
   Let esi=Mirror$(esi)
   PrintLine esi         ; back to   Masm32 is great

Enjoy :bg

jj2007

#278
Incremental update - the Unicode version of ExpandEnv$:

include \masm32\MasmBasic\MasmBasic.inc
   Init
   wPrint wChr$("This is the full path in Unicode: ["), wExpandEnv$("%ProgramFiles%\Microsoft Office\OFFICE11\WINWORD.EXE", 1), wChr$("]"), wCrLf$

   wMsgBox 0, wCat$("This is the full path: ["+wExpandEnv$("%ALLUSERSPROFILE%", 1)+"]"), "Unicode:", MB_OK
   wMsgBox 0, wCat$("This is the full path: ["+wExpandEnv$("%ProgramFiles%\Microsoft Office\OFFICE11\WINWORD.EXE", 1)+"]"), "Unicode:", MB_OK
   Exit
end start

An optional "...xxx", 1 forces ExpandEnv$ to deliver the long name of a path. This will not work for constructs like ExpandEnv$("%PROCESSOR_ARCHITECTURE%, the OS is %OS%, and the CPU is %PROCESSOR_IDENTIFIER%"), of course.

Only two files, MasmBasic.inc and MbGuide.rtf, changed, therefore the "incremental" update attached.
EDIT: See below, full update of 24 Oct on top of thread.

There is a good overview of available preset environment variables here. Note the dynamic variables mentioned there (e.g. %TIME%) will not expand.

Here is a mini-app showing your current environment variables:

include \masm32\MasmBasic\MasmBasic.inc   ; download
   Init
   push esi
   call GetEnvironmentStrings
   xchg eax, esi
   push esi
@@:   lodsb
   dec al
   jns @B
   mov [esi-1], 10
   lodsb
   dec al
   jns @B
   pop eax
   pop esi
   Inkey eax
   Exit
end start


P.S.: I wonder what %ProgramFiles% shows on a Chinese Windows version... ::)

jj2007

Str$() returned wrong values for structure members. Fixed in version 24 Oct 2011:

include \masm32\MasmBasic\MasmBasic.inc   ; download
.data
pxy   POINT <12, 34>
   Init
   Inkey Str$("The point coordinates are %i and ", pxy.x), Str$(pxy.y)
   Exit
end start

Apologies to those 20 who downloaded the buggy version.

Minor improvements:

a) wChr$ accepts now a register or variable - handy if you got an ANSI string but need a Unicode string e.g. for GdiPlus:

   mov eax, Chr$("This is an ANSI string")
   wPrint wChr$("Not true: "), wChr$(eax)

b) Unicode to console printing is now more likely to succeed, but you may need to set console properties by hand to Lucida console:

include \masm32\MasmBasic\MasmBasic.inc
   Init
   wInkey wRes$(801)   ; needs a string in the resource file as shown below
   Exit
end start

STRINGTABLE
BEGIN
   801,   "أدخل النص هنا"               ; "Enter text here" in Arabic
END

Unicode and console is a tricky story, but so far the snippet above works for me. Internally, it uses
invoke SetConsoleOutputCP, CP_UTF8
and it sets the console color (see ConsoleColor in MbGuide.rtf). Note that if one of these two actions is not performed, the console will not display Arabic or Chinese characters properly, even if you have set Lucida Console. Note also that some colors work, others don't, and it depends basically on whether the puter booted with the left or the right foot. It is messy - Windows developers apparently have lost interest in console apps.

jj2007

Update 28 October:
- minor bugfix in RichMasm (the tooltips over URLs did not disappear)
- a wrapper for GdipDrawImageRectI (sample app attached - a simple image viewer for jpg, bmp, gif and png files)

ImgPaint
   GetFiles *.jpg         ; load all jpg files in this folder and below
   
AddFiles *.png         ; add all png files
   
[/b]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   - [/b]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 ;-)



The image viewer:

Quoteinclude \masm32\MasmBasic\MasmBasic.inc   ; download

.data?
hStatic   dd ?   ; static control serves as canvas
ImgCounter   dd ?   ; Files$(0...MbGetFileCount)

   
Init         [/color]; initialise MasmBasic
   mov esi, CL$()   ; get first arg of commandline
   GfNoRecurse=1   ; search only in the current folder
   .if Exist(esi)
      Let esi=Left$(esi, Rinstr(esi, "\"))+"*"+Right$(esi, 4)
      GetFiles esi   ; show files with the same extension
   .else
      GetFiles *.jpg   ; load Files$() array with
      AddFiles *.png   ; typical image files
      AddFiles *.gif
   .endif
   call WinMain
   
Exit
[/color]
WinMain proc
LOCAL msg:MSG, wcex:WNDCLASSEX
   call ClearLocVars   ; zero WNDCLASSEX
   ebxNull equ <ebx>
   xor ebx, ebx   ; ebxNull
   lea esi, wcex
   wc equ <[esi.WNDCLASSEX]>
   m2m wc.cbSize, WNDCLASSEX
   m2m wc.style, CS_HREDRAW or CS_VREDRAW
   mov wc.lpfnWndProc, offset WndProc
   m2m wc.hbrBackground, COLOR_BTNFACE+1
   mov wc.lpszClassName, Chr$("MB GUI")
   mov wc.hInstance, rv(GetModuleHandle, ebxNull)
   mov wc.hIcon, rv(LoadIcon, eax, IDI_APPLICATION)   ; eax=hInstance
   mov wc.hIconSm, eax
   mov wc.hCursor, rv(LoadCursor, ebxNull, IDC_ARROW)
   invoke RegisterClassEx, esi
   invoke CreateWindowEx, WS_EX_CLIENTEDGE,
      wc.lpszClassName, ebxNull,
      WS_CAPTION or WS_THICKFRAME or WS_CLIPCHILDREN or WS_VISIBLE,
      -127, -127, ebxNull, ebxNull,\   ; for now, outside of visible screen
      ebxNull, ebxNull, wc.hInstance, ebxNull
   invoke CreateWindowEx, ebxNull, Chr$("static"), ebxNull,
      WS_CHILD or WS_VISIBLE, ebxNull, ebxNull, ebxNull, ebxNull,
      eax, 123, wc.hInstance, ebxNull   ; eax=retval cwex main, 123=IdStatic
   mov hStatic, eax
   add esi, wc.cbSize   ; lea esi, msg but one byte shorter ;-)
   .Repeat
      invoke GetMessage, esi, ebxNull, ebxNull, ebxNull
      .Break .if !eax
         ; invoke TranslateMessage, esi
         invoke DispatchMessage, esi
   .Until 0
   ret
WinMain endp

WndProc proc uses edi esi ebx hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL rc:RECT, pt:POINT

  SWITCH uMsg
  CASE WM_DESTROY
   invoke PostQuitMessage, NULL

  CASE WM_KEYUP
     call ArrowHandler   ; left/up=previous, right down=next image, [Ctrl] PageDown/Up, Home, End

  CASE WM_SIZE
   call CenterImage   ; resize window to fit image #1

  CASE WM_GETMINMAXINFO
     mov eax, lParam
     and [eax.MINMAXINFO.ptMinTrackSize.x], 0   ; for very small fotos, e.g. icons
     and [eax.MINMAXINFO.ptMinTrackSize.y], 0

  CASE WM_PAINT
   invoke GetClientRect, hWnd, addr rc
   invoke MoveWindow, hStatic, 0, 0, rc.right, rc.bottom, 0
   invoke InvalidateRect, hStatic, 0, 0
   mov esi, Files$(ImgCounter)
   ImgPaint hStatic, 0, esi      ; <<<< GdiPlus
GdipDrawImageRectI wrapper[/color]
      SetWin$(hWnd)="Viewing "+esi

  ENDSW
  invoke DefWindowProc, hWnd, uMsg, wParam, lParam
 
ret

CenterImage:
...
ArrowHandler:
...
   retn 0
WndProc endp

end start

juozas

ok. downloaded latest version, updated files, opened masmbasic, got build errors on SkelMasmBasic...
Quote*** user-defined OPT_xx variables: ***

*** SkelMasmBasic.rc not found, will try rsrc.rc ***
OPT_Arg1: "This is a command line with spaces"
OPT_Arg2: "The second command line argument"
OPT_Out:  "SkelMasmBasic.exe"
OPT_Res:  rsrc


*** Assemble, link and run SkelMasmBasic ***

Microsoft (R) Windows (R) Resource Compiler Version 6.1.7600.16385

Copyright (C) Microsoft Corporation.  All rights reserved.


*** Assemble using \Masm32\bin\ml /nologo /c /coff  /Fl /Sn /Fo "SkelMasmBasic" ***
Assembling: tmp_file.asm
tmp_file.asm(23) : error A2008: syntax error : ,
wChr$(2): Macro Called From
  wPrint(1): Macro Called From
   tmp_file.asm(23): Main Line Code
*** Assembly Error ***

Screenshot below:



Jwasm gives me error too:
*** user-defined OPT_xx variables: ***

*** SkelMasmBasic.rc not found, will try rsrc.rc ***
OPT_Arg1: "This is a command line with spaces"
OPT_Arg2: "The second command line argument"
OPT_Out:  "SkelMasmBasic.exe"
OPT_Res:  rsrc


*** Assemble, link and run SkelMasmBasic ***

Microsoft (R) Windows (R) Resource Compiler Version 6.1.7600.16385

Copyright (C) Microsoft Corporation.  All rights reserved.


*** Assemble using \Masm32\bin\JWasm /nologo /c /coff  /Fl /Sn /Fo "SkelMasmBasic" ***
Tmp_File.asm(23) : Error A2227: Missing right parenthesis in expression
wChr$(1)[MasmBasic.inc]: Macro called from
  Tmp_File.asm(23): Main line code
Tmp_File.asm: 34 lines, 1 passes, 78 ms, 0 warnings, 1 errors
*** Assembly Error ***
Сделано в СССР

jj2007

      Open "O", #1, "Wide.txt"
      wPrint #1, wChr$("This is Unicode", 13, 10)   ; just for fun, we write a wide string to file
      Close


Thanks for the bug report, Jouzas.

Old & buggy:
wChr$ MACRO args:VARARG
LOCAL argByte, argWide, oo
  oa = (opattr args) AND 127
  if (oa eq atImmediate) or (oa eq 0)   ; ml needs the zero


New & perfect:
wChr$ MACRO args:VARARG
LOCAL argByte, argWide, oo, arg1
  FOR arg1, <args>
   oa = opattr arg1
   EXITM
  ENDM
  if (oa eq atImmediate) or (oa eq 0)   ; ml needs the zero


The FOR arg1, <args> is a dirty hack but it works. New version 29 Oct attached on top of thread.
The reason for this bug is actually that wChr$() learnt to deal with non-immediates, e.g. wChr$(esi), so I had to check the opattr now - and it fails for more than one arg.

jj2007

This is a special update dedicated to DednDave :bg

Quote from: dedndave on October 28, 2011, 02:54:07 AM
the simpler method...
        mov     ebx,offset MyStruct
        mov     al,[ebx]._TEST.Member


Let's go a step further, and create a global structure with 256 bytes of Frequently Used Variables (FUV):

include \masm32\MasmBasic\MasmBasic.inc   ; download

GlobalVars STRUCT   ; 64+1 members
gsA   dd ?   ; DWORD #1 of 65
gsB   dd ?
...
...
gspZ   dd ?
gspRC   RECT <>   ; any name and type allowed here
gsp4   dd ?
gsp5   dd ?   ; DWORD #64
gsp6   dd ?   ; just for fun: #65, outside -128...+127 range
GlobalVars ENDS
.data
MyRc   RECT <12, 34, 56, 78>
Before   dd 123
ggv   GlobalVars <>   ; would normally be in the .data? section
After   dd 456


The point here is that you can get a "privileged access" to 64 dwords (or a lower number of other types, e.g. RECT) by using e.g. ebx as a permanent pointer:

   Init
   gv equ <[ebx.GlobalVars-128]>
   mov ebx, offset ggv+128   ; place ebx in the middle of the structure
   mov eax, 111
   int 3   ; Olly will stop here
   mov ggv.gsA, eax   ; the "normal" addressing mode, 5 bytes
   mov gv.gsA, eax   ; relative to ebx
   mov
gv.gsB, eax
   mov
gv.gsp4, eax
   mov gv.gsp5, eax   ; relative to ebx
   mov gv.gsp6, eax   ; relative to ebx but gsp6 is outside of  -128...+127 range, 6 bytes
   mov ggv.gsp5, eax   ; the "normal" addressing mode, 5 bytes


Now the first good news here is that Dave's syntax allows a very convenient text equate: gv equ <[ebx.GlobalVars-128]>
The second good news is that both the Str$() and the deb macro now understand the equate, as shown below:

   debExpand=0
   deb 4, "Rect short", gv.gspRC.left, gv.gspRC.top, gv.gspRC.right, gv.gspRC.bottom
   debExpand=1
   deb 4, "Rect expanded", gv.gspRC.left, gv.gspRC.top, gv.gspRC.right, gv.gspRC.bottom
   Print Str$("\nRect=%i", gv.gspRC.left), "/", Str$(gv.gspRC.top), "/", Str$(gv.gspRC.right), "/", Str$(gv.gspRC.bottom), CrLf$, CrLf$

Output:
Rect short
gv.gspRC.left   12
gv.gspRC.top    34
gv.gspRC.right  56
gv.gspRC.bottom 78

Rect expanded
[ebx.GlobalVars-128].gspRC.left 12
[ebx.GlobalVars-128].gspRC.top  34
[ebx.GlobalVars-128].gspRC.right        56
[ebx.GlobalVars-128].gspRC.bottom       78

Rect=12/34/56/78

dedndave

thanks, Jochen   :bg

i had not thought of using up the neg offsets   :U