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
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
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