Correct idiom for passing CString via structure?

OK, we’ve all been bitten by this at some point, but I THOUGHT I had this straight with Xojo:

I have a struct, foo_struct that contains a CSTRING:

CString bar1
CString bar2

…this struct gets passed by reference to a DLL function (and generally, it works fine).

EXCEPT: I’m assigning to that CString from a String (legal but probably not smart):

Var myString1 as String = "some text"
Var myString2 as String = "more text"

Var myStruct as foo_struct

myStruct.bar1 = myString1
myStruct.bar2 = myString2

…at this point, fields bar1 and bar 2 are (of course) NOT CStrings with my expected text.

So, of course, I need to first assign the Strings to a Var CString:

Var myCString1 as CString = myString1
Var myCString2 as CString = myString2

myStruct.bar1 = myCString1
myStruct.bar2 = myCString2

Of course, that WORKS. But is that the CORRECT idiom for Xojo?

Strings automatically convert to CStrings, so it is legal to assign them directly. Also, strings in a Struct are fixed length and zero filled, effectively making them a CString, so you can use String and CString interchangeably in the Struct. Ie., bar1 and bar2 could be defined as String instead of CString and it would still work in the declare.

2 Likes

In fact, I would go so far as to say that bar1 and bar2 should be defined as String in the Struct, not CString. Using CString in a struct is confusing.

2 Likes

@Tim_Hare – But what I’m saying is that in the first part of my example, where I am assigning them directly to the struct’s CString field – by the time that struct is passed INTO my DLL, those aren’t the CStrings I’m expecting.

bar1 and bar2 should be defined as String in the Struct

So are you saying that even if I pass a struct by reference into a DLL method, that those fields, even if defined as a Xojo String, will magically be char* pointers to the DLL? (really?)

Now that is a different question. The struct allocates a fixed length space and stores the string there (Whether you define it as String or CString, it’s all the same - CString is defined as an array of characters terminated by a /x00.) Char * is different; it’s a 4-byte pointer to a section of memory that contains a CString. Now you have to start manipulating Ptr values. It’s been a while, so I’ll have to refresh my memory on what that entails. I think you’ll have to allocate a memoryblock to hold the strings themselves, but it’s all a little hazy.

The struct allocates a fixed length space and stores the string there (Whether you define it as String or CString, it’s all the same - CString is defined as an array of characters terminated by a /x00.)

@Tim_Hare – I think that’s not correct. If you look at Xojo’s struct definition (this for the actual structure I’ve defined), you can see that the CString’s are just pointers:

I stand corrected. Then, yes, you should use a CString variable as an intermediary, if regular String variables are not working. Though in theory they should.

I THINK the problem with just directly assigning without the intermediary CString variable(s) is that it fools Xojo’s reference counting. In theory, Xojo itself should either disallow the direct assignment from String to CString structure member – or at least throw up a warning.

That sounds like a bug. When you assign a String to a CString variable, Xojo creates a copy of it, because Strings are immutable and we can’t have the declare changing a String. It all works as it should when you pass a String directly to a declare where you haved defined the parameter as CString, but if it doesn’t work with a Struct, that’s a bug.

Agreed. I noticed I could pass a String as a CString parameter, and it always did the right thing.

How should that bug be reported?

Bugs are reported through the Feedback app. It can be downloaded from xojo.com.

Done:

“67858 - Assignment of String to structure member of type CString doesn’t work”

A cstring adds a \0 or null character. A string doesn’t do this. A string in this case i equivalent to a c char array. So you should use string instead since the lenght is pre defined.

@DerkJ — I’m intentionally using a C-string because the structure that contains it is being passed into a DLL with a C-API. It would be lovely to pass a Xojo String, but on the DLL side the code behind the C-API is C++ code, and that CString (which is passed as a pointer, NOT a C-style character array) becomes a C++ std::string. In other words, Xojo Strings are not part of the binary API of C++’s standard library.

It’s necessary to pass a CString as a pointer — a char* — because otherwise you have a data structure that would need to be variable-sized, which, if it contained a lot of strings, would be unnecessarily wasteful. Since this is a binary API into the DLL, so long as the CString data persists long enough for my API call into the DLL, a pointer is perfect.

