' Demonstrate one way of creating dynamic strings in TYPE structures.
#Compile Exe
#Include "win32api.inc"
Type CUSTOMER_TYPE
nCustID As Long
zCustName As Asciiz Ptr ' dynamic string
zAddress As Asciiz Ptr ' dynamic string
nUnpaid As Single
nActive As Byte
End Type
'//
'// Manually allocate a block of memory at the specified location
'//
Function MemAlloc(ByVal nSize As Long) As Dword
If nSize > 0 Then
Function = HeapAlloc( GetProcessHeap(), %HEAP_ZERO_MEMORY, nSize)
End If
End Function
'//
'// Free memory located at a specified location
'//
Function MemFree(ByRef pMem As Dword) As Long
If pMem Then
Function = HeapFree( GetProcessHeap(), 0, ByVal pMem)
pMem = 0
End If
End Function
'//
'// Sub that will assign a dynamic string to a memory location
'// and free any memory that may have already been allocated.
'// pMem must be ByRef.
'//
Sub MemString( ByRef pMem As Dword, _
ByVal sData As String )
If pMem Then HeapFree( GetProcessHeap(), 0, ByVal pMem) ' free any existing memory
sData = sData & $Nul
pMem = HeapAlloc( GetProcessHeap(), %HEAP_ZERO_MEMORY, Len( sData ) + 1)
If pMem Then Poke$ pMem, sData
End Sub
Function PBMain() As Long
'-------------------------------------------------------------------------
' (1) Using manual memory allocation to manage dynamic strings in TYPE's.
'-------------------------------------------------------------------------
' Assign some default values to a TYPE structure
Local cust As CUSTOMER_TYPE
cust.nCustID = 1000
cust.nUnpaid = 2300.00
cust.nActive = %TRUE
MemString cust.zCustName, "Paul Squires"
MemString cust.zAddress, "SomeTown, Anywhere, 12345"
' Display the record
MsgBox "nCustID = " & Format$(cust.nCustID) & $CrLf & _
"nUnpaid = " & Format$(cust.nUnpaid, "######.00") & $CrLf & _
"nActive = " & IIf$(cust.nActive, "TRUE", "FALSE" ) & $CrLf & _
"zCustName = " & cust.@zCustName & $CrLf & _
"zAddress = " & cust.@zAddress
' Modify the name and address.
' The functions take care of freeing the original string memory
' and then creating a new string.
MemString cust.zCustName, "Paul Squires (PlanetSquires)"
MemString cust.zAddress, "MyTown, CloseBy, 55555"
' Display the modified record
MsgBox "nCustID = " & Format$(cust.nCustID) & $CrLf & _
"nUnpaid = " & Format$(cust.nUnpaid, "######.00") & $CrLf & _
"nActive = " & IIf$(cust.nActive, "TRUE", "FALSE" ) & $CrLf & _
"zCustName = " & cust.@zCustName & $CrLf & _
"zAddress = " & cust.@zAddress
' Free the allocated memory for the dynamic strings.
MemFree cust.zCustName
MemFree cust.zAddress
'-------------------------------------------------------------------------
' (2) Let's get fancy... Create the TYPE structure itself dynamically
' and add some dynamic strings to it.
'-------------------------------------------------------------------------
Local pCust As CUSTOMER_TYPE Ptr
pCust = MemAlloc( SizeOf(CUSTOMER_TYPE) )
@pCust.nCustID = 5000
@pCust.nUnpaid = 9000.00
@pCust.nActive = %TRUE
MemString @pCust.zCustName, "Jose Roca"
MemString @pCust.zAddress, "Across the Pond, OverThere, 919191"
' Display the record
MsgBox "nCustID = " & Format$(@pCust.nCustID) & $CrLf & _
"nUnpaid = " & Format$(@pCust.nUnpaid, "######.00") & $CrLf & _
"nActive = " & IIf$(@pCust.nActive, "TRUE", "FALSE" ) & $CrLf & _
"zCustName = " & @pCust.@zCustName & $CrLf & _
"zAddress = " & @pCust.@zAddress
' Free the allocated memory for the dynamic strings. Not really
' necessary at this point because the application is about to
' terminate and Windows will reclaim the memory automatically, but
' it is good programming practice nonetheless.
MemFree @pCust.zCustName
MemFree @pCust.zAddress
' Also free the dynamically created TYPE (pCust) itself
MemFree pCust
End Function
I think we should clarify here that any PTR reference does not define string or any
other type of space in memory. It can only be made to point to existing structures,. Strings are only allocated when actually defined somewhere, at which
point you can establish a pointer reference to them. So a TYPE structure can
either contain strings which are allocated when you DIM a variable of that type,
or if they have string pointers as you advise, those strings must be allocated elsewhere, since only the 32-bit pointer reference will be allocated in the new
variable of that type. And the pointer references will be set to NUL until such
time as you create a reference to the specific string in question. Firther, you
specified here that these are pointers to ASCIIZ strings, which have a maximum
length specified, but you specifically mentioned dynamic strings, which one would think of as the dynamic, variable length variety. That's a different anamal. If you set the PTR references to the string locations for this type of string, you are at risk of the STRPTR() value becoming invalid, because the Heap
where these strings are stored is constantly being released and reallocated as
other strings have their contents changed or discarded. For these strings, you
might better use VARPTR(), which will remain constant, and which will give you
access to both the location of the string contents and the length (number of
characters currently allocated to the string).
PTR to ASCIIZ are bad beats. They can produce unwanted behave if user is not conscious of what he/she is doing. There are also some limitations if that pointer is passed to something expecting big dynamic string because ASCIIZ strings are limited to about 16Mb. Also user must be aware he/she cannot embed NULL chars as real chars. If user is all aware, than no problem.
A different approach can be the one I suggested in another post that is to use just a LONG and using PB power to do the hard work:
'---Define some LONGs in place of strings
type MyUDT
aString1 as long
aString2 as long
end type
'---Define a UDT variable
dim MyVar as MyUDT
'---Define a dummy string that in reality is a place holder pointer pointing to the LONG inside the UDT
dim DummyString as string at varptr(MyVar.aString1)
'---Do what you need with DummyString like any other standard dynamic string
DummyString = "abcdef"
DummyString = repeat$(10, DummyString)
...
'---The bad part, free allocated string data manually
remove MyString
In this case DIM ... AT will make the hard work. Programmer have to just deal with deallocation.
Also
REDIM DummyString(1& to 1&) AS STRING AT VARPTR(MyVar.aString1)
can be used when accessing different string dynamically because you can use REDIM ... AT many times in code always with the same DummyString name but at different mem locations.
ADDED: I've not made any speed test of using DIM or REDIM ... AT. Maybe worth to check how much CPU is taken when making a lot of REDIM ... AT
Ciao
Eros
Hi Eros,
this looks like a good approach !
DIM .. AT is very powerful.
Thanks,
Petr
The most interesting part of this approach is that after DIM or REDIM ... AT, the string is a 100% dynamic string
It can be used in any place a dynamic string is used in exact the same way as a standard dynamic string, passed BYREF or BYVAL.
Working with pointers on such created strings will follow the same rules as a standard dynamic string.
In any case, every approach is interesting. It depends on what you need to do. Than keep the best fits your needs.
Personally I do not like to deal with ASCIIZ strings because of the limitations they have especially on NULL chars. But this is just my personal approach.
Eros
I guess my use of "dynamic string" is a little misleading. I am not referring to an OLE string but rather the fact that you can create strings in a TYPE that need not be of one pre-defined length. I guess that embedded Nulls would be an issue but for my uses I have never had to deal with them so I have always stuck with my code above.
I could have used the OLE engine directly:
#Compile Exe
#Include "win32api.inc"
Function PBMain() As Long
Local st As String
Local pStr As Asciiz Ptr
st = "Paul Squires"
' Example of using the OLE dynamic string functions. Be aware
' of the ByVal's... they are needed.
pStr = SysAllocStringByteLen( ByCopy st, ByVal Len(st) )
MsgBox "pStr=" & Format$(pStr) & $CrLf & _
"len=" & Format$(SysStringByteLen( ByVal pStr )) & $CrLf & _
"String=" & @pStr & $CrLf & _
"len=" & Format$(Len(@pStr))
' Free the string
SysFreeString ByVal pStr
' Error <> 0 indicates that string was not freed.
MsgBox "String freed. GetLastError=" & Format$(GetLastError)
End Function
No problem here. For me it was clear what "dynamic" stand for in this post.
Every technique is really interesting because it shows the many different ways things can be done.
Maybe a little confusing for those start programming of for those gui that have never deal with pointers.
Thanks a lot
Eros