• Welcome to Jose's Read Only Forum 2023.
 

FreeBASIC CWstr

Started by Juergen Kuehlwein, April 09, 2018, 11:39:00 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Juergen Kuehlwein

Well José i would have preferred to ask these questions here http://www.planetsquires.com/protect/forum/index.php?topic=4049.0, but registration is not possible as it seems.

First of all, congratulations for what you have done with WinFBX! I´m most interested in the Unicode string part (CWstr, CBstr) for FreeBASIC.


1.) My first question is, why two versions of wide strings? I read a post, where you stated you are using CBstr for COM - is CWstr not working for COM ?


2.) "sptr" and "vptr" do the same thing, i would have expected "sptr" to return a pointer to the data and "vptr" to return a pointer to the type - just like "strptr" and "varptr". Is this by intention or by error ?


3.) There is a problem with "LEFT", "RIGHT" and some other statements in FreeBASIC, maybe i found a solution for this.

PRIVATE FUNCTION Left OVERLOAD (BYREF cws AS CWSTR, BYVAL nChars AS INTEGER) byref as wstring

static s as cwstr

   s = LEFT(**cws, nChars)
   RETURN *cast(wstring ptr, *s)


END FUNCTION

Implementing this you may use "LEFT" (and others) in regular code just as usual (without the need of "**"). Do you see any problems here ?


4.) To my surprise the following code:

himage = loadimage(hinst, d, %IMAGE_BITMAP, 0, 0, %LR_DEFAULTCOLOR)

works for "DIM d AS STRING" AND for "DIM d AS CWstr" - how could that happen ?

"d" (the bitmaps resource name - STRING or CWstr) is passed: BYVAL NAME AS LPCSTR. I understand how this works for a STRING, but i don´t understand how this could work for a CWstr - but it definitely does (32 and 64 bit) while

himage = loadimage(hinst, STRPTR(d), %IMAGE_BITMAP, 0, 0, %LR_DEFAULTCOLOR)

works for a string, but not for a CWstr.


5.) "STRPTR" doesn´t work for a CWstr at all (compiler error), you must use "STRPTR(**d) or just *d, how could we make it accept "STRPTR(d)" ?


6.) having the exact same syntax for STRING (ANSI) and CWstr (Unicode) would make it possible to avoid multiple

#ifdef %unicode   
dim   d AS CWstr
#ELSE
dim   d AS STRING
#ENDIF

and to have one

#ifdef %unicode   
#define dstr CWstr
#ELSE
#define dstr STRING
#ENDIF

... and then ...

DIM d AS dstr

where "dstr" stands for "dynamic string" and could be used for ANSI and Unicode - if they only shared the same syntax...


Eager to hear your explanations and thoughts,


JK

José Roca

Quote
Well José i would have preferred to ask these questions here http://www.planetsquires.com/protect/forum/index.php?topic=4049.0, but registration is not possible as it seems.

See the "READ ME FIRST" post: http://www.planetsquires.com/protect/forum/index.php?topic=3777.0

Quote
Registrations are disabled.

You MUST email support@planetsquires.com and request to be added as a member.

Please provide a "username" or "handle" when you request registration.

José Roca

#2
Quote
1.) My first question is, why two versions of wide strings? I read a post, where you stated you are using CBstr for COM - is CWstr not working for COM ?

COM uses BSTRings, that are allocated and freed by the Windows OLE library. A CWSTR is a class with an underlying double null terminated buffer that is allocated, manipulated and freed by the class, using string build techniques. The advantage of using CWSTR over CBSTR for general purpose is that CWSTR is much faster.

A BSTR carries its length with it, but a pointer to a CWSTR not. Therefore, it is not suitable for use with COM.

José Roca

#3
Quote
2.) "sptr" and "vptr" do the same thing, i would have expected "sptr" to return a pointer to the data and "vptr" to return a pointer to the type - just like "strptr" and "varptr". Is this by intention or by error ?

They don't do the same thing. sptr returns a pointer to the CWSTR buffer variable and vptr to the string data. You can use * or ** instead. I have implemented sptr and vptr for those that are not comfortable using * or **. I ended having to use ** instead of @ because, otherwise, I could not use @ to get the address of the class.

However, with CBSTR, vptr is important to be used with OUT parameters:


' ========================================================================================
' * Frees the underlying BSTR and returns the BSTR pointer.
' To pass the underlying BSTR to an OUT BYVAL BSTR PTR parameter.
' If we pass a CBSTR to a function with an OUT BSTR parameter without first freeing it
' we will have a memory leak.
' ========================================================================================
PRIVATE FUNCTION CBStr.vptr () AS AFX_BSTR PTR
   CBSTR_DP("CBSTR vptr")
   IF m_bstr THEN
      SysFreeString(m_bstr)
      m_bstr = NULL
   END IF
   RETURN @m_bstr
END FUNCTION
' ========================================================================================




José Roca

#4
Quote
3.) There is a problem with "LEFT", "RIGHT" and some other statements in FreeBASIC, maybe i found a solution for this.

I'm already using


' ========================================================================================
PRIVATE FUNCTION Left OVERLOAD (BYREF cws AS CWSTR, BYVAL nChars AS INTEGER) AS CWSTR
   RETURN LEFT(*cast(WSTRING PTR, cws.m_pBuffer), nChars)
END FUNCTION
' ========================================================================================
' ========================================================================================
PRIVATE FUNCTION Right OVERLOAD (BYREF cws AS CWSTR, BYVAL nChars AS INTEGER) AS CWSTR
   RETURN RIGHT(*cast(WSTRING PTR, cws.m_pBuffer), nChars)
END FUNCTION
' ========================================================================================
' ========================================================================================
PRIVATE FUNCTION Val OVERLOAD (BYREF cws AS CWSTR) AS DOUBLE
   RETURN .VAL(*cast(WSTRING PTR, cws.m_pBuffer))
END FUNCTION
' ========================================================================================


You can use CWSTR as if it was a native data type, even with arrays. Currently, it works with all the intrinsic FreeBasic functions and operators except MID when used as a statement. Something like MID(cws, 2, 1) = "x" compiles but does not change the contents of the dynamic unicode string. MID(cws.wstr, 2, 1) = "x" or MID(**cws, 2, 1) = "x" works. This is because when simply passing cws, FreeBasic creates a temporary WSTRING and changes the content of that temporary string.

José Roca

#5
Quote
4.) To my surprise the following code:
Code: [Select]

himage = loadimage(hinst, d, %IMAGE_BITMAP, 0, 0, %LR_DEFAULTCOLOR)

works for "DIM d AS STRING" AND for "DIM d AS CWstr" - how could that happen ?

Because of casting. I have an overloaded cast operator that returns a pointer to the content of the underlying null terminated buffer.


' ========================================================================================
' Returns the string data (same as **).
' ========================================================================================
PRIVATE OPERATOR CWstr.CAST () BYREF AS WSTRING
   CWSTR_DP("CWSTR CAST BYREF AS WSTRING - buffer: " & .WSTR(m_pBuffer))
   OPERATOR = *cast(WSTRING PTR, m_pBuffer)
END OPERATOR
' ========================================================================================


José Roca

#6
Quote
5.) "STRPTR" doesn´t work for a CWstr at all (compiler error), you must use "STRPTR(**d) or just *d, how could we make it accept "STRPTR(d)" ?

You will have to undef STRPTR and then implement overloads for all the other data types. Too much messy work for something that it is unneeded, since by virtue of the cast operator you don't need to use STRPTR: just pass the CWSTR variable.


' ========================================================================================
' Returns a pointer to the CWSTR buffer.
' ========================================================================================
PRIVATE OPERATOR CWstr.CAST () AS ANY PTR
   CWSTR_DP("CWSTR CAST ANY PTR - buffer: " & .WSTR(m_pBuffer))
   OPERATOR = cast(ANY PTR, m_pBuffer)
END OPERATOR
' ========================================================================================


José Roca

#7
Quote
6.) having the exact same syntax for STRING (ANSI) and CWstr (Unicode) would make it possible to avoid multiple

I no longer use ANSI. Like the windows API, all my framework uses Unicode. You can use ansi strings with it if you want: they will be automatically converted. My only use for FreeBasic ansi strings is when I want to easily allocate a byte buffer instead of using manual allocation/deallocation.

Where is the need of using?


#ifdef %unicode   
#define dstr CWstr
#ELSE
#define dstr STRING
#ENDIF


