views:

79

answers:

2

I have a class (JSObject) that implements the IDispatch interface. The class is exposed to JavaScript running in my hosted web browser control (IWebBrowser2).

See more here about how this works: http://stackoverflow.com/questions/3747414/calling-c-function-from-javascript-script-running-in-a-web-browser-control

I can call in to JSObject from my JavaScript code, and I can receive returned integers/longs. But something goes wrong when the function returns a string (BSTR).

This is a part of the IDispatch::Invoke() code:

int lenW = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, "Returned string", -1, 
    NULL, 0);
BSTR bstrRet = SysAllocStringLen(0, lenW);
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, "Returned string", -1, bstrRet, 
    lenW);

pVarResult->vt = VT_BSTR;
pVarResult->bstrVal = bstrRet;

// Who calls SysFreeString(bstrRet);?

With the above code you can alert() the returned string, but you can not add to it. alert(returnedString + "foo"); will only show "Returned string". The "foo" part is not added to the string. There seems to be something wrong with the end of the string somehow. Any ideas anyone?

Also, am I leaking memory here since I'm not calling SysFreeString()?

EDIT:

I temporarily included atlbase.h so I could use CComBSTR. The above code now looks like this:

pVarResult->vt = VT_BSTR;
pVarResult->bstrVal = CComBSTR("test string");

Stepping through that code definitely shows that pVarResult is "test string" all the way until the function returns. But when I alert() the returned string in my JavaScript code I get "expanded". alert(returnedString + "foo") is "expandedfoo". So it is a small step in the right direction as you can add to the returned string. But it's also a step in the wrong direction as the returned string isn't what I really returned...

*pVarResult = CComVariant("test string");

That code gives the same results as the code in the previous listing (using CComBSTR).

A: 

Debugging the code below a few interesting things becomes apparent.

int lenW = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, "testing testing", -1, 
    NULL, 0);
BSTR bstrRet = SysAllocStringLen(0, lenW);
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, "testing testing", -1, bstrRet, 
    lenW);

BSTR bstrRet2 = SysAllocString(L"testing testing");

int len1 = SysStringByteLen(bstrRet);
int len2 = SysStringByteLen(bstrRet2);

len1 is 32. len2 is just 30. SysAllocString works with the JavaScript code I have, the other method does not.

Looking at the memory where bstrRet is allocated I can see it ends with 0x00 0x00 0x00 0x00 while bstrRet2 only has 0x00 0x00. So my guess is that an extra null-terminator is sent to the JavaScript code when using bstrRet that throws it off. That's why you can't append anything to it. bstrRet2 does not have the extra null-terminator.

Knowing that the original code in the question can be made to work if it's modified like this:

int lenW = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, "Returned string", -1, 
    NULL, 0) - 1;
BSTR bstrRet = SysAllocStringLen(0, lenW);
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, "Returned string", lenW*2, bstrRet, 
    lenW);

pVarResult->vt = VT_BSTR;
pVarResult->bstrVal = bstrRet;

I'm not sure if doing lenW*2 is safe, but that code seems to work this far in the limited testing I've done.

Tobbe
+1  A: 

The first MultiByteToWideChar() call returns the amount of characters needed to store the string, including the null-terminator. Then SysAllocStringLen() allocates a buffer for lenW+1 characters (one more than needed) and already null-terminates it.

As the MultiByteToWideChar() also writes a null-terminator, you end up with two at the end of the string. For BSTRs embedded null-characters are possible as they are length prefixed, so the JScript implementation probably concatenates without removing the additional one... thus you end up with a string with an embedded null-character in the middle which will only be printed partially.

Long story short, fix the lengths:

lenW = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, str, -1, NULL, 0);
bstrRet = SysAllocStringLen(0, lenW-1);
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, str, -1, bstrRet, lenW-1);

As mentioned in the comment, the string is to be freed by the caller - the memory management rules dictate that out-parameters are owned by the caller.

Georg Fritzsche
So, just to make sure I get this. In the last MultiByteToWideChar call we could just use lenW as well as lenW-1, right? With lenW-1 we don't copy the \0 in str and use the one SysAllocStringLen has already provided for us. With just lenW we overwrite the \0 SysAllocStringLen created with the one from str. Correct?
Tobbe
@Tobbe: Correct, with the above code `SysAllocStringLen()` allocates a buffer for `lenW` characters and you'd overwrite the last one, the null-terminator.
Georg Fritzsche