PlanetSquires Forums

Support Forums => Other Software and Code => Topic started by: Paul Squires on August 25, 2015, 09:41:16 AM

Title: Strings in FreeBASIC
Post by: Paul Squires on August 25, 2015, 09:41:16 AM
I am gearing up to start changing all my code from ANSI strings to Unicode WSTRINGs so I want to be 100% sure that I do it right. I know that FB does not have a dynamic wstring data type. I was testing strings being sent to subs/functions via parameters and as Jose has indicated, using ByRef as Const WString seems to correctly accept any type of incoming string (see code below).

When designing my functions that return a string, I am confident(?) that just using "String" as the return will work correctly. The compiler will coerce/convert the standard to the correct variable type that is receiving the string data (see code below). Best of all, none of these internal string conversion produce any compiler warnings.

Am I correct in going with the premise that:
1) For incoming function string parameters that using "ByRef As Const WString" will work in all cases.
2) Using "As String" as the return datatype from a string function will work in all cases.



#Define UNICODE


Function MyFunction( ByRef wst As Const WString ) As String

   ? "InFunction:", wst, Len(wst), Sizeof(wst)
   
   Return wstr2
End Function


Dim st      As String
Dim zst     As ZString * 100
Dim wst     As WString * 100
Dim wreturn As WString * 100

st  = "This is a string"
zst = "This is a zstr"
wst = "This is a wide string"

wreturn = MyFunction(st)
? "Return:", wreturn, Len(wreturn), Sizeof(wreturn)

wreturn = MyFunction(zst)
? "Return:", wreturn, Len(wreturn), Sizeof(wreturn)

wreturn = MyFunction(wst)
? "Return:", wreturn, Len(wreturn), Sizeof(wreturn)

wreturn = MyFunction("This is a literal string")
? "Return:", wreturn, Len(wreturn), Sizeof(wreturn)

Sleep


Title: Re: Strings in FreeBASIC
Post by: José Roca on August 25, 2015, 09:52:17 AM
If you have a WSTRING and want to return a STRING, you can do:

FUNCTION = STR(*wstr)

This is what I'm doing in the function ToAnsi of my BSTR class


' ========================================================================================
' Returns the text of the string converted to ansi
' ========================================================================================
FUNCTION CBStr.ToAnsi () AS STRING
   IF m_bstr THEN FUNCTION = STR(*m_bstr)
END FUNCTION
' ========================================================================================

Title: Re: Strings in FreeBASIC
Post by: José Roca on August 25, 2015, 10:02:06 AM
Quote
Str also converts Unicode character strings to ASCII character strings. Used this way it does the opposite of WStr. If an ASCII character string is given, that string is returned unmodified.

It is like using ACODE$ in PowerBASIC.
Title: Re: Strings in FreeBASIC
Post by: José Roca on August 25, 2015, 10:25:17 AM
Quote
Am I correct in going with the premise that:
1) For incoming function string parameters that using "ByRef As Const WString" will work in all cases.
2) Using "As String" as the return datatype from a string function will work in all cases

In my experience the conversions are automatic. This is also what happens with PowerBASIC.

However, you can also use WSTR and STR as the equivalents of PB's UCODE$ and ACODE$.
Title: Re: Strings in FreeBASIC
Post by: José Roca on August 25, 2015, 10:48:40 AM
But the purpose of changing it to unicode is to return an unicode string. If you're going to return an ansi string anyway, then you don't need to modify your code.

For example, if your function is declared as:


FUNCTION Foo (BYVAL s AS STRING) AS STRING
   FUNCTION = s
END FUNCTION


or


FUNCTION Foo (BYREF s AS STRING) AS STRING
   FUNCTION = s
END FUNCTION


You can call it as:


DIM wsz AS WSTRING * 20 = "Test string"
Foo wsz


And FB will convert the passed parameter to ansi automatically without warnings.
Title: Re: Strings in FreeBASIC
Post by: José Roca on August 25, 2015, 11:33:57 AM
To be clear: It does not make sense to modify your code if you are going to return an ansi string anyway.

However, for calling API functions, we don't need to call the "A" versions since most of them are only wrappers that convert the passed string parameters to and back to Unicode and call the "W" function.

My CWindow class can be used both passing FB STRINGs and WSTRINGs, and works both with 32 and 64 bit. Therefore, there is not need for different versions, unless compiled as an object file.

Without native support for OLE strings, the ways to return an unicode string are:

1) To allocate it with SysAllocString and return the handle.

2) To allocate memory, copy the contents of the string to it and return the pointer.

3) To pass a WSTRING by reference.

1 and 2 have the problem of not having FB native statements to deal with it. You must also free the memory.

Regarding 3), the way to proceed is like several API functions do:


FUNCTION Foo (BYREF wsz AS WSTRING, BYVAL nSize AS INTEGER) AS INTEGER
   IF nSize < wanted size THEN return the wanted size and exit the function
   Otherwise, copy the string to wsz
END FUNCTION