Use always unicode and forget the API "A" functions. In Windows, the "A" functions are mere wrappers that convert the string parameters to unicode and call the "W" functions. Therefore, they are slightly slower and use more memory. If I could, I would remove all that ansi stuff.

José Roca

BTW if you enable the Scintilla control to use UTF and save the file as UTF8 or UTF16, with BOM, you can use unicode string literals, e.g.

DIM cws AS CWSTR = "Дми́трий Дми́триевич Шостако́вич"

Paul's WinFBE editor for FreeBasic allows to do it.

Juergen Kuehlwein

Thanks José for you enlightening reply!

@ 5.) so the compiler looks, if there is a matching cast operator for a type, when it encounters an unexpected type (ptr) ? - that´s pretty cool. But why then "STRPTR" and others keep failing ? Maybe the compiler doesn´t do this in all cases ? I understand and i accept that just omitting "STRPTR" is a perfectly working solution. But i want to understand the background, why it works here and doesn´t work there


@ 3.) please try the following code:

#Include Once "windows.bi"
#Include Once "Afx\AfxStr.inc"

DIM d AS CWstr = "12345"

SELECT CASE LEFT(d, 2)
  case "12"
    print "ok"
end select

sleep

it throws a compiler error! I think this is because "SELECT CASE" expects one of the native string types, but it receives a WCstr (which it doesn´t know). If you use my function (3.)), it works, because "SELCET CASE" gets a WSTRING (which it does know). My code works at the price of an intermediate copy, but maybe we could get rid of this copy. My tries kept failing, but you are the by far more experienced person with FreeBASIC, so maybe you know how to do it.



Is it possible to overload the "MID" sub in a similar way ? So you don´t have to code "**":

d = MID(**d, 2,2)

and can do

d = mid(d, 2,2)

with CWstr



Quote
BTW if you enable the Scintilla control to use UTF and save the file as UTF8 or UTF16, with BOM, you can use unicode string literals, e.g.

DIM cws AS CWSTR = "Дми́трий Дми́триевич Шостако́вич"

Paul's WinFBE editor for FreeBasic allows to do it.


Thanks, i will have a look at it


JK

José Roca

> Is it possible to overload the "MID" sub in a similar way ? So you don´t have to code "**":

There is not neet to overload it. Thanks to the cast operator that I have implemented it works as is:


DIM d AS CWstr = "12345"
d = MID(d, 2, 2)
print d


Prints: 23.

José Roca

With SELECT CASE LEFT you need to use SELECT CASE LEFT(**d, 2).

Using ** is the fastest way, specially when working with big strings, since you access directly the string data without having to create intermediate strings.

However, there is no problem with MID. This works :

SELECT CASE MID(d, 1, 2)

The only problem are LEFT and RIGHT.

José Roca

Quote
@ 5.) so the compiler looks, if there is a matching cast operator for a type, when it encounters an unexpected type (ptr) ? - that´s pretty cool. But why then "STRPTR" and others keep failing ? Maybe the compiler doesn´t do this in all cases ? I understand and i accept that just omitting "STRPTR" is a perfectly working solution. But i want to understand the background, why it works here and doesn´t work there

Because STRPTR is only prepared to work with STRING and WSTRING. An when used with WSTRING you must be aware that it still returns a ZSTRING PTR, not a WSTRING PTR.

There are operators (most of them) that are prepared to work with user defined types and others not.

Juergen Kuehlwein

Sorry, stupid me, i mixed it up


dim d as CWSTR = "123456789"
mid(d, 2,2) = ""AB"

doesn´t work, but

mid(**d,2,2) = "AB"

does

I want to get rid of the need to prepend "**" in this case (maybe by an overloaded sub "MID" ...)



Quote
With SELECT CASE LEFT you need to use SELECT CASE LEFT(**d, 2).
You don´t, if you implement it the way i proposed - and you may use "**" without problems nevertheless, if it must be as fast as possible. I wonder, if it is possible to have the best of both ways by avoiding the intermediate copy, but with my (still) limited knowledge in FreeBASIC, i wasn´t able to find a solution - maybe you can do it. 



JK

José Roca

Keep in mind that FB, as any other programming language, has bugs and quirks.

The behavior of LEFT and RIGHT with user defined types has already been reported.
See: https://sourceforge.net/p/fbc/bugs/843/

So maybe one day it will work without workarounds. Then I will have to remove the current overloads for them.