Declare to external dll ptr and string array problem

Hi. I’m new to declares and am struggling to understand how to declare a dll function that has a CString array paramater and then access that array as a Xojo string array. I’m assuming that I have to use a pointer to the CString array instead. Here’s the call:


Soft Declare Function GetSynonymList lib "My_DLL_Lib" (synonyms As Ptr, word As CString) As Integer

// Result is the count of synonyms found, i.e. the number of CString items in synonyms array

Dim synonyms() As String
Dim result As Integer

result = GetSynonymList(synonyms, "grateful")

If result > 0 then synonyms have been found so I need to loop throught the referenced “synonyms” array pointer but don’t know how.

Can anyone please advise me on how to do this?

Thanks.

It really depends on the implementation of GetSynonymList in the dll. In general, though, there is no automatic conversion of an array of strings built in to Xojo to help you. You’re going to have to get an amorphous chunk of memory back from the dll and pick out the contents yourself.

You will need to pass a MemoryBlock to the first parameter, not a string or array of strings. Passing a string to the second parameter is correct, as Xojo does do an automatic conversion from String to CString when passing data TO the dll (it does not do an automatic conversion when getting data back from the dll).

The first consideration will be, Who is responsible for allocating the memory for the array of strings? It can go either way. MS tends to require the caller to allocate the memory (so that he will be responsible for freeing the memory - it’s just cleaner if the caller is responsible for both ends of the memory management). In that scenario, you first call the dll with a nil synonyms parameter and it responds with the amount of memory that would be required if it ran the query using your word. I’m guessing that’s not the way your call is set up. The alternative would be that the caller allocates a “sufficiently large” block of memory and passes that to the dll. The dll would return an error if the block were not large enough.

The other scenario is where the dll itself allocates the memory. It would usually also provide another call to free that memory.

So, in your dll, who allocates the memory? And who is responsible for freeing it?

Typically, an array of strings in a C type layout will be formatted as a block of pointers followed by the data being pointed to. So if you had 3 strings returned, you would have 3 4-byte values at the beginning of the block of memory. The 12th byte would be the beginning of the first CString and run for the length of the string plus a null byte. The next byte following that run of bytes would be the start of the next string, etc.

Occasionally, a dll call will return an array of references to static strings (a list of device names, for example) that don’t need to be allocated or freed, since they are part of the OS data. The documentation for the dll call will make this clear. In that case, you’ll get a block of memory that contains just the 4-byte pointers. That block will need to be allocated/freed, but the strings it points to won’t.

The short answer to your question is, you’ve gone beyond the automatic conversions that Xojo can provide and need to deal with the specifics of the data layout that your dll uses.

Hi Tim. Thanks for your detailed reply. Please bear with me here because I don’t understand all of it too well.

The example I posted is actually a trimmed down example of what I was trying to achieve because I thought it would be easier. But what I am actually trying to do is access the Hunspell dll to get a list of word “suggestions” (not synonyms) from the Hunspell C++ library.

Here is the original dll C++ code:

/* spell(word) - spellcheck word
 * output: 0 = bad word, not 0 = good word
 */
LIBHUNSPELL_DLL_EXPORTED int Hunspell_spell(Hunhandle *pHunspell, const char *);

/* suggest(suggestions, word) - search suggestions
 * input: pointer to an array of strings pointer and the (bad) word
 *   array of strings pointer (here *slst) may not be initialized
 * output: number of suggestions in string array, and suggestions in
 *   a newly allocated array of strings (*slts will be NULL when number
 *   of suggestion equals 0.)
 */
LIBHUNSPELL_DLL_EXPORTED int Hunspell_suggest(Hunhandle *pHunspell, char*** slst, const char * word);

