Question

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?!

Was it helpful?

Solution

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); 
} 

OTHER TIPS

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;
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top