This is a simple utility that demonstrates one approach to the issue of looking at members of a UDT when debugging a program.

It simply consists of a DLL you load into your program ... you simply call the UDT_Init function, giving it the name of the UDT you want to look at as well as the filename it resides in.

Then whenever you want to have a look at the UDT simply call UDT_Update from your code.

When calling UDT_Update you provide the address of the UDT, so you can give it the address of any member of a UDT array, such as VARPTR(MyUDT(3)).

It doesn't support viewing nested UDTs although it will show the address of those UDTs, but it DOES support arrays inside the UDT. It simply expands them. For example, "bBytes(2) AS BYTE" will become:
bBytes(0) AS BYTE
bBytes(1) AS BYTE
bBytes(2) AS BYTE

Consider for example the following simple UDT ...
TYPE MyUDT
xString AS STRING * 15
xAsciiz AS ASCIIZ * 20
xByte AS BYTE
xDword AS DWORD
END TYPE


And then initialised with some dummy data ...
UDT.xString = "Im a string"
UDT.xAsciiz = "Im an asciiz"
UDT.xByte = &h69
UDT.xDword = &hA0A1A2A3


UDTView shows it like this:



udtview.bas
Code:
#PBFORMS CREATED V1.51
#COMPILE DLL "udtview.dll"

#INCLUDE "WIN32API.INC"
#INCLUDE "COMMCTRL.INC"
#INCLUDE "PBForms.INC"

#PBFORMS BEGIN CONSTANTS
%DIALOG1         =  101
%LISTVIEW        = 1002
#PBFORMS END CONSTANTS

DECLARE CALLBACK FUNCTION ShowDIALOG1Proc()
DECLARE FUNCTION ShowDIALOG1(BYVAL hParent AS DWORD) AS LONG
#PBFORMS DECLARATIONS

GLOBAL hDlg1 AS DWORD, hList AS DWORD, ListItems AS DWORD
GLOBAL gBASFile AS STRING, gUDTName AS STRING, gUDTAddr AS DWORD, gUDTSize AS DWORD

TYPE Var
 szName AS ASCIIZ * 128
 szType AS ASCIIZ * 20
 wLen AS WORD
END TYPE
GLOBAL Vars() AS Var

SUB AdjustListviewHeaders
 LOCAL i AS DWORD
 ListView_SetColumnWidth(hList, 0, %LVSCW_AUTOSIZE)
 FOR i = 1 TO 4: ListView_SetColumnWidth(hList, i, %LVSCW_AUTOSIZE_USEHEADER): NEXT
END SUB

SUB AddToList(sName AS STRING, sType AS STRING, BYVAL dwLen AS DWORD, BYVAL dwAddr AS DWORD, sData AS STRING)
  LOCAL tLVI   AS LV_ITEM, szBuf AS ASCIIZ * 2048
  REDIM PRESERVE Vars(ListItems+1) AS Var
  Vars(ListItems).szName = sName
  Vars(ListItems).szType = sType
  Vars(ListItems).wLen = dwLen
  tLVI.stateMask = %LVIS_FOCUSED
  tLVI.pszText   = VARPTR(szBuf)
  tLVI.iItem     = ListItems
  szBuf         = sName
    tLVI.iSubItem = 0:  tLVI.lParam   = ListItems
    tLVI.mask = %LVIF_TEXT OR %LVIF_PARAM OR %LVIF_STATE
    ListView_InsertItem(hList, tLVI)
  szBuf         = sType
    tLVI.iSubItem = 1:  tLVI.lParam   = ListItems
    tLVI.mask = %LVIF_TEXT
    ListView_SetItem(hList, tLVI)
  szBuf         = TRIM$(STR$(dwLen))
    tLVI.iSubItem = 2:  tLVI.lParam   = ListItems
    tLVI.mask = %LVIF_TEXT
    ListView_SetItem(hList, tLVI)
  szBuf         = HEX$(dwAddr,8)
    tLVI.iSubItem = 3:  tLVI.lParam   = ListItems
    tLVI.mask = %LVIF_TEXT
    ListView_SetItem(hList, tLVI)
  szBuf         = sData
    tLVI.iSubItem = 3:  tLVI.lParam   = ListItems
    tLVI.mask = %LVIF_TEXT
    ListView_SetItem(hList, tLVI)
  INCR ListItems
