1

I've written some code in C for converting strings passed from VBA, when the C code is called from VBA from a MacOSX dylib. I got some good hints here, and since I only care about ASCII strings I've written the following functions to convert the BSTR to a simple char*:

#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include "myheader.h"

size_t vbstrlen(BSTR *vbstr)
{
    size_t len = 0U;
    while(*(vbstr++)) ++len;
    len = len*2;
    return len;
}

void vbstochr(BSTR *vbstr, char** out)
{
    int len2 = vbstrlen(vbstr);
    char str[len+1];

    int i;

    for(i = 0; i < len; i++)
    {
        str[i] = (char) (((uint16_t*) vbstr)[i]);
    }

    str[i] = '\0';

    asprintf(out, str);
}

int test(BSTR *arg1)
{
    char* convarg;
    vbstochr(arg1, &convarg);

    return 1;
}

The myheader.h looks like this:

typedef uint16_t OLECHAR;
typedef OLECHAR * BSTR;

. I used uint16_t because of the 4 byte (not 2 byte) wchar_t in the MacOSX C compiler. I added a breakpoint after vbstochar is called to look at the content of convarg, and it seems to work when called from Excel.

So this works, but one thing I don't understand is why I have to multiply my len in the vbstrlen function by 2. I'm new to C, so I had to read up on pointers a little bit - and I thought since my BSTR contains 2 byte characters, I should get the right string length without having to multiply by two? It would be great if someone could explain this to me, or post a link to a tutorial?

Also, my functions with string arguments work when called in VBA, but only after the first call. So when I call a function with a BSTR* argument from a dylib for the first time (after I start the application, Excel in this case), the BSTR* pointer just points at some (random?) address, but not the string. When I call the function from VBA a second time, everything works just fine - any ideas why this is the case?!

Community
  • 1
  • 1
Martin
  • 146
  • 7
  • This code cannot compile. Post *real* code, warts and all. – Hans Passant Apr 10 '12 at 01:11
  • @HansPassant The `vbstrlen` bit, with trimming non vital `#include`'s, does compile but, and compiled in a dylib and used from excel 2011 VBA, keeps return 0 for any VBA string. (With `typedef wchar_t* BSTR;`.) – Olórin Mar 08 '16 at 16:29

2 Answers2

2

A BSTR has an embedded length, you do not need to calculate the length manually.

As for the need to multiply the length by 2, that is because a BSTR uses 2-byte characters, but char is only 1 byte. You coded your vbstrlen() function to return the number of bytes in the BSTR, not the number of characters.

Since you are only interested in ASCII strings, you can simplify the code to the following:

#include <stdlib.h> 
#include <stdio.h> 
#include <stdint.h> 
#include "myheader.h" 

size_t vbstrlen(BSTR *vbstr) 
{ 
    if (vbstr)
      return *(((uint32_t*)vbstr)-1);
    return 0; 
} 

void vbstochr(BSTR *vbstr, char** out) 
{ 
    size_t len = vbstrlen(vbstr); 
    char str[len+1] = {0};

    for(size_t i = 0; i < len; ++i) 
        str[i] = (char) vbstr[i]; 

    asprintf(out, str); 
} 
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • I tested your vbstrlen function in a dylib used in VBA (with excel 2011 on mac), it keeps returning 0 for the "toto" string. Any idea ? – Olórin Mar 08 '16 at 16:18
  • According to [this](http://www.codeproject.com/Articles/810282/Microsoft-Office-VBA-to-the-Macs) and [this](http://stackoverflow.com/questions/9833808/mac-office-2011-vba-and-dylib), it seems that Excel 2011 VBA on a MacOSX *does not* exchange strings with a dylib using the same in-memory format that a COM BSTR uses. It uses null-terminated `char*` strings instead. Also, a tricky part is that the dylib cannot allocate an output string and return it to VBA, as VBA will not free it correctly, so your VBA code will have to allocate the string and pass it to the dylib to fill in. – Remy Lebeau Mar 08 '16 at 16:45
  • One question concerning the tricky part : it his also the case in windows or ? – Olórin Mar 09 '16 at 21:05
  • On Windows, VB/VBA and COM all use `BSTR` strings, and `BSTR` is allocated using a system-provided allocator, so one module/process can allocate memory (`SysAllocString()`, etc) that can be freed using the same allocator in another module/process (`SysFreeString()`, etc). This is fundamental to COM programming. – Remy Lebeau Mar 09 '16 at 21:20
  • Yes I know, sorry for the extremely bad phrasing, I mean : `SysAllocString` and `SysFreeString ` are c++ only functions, right ? So I may `SysAllocString`a string that I'll push to vba via a dll, but I won't `SysFreeString` it so that VBA will take care of the memory release - on windows. (And impossible on mac os x you say.) As I am not an expert, what is done behind the scenes under windows in vba to make it able to take care of this memory release ? – Olórin Mar 09 '16 at 22:19
  • They are Windows API functions, not C++ functions. They are implemented at the OS layer. Different processes/modules that call the `SysAlloc...()`/`SysFree...()` functions are sharing a single system-provided memory manager, that is why they can free each other's allocated memory across process/module boundaries. – Remy Lebeau Mar 09 '16 at 22:28
  • Ok makes sense... Just to be sure, there's really no way of constructing a string in c++ and passing it to the dylib in VBA ? – Olórin Mar 11 '16 at 16:22
0

The chances are that the VB string is a UTF-16 string that uses 2 bytes per character (except for characters beyond the BMP, Basic Multilingual Plane, or U+0000..U+FFFF, which are encoded as surrogate pairs). So, for your 'ASCII' data, you will have alternating ASCII characters and zero bytes. The 'multiply by 2' is because UTF-16 uses two bytes to store each counted character.

This is almost definitive when we see:

typedef uint16_t OLECHAR;
typedef OLECHAR * BSTR;
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278