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*
为接收字符串参数的基本类型,同时考虑BSTR
和VARIANT
类型(基本类型仍为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
转换为其中存储的数据,具体方法参考
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传入字符串的基本类型。BSTR
和VARIANT
也可以接收字符串,对应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