END SUB


FUNCTION VarOffset(BYVAL dwVar AS DWORD) AS DWORD
LOCAL I AS DWORD, dwOffset AS DWORD
IF dwVar = 0 THEN EXIT FUNCTION
FOR i = 0 TO dwVar - 1
    dwOffset = dwOffset + Vars(i).wLen
NEXT i
FUNCTION = dwOffset
END FUNCTION


SUB UDT_Update ALIAS "UDT_Update" (BYVAL dwUDTAddr AS DWORD, BYVAL idx AS DWORD) EXPORT
  LOCAL tLVI   AS LV_ITEM, szBuf AS ASCIIZ * 2048, i AS DWORD
  LOCAL bPtr AS BYTE PTR, dwPtr AS DWORD PTR, szPtr AS ASCIIZ PTR, wPtr AS WORD PTR, dwOffset AS DWORD, qPtr AS DOUBLE PTR
  gUDTAddr = dwUDTAddr
  DIALOG SET TEXT hDlg1, "UDTView - Viewing " & gUDTName & "(" & TRIM$(STR$(idx)) & ") at 0x" & HEX$(gUDTAddr,8)
  tLVI.stateMask = %LVIS_FOCUSED
  tLVI.pszText   = VARPTR(szBuf)
  FOR i = 0 TO ListItems - 1
    szBuf = ""
    tLVI.iItem    = i
    SELECT CASE Vars(i).szType
        CASE "STRING", "ASCIIZ":
            dwOffset = gUDTAddr + VarOffset(BYVAL i)
            szBuf = PEEK$(dwOffset, Vars(i).wLen)
        CASE "ASCIIZ PTR", "ASCIZ PTR", "STRING PTR":
            dwPtr = gUDTAddr + VarOffset(BYVAL i)
            szBuf = "0x" & HEX$(dwPtr,8) & ": " & PEEK$(@dwPtr,4) & "..."
            dwOffset = dwPtr
        CASE ELSE:
            SELECT CASE Vars(i).wLen
                CASE 1: bPtr = gUDTAddr + VarOffset(BYVAL i): dwOffset = bPtr
                        szBuf = "0x" & HEX$(@bPtr,2) & "  (" & TRIM$(STR$(@bPtr)) & ")"
                CASE 2: wPtr = gUDTAddr + VarOffset(BYVAL i): dwOffset = wPtr
                        szBuf = "0x" & HEX$(@wPtr,4) & "  (" & TRIM$(STR$(@wPtr)) & ")"
                CASE 4: dwPtr = gUDTAddr + VarOffset(BYVAL i): dwOffset = dwPtr
                        szBuf = "0x" & HEX$(@dwPtr,8) & "  (" & TRIM$(STR$(@dwPtr)) & ")"
                CASE ELSE: dwPtr = gUDTAddr + VarOffset(BYVAL i): dwOffset = dwPtr
                        szBuf = "0x" & HEX$(dwPtr,8)
            END SELECT
    END SELECT
    tLVI.iSubItem = 4 'lCol
    tLVI.lParam   = i
    tLVI.mask = %LVIF_TEXT
    ListView_SetItem(hList, tLVI)
    szBuf = HEX$(dwOffset,8)
    tLVI.iSubItem = 3 'lCol
    tLVI.lParam   = i
    tLVI.mask = %LVIF_TEXT
    ListView_SetItem(hList, tLVI)
  NEXT i
END SUB