Title: Re: Strings in FreeBASIC
Post by: José Roca on August 25, 2015, 11:52:15 AM
During the beta testing of PB 10, I had to do all the testing of COM and Unicode because nobody else, except Bob, seemed to grasp it.
Title: Re: Strings in FreeBASIC
Post by: Paul Squires on August 25, 2015, 12:38:09 PM
Thanks Jose, I thought that maybe the return STRING would automatically be converted to a WSTRING if the data type that the function is assigning the return value to was a WSTRING. See the test code below. Looking at the string in memory verifies that it is not converted to Unicode automatically because I do not see any 00 between the character values.


#Lang "FB"
#Define UNICODE

'
' Create a function that will return a WString. The compiler
' will convert the return STRING data to the appropriate data
' type and assign it to the variable accepting the return value.
'
Function MyFunction() As String
   Dim MyWString As WString * 100 = "WString from function"
   Return WSTR(MyWString)
End Function


Dim st  As String
Dim zst As ZString * 100
Dim wst As WString * 100


' STRING returned from function as STRING
st = MyFunction()
? "Return:", st, Len(st)


' STRING returned from function as ZSTRING
zst = MyFunction()
? "Return:", zst, Len(zst), Sizeof(zst)
' Verify the contents of the ZSTRING
For i As Integer = 0 To Len(zst) - 1
   ? zst[i];
Next   
?

' STRING returned from function as WSTRING
wst = MyFunction()
? "Return:", wst, Len(wst), Sizeof(wst)
' Verify that the wst actually contains unicode characters (it appears that it does not)
For i As Integer = 0 To Len(wst) - 1
   ? wst[i];
Next   
?

Sleep



I am looking at your Option "3) To pass a WSTRING by reference". I guess it implies that there will need to be two different functions for every function that wants to return a string to the caller depending on whether STRING or WSTRING is desired. For example, how would you re-code the following function from your collection of wrapper routines to ensure that both types of strings could be accommodated?


' ========================================================================================
' Gets the text of a window.
' Note: GetWindowText cannot retrieve the text of a control in another application.
' ========================================================================================
Function AfxGetWindowText (ByVal HWnd As Long) As WString
   Local nLen   As Long
   Local wbuffer As WString
   nLen = SendMessageW(HWnd, %WM_GETTEXTLENGTH, 0, 0)
   wbuffer = Space$(nLen + 1)
   nLen = SendMessageW(HWnd, %WM_GETTEXT, nLen + 1, ByVal Strptr(wbuffer))
   Function = Left$(wbuffer, nLen)
End Function



Title: Re: Strings in FreeBASIC
Post by: José Roca on August 25, 2015, 01:14:21 PM
Without native support in FB for dynamic unicode strings, it is not possible to write functions that return an string that can be used in the same way. If at least WSTRING (that in reality is a WSTRINGZ) was like the native STRING type, but working with Unicode...

Remember that the lack of native support for Unicode dynamic strings has always been my first objection to this compiler.
Title: Re: Strings in FreeBASIC
Post by: Paul Squires on August 25, 2015, 01:42:01 PM
Thanks - I understand it all now. I am wondering if maybe the best route would now to be to build all my code just using your new BTR class? I want to use Unicode in all my code but it would be nice to have easier conversion between ansi and Unicode.

Dynamic Unicode strings has come up in the FB forum before (not very often, but it has). The conversation always stalls around the many different Unicode encodings especially when using Linux. In Windows we use 2-byte Unicode so it is pretty standardized. Those developing the compiler would like a dynamic Unicode data type but the work involved given all the different encodings is pretty daunting. Not to say that it will never happen, but it could be a while. The advice I was given when I asked was to write my own class.... and that's what you have done with your AfxBstr.  :)

BTW, can you post your latest code so I continue to code with it and understand its implications and usage better?
Title: Re: Strings in FreeBASIC
Post by: José Roca on August 25, 2015, 02:15:21 PM
See attachement. I have changed the name to CBStr. I like shorter names. I used the prefix Afx with PB because it had no namespaces.

I have added several overloaded operators. I will try to add another Let (=) operator that accepts an OLE string handle. This way, we could write the function as returning an OLE string handle allocated with SysAllocString and attach it to the class easily.

I'm thinking in something like


DIM bs AS CBStr
bs = SomeFunction () that returns an OLE string handle.


Still learning. I just have began to use FB a few days ago and still don't know all the possibilities of the language.

Code of the test program I'm currently using. Shows the usage of some of the procedures and operators.


#define unicode
#INCLUDE ONCE "windows.bi"
#INCLUDE ONCE "Afx/CBStr.inc"

using Afx.CBStrClass

'DIM p AS CBStr PTR
'p = @bs
'print p->Length
'Print *p->Handle

'DIM bs1 AS CBStr = "1st string"
'DIM bs2 AS CBStr = "2nd string"
'bs1.Append *bs2.Handle
'PRINT *bs1.Handle
' -- Replaced with the += or &= oprators
'bs1 &= bs2
'PRINT *bs1.Handle


'DIm bs AS CBStr = "Test string"
'DIM s AS WSTRING * 20 = "Test string"
'IF s = bs THEN PRINT "equal"

DIM bs1 AS CBStr = "Test string"
DIM bs2 AS CBStr = "pepe"
'bs1 = bs2
print *bs1.Handle
'print *bs2.Handle
print UCASE(*bs1.Handle)
print bs1.ToAnsi

'DIM bs1 AS CBStr = "Test string 1"
'DIM bs2 AS CBStr = "Test string 2"
'IF bs1 < bs2 THEN print "less"
'print "pepe"

'bs.MakeUpper
'PRINT *bs.Handle
'bs.MakeLower
'PRINT *bs.Handle
'PRINT WSTR(*bs.Handle)
'print bs.Len
'print ucase(*bs.Handle)
'Print bs.ToAnsi; "..."

print "press esc"
dim as string k
do
k = inkey( )
loop until k <> ""


Title: Re: Strings in FreeBASIC
Post by: José Roca on August 25, 2015, 02:31:35 PM
Quote from: TechSupport on August 25, 2015, 12:38:09 PM
Thanks Jose, I thought that maybe the return STRING would automatically be converted to a WSTRING if the data type that the function is assigning the return value to was a WSTRING. See the test code below. Looking at the string in memory verifies that it is not converted to Unicode automatically because I do not see any 00 between the character values.


#Lang "FB"
#Define UNICODE

'
' Create a function that will return a WString. The compiler
' will convert the return STRING data to the appropriate data
' type and assign it to the variable accepting the return value.
'
Function MyFunction() As String
   Dim MyWString As WString * 100 = "WString from function"
   Return WSTR(MyWString)
End Function


> WSTR(MyWString)

As the string is already a WString, WSTR is ignored.

As the return type is a String, the compiler converts automatically the WString to Ansi.

With PB9 we could use a STRING to store an unicode string, but that was because PB ansi strings are also OLE strings, whereas FB dynamic strings use an ASCIIZ pointer for storage and therefore can't use embedded nulls.
Title: Re: Strings in FreeBASIC
Post by: José Roca on August 25, 2015, 02:49:21 PM
Got it. New attachement.


' ========================================================================================
OPERATOR CBStr.Let (BYREF bstrHandle AS BSTR)
   IF bstrHandle = NULL THEN EXIT OPERATOR
   IF m_bstr THEN SysFreeString(m_bstr)
   m_bstr = bstrHandle
END OPERATOR
' ========================================================================================


Test example:


#define unicode
#INCLUDE ONCE "windows.bi"
#INCLUDE ONCE "Afx/CBStr.inc"

FUNCTION Foo () AS BSTR
   DIM bstrHandle AS BSTR
   bstrHandle = SysAllocString("Test string")
   FUNCTION = bstrHandle
END FUNCTION

using Afx.CBStrClass

DIM bs AS CBSTR
bs = Foo
print *bs.Handle

print "press esc"
dim as string k
do
k = inkey( )
loop until k <> ""


As the ole string returned by the function is attached to the class, it will be freed when the class is destroyed.
Title: Re: Strings in FreeBASIC
Post by: José Roca on August 25, 2015, 02:50:55 PM
So we can now to write similar functions, the "A" version returning a STRING and the Unicode one returning a BSTR.

The good thing about this compiler is that it provides ways to extend it. These "building" features are what I always asked to Bob, instead of wasting the time with the obsolete DDT engine.
Title: Re: Strings in FreeBASIC
Post by: Paul Squires on August 25, 2015, 03:16:07 PM
So you would envision something like this:

#IfDef UNICODE
   #Define AfxGetWindowText AfxGetWindowTextW
#Else
   Function AfxGetWindowText AfxGetWindowTextA
#EndIf
                                             
Declare Function AfxGetWindowTextW(ByVal HWnd As HWnd) As CBStr                                             
Declare Function AfxGetWindowTextA(ByVal HWnd As HWnd) As String
Title: Re: Strings in FreeBASIC
Post by: José Roca on August 25, 2015, 03:27:16 PM
Almost. As BSTR, not as CBSTR.


#IfDef UNICODE
   Function AfxGetWindowText (ByVal HWnd As HWnd) As BSTR
#Else
   Function AfxGetWindowText (ByVal HWnd As HWnd) As String
#EndIf
[code]
Title: Re: Strings in FreeBASIC
Post by: Paul Squires on August 25, 2015, 03:33:36 PM
Got it. Thanks. :)

#IfDef UNICODE
   #Define AfxGetWindowText AfxGetWindowTextW
#Else
   #Define AfxGetWindowText AfxGetWindowTextA
#EndIf
                                             
Declare Function AfxGetWindowTextW(ByVal HWnd As HWnd) As BSTR
Declare Function AfxGetWindowTextA(ByVal HWnd As HWnd) As String
Title: Re: Strings in FreeBASIC
Post by: José Roca on August 25, 2015, 03:46:35 PM
An example:


' ========================================================================================
' Gets the text of a window.
' Note: GetWindowText cannot retrieve the text of a control in another application.
' ========================================================================================
FUNCTION AfxGetWindowTextW (BYVAL hwnd AS HWND) AS BSTR
   DIM nLen AS LONG
   DIM bstrHandle AS BSTR
   DIM wbuffer AS WSTRING * MAX_PATH
   nLen = SendMessageW(hwnd, WM_GETTEXTLENGTH, 0, 0)
   nLen = SendMessageW(hwnd, WM_GETTEXT, nLen + 1, CAST(LPARAM, @wbuffer))
   IF nLen THEN bstrHandle = SysAllocString(@wBuffer)
   FUNCTION = bstrHandle
