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
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
' ========================================================================================
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.
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$.
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.
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
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.
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
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.
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?
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 <> ""
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.
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.
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.
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
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]
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
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.
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.
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
' ========================================================================================
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
' ========================================================================================
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.
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
' ========================================================================================
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
' ========================================================================================
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? :)
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.
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
' ========================================================================================
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
> 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 **.
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.
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 :)
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
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)
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.
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 ...
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.