My code to create the hunspell object (not shown here) and check whether the “word” is misspelled works but the “suggest” call is the problem one:


    Soft Declare Function Hunspell_spell lib dylibLocation (obj As Ptr, word As CString) As Integer // check spelling
    Soft Declare Function Hunspell_suggest lib dylibLocation (obj As Ptr, suggs As Ptr, word As CString) As Integer // return suggestion count and suggestions
   
 // I've already created "spellObj" reference using another declare
    Dim result As Integer
    Dim m As MemoryBlock

      result = Hunspell_spell(spellObj, "bubbble") // 0 means misspelled
      
      If result = 0 then
        
        msgbox "Word is misspelled."
        
        // This is what I do not know how to handle...
        
        result = Hunspell_suggest(spellObj, m, "bubbble")

        // and then loop through returned array...

       Else
          msgbox "Word is spelled correctly."
       End If

 Hunspell_destroy(spellObj) // destory hunspell object

I am assuming that the dll handles the memory management so do not know how to allocate a size to the memoryblock or access the string of words returned in it.

Have you tried passing byref suggs As Ptr? Then to dll would set it to point at the newly created array… I think char*** slst means something like “array pointer reference” I could be totally wrong though…

Hi Jim. I haven’t yet because until I know for sure how to handle the memoryblock or ptr return value it would not help me. Once I know how to get the string array back from the memoryblock (and loop through it for each returned suggested word string) then I guess I can test using ByRef if necessary.

I’ve managed to get information back into the memoryblock but that’s only by setting the byte size as (5000) upon creation since I have no idea how big the return value is because that is only known when the declare function is called.

If I don;t set the memoryblock size first I get a nil object exception. I’m sure there must be a way to pass a nil value memoryblock or empty memoryblock and have the declare function set the size upon its creation…but I don’t know how.

If hunspell handles memory management (which the call to hunspell_destroy suggests) then you should pass suggs byref, since hunspell is going to create the memory and pass it to you, rather than fill in the memory that you pass to it.

ByVal (the default) = I create the memoryblock and you fill it in
ByRef = you create the memoryblock and tell me where it is.

Hmm… looks like it returns the number of results and I’m betting the array is a sequence of ptrs to cstrings. So you should be able to loop through the memoryblock and get each result as a memoryblock… then get the string from each memoryblock. I don’t remember how to dereference the ptrs though.

Right. Okay. I think I understand. So can you show me how to code this properly since I have no idea.

Do I create Dim m as memoryblock before the suggestions declare call and pass the “m” in the declare or not? If I declare it then how do I pass it without a size and without creating a nil object exception?

And how do I handle the results and traverse the memoryblock reference?

I’m sorry, This really is my first time at dealing with declares to an external dll and using memory blocks.

I have been googling for hours on C++ and both the old and new Xojo forums trying to understand this and my head is hurting.

Thanks again.

You should pass m, the memoryblock, ByRef so hunspell_suggest can set it. You’re getting a nilobjectexception because you are passing it byval instead of byref.

    Soft Declare Function Hunspell_suggest lib dylibLocation (obj As Ptr, ByRef suggs As Ptr, word As CString) As Integer // return suggestion count and suggestions

Unfortunately, I’m in the same boat as Jim. My pointer dereferencing is a little foggy. Maybe one of the Xojo engineers can chime in. I’ll hunt around for some sample code.

Okay so I’ve tried that out (using ByRef) and this is what happens.

Test 1

dim m as memoryblock
result = Hunspell_suggest(spellObj, m, "bubbble")
compile error: Parameters are not compatible with this function

It works if I do dim m as new memoryblock(5000) etc but not without the byte size.

Test 2

dim p as Ptr
result = Hunspell_suggest(spellObj, p, "bubbble")

This works and result = 7.

right, so then if you do

dim m as memoryblock m=p
m should be a valid memoryblock with size unknown (but you can assume it is the returned length*length of a ptr)

Looping through the values would be something like

dim i as integer dim resPtr as ptr dim resMem as memoryblock dim resString as string for i=0 to result-1 resPtr= m.ptr(i*4) resMem=resPtr resString=resMem next

(I haven’t tested any of the BTW…)

I was about to post Jim’s code. The only change would be

theString = resMem.CString(0)

I was just about to edit that :slight_smile:

That worked! Thank you both so much! :slight_smile: