0

Just as the question says. I would like to be able to make an array of strings in some VBA code, in this code I would like to make them a certain length. Then how would I pass them into a C++ DLL, in the DLL I would like to store a string in each element and make these elements accessible from the VBA afterwords. I have already done this for a single string, and for a double array as follows.

Dim myStr As String * sizeConst

Dim dataArray() As Double
ReDim dataArray(0 To (arrayLength - 1)) As Double

Then passing them to a DLL from within the VBA.

Public Declare Function myFunc _
Lib "PathToDLL.dll" _
(myStr As String, ByVal sizeConst As Integer, dataArray As Double, ByVal arrayLength As Long) As Long

Then in the DLL I can step through each element in the double array. However I don't know how to do this for a string array. I am unsure about the actual memory size of the strings I would be passing in from the VBA would they be of size sizeConst + 1? I need to know this to know how much I should increment to get to the next string element. Can someone show me how to declare a string array in VBA with a constant length for each element. Then how to pass that array to the DLL and how to increment to the next element in the string array in the DLL.

Cœur
  • 37,241
  • 25
  • 195
  • 267
  • Thank you for down-voting my answer. If the answer for the question: "how to create an array of string of fixed size" does not met your criteria, you have to be more specific and provide proper details about your issue. – Maciej Los Jun 20 '17 at 06:36
  • @MaciejLos The important part is "Then how would I pass them into a C++ DLL". See https://stackoverflow.com/questions/44396538/vba-excel-and-c-dll-specifically-problems-with-strings#comment76262029_44396538 for more context. – GSerg Jun 20 '17 at 07:02
  • @GSerg, This is the second part of question. I provided this information: `Note that your dll expects as an input **myStr As String**, not an array of strings`, because of function declaration. The trick is the skill to ask a question! – Maciej Los Jun 20 '17 at 07:58
  • @MaciejLos It is a common practice in VBA to declare the API parameter as "byref something" and then pass `arr(lbound(arr))`. Then the called site will have access to the entire array. – GSerg Jun 20 '17 at 08:02
  • Maciej Los If you are talking to me about down-voting your answer, It was not me, I do not even have enough reputation to do so as I am new to stackOverflow. Thank you once again GSerg, and thank you for trying to help Maciej Los it was appreciated. – Alex Wardlow Jun 20 '17 at 23:31

1 Answers1

2

When you declare arr() as string in a Declared function, VB will send out an LPSAFEARRAY* (a double pointer, SAFEARRAY**) consisting of BSTRs (FADF_BSTR | FADF_HAVEVARTYPE). That is, the conversion to LPSTR will not be performed.

On the C++ side you would declare the parameter as LPSAFEARRAY* and use the SafeArray* family of functions to manipulate the data.

Note that you are responsible for freeing BSTRs you are going to replace, if you are going to replace them.

You will get all the information about the array size and dimensions from the LPSAFEARRAY, and each BSTR stores its string length.


Same happens when you declare arr() as string * some_length, but this time the SAFEARRAY will be of FADF_HAVEVARTYPE only, and SafeArrayGetVartype will return VT_ERROR. Each element of the safe array will be a preallocated blob of some_length wide chars (some_length * 2 bytes), with no zero terminator or string length stored separately. Arguably it's even easier to work with, because it's preallocated, you can simply fill the existing memory of each element.


If you were to use mere strings, without fixed length, you could also pass them in the same way you are passing the pointer to double:

declare function myFunc ... (byval strings as longptr, byval count as long)
dim s() as string
redim s(1 to 5)

myFunc varptr(s(lbound(s))), ubound(s) - lbound(s) + 1

Then on the C++ side you would receive BSTR* of the first string, and to advance to the next string you would add 1 to it like you would normally do with pointer math. You need to pass number of elements as a separate parameter to know how many strings there are.

This will not work for fixed-length strings.

GSerg
  • 76,472
  • 17
  • 159
  • 346
  • Thanks GSerg. I think I will simply use the non-fixed length example. how come the type on the c++ side would be LPWSTR rather than a pointer to the first BSTR, wouldn't there be extra memory from the other BSTR elements (the memory that stores the size) that I would have to dodge? – Alex Wardlow Jun 20 '17 at 23:39
  • I want to stress again, how thankful I am of the help you are providing me in my previous post and this one as well. – Alex Wardlow Jun 20 '17 at 23:46
  • @AlexWardlow I'm used to calling it `LPWSTR` because that's what happens most of the time on the C++ side. Yes, the pointers will be `BSTR*` too. You won't need to dodge anything because this is how `BSTR` is designed and because the string data (as opposed to string pointers) is not stored in a continuous block of memory one after another. – GSerg Jun 21 '17 at 06:49
  • If I do not have *# to define length how big will they be. Len(s) displays 0. Will I have to do some allocation on the DLL side? Currently it crashes. Same with original code with a single string without the *# to define length. So I assume it is a memory allocation issue right? – Alex Wardlow Jun 22 '17 at 14:43
  • @AlexWardlow The usual scenario is that the DLL has access to the converted string buffer provided by the compiler, so it has to use whatever space the string already has. If you don't preallocate string on the VB side you will have no space. You can go with this approach and preallocate on VB side. If you like you also can allocate `BSTR`s on the DLL side, just be sure to do it properly (https://msdn.microsoft.com/en-us/library/windows/desktop/ms221481(v=vs.85).aspx, https://msdn.microsoft.com/en-us/library/windows/desktop/ms221458(v=vs.85).aspx etc). – GSerg Jun 22 '17 at 15:19
  • I am afraid that I am still not getting it. I looked around for some examples but they seem scarce. If I wanted to do the easy option of preallocating in the VBA and sending a LPSAFEARRAY*. Can you be more specific on how I would do that? How would I send out this multielemet array from VBA, like the double array? How would I access/change the string inside in the DLL? Thanks again for your help, sorry I need more. – Alex Wardlow Jun 27 '17 at 19:05
  • @AlexWardlow You create the array as usual in VB. You assign a dummy string for each member of the array (e.g. `= String(0, 100)`). You pass that array to the dll. You access the array elements with the `SafeArray*` family of functions like I said in the answer. – GSerg Jun 27 '17 at 19:09
  • I am trying to set each element to a desiredString of length stringLength, after which I increment currentElement, is this right? mbstowcs(SafeArrayGetElement(ar(), 0, currentElement),desiredString,stringLength); – Alex Wardlow Jun 27 '17 at 19:36
  • @AlexWardlow no, it's not. Second parameter for `SafeArrayGetElement` is a pointer to array indices, it cannot be `0`. `SafeArrayGetElement` returns `HRESULT` which cannot be passed to `mbstowcs`. – GSerg Jun 27 '17 at 20:33
  • Hello again. So I finally got around to this code at the end of the last month and it is so close to being where I want it. But I have hit a snag. Could you help me out this one last time in implementing what we talked about in this post? Here is the link to my current issue. https://stackoverflow.com/questions/45516146/random-characters-from-casting-arduino-string-to-excel-bstr-through-c-dll?noredirect=1#comment77993601_45516146 – Alex Wardlow Aug 04 '17 at 22:11