SUB LoadUDT
 LOCAL hFile AS DWORD, sBuf AS STRING, lUDT AS STRING, sVar AS STRING, sFullType AS STRING, sType AS STRING, dwLen AS DWORD, i AS DWORD, dwCnt AS DWORD
 lUDT = LCASE$(gUDTName)
 hFile = FREEFILE
 OPEN gBASFile FOR INPUT AS #hFile
 DO UNTIL EOF(hFile)
     LINE INPUT #hFile, sBuf
     IF UCASE$(LEFT$(sBuf,5)) = "TYPE " THEN
         sBuf = TRIM$(sBuf)
         REPLACE "  " WITH " " IN sBuf
         IF LCASE$(sBuf) = "type " & lUDT THEN
             DO
                 LINE INPUT #hFile, sBuf
                 sBuf = TRIM$(sBuf)
                 IF sBuf = "" THEN ITERATE
                 IF UCASE$(sBuf) = "END TYPE" THEN GOTO UDTLoaded
                 sVar = LEFT$(sBuf, INSTR(1, sBuf, " ") - 1)
                 IF INSTR(1, sBuf, "'") > 0 THEN
                    sBuf = LEFT$(sBuf, INSTR(1, sBuf, "'") - 1)
                    sBuf = RTRIM$(sBuf)
                 END IF
                 sBuf = UCASE$(sBuf)
                 i = INSTR(1, sBuf, " AS ") + 4
                 sFullType = RIGHT$(sBuf, LEN(sBuf) - i + 1)
                 sType = sFulltype
                 IF INSTR(1, sType, " ") > 0 THEN sType = LEFT$(sType, INSTR(1, sType, " ") - 1)
                 IF RIGHT$(sFulltype,4) = " PTR" OR RIGHT$(sFulltype,8) = " POINTER" THEN
                     sType = sFulltype
                     dwLen = 4
                 ELSE
                     SELECT CASE sType
                         CASE "STRING", "ASCIIZ", "ASCIZ": dwLen = VAL( RIGHT$(sFulltype, LEN(sFulltype) - INSTR(-1, sFulltype, "*") - 1) )
                         CASE "BYTE": dwLen = 1
                         CASE "INTEGER", "WORD": dwLen = 2
                         CASE "DWORD", "LONG", "SINGLE": dwLen = 4
                         CASE "QUAD", "DOUBLE", "CURRENCY", "CURRENCYX", "CUR", "CURX": dwLen = 8
                         CASE "EXT", "EXTENDED": dwLen = 10
                         CASE ELSE: dwLen = 4
                     END SELECT
                 END IF
                 IF LEN(sType) > 19 THEN sType = LEFT$(sType, 19) & ".."
                 IF INSTR(1, sVar, "(") > 0 THEN
                     sVar = LEFT$(sVar, LEN(sVar) - 1)
                     dwCnt = VAL(RIGHT$(sVar, LEN(sVar) - INSTR(1, sVar, "(")))
                     sVar = LEFT$(sVar, INSTR(1, sVar, "("))
                     FOR i = 0 TO dwCnt
                         AddToList sVar & TRIM$(STR$(i)) & ")", sType, BYVAL dwLen, BYVAL 0, ""
                     NEXT i
                 ELSE
                     AddToList sVar, sType, BYVAL dwLen, BYVAL 0, ""
                 END IF
             LOOP
         END IF
     END IF
 LOOP
UDTLoaded:
 CLOSE #hFile
 UDT_Update BYVAL gUDTAddr, BYVAL 0
 AdjustListviewHeaders
END SUB

FUNCTION LIBMAIN(BYVAL hInstance   AS LONG, _
                 BYVAL fwdReason   AS LONG, _
                 BYVAL lpvReserved AS LONG) EXPORT AS LONG
  IF fwdReason = %DLL_PROCESS_ATTACH THEN
       PBFormsInitComCtls (%ICC_WIN95_CLASSES OR %ICC_DATE_CLASSES OR %ICC_INTERNET_CLASSES)
  END IF
  FUNCTION = 1
END FUNCTION