END FUNCTION
' ========================================================================================


Usage:


DIM bs AS CBStr
bs = AfxGetWindowTextW(hwnd)
print *bs.Handle    ' Unicode
Print bs.ToAnsi ' Ansi


Will have to change DIM wbuffer AS WSTRING * MAX_PATH to a WSTRING pointer and allocate the buffer dynamically according to te length returned by WM_GETTEXTLENGTH.
Title: Re: Strings in FreeBASIC
Post by: José Roca on August 25, 2015, 03:51:01 PM
Since the returned data type is a BSTR, FB will call this version of the overloaded = operator


' ========================================================================================
OPERATOR CBStr.Let (BYREF bstrHandle AS BSTR)
   IF bstrHandle = NULL THEN EXIT OPERATOR
   IF m_bstr THEN SysFreeString(m_bstr)
   m_bstr = bstrHandle
END OPERATOR
' ========================================================================================


That attaches the BSTR handle to the class.
Title: Re: Strings in FreeBASIC
Post by: José Roca on August 25, 2015, 04:17:59 PM
New version:


' ========================================================================================
' Gets the text of a window.
' Note: GetWindowText cannot retrieve the text of a control in another application.
' ========================================================================================
FUNCTION AfxGetWindowTextW (BYVAL hwnd AS HWND) AS BSTR
   DIM nLen AS LONG, bstrHandle AS BSTR, pwbuffer AS WSTRING PTR
   FUNCTION = NULL
   nLen = SendMessageW(hwnd, WM_GETTEXTLENGTH, 0, 0)
   IF nLen = 0 THEN EXIT FUNCTION
   pwBuffer = CAllocate(nLen + 1)
   IF pwBuffer = NULL THEN EXIT FUNCTION
   nLen = SendMessageW(hwnd, WM_GETTEXT, nLen + 1, CAST(LPARAM, pwbuffer))
   IF nLen THEN bstrHandle = SysAllocString(pwBuffer)
   DeAllocate pwBuffer
   FUNCTION = bstrHandle
END FUNCTION
' ========================================================================================

Title: Re: Strings in FreeBASIC
Post by: José Roca on August 25, 2015, 05:38:44 PM
New operator (*). Now we case use print **bs (notice the double indirection) instead of print *bs.Handle.


' ========================================================================================
OPERATOR * (BYREF pBStr AS CBStr) AS BSTR
   OPERATOR = pBStr.Handle
END OPERATOR
' ========================================================================================

Title: Re: Strings in FreeBASIC
Post by: José Roca on August 25, 2015, 06:57:14 PM
New operators (&) and (+). Concatenates BSTRings with other BSTRings or FB Strings, WStrings or literals. Returns  a new BSTR with the contents.


' ========================================================================================
' Concatenates two BSTRs and returns a new BSTR.
' Usage:
' DIM b1 AS CBStr = "Test string 1"
' DIM b2 AS CBStr = " - concatenated string"
' DIM b AS CBStr
' b = b1 & b2
' Print **b
' ========================================================================================
OPERATOR & (BYREF pBStr1 AS CBStr, BYREF pBSTR2 AS CBStr) AS BSTR
   DIM n1 AS INTEGER, n2 AS INTEGER, b AS BSTR
   n1 = SysStringLen(*pBStr1.Handle)
   n2 = SysStringLen(*pBStr2.Handle)
   b = SysAllocStringLen(NULL, n1+n2)
   IF b = NULL THEN EXIT OPERATOR
   IF n1 THEN memcpy(b, pBStr1.Handle, n1 * SIZEOF(WSTRING))
   IF n2 THEN memcpy(b+n1, pBStr2.Handle, n2 * SIZEOF(WSTRING))
   OPERATOR = b
END OPERATOR
' ========================================================================================

' ========================================================================================
' Concatenates a BSTR and a WSTRING and returns a new BSTR.
' Usage:
' DIM b1 AS CBStr = "Test string 1"
' DIM wsz AS WSTRING = " - concatenated string"
' DIM b AS CBStr
' b = b1 & wsz
' Print **b
' Note: Instead of wsz, we can also pass an string literal or a FB string
' ========================================================================================
OPERATOR & (BYREF pBStr AS CBStr, BYREF wszStr AS WSTRING) AS BSTR
   DIM n1 AS INTEGER, n2 AS INTEGER, b AS BSTR
   n1 = SysStringLen(*pBStr.Handle)
   n2 = .LEN(wszStr)
   b = SysAllocStringLen(NULL, n1+n2)
   IF b = NULL THEN EXIT OPERATOR
   IF n1 THEN memcpy(b, pBStr.Handle, n1 * SIZEOF(WSTRING))
   IF n2 THEN memcpy(b+n1, @wszStr, n2 * SIZEOF(WSTRING))
   OPERATOR = b
END OPERATOR
' ========================================================================================

' ========================================================================================
' Concatenates a BSTR and a WSTRING and returns a new BSTR.
' Usage:
' DIM wsz AS WSTRING = "Test string 1"
' DIM b1 AS CBStr = " - concatenated string"
' DIM b AS CBStr
' b = wsz & b1
' Print **b
' Note: Instead of wsz, we can also pass an string literal or a FB string
' ========================================================================================
OPERATOR & (BYREF wszStr AS WSTRING, BYREF pBStr AS CBStr) AS BSTR
   DIM n1 AS INTEGER, n2 AS INTEGER, b AS BSTR
   n1 = .LEN(wszStr)
   n2 = SysStringLen(*pBStr.Handle)
   b = SysAllocStringLen(NULL, n1+n2)
   IF b = NULL THEN EXIT OPERATOR
   IF n1 THEN memcpy(b, @wszStr, n1 * SIZEOF(WSTRING))
   IF n1 THEN memcpy(b+n1, pBStr.Handle, n2 * SIZEOF(WSTRING))
   OPERATOR = b
END OPERATOR
' ========================================================================================

' ========================================================================================
' Concatenates two BSTRs and returns a new BSTR.
' Usage:
' DIM b1 AS CBStr = "Test string 1"
' DIM b2 AS CBStr = " - concatenated string"
' DIM b AS CBStr
' b = b1 + b2
' Print **b
' ========================================================================================
OPERATOR + (BYREF pBStr1 AS CBStr, BYREF pBSTR2 AS CBStr) AS BSTR
   DIM n1 AS INTEGER, n2 AS INTEGER, b AS BSTR
   n1 = SysStringLen(*pBStr1.Handle)
   n2 = SysStringLen(*pBStr2.Handle)
   b = SysAllocStringLen(NULL, n1+n2)
   IF b = NULL THEN EXIT OPERATOR
   IF n1 THEN memcpy(b, pBStr1.Handle, n1 * SIZEOF(WSTRING))
   IF n2 THEN memcpy(b+n1, pBStr2.Handle, n2 * SIZEOF(WSTRING))
   OPERATOR = b
END OPERATOR
' ========================================================================================

' ========================================================================================
' Concatenates a BSTR and a WSTRING and returns a new BSTR.
' Usage:
' DIM b1 AS CBStr = "Test string 1"
' DIM wsz AS WSTRING = " - concatenated string"
' DIM b AS CBStr
' b = b1 + wsz
' Print **b
' Note: Instead of wsz, we can also pass an string literal or a FB string
' ========================================================================================
OPERATOR + (BYREF pBStr AS CBStr, BYREF wszStr AS WSTRING) AS BSTR
   DIM n1 AS INTEGER, n2 AS INTEGER, b AS BSTR
   n1 = SysStringLen(*pBStr.Handle)
   n2 = .LEN(wszStr)
   b = SysAllocStringLen(NULL, n1+n2)
   IF b = NULL THEN EXIT OPERATOR
   IF n1 THEN memcpy(b, pBStr.Handle, n1 * SIZEOF(WSTRING))
   IF n2 THEN memcpy(b+n1, @wszStr, n2 * SIZEOF(WSTRING))
   OPERATOR = b
END OPERATOR
' ========================================================================================

' ========================================================================================
' Concatenates a BSTR and a WSTRING and returns a new BSTR.
' Usage:
' DIM wsz AS WSTRING = "Test string 1"
' DIM b1 AS CBStr = " - concatenated string"
' DIM b AS CBStr
' b = wsz + b1
' Print **b
' Note: Instead of wsz, we can also pass an string literal or a FB string
' ========================================================================================
OPERATOR + (BYREF wszStr AS WSTRING, BYREF pBStr AS CBStr) AS BSTR
   DIM n1 AS INTEGER, n2 AS INTEGER, b AS BSTR
   n1 = .LEN(wszStr)
   n2 = SysStringLen(*pBStr.Handle)
   b = SysAllocStringLen(NULL, n1+n2)
   IF b = NULL THEN EXIT OPERATOR
   IF n1 THEN memcpy(b, @wszStr, n1 * SIZEOF(WSTRING))
   IF n1 THEN memcpy(b+n1, pBStr.Handle, n2 * SIZEOF(WSTRING))
   OPERATOR = b
END OPERATOR
' ========================================================================================


Note: If you don't assign the returned BSTR to a variable of type CBStr, but to a variable of type BSTR, then you will have to free the returned BSTR with SysFreeString.
Title: Re: Strings in FreeBASIC
Post by: José Roca on August 25, 2015, 07:54:20 PM
Changed the function Compare, that was using WSTRINGs instead of BSTRs, and added CompareI, CompareN and CompareNI.


' ========================================================================================
' Compares two BSTRs. The comparison is case sensitive.
' Returns zero if the strings are identical. Returns a positive value if the string pointed
' to by lpStr1 is alphabetically greater than that pointed to by lpStr2. Returns a negative
' value if the string pointed to by lpStr1 is alphabetically less than that pointed to by lpStr2.
' ========================================================================================
FUNCTION CBStr.Compare (BYREF pBStr1 AS CBStr, BYREF pBStr2 AS CBStr) AS LONG
   FUNCTION = StrCmpW(*pBStr1.Handle, *pBStr2.Handle)
END FUNCTION
' ========================================================================================

' ========================================================================================
' Compares two BSTRs. The comparison is not case sensitive.
' Returns zero if the strings are identical. Returns a positive value if the string pointed
' to by lpStr1 is alphabetically greater than that pointed to by lpStr2. Returns a negative
' value if the string pointed to by lpStr1 is alphabetically less than that pointed to by lpStr2.
' ========================================================================================
FUNCTION CBStr.CompareI (BYREF pBStr1 AS CBStr, BYREF pBStr2 AS CBStr) AS LONG
   FUNCTION = StrCmpIW(*pBStr1.Handle, *pBStr2.Handle)