I normally try to avoid a C-style string when possible, but they’re not inherently evil: they can be composed of bytes of UTF-8, which a C++ std::string is happy to accept as Unicode encoding.

An interesting note about modern C++ std::string objects: they automatically do something called SSO, or Short String Optimization: for short strings under a specific threshold (usually the size of three pointers — ie 24 bytes), the object is created as a fixed-size structure that CONTAINS the data, thus avoiding any heap allocation with new / malloc. For lots of short strings this is a fast, efficient and flexible memory strategy.

You can set the string lenght in a structure:

MyVal As String * 20 // 20 bytes string

Or use CString but then you can store 1 byte less since cstring requires as null byte. Placing this after the actual length may cause crashes.

It should work but only if the length allows for it. We are also using this but without issues…

Here is how we have the structures;

Schermafbeelding 2022-02-26 om 16.55.28

Your reading code for strings (with UTF-8 encoding may be):

Var value As String = theStructure.description
value = value.DefineEncoding(Encodings.UTF8)
Return value

And it should work with c strings and/or char arrays.

The problem with this is that xojo doesn’t account for the nulls in the string this way. So a string has unendless amount of data as if a mem block with -1 size.

You better define as the c-type (in the structure) and revert back wen possible.

Would you mind showing the API function?
Your structure cannot be right. A structure is a memory block of a fixed size. You are reserving CStrings of Len 0 with your structure. They are 4 Bytes long, so there is only space for the ending 0 Char …
Either the CStrings have fixed lengths. In that case you have to define their standard (maximum) length in the structure.
Or you missed the * in the API definition which would mean that instead of a CString a Ptr to one is expected.

Currently if you try to assign a (C)String directly to your structure the compiler will try to fill the reserved memory space. Which does not suffice. It is debatable if an OOB exception would be a good idea in these cases.
My guess is you will have to pass the Ptrs to the Strings instead.

Would you mind showing the API function?

@Ulrich_Bogun – sure, happy to:

// This is the external C-API of the C++ DLL:
extern "C" MY_DLL_EXPORT int64_t CallDLLWithMultipleParams(const char* functionName, input_params * inParams, return_value * retVal);

This is the Xojo public function in module MyDLL that actually calls the DLL function:

Public Function XojoCallMyDLLMultipleParams (functionName as CString, ByRef inParam as MyApp.input_values, ByRef retVal as MyApp.return_value) As Int64

Declare Function CallDLLWithMultipleParams Lib "MyClientWinDLL.dll" (functionName as CString, ByRef inParam as MyApp.input_values, ByRef retVal as MyApp.return_value) as Int64
    
    Var retErr as Integer = CallPLMSMultipleParams(functionName, inParam, retVal)
    
    return retErr
  
End Function

Your structure cannot be right. A structure is a memory block of a fixed size. You are reserving CStrings of Len 0 with your structure. They are 4 Bytes long, so there is only space for the ending 0 Char …

Well, when I measure the actual size of that structure at runtime, the CString fields are in fact 8 bytes (because the Xojo’s IDE only shows the struct size as 32-bit, even though I’m only building a 64-bit app. This is a clear indication that they are in fact 64-bit pointers.

Var returnedValues as MyApp.return_value
Var inParams as MyApp.input_values

// assignment from Xojo String to CString first is REQUIRED

Var st1 as CString = aGuidString
Var st2 as CString = anotherGUIDString

inParams.fPLMSParamString1 = st1
inParams.fPLMSParamString2 = st2

inParams.fPLMSParamCount = 2

Var result as Int64 = MyDLL.XojoCallMyDLLMultipleParams("DLLFunctionName", inParams, returnedValues))

What I’m doing above IS working, and my strings that are way longer than 4 or 8 bytes. The DLL correctly receives and interprets those structure fields as a char * (a pointer to a c-string).

So, to be clear, if you put “name as cstring * 16”, then YES, a fixed size byte array of 16 bytes is allocated in the structure.

However, if you simply put “name as cstring”, then it appears there will be a POINTER allocated.

BUT – while you can assign an already allocated CString to that “name as cstring” field, you CANNOT assign a String to that field. Probably because the only thing that can be assigned is an already allocated CString.

I just ran some tests using a simple xojo console app, which confirmed this – UNTIL Xojo just simply stopped launching my simple (debug) console app.