SUB CreateListHeaders
    LOCAL lCol   AS LONG
    LOCAL hCtl   AS DWORD
    LOCAL tLVC   AS LV_COLUMN
    LOCAL tLVI   AS LV_ITEM
    LOCAL szBuf  AS ASCIIZ * 32
    LOCAL lStyle AS LONG
    lStyle = ListView_GetExtendedListViewStyle(hList)
    ListView_SetExtendedListViewStyle(hList, lStyle OR %LVS_EX_FULLROWSELECT _
        OR %LVS_EX_GRIDLINES)
    tLVC.mask    = %LVCF_FMT OR %LVCF_TEXT OR %LVCF_SUBITEM
    tLVC.fmt     = %LVCFMT_LEFT
    tLVC.pszText = VARPTR(szBuf)
    szBuf = "Name":   tLVC.iOrder = 0
    ListView_InsertColumn(hList, 0, tLVC)
    szBuf = "Type":   tLVC.iOrder = 1
    ListView_InsertColumn(hList, 1, tLVC)
    szBuf = "Len":    tLVC.iOrder = 2
    ListView_InsertColumn(hList, 2, tLVC)
    szBuf = "Address":   tLVC.iOrder = 3
    ListView_InsertColumn(hList, 3, tLVC)
    szBuf = "Data":   tLVC.iOrder = 4
    ListView_InsertColumn(hList, 4, tLVC)
    FOR lCol = 0 TO 4: ListView_SetColumnWidth(hList, lCol, %LVSCW_AUTOSIZE_USEHEADER): NEXT
END SUB

CALLBACK FUNCTION ShowDIALOG1Proc()
    SELECT CASE AS LONG CBMSG
        CASE %WM_INITDIALOG
            LoadUDT

        CASE %WM_SIZING            '// To prevent window from getting too small
            LOCAL rc AS RECT, pRc AS RECT PTR
            pRC = CBLPARAM
            GetWindowRect CBHNDL, rc
            IF (@pRc.nRight - @pRc.nLeft) < 200 THEN   '// Prevent change of width
                @pRc.nLeft  = rc.nLeft: @pRc.nRight = rc.nLeft + 200
            END IF
            IF (@pRc.nBottom - @pRc.nTop) < 150 THEN   '// Prevent change of height
                @pRc.nTop  = rc.nTop: @pRc.nBottom = rc.nTop + 150
            END IF
            FUNCTION = 1

        CASE %WM_SIZE      '// Resize controls when window is resized
            LOCAL bx AS LONG, by AS LONG, x AS LONG, y AS LONG
            IF IsIconic(CBHNDL) THEN EXIT SELECT                  '// Ignore if the window is minimized
            CONTROL GET SIZE CBHNDL, %LISTVIEW TO bx, by
            DIALOG GET SIZE CBHNDL TO x, y
            CONTROL SET SIZE CBHNDL, %LISTVIEW, x - 5, y - 15
            DIALOG REDRAW CBHNDL
    END SELECT
END FUNCTION

FUNCTION ShowDIALOG1(BYVAL hParent AS DWORD) AS LONG
    LOCAL lRslt AS LONG
#PBFORMS BEGIN DIALOG %DIALOG1->->
    LOCAL hDlg  AS DWORD
    DIALOG NEW hParent, "UDTView", 63, 60, 230, 142, %WS_POPUP OR %WS_BORDER _
        OR %WS_DLGFRAME OR %WS_CAPTION OR %WS_SYSMENU OR %WS_MINIMIZEBOX OR %WS_THICKFRAME OR _
        %WS_MAXIMIZEBOX OR %WS_CLIPSIBLINGS OR %WS_VISIBLE OR %DS_MODALFRAME _
        OR %DS_CENTER OR %DS_3DLOOK OR %DS_NOFAILCREATE OR %DS_SETFONT, _
        %WS_EX_CONTROLPARENT OR %WS_EX_LEFT OR %WS_EX_LTRREADING OR _
        %WS_EX_RIGHTSCROLLBAR, TO hDlg
    CONTROL ADD "SysListView32", hDlg, %LISTVIEW, "SysListView32_1", _
        0, 0, 230, 140, %WS_CHILD OR %WS_VISIBLE OR %WS_TABSTOP OR _
        %LVS_REPORT OR %LVS_SHOWSELALWAYS, %WS_EX_LEFT OR %WS_EX_CLIENTEDGE _
        OR %WS_EX_RIGHTSCROLLBAR
