【问题标题】:Returning string to JavaScript from C++ function从 C++ 函数返回字符串到 JavaScript
【发布时间】:2011-04-14 19:59:35
【问题描述】:

我有一个实现 IDispatch 接口的类 (JSObject)。该类暴露给在我的托管 Web 浏览器控件 (IWebBrowser2) 中运行的 JavaScript。

在此处查看有关其工作原理的更多信息:Calling C++ function from JavaScript script running in a web browser control

我可以从我的 JavaScript 代码中调用 JSObject,并且我可以接收返回的整数/长整数。但是当函数返回一个字符串(BSTR)时出现了问题。

这是IDispatch::Invoke()代码的一部分:

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

使用上面的代码你可以alert()返回的字符串,但是你不能添加它。 alert(returnedString + "foo"); 只会显示“返回的字符串”。 “foo”部分不会添加到字符串中。不知何故,字符串的结尾似乎有问题。有什么想法吗?

另外,因为我没有打电话给SysFreeString(),所以我会在这里泄漏内存吗?

编辑:

我暂时包含了 atlbase.h,所以我可以使用 CComBSTR。上面的代码现在看起来像这样:

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

单步执行该代码肯定表明 pVarResult 一直是“测试字符串”,直到函数返回。但是当我在我的 JavaScript 代码中 alert() 返回的字符串时,我得到了“扩展”。 alert(returnedString + "foo") 是“扩展的foo”。所以这是朝着正确方向迈出的一小步,因为您可以添加到返回的字符串中。但这也是朝着错误方向迈出的一步,因为返回的字符串不是我真正返回的......

*pVarResult = CComVariant("test string");

该代码给出的结果与前面列表中的代码相同(使用 CComBSTR)。

【问题讨论】:

  • 我没有看到它(用于 ATL),但仅供参考:输出参数归调用者所有,因此调用者释放了字符串。请参阅memory management rules here
  • @Georg:太好了。所以至少我没有泄漏内存。

标签: javascript c++ windows com iwebbrowser2


【解决方案1】:

调试代码下面的一些有趣的事情变得显而易见。

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 是 32。len2 只是 30。SysAllocString 适用于我的 JavaScript 代码,而其他方法则不能。

查看分配 bstrRet 的内存,我可以看到它以 0x00 0x00 0x00 0x00 结尾,而 bstrRet2 只有 0x00 0x00。所以我的猜测是,当使用 bstrRet 时会向 JavaScript 代码发送一个额外的空终止符,从而将其丢弃。这就是为什么你不能在它上面附加任何东西。 bstrRet2 没有额外的空终止符。

知道如果像这样修改问题中的原始代码可以工作:

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;

我不确定执行 lenW*2 是否安全,但在我所做的有限测试中,该代码似乎可以正常工作。

【讨论】:

    【解决方案2】:

    第一个MultiByteToWideChar() 调用返回存储字符串所需的字符数,包括空终止符。然后SysAllocStringLen()lenW+1 字符分配一个缓冲区(比需要的多一个)并且已经空终止它。

    由于MultiByteToWideChar() 也写了一个空终止符,所以你在字符串的末尾有两个。对于BSTRs,嵌入的空字符是可能的,因为它们是长度前缀的,因此 JScript 实现可能会在不删除附加的情况下连接...因此您最终会得到一个中间嵌入空字符的字符串,这只会部分打印。

    长话短说,固定长度:

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

    正如评论中提到的,该字符串将由调用者释放 - memory management rules 指示输出参数归调用者所有。

    【讨论】:

    • 所以,只是为了确保我明白这一点。在最后的 MultiByteToWideChar 调用中,我们可以只使用 lenW 和 lenW-1,对吗?使用 lenW-1 我们不会复制 str 中的 \0 并使用 SysAllocStringLen 已经为我们提供的那个。我们只用 lenW 覆盖了用 str 中的一个创建的 \0 SysAllocStringLen。对吗?
    • @Tobbe:正确,上面的代码SysAllocStringLen()lenW 字符分配了一个缓冲区,你会覆盖最后一个,即空终止符。
    猜你喜欢
    • 2013-12-10
    • 2014-11-06
    • 2011-05-29
    • 2013-03-25
    • 1970-01-01
    • 2021-05-12
    相关资源
    最近更新 更多