• Welcome to Jose's Read Only Forum 2023.

FB and PB string integration in DLL

Started by Eros Olmi, June 19, 2007, 01:18:47 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Eros Olmi

I do not know if I am out of forum scope but I'm very interested in writing DLLs using FB.
In particular I'm interested on developing FB DLLs interacting with PB main EXE passing in/out dynamic strings.

It seems PB dynamic strings are very close to FB one but on my tests I get strange results.

Example. Here thinBasic_ParseString is a PB function that has a BYREF string as parameter.

sub msgbox(s As String)
MessageBox(null, WStr(s), WStr("Info"), MB_OK Or MB_ICONINFORMATION)
End sub

Function Exec_TestString() As String
Dim s As string
Print "s=" & s
function = s
End Function

My results are:
Print "s=" & s   '---Print nothing
msgbox(s)      '---Show the string returned by thinBasic_ParseString
function = s   '---return nothing

Does someone have any idea/examples?

Thanks a lot
thinBasic Script Interpreter - www.thinbasic.com | www.thinbasic.com/community
Win7Pro 64bit - 8GB Ram - Intel i7 M620 2.67GHz - NVIDIA Quadro FX1800M 1GB

José Roca

A FreeBASIC String is essentially a built-in user-defined type that, among other things, contains a pointer to the string data and it's length. This type is called a 'descriptor' internally, and it is this descriptor that is passed to procedures when it's parameters are passed by reference.
When passed by value, however, the actual string data is passed, not the descriptor. Internally, the string data is terminated with the NULL character (chr(0)). When the temporary String is created for the procedure, FreeBASIC copies the string data passed up until the first NULL character. If the String argument passed contained NULL characters within the string, then the rest of the string data after the first NULL is not copied, and the string is truncated. You also run the risk of writing outside the bounds of the allocated memory.
This behavior will eventually be fixed to act as it should: a temporary descriptor will be created for the procedure and a 'deep copy' of it's fields will be made (notably, the entire string data itself will be copied into new memory, and the new descriptor will point to that memory).
To be safe, pass Strings by reference. If you need to simulate the correct behavior of passing a String by value, either 1) create a temporary String, initialize it with the String you want to pass and pass the temporary by reference, or 2) pass the String by reference and have the procedure create and initialize a temporary String and work with the temporary.

Since FreeBasic uses a proprietary format, you will need to write your own functions to read the FreeBasic string descriptor, retrieve the length and address of the data and use CopyMemory to copy the data to the PB string.

Charles Pegge

I noticed that in Powerbasic the address STRPTR(S)-4 always contains the exact length of the dynamic string S whereas in Freebasic, this address holds a value which is always larger than the designated string length. I presume it is the length of the buffer containing the string within it.

So I think the safest way to is to pass the strptr and the length of the string explicitly, This will ensure that string data is always independent of language, whether it is Freebasic, Powerbasic or C under MS or Linux.

You can add a null to the end of the string too.

Then it does not matter whether your string is dynamic, static or null-terminated. Your DLL or SO will see them the same way.

Donald Darden

Eros' code example is only partial - there are dependencies, such as MessageBox,
WStr(), null, and thinbasic_ParseString() are undefined as it stands.  It is always better to provide a complete working example in order to test it adequately.

Your most likely problem with this example is the use of WStr(), which is likely a function to convert ASCII coded string to Unicode (2-byte) strings.  MessageBox
works with ASCII coded strings, so the extra byte, which would be zero, is probably seen as a string truncator.  Try it without WStr().

Charles discovery that LEN(nn$) in PowerBasic appears 4 bytes before the location in memory where  STRPTR(nn$) is stored, echos my own findings, and is in contrast to the positon of LEN(nn$) in FreeBasic, which appears 4 bytes after the point in memory where STRPTR$(nn$) appears.  In both cases VARPTR(nn$) points to the location where STRPTR(nn$) is stored.  VARPTR(nn$) is what you get when a string is passed BYREF.  That gives you a connection between VARPTR(), STRPTR(), and LEN() as to where they reside in memory with respect to each other.  STRPTR() of course points to the first byte or character of the string itself.

José Roca

PowerBASIC uses the API function SysStringByteLen to allocate dynamic strings. SysAllocStringByteLen takes an ANSI string as input, and returns a BSTR that contains an ANSI string. Does not perform any ANSI-to-Unicode translation. The returned handles are used to perform string manipulation, and to free the memory calling SysFreeString.

The BSTR data type is defined in the OLE 2.0 specification.

Length   Data
xxxx     xxxxxxxxxxxxxxxxxxxxxxxxxxx

BSTRs are null terminated.

BTW unlike the PB Forum, this forum does word wrapping. You don't need to press ENTER to cut long lines.

Donald Darden