END FUNCTION
' ========================================================================================

' ========================================================================================
' Compares a specified number of characters from the beginning of two BSTRs.
' nChars is the number of characters from the beginning of each string to be compared.
' The comparison is case sensitive.
' Returns zero if the strings are identical. Returns a positive value if the string pointed
' to by lpStr1 is alphabetically greater than that pointed to by lpStr2. Returns a negative
' value if the string pointed to by lpStr1 is alphabetically less than that pointed to by lpStr2.
' ========================================================================================
FUNCTION CBStr.CompareN (BYREF pBStr1 AS CBStr, BYREF pBStr2 AS CBStr, BYVAL nChars AS LONG) AS LONG
   FUNCTION = StrCmpNW(*pBStr1.Handle, *pBStr2.Handle, nChars)
END FUNCTION
' ========================================================================================

' ========================================================================================
' Compares a specified number of characters from the beginning of two BSTRs.
' nChars is the number of characters from the beginning of each string to be compared.
' The comparison is case sensitive.
' Returns zero if the strings are identical. Returns a positive value if the string pointed
' to by lpStr1 is alphabetically greater than that pointed to by lpStr2. Returns a negative
' value if the string pointed to by lpStr1 is alphabetically less than that pointed to by lpStr2.
' ========================================================================================
FUNCTION CBStr.CompareNI (BYREF pBStr1 AS CBStr, BYREF pBStr2 AS CBStr, BYVAL nChars AS LONG) AS LONG
   FUNCTION = StrCmpNIW(*pBStr1.Handle, *pBStr2.Handle, nChars)
END FUNCTION
' ========================================================================================

Title: Re: Strings in FreeBASIC
Post by: José Roca on August 25, 2015, 08:09:04 PM
Added another Let (=) operator with a String parameter. The compiler was finding ambiguous the use of

DIM b AS CBStr
b = "Test string"


' ========================================================================================
OPERATOR CBStr.Let (BYREF szStr AS STRING)
   IF m_bstr THEN SysFreeString(m_bstr)
   m_bstr = SysAllocString(WSTR(szStr))
END OPERATOR
' ========================================================================================


Title: Re: Strings in FreeBASIC
Post by: José Roca on August 25, 2015, 08:20:01 PM
Please note that the FreeBasic intrinsic string functions can be used with CBStr strings, e.g.


DIM b AS CBStr = "Test string"
DIM wsz AS WSTRING * 250
wsz = MID(**b, 6)
print wsz



If the target is an asi string, the compiler will automatically convert it to ansi:


DIM b AS CBStr = "Test string"
DIM s AS STRING
s = MID(**b, 6)
print s



Same for UCase, LCase, Trim, etc.

This is becoming a tutorial, isn't it? :)
Title: Re: Strings in FreeBASIC
Post by: Paul Squires on August 25, 2015, 09:10:24 PM
Quote from: Jose Roca on August 25, 2015, 08:20:01 PM
This is becoming a tutorial, isn't it? :)


Lol :)    Yes, an awesome tutorial !!!

This string class will be indespensible moving forward with FB.

Title: Re: Strings in FreeBASIC
Post by: José Roca on August 25, 2015, 09:28:19 PM
Until yesterday I didn't know how to write overloaded operators. Nothing like begin to write code and to figure solutions for the problems. This and a future class for variants will make COM programming much easier. I'm not obsessed with COM. It just happens that most of the current M$ technologies are COM based. And Unicode is essential.

Now, doing some cleaning to reduce bloat.

Removed some unneeded procedures.

Attach
No longer needed because now we can use the = operator.

ToAnsi
No longer needed because now we cab use STR(**bstring)

Equal
No longer needed because now we can use the = operator.

Compare, CompareI, CompareN, CompareNI
Not really needed because we can call the API functions easily, e.g.
DIM b1 AS CBStr = "Test string"
DIM b2 AS CBStr = "Test string 2"
print StrCmpW(**b1, **b2)

Added the Concat auxiliary function, now used by all the & and + operators.


' ========================================================================================
' Concatenates two WSTRINGs and returns a new BSTR.
' ========================================================================================
FUNCTION CBStr.Concat (BYREF wszStr1 AS CONST WSTRING, BYREF wszStr2 AS CONST WSTRING) AS BSTR
   DIM n1 AS INTEGER, n2 AS INTEGER, b AS BSTR
   n1 = .LEN(wszStr1)
   n2 = .LEN(wszStr2)
   b = SysAllocStringLen(NULL, n1+n2)
   IF b = NULL THEN EXIT FUNCTION
   IF n1 THEN memcpy(b, @wszStr1, n1 * SIZEOF(WSTRING))
   IF n2 THEN memcpy(b+n1, @wszStr2, n2 * SIZEOF(WSTRING))
   FUNCTION = b
END FUNCTION
' ========================================================================================

' ========================================================================================
' Concatenates two BSTRs and returns a new BSTR.
' Usage:
' DIM b1 AS CBStr = "Test string 1"
' DIM b2 AS CBStr = " - concatenated string"
' DIM b AS CBStr
' b = b1 & b2
' Print **b
' ========================================================================================
OPERATOR & (BYREF pBStr1 AS CBStr, BYREF pBStr2 AS CBStr) AS BSTR
   OPERATOR = pBStr1.Concat(*pBStr1.Handle, *pBStr2.Handle)
END OPERATOR
' ========================================================================================

' ========================================================================================
' Concatenates a BSTR and a WSTRING and returns a new BSTR.
' Usage:
' DIM b1 AS CBStr = "Test string 1"
' DIM wsz AS WSTRING * 250 = " - concatenated string"
' DIM b AS CBStr
' b = b1 & wsz
' Print **b
' Note: Instead of wsz, we can also pass an string literal or a FB string
' ========================================================================================
OPERATOR & (BYREF pBStr AS CBStr, BYREF wszStr AS WSTRING) AS BSTR
   OPERATOR = pBStr.Concat(*pBStr.Handle, wszStr)
END OPERATOR
' ========================================================================================

' ========================================================================================
' Concatenates a BSTR and a WSTRING and returns a new BSTR.
' Usage:
' DIM wsz AS WSTRING * 250 = "Test string 1"
' DIM b1 AS CBStr = " - concatenated string"
' DIM b AS CBStr
' b = wsz & b1
' Print **b
' Note: Instead of wsz, we can also pass an string literal or a FB string
' ========================================================================================
OPERATOR & (BYREF wszStr AS WSTRING, BYREF pBStr AS CBStr) AS BSTR
   OPERATOR = pBStr.Concat(wszStr, *pBStr.Handle)
END OPERATOR
' ========================================================================================

' ========================================================================================
' Concatenates two BSTRs and returns a new BSTR.
' Usage:
' DIM b1 AS CBStr = "Test string 1"
' DIM b2 AS CBStr = " - concatenated string"
' DIM b AS CBStr
' b = b1 + b2
' Print **b
' ========================================================================================
OPERATOR + (BYREF pBStr1 AS CBStr, BYREF pBStr2 AS CBStr) AS BSTR
   OPERATOR = pBStr1.Concat(*pBStr1.Handle, *pBStr2.Handle)
END OPERATOR
' ========================================================================================

' ========================================================================================
' Concatenates a BSTR and a WSTRING and returns a new BSTR.
' Usage:
' DIM b1 AS CBStr = "Test string 1"
' DIM wsz AS WSTRING * 250 = " - concatenated string"
' DIM b AS CBStr
' b = b1 + wsz
' Print **b
' Note: Instead of wsz, we can also pass an string literal or a FB string
' ========================================================================================
OPERATOR + (BYREF pBStr AS CBStr, BYREF wszStr AS WSTRING) AS BSTR
   OPERATOR = pBStr.Concat(*pBStr.Handle, wszStr)
END OPERATOR
' ========================================================================================

' ========================================================================================
' Concatenates a BSTR and a WSTRING and returns a new BSTR.
' Usage:
' DIM wsz AS WSTRING * 250 = "Test string 1"
' DIM b1 AS CBStr = " - concatenated string"
' DIM b AS CBStr
' b = wsz + b1
' Print **b
' Note: Instead of wsz, we can also pass an string literal or a FB string
' ========================================================================================
OPERATOR + (BYREF wszStr AS WSTRING, BYREF pBStr AS CBStr) AS BSTR
   OPERATOR = pBStr.Concat(wszStr, *pBStr.Handle)
END OPERATOR
' ========================================================================================

Title: Re: Strings in FreeBASIC
Post by: Paul Squires on August 25, 2015, 09:51:17 PM
I am loving the class so far. You have done so much with it in such a short time.

I especially like the double asterisk notation.  **bstring instead of *bstring.handle   That is really cool.

You can't do the following yet, right? It prints garbage for me.


Dim bs1 As CBStr = "Test string 1"
Dim bs2 As CBStr = "Test string 2"
Dim bs3 As CBStr

bs3 = **bs1 & **bs2 
Print **bs3       


This works though...

Dim st As WString * 100

Dim bs1 As CBStr = "Test string 1"
Dim bs2 As CBStr = "Test string 2"
Dim bs3 As CBStr

st = **bs1 & **bs2 
Print st       


Title: Re: Strings in FreeBASIC
Post by: José Roca on August 25, 2015, 10:05:26 PM
> I especially like the double asterisk notation.  **bstring instead of *bstring.handle   That is really cool.

This has been the hardest one to figure.

> You can't do the following yet, right? It prints garbage for me.


Dim bs1 As CBStr = "Test string 1"
Dim bs2 As CBStr = "Test string 2"
Dim bs3 As CBStr

bs3 = **bs1 & **bs2 
Print **bs3


Since the target is a CBStr, you have to use


Dim bs1 As CBStr = "Test string 1"
Dim bs2 As CBStr = "Test string 2"
Dim bs3 As CBStr

bs3 = bs1 & bs2 
Print **bs3


> This works though...

Because the target is a WSTRING.

