C++ DLL与VBA之间的字符串传递:传入字符串

发布于:2019-08-31 | 分类:python/vba/cpp


前文以char*类型接收Excel中传入的字符串参数,其结果是C++接口在VBA宏代码中工作正常,却不能正确解析单元格公式调用时传入的参数;而一旦经过VBA函数的简单包装,则可以正常应用于VBA和单元格公式。本文实例演示C++ DLL中以不同类型接收分别从Excel公式和VBA传递字符串的方法。

原因分析

导致开头提及问题的原因是,VBA宏代码和Excel公式传递字符串参数的编码方式略有不同

  • Excel公式以Unicode数组格式传递字符串,VBA则以ANSI数组方式传递和显示字符串。char*对应了ANSI字符串,因此C++接口对VBA传参成功而公式传参失败。

  • 单元格Unicode字符串传入VBA函数中会自动进行ANSI编码转换,所以VBA包装后的版本对VBA和单元格公式都是有效的。

  • C++中与Unicode字符串对应的是宽字符串类型wchar_t*

因此,以char*类型接收字符串的接口函数仅对VBA有效,经过VBA包装后可以正确应用于单元格公式。若想将C++接口直接正确地应用于单元格公式,DLL中需要以wchar_t*类型接收参数。相应地,VBA中则需要先使用StrConv()转换为Unicode字符串;如果返回值也是wchar_t*类型,同理需要StrConv()转换回ANSI字符串。

传参示例

char*为基本类型的传参示例参考前文代码,结合VBA函数的封装,已经可以满足所有字符串传参和返回的应用场景。

本文以wchar_t*为接收字符串参数的基本类型,同时考虑BSTRVARIANT类型(基本类型仍为wchar_t*),演示大写转换函数。

0. char*wchar_t*的转换

对于示例问题,我们复用前文基于char*版本的基本转换函数upper_heap()。因此基本流程:接收wchar_t*->转为char*->代入upper_heap()->转换回wchar_t*。开始之前,给出char*wchar_t*的转换函数:

char* Common::Wchar2char(const wchar_t* wchar)
{
    size_t len = wcslen(wchar) + 1;
    size_t converted = 0;
    char* str;
    str = (char*)malloc(len* sizeof(char));
    wcstombs_s(&converted, str, len, wchar, _TRUNCATE);

    return str;
}

wchar_t* Common::Char2wchar(const char* str)
{
    size_t len = strlen(str) + 1;
    size_t converted = 0;
    wchar_t* wchar;
    wchar = (wchar_t*)malloc(len* sizeof(wchar_t));
    mbstowcs_s(&converted, wchar, len, str, _TRUNCATE);

    return wchar;
}

1. 以wchar_t*接收字符串

BSTR __stdcall upper_bstr_wchar(const wchar_t* wchar)
{
    char* str = Common::Wchar2char(wchar);
    char* res = upper_heap(str);
    wchar_t* wchar_res = Common::Char2wchar(res);

    BSTR bstr = SysAllocString(wchar_res);

    free(str);
    free(wchar_res);
    delete[] res;

    return bstr;
}

从之前的分析可知upper_bstr_wchar()可以正确应用于单元格公式,但需要经过编码转换才能应用于VBA。相应地,VBA函数的简单封装如下:

Declare Function upper_bstr_wchar Lib "Sample_Passing_String.dll" (ByVal str$) As String

Function upper_vba_bstr_wchar(str As String) As String
    upper_vba_bstr_wchar = StrConv(upper_bstr_wchar(StrConv(str, vbUnicode)), vbFromUnicode)
End Function

其中传参时用StrConv(str, vbUnicode)转换为Unicode字符串,返回时用StrConv(str, vbFromUnicode)转换回ANSI字符串。

2. 以BSTR接收字符串

如果以BSTR类型传入,直接转为wchar_t*即可:

BSTR __stdcall upper_bstr_bstr(BSTR str)
{
    return upper_bstr_wchar(str);
}

同理VBA端的简单包装:

Declare Function upper_bstr_bstr Lib "Sample_Passing_String.dll" (ByVal str As String) As String

Function upper_vba_bstr_bstr(str As String) As String
    upper_vba_bstr_bstr = StrConv(upper_bstr_bstr(StrConv(str, vbUnicode)), vbFromUnicode)
End Function

3. 以VARIANT接收字符串

当以VARIANT类型接收单元格公式中传入的单元格对象时,需要将Range转换为其中存储的数据,具体方法参考

A C++ DLL for Excel that uses Arrays and Ranges

BSTR __stdcall upper_bstr_var(VARIANT cell)
{
    // convert from CELL
    // https://www.codeproject.com/Articles/17733/A-C-DLL-for-Excel-that-uses-Arrays-and-Ranges

    cell = Common::CheckExcelArray(cell);

    BSTR bstr = cell.vt == VT_BSTR ? cell.bstrVal : SysAllocString(L"");    
    return upper_bstr_bstr(bstr);
}

注意这种情况下应该直接传入VBA的ANSI字符串而无需Unicode转换:

Declare Function upper_bstr_var Lib "Sample_Passing_String.dll" (ByVal str As Variant) As String

Function upper_vba_bstr_var(str As String) As String
    upper_vba_bstr_var = StrConv(upper_bstr_var(str), vbFromUnicode)
End Function

总结

  • char*wchar_t*是C++中接收Excel传入字符串的基本类型。BSTRVARIANT也可以接收字符串,对应wchar_t*类型。

  • 结合C++接口和VBA函数封装,char*wchar_t*任一方式都可以满足所有字符串传参和返回值需求。

    • char*面向直接VBA代码调用,VBA函数封装后可以应用于单元格公式
    • whar_t*直接面向单元格公式,VBA函数封装后可以应用于VBA代码

最后,本文完整代码:

https://github.com/dothinking/blog/tree/master/samples/vba_cpp_dll/Sample_Pass_String