#PBFORMS END DIALOG
    CONTROL HANDLE hDlg, %LISTVIEW TO hList
    hDlg1 = hDlg
    CreateListHeaders
    DIALOG SHOW MODAL hDlg, CALL ShowDIALOG1Proc TO lRslt
#PBFORMS BEGIN CLEANUP %DIALOG1
#PBFORMS END CLEANUP
    FUNCTION = lRslt
END FUNCTION

FUNCTION DlgThread (BYVAL x AS LONG) AS LONG
ShowDIALOG1 %HWND_DESKTOP
END FUNCTION

SUB UDT_Init ALIAS "UDT_Init" (sBASFile AS STRING, sUDTName AS STRING, BYVAL dwUDTAddr AS DWORD, BYVAL dwUDTSize AS DWORD) EXPORT
    LOCAL hThread AS DWORD
    IF DIR$(sBASFile,39) = "" THEN
        MSGBOX "Source file not found: " & sBASFile
        EXIT SUB
    END IF
    gBASFile = sBASFile: gUDTName = sUDTName: gUDTAddr = dwUDTAddr: gUDTSize = dwUDTSize
    THREAD CREATE DlgThread(BYVAL 0) TO hThread
END SUB

SUB UDT_End ALIAS "UDT_End" () EXPORT
    REDIM Vars(0) AS Var:
    DIALOG END hDlg1
END SUB

Here is a simple example of how to use the DLL. You simply call UDT_Init at the start to tell the DLL which UDT in which source file you want to look at, and then call UDT_Update whenever you want to refresh the view.

calltest.bas
Code:
#COMPILE EXE
#INCLUDE "win32api.inc"

'// UDTView declarations
DECLARE SUB UDT_Init LIB "udtview.dll" ALIAS "UDT_Init" (sBASFile AS STRING, sUDTName AS STRING, BYVAL dwUDTAddr AS DWORD, BYVAL dwUDTSize AS DWORD)
DECLARE SUB UDT_Update LIB "udtview.dll" ALIAS "UDT_Update" (BYVAL dwUDTAddr AS DWORD, BYVAL idx AS DWORD)
DECLARE SUB UDT_End LIB "udtview.dll" ALIAS "UDT_End" ()
'\\ End of UDTView decs

TYPE MyUDT
    xString AS STRING * 15      '15
    xAsciiz AS ASCIIZ * 20      '1
    xByte AS BYTE               '1
    xDword AS DWORD             '4
END TYPE


FUNCTION PBMAIN() AS LONG
 LOCAL sBasfile AS STRING, sUDTName AS STRING, UDT AS MyUDT

 '// Fill with dummy data
 UDT.xAsciiz    = "Im an asciiz"
 UDT.xString    = "Im a string"
 UDT.xByte      = &h69
 UDT.xDword     = &hA0A1A2A3

 '// Initialise the DLL
 sBasfile = "c:\dev\pb\utilities\udtview\calltest.bas"  '// CHANGE BEFORE COMPILING!
 sUDTName = "MyUDT"
 UDT_Init sBASFile, sUDTName, BYVAL VARPTR(UDT), BYVAL SIZEOF(UDT)
 
 STDOUT "Press enter to change the values ...";: WAITKEY$

 UDT.xAsciiz    = "This is a NEW asciiz!"
 UDT.xString    = "And this is a NEW string!"
 UDT.xByte      = &hCC
 UDT.xDword     = &hF1F2F3F4

 '// Update UDTView
 UDT_Update BYVAL VARPTR(UDT), BYVAL 2

 STDOUT "Done": WAITKEY$
 UDT_End
END FUNCTION
Attached Files