coercing/casting

Discussion of issues related to writing ZBasic applications for targets other than ZX devices, i.e. generic targets.
Post Reply
stevech
Posts: 715
Joined: 22 February 2006, 20:56 PM

coercing/casting

Post by stevech »

In ZBasic, is there a way to derive a byte array reference variable (pointer?) given a string's data address (mystring.dataAddress)? Then use that reference variable as a parameter passed to a function?

Kind of like a (cast *) in C.
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Re: coercing/casting

Post by dkinzer »

stevech wrote:In ZBasic, is there a way to derive a byte array reference variable (pointer?) given a string's data address (mystring.dataAddress)?
For the specific case of a reference to a Byte array, you can use CByteArray() which essentially "casts" an integral value to be a reference to a Byte array. For the general case, you can use an intermediate ByRef variable thus:

Code: Select all

Sub Main()
    ' using a ByRef variable
    Dim s as String
    Dim a() as Byte ByRef
    a.DataAddress = s.DataAddress
    Call foo(a)

    ' using CByteArray()
    Call foo(CByteArray(s.DataAddress))
End Sub

Sub foo(ByRef myArray() as Byte)
End Sub
Examining the code produced (below) you can see that the two methods are equivalent with regard to the value that gets passed to foo().

Code: Select all

void
zf_Main(void)
{
    zbString_t zv_s;
    uint8_t *zv_a;

    zbStrInit(&zv_s, ZB_AN_STR);
    zv_a = (uint8_t *)(uint16_t)&zv_s;
    zf_foo&#40;zv_a&#41;;                         <-- using the ByRef variable
    zf_foo&#40;&#40;uint8_t *&#41;&#40;uint16_t&#41;&zv_s&#41;;   <-- using CByteArray
    zbStrFree&#40;&zv_s&#41;;
&#125;
It is important to note that s.DataAddress does not yield the address of the first character of a string. Rather, it yields the address of the internal representation of a ZBasic string. Included in that representation (either implicitly or explicitly, depending on the String type) is a pointer to the first character of the string which may be in RAM, in Persistent Memory or in Program Memory. The StrAddress() and StrType() functions can be useful when working with String types.

If you're wanting to pass a string to a C function, you'll need to convert the ZBasic string to null-terminated C string and pass the address of the latter.
- Don Kinzer
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Re: coercing/casting

Post by dkinzer »

dkinzer wrote:If you're wanting to pass a string to a C function, you'll need to convert the ZBasic string to null-terminated C string and pass the address of the latter.
The function NullTermString() below may be useful for this purpose. It guarantees that the returned string is null terminated and that it resides in RAM. This allows the return value of StrAddress() to be passed to a C function that is expecting a RAM-based, null terminated string.

The Main() and dspStrData() subroutines are useful for demonstrating that the function works correctly.

Code: Select all

Dim ps as Persistent BoundedString&#40;10&#41;

Sub Main&#40;&#41;
    Dim s as String
    s = NullTermString&#40;"abc"&#41;
    Call dspStrData&#40;s&#41;
    s = "def" & s
    s = NullTermString&#40;s&#41;
    Call dspStrData&#40;s&#41;
    s = NullTermString&#40;"a"&#41;
    Call dspStrData&#40;s&#41;
    s = NullTermString&#40;"able" & Chr&#40;0&#41;&#41;
    Call dspStrData&#40;s&#41;
    ps = "baker" & Chr&#40;0&#41;
    s = NullTermString&#40;ps&#41;
    Call dspStrData&#40;s&#41;
    s = NullTermString&#40;""&#41;
    Call dspStrData&#40;s&#41;
End Sub

'
'' dspStrData
'
' Display information about a string including its type, storage address,
' length and content.
'
Sub dspStrData&#40;ByVal s as String&#41;
    Dim slen as Integer
    slen = Len&#40;s&#41;
    Debug.Print "type=&H"; CStrHex&#40;StrType&#40;s&#41;&#41;; ", addr=&H"; CStrHex&#40;StrAddress&#40;s&#41;&#41;; ", length="; slen; " &#91;";
    Dim i as Integer
    Dim addr as UnsignedInteger
    addr = StrAddress&#40;s&#41;
    Dim c as Byte Based addr
    For i = 1 to slen
        If &#40;i > 1&#41; Then
            Debug.Print ", ";
        End If
        Debug.Print "&H"; CStrHex&#40;c&#41;;
        addr = addr + 1
    Next i
    Debug.Print "&#93;"
End Sub

'
'' NullTermString
'
' Given a string &#40;of any length other than the maximum string length&#41;
' and located in RAM, Program Memory or Persistent Memory, return a
' new string that is guaranteed to reside in RAM and is null terminated.
'
Function NullTermString&#40;ByVal s as String&#41; as String
    Const MaxStrLen as Integer = 255
    Dim slen as Integer
    NullTermString = ""
    slen = Len&#40;s&#41;
    If &#40;&#40;slen = 0&#41; Or &#40;Asc&#40;s, slen&#41; <> 0&#41;&#41; Then
        ' zero-length string or string not already null terminated
        If &#40;slen < 2&#41; Then
            ' resulting string will be 1 or 2 characters, construct in-situ
            Dim strData&#40;&#41; as Byte Based NullTermString.DataAddress
            strData&#40;1&#41; = CByte&#40;slen + 1&#41;
            strData&#40;2&#41; = &He5
            strData&#40;3&#41; = IIF&#40;slen = 0, 0, Asc&#40;s, 1&#41;&#41;
            strData&#40;4&#41; = 0
        ElseIf &#40;slen < MaxStrLen&#41; Then
            ' resulting string will be at least 3 characters in allocated RAM
            NullTermString = s & Chr&#40;0&#41;
        End If
    Else
        ' string is already null-terminated, ensure it is in RAM
        Select Case StrType&#40;s&#41;
        Case &He2, &He3
            ' source string is in ProgMem or Persistent Memory
            NullTermString = Left&#40;s, slen&#41;
        Case Else
            ' source string is already in RAM
            NullTermString = s
        End Select
    End If
End Function
- Don Kinzer
stevech
Posts: 715
Joined: 22 February 2006, 20:56 PM

Post by stevech »

thanks much.
Not yet trying to pass data to C function.. all ZBasic.




From AN218.pdf at RS485Send(), for bytes, I see this code showing a method to access an array of bytes passed to a function. Isn't there a less obtuse way? More like a simple pointer/address to an array, passed as a param, and used directly as one would in C, like
uint8_t bytes[] or bytes* in C
then in ZB
byref bytes() as byte
with
b = bytes(n)


I'm stumped on how an ISR can use a global byte address pointer to a buffer to store received data at each interrupt, where the pointer is established by the non-ISR code prior to starting up the device that will interrupt with data to read. I can try to adapt that code in RS485Send() but the relationship, in that code, of buffer() and "data" and "address" is, esp. in an ISR, is befuddling to me being so C-entrenched.
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Post by dkinzer »

stevech wrote:From AN218.pdf at RS485Send(), for bytes, I see this code showing a method to access an array of bytes passed to a function. Isn't there a less obtuse way?
Yes. The code in RS485Send() could have simply indexed the array directly. I've rewritten the code that way (untested).

Code: Select all

Public Sub RS485Send&#40;ByRef buffer&#40;&#41; as Byte, ByVal length as UnsignedInteger&#41;
    ' make sure channel exists
    If channel = 0 Then
        Exit Sub
    End If

    ' calculate and add CRC as last two bytes of buffer &#40;a 1-based array&#41;
    Dim CRC as UnsignedInteger
#if Option.CPUType = "mega32"    
    CRC = calcCRC16&#40;buffer, length&#41;
#else    
    CRC = CRC16&#40;buffer, length, &H8005, &Hffff, &H03&#41;
#endif
    buffer&#40;length + 1&#41; = LoByte&#40;CRC&#41;
    buffer&#40;length + 2&#41; = HiByte&#40;CRC&#41;
    length = length + 2
    
    ' start sending of message by enabling RS485 driver
    Call PutPin&#40;sendReceivePin, zxOutputHigh&#41;

    ' send half a transmit buffer's worth of data at a time until everything is sent
    Dim idx as UnsignedInteger
    idx = 1
    Do While length <> 0 
        ' calculate how much more data to send
        Dim chunkSize as UnsignedInteger
        If length > OUTPUT_BUFSIZE \ 2 Then
            chunkSize = OUTPUT_BUFSIZE \ 2
        Else    
            chunkSize = length
        End If    
        
        ' queue up next chunk of data
        Call PutQueue&#40;outQueue, buffer&#40;idx&#41;, chunkSize&#41;

        ' prepare for the next pass
        length = length - chunkSize
        idx = idx + chunkSize
        
        ' wait until the transmit buffer is less than half full
        Call waitForSend&#40;CINT&#40;OUTPUT_BUFSIZE \ 2&#41;&#41;
    Loop

    ' wait for remaining bytes to finish sending i.e. transmit buffer is empty
    Call waitForSend&#40;0&#41;    
    Call Sleep&#40;messageFrameInterval&#41;
    
    ' disable the RS485 driver and re-enable the receiver
    Call PutPin&#40;sendReceivePin, zxOutputLow&#41;
End Sub
stevech wrote:More like a simple pointer/address to an array, passed as a param, and used directly as one would in C ...
In ZBasic, an array is always passed by reference so the receiving procedure gets a pointer to the data, exactly as you would in C. However, ZBasic doesn't have a pointer to type type; the closest you can get is to define an integral variable to which you assign the address of interest and then define a variable of the desired type that is Based at that address. Referring to the based variable yields automatic dereferencing (much like a reference in C++) whereas in C you have to explicity dereference.

Code: Select all

C&#58;
uint8_t buf&#91;50&#93;;
uint8_t *ptr = &buf&#91;10&#93;;
*ptr++ = val;

ZBasic&#58;
Dim buf&#40;1 to 50&#41; as Byte
Dim addr as UnsignedInteger
Dim data as Byte Based addr
addr = buf.DataAddress + 10
data = val
addr = addr + 1
In the ZBasic code, data is a byte whose address is the value of the addr variable just as in the C code where *p is a byte whose address is p.
stevech wrote:I'm stumped on how an ISR can use a global byte address pointer to a buffer to store received data at each interrupt, where the pointer is established by the non-ISR code prior to starting up the device that will interrupt with data to read.
Depending on the requirements, you could just use a global Byte array for the buffer and and index value for where to write next. The same functionality can be achieved by having a global UnsignedInteger address of where to write next and have the ISR define a Byte variable Based at that address. Which you elect to use is more a matter of style than substance.
- Don Kinzer
stevech
Posts: 715
Joined: 22 February 2006, 20:56 PM

Post by stevech »

thanks... I'll try the based data dereference method - because the byte array's base address varies. This is because the application passes the buffer to use in the ISR and that may vary.
Post Reply