My mistake.  I had the impression that Eros' example was written for FreeBasic,
but going back and reading it, he was using ThinBasic, which has as its base PowerBasic.  I guess I lost track of what he was asking.  Not having tried ThinBasic yet, I'm far less certain about my answer above.

I am aware that this forum supports auto wordwrap, but the PowerBasic, where I do a lot of posting, does not, and having developed the habit of hitting Enter at the right side of dialog screen is hard to break.

I may have to look at ThinBasic at some point, but I have hardly begun to really try to get on top of FreeBasic yet.  Charles' interest in FreeBasic got me partially started, and finding that it is a cross-platform compiler, currently working with DOS, Windows, and Linux, was the final draw.  As I've said before, I'm not planning to migrate to Vista, and once support for Vindows 2K/XP is dropped,
I will likely switch to Linux, and look for a way to run Windows Apps in virtual mode.  At least that is my current plan.  Moving to Linux would be made easier if I had a basic programming language in common to both.  That could well be
FreeBasic if it proves to meet my expectations.

Eros Olmi

Hi Donald,

no problem. I was asking that because I'm trying to use FreeBasic for SDK thinBasic development.
If you will play with thinBasic you will see that it has a modular structure in the sense that the main parsing, memory handling, scriptf flow control structure, string and math functions are inside a Core module developed using Power Basic. Than there are many satellite modules (read dlls) that implements thinBasic language adding new keywords to the interpreter.

To develop those modules we have created an SDK (mainly functions interfaces to main Core module). We have created SDK for Power Basic, for C, for IBasic and for ASM. Now we are trying to make SDK for FreeBasic but I need to discover exactly how string hare handled, otherwise I have to write my own interfaces like we did for SDK in C.

In any case, thinBasic is an interpreter. Very fast and witha  lot of functions covering many different aspects. We are making it even faster and stable. It is  a typed language with all the data types supported by Power Basic. In memory, variables are handled exactly like Power Basic does so VARPTR, STRPTR and any pointer to a variable are 100% compatible with Power Basic handling even if we use our way to store and retrieve them.

Maybe, if interested, I can give more indications in future

thinBasic Script Interpreter - www.thinbasic.com | www.thinbasic.com/community
Win7Pro 64bit - 8GB Ram - Intel i7 M620 2.67GHz - NVIDIA Quadro FX1800M 1GB

José Roca

This is the structure used by FreeBasic as a string descriptor:

  123 /** Structure containing information about a specific string.
  124  *
  125  * This structure hols all informations about a specific string. This is
  126  * required to allow BASIC-style strings that may contain NUL characters.
  127  */
  128 typedef struct _FBSTRING {
  129     char           *data;    /**< pointer to the real string data */
  130     int             len;     /**< String length. */
  131     int             size;    /**< Size of allocated memory block. */
  132 } FBSTRING;

The runtime library is written in C and they use malloc to allocate the memory for the strings.

Eros Olmi

Thanks a lot José.
I will implement some new thinBasic functions to handle that structure.

Funny, if structure could just be like
typedef struct _FBSTRING {
     int             len;     /**< String length. */
     char           *data;    /**< pointer to the real string data */
     int             size;    /**< Size of allocated memory block. */

it could be very close to PB string.

thinBasic Script Interpreter - www.thinbasic.com | www.thinbasic.com/community
Win7Pro 64bit - 8GB Ram - Intel i7 M620 2.67GHz - NVIDIA Quadro FX1800M 1GB

Donald Darden

Actually, if you pass a string's VARPTR().  you should be able to use PEEK$() to create a local or static string and grab the string contents from the other language format.  Declare your function to return a dword or equivalent type, use a STATIC local string for your results, and return the VARPTR() to it.  You can then use PEEK$() again with the returned value in the other language to copy the string contents to a string there.  Declaring the final result string as STATIC in the function should keep it intact after you exit the function.

I haven't tried it yet, as I am going out of town shortly for the day.  You can
probably whip up a FB_Format type in PowerBasic, and a PB_Format type in FreeBasic to make this more eligant, but it seems straightforward enough.

This approach requires that the DLL be written to work specifically with the
other language.  If you want to work with existing DLLs. you have to accomodate their calling conventions.  Here you would create a type that
would support the call, define some variables of that type, and fill the corresponding fields in that variable with STRPTR() and LEN() information concerning the string you want to pass.  Then you would pass a pointer to that variable in lieu of actually passing a string as a parameter.  I will have to think a little bit about what happens when PowerBasic returns a string though.  That could be a little bit tricky to handle.  I don't know right now if it returns the string's handle or what.  The good news is that PowerBasic can only return a
string or a number from a function.  The bad news is that a PowerBasic procedure might actually modify a parameter in place, such as modify an existing string, and that would not bode well for string integrity.