**bs returns a WSTRING pointer to the beginning of the string data, like STRPTR. We only need to use ** when we want to use a CBStr with a FB instrinsic function, that requires a WSTRING pointer, or a function that also requires an WSTRING pointer. For all the procedures and operators of the class, just use bs, without the **.
Title: Re: Strings in FreeBASIC
Post by: José Roca on August 25, 2015, 10:10:56 PM
It is not possible to overload the & operator having two WSTRING parameters because this is already defined by the compiler and gives a duplicate definition error.
Title: Re: Strings in FreeBASIC
Post by: Paul Squires on August 25, 2015, 10:18:11 PM
Quote from: Jose Roca on August 25, 2015, 10:05:26 PM
Since the target is a CBStr, you have to use


Dim bs1 As CBStr = "Test string 1"
Dim bs2 As CBStr = "Test string 2"
Dim bs3 As CBStr

bs3 = bs1 & bs2 
Print **bs3



Ahhhhhh, now that make sense. Thanks :)

Title: Re: Strings in FreeBASIC
Post by: José Roca on August 25, 2015, 10:51:11 PM
There are many string functions in the API and the C runtimes than can be used with PB. It would be useful to collect information about them in order to not reinvent the wheel.

For example:

wsprintf function
https://msdn.microsoft.com/en-us/library/windows/desktop/ms647550(v=vs.85).aspx
(many other string functions in that link)

Usage example:


DIM wszBuffer AS WSTRING * 260
DIM wszStr AS WSTRING * 260 = "Runing at (%i FPS) frames per second"
DIM fps AS INTEGER = 300
wsprintfW @wszBuffer, @wszStr, fps
print wszBuffer

Title: Re: Strings in FreeBASIC
Post by: José Roca on August 27, 2015, 04:53:00 PM
I have removed the procedures MakeUpper and MakeLower because you can use instead


DIM bs AS CBStr = "Test string"
**bs = LCASE(**bs)   ' or UCASE
print **bs


I also have overloaded the operator LEN, so instead of bs.Len(bs) you can use


DIM bs AS CBStr = "Test string"
print LEN(bs)
-or-
print LEN(**bs)

Title: Re: Strings in FreeBASIC
Post by: José Roca on August 28, 2015, 12:52:48 AM
I was wondering why the application crashed if I used something like


DIM bs AS CBStr
bs = UCASE("Ucase test")


and the reason is that FB creates temporary WSTRINGs, that don't have a descriptor, not real BSTRs.

Since BSTR is defined as WSTRING PTR in the FB headers, the same = operator was being called either if the passed string was a real OLE string handle or a temporary FB WSTRING.

So I have changed the code of the operator to detect if it is an OLE string or not:


' ========================================================================================
OPERATOR CBStr.Let (BYREF bstrHandle AS BSTR)
   IF bstrHandle = NULL THEN EXIT OPERATOR
   ' Free the current OLE string
   IF m_bstr THEN SysFreeString(m_bstr)
   ' Detect if the passed handle is an OLE string
   ' If it is an OLE string it must have a descriptor; otherwise, don't
   ' Get the length looking at the descriptor
   DIM res AS INTEGER = PEEK(DWORD, CAST(ANY PTR, bstrHandle) - 4) \ 2
   ' If the retrieved length if the same that the returned by LEN, then it must be an OLE string
   IF res = .LEN(*bstrHandle) THEN
      ' Attach the passed handle to the class
      m_bstr = bstrHandle
   ELSE
      ' Allocate an OLE string with the contents of the string pointer by bstrHandle
      m_bstr = SysAllocString(*bstrHandle)
   END IF
END OPERATOR
' ========================================================================================


Now we can do things like:


DIM bs AS CBStr = "Test"
bs = bs & " string"

--or--
bs = UCASE("Ucase test")

--or--
bs = "test " & "string"


etc.

As well as assigning real OLE strings:


FUNCTION Foo () AS BSTR
   FUNCTION = SysAllocString("Foo Test")
END FUNCTION

DIM bs AS CBStr = Foo

--or--
DIM bs AS CBStr
bs = Foo


If we want to use instrinsic FB functions with real OLE string handles, we have to deference it, since as I said, in the FB headers a BSTR is declared as a WSTRING pointer.


DIM bs AS CBStr
bs = UCASE(*Foo)


and if it is a variable of the type CBStr (my OLE string class), you have to use double indirection.


PRINT **bs


Problem:

Although bs = UCASE(*Foo) works, and also bs = *Foo & " --- " & *Foo, it should create a memory leak, since the OLE strings returned by the Foo function aren't being freed.

Therefore, we may need to do something like


DIM foo1 AS CBStr = Foo
DIM foo2 AS CBStr = Foo
DIM bs AS CBStr
bs = foo1 & " --- " & foo2
print **bs


It is the only caveat. Don't know if I can do something more.
Title: Re: Strings in FreeBASIC
Post by: Theo Gottwald on August 31, 2015, 07:46:55 AM
Wouldn't it be a good idea to talk to the FB-Compiler makers to get your work "hardcoded", Jose?
Freebasic is written ion Freebasic therefore it should be possible ...
Title: Re: Strings in FreeBASIC
Post by: José Roca on August 31, 2015, 01:30:40 PM
They don't need me to add support for OLE strings. It is not rocket science. What they lack is the will to do it, since it is a Windows only feature and they